2024-06-30 04:24:01 +00:00
/*
* Copyright ( c ) 2024 , Andrew Kaster < akaster @ serenityos . org >
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include <AK/Debug.h>
2024-07-30 18:01:05 +00:00
# include <LibCore/ArgsParser.h>
2024-08-24 16:15:20 +00:00
# include <LibCore/Environment.h>
2024-08-28 19:29:51 +00:00
# include <LibCore/StandardPaths.h>
2024-10-07 17:36:17 +00:00
# include <LibCore/System.h>
2024-08-24 16:15:20 +00:00
# include <LibCore/TimeZoneWatcher.h>
2024-08-28 19:29:51 +00:00
# include <LibFileSystem/FileSystem.h>
2024-06-30 04:24:01 +00:00
# include <LibImageDecoderClient/Client.h>
# include <LibWebView/Application.h>
2024-09-05 22:19:51 +00:00
# include <LibWebView/CookieJar.h>
# include <LibWebView/Database.h>
2024-11-13 20:33:02 +00:00
# include <LibWebView/HelperProcess.h>
2024-07-30 18:01:05 +00:00
# include <LibWebView/URL.h>
2024-08-28 14:26:11 +00:00
# include <LibWebView/UserAgent.h>
2024-06-30 04:24:01 +00:00
# include <LibWebView/WebContentClient.h>
namespace WebView {
Application * Application : : s_the = nullptr ;
2024-07-30 18:01:05 +00:00
Application : : Application ( )
2024-06-30 04:24:01 +00:00
{
VERIFY ( ! s_the ) ;
s_the = this ;
2024-08-24 16:15:20 +00:00
// No need to monitor the system time zone if the TZ environment variable is set, as it overrides system preferences.
if ( ! Core : : Environment : : has ( " TZ " sv ) ) {
if ( auto time_zone_watcher = Core : : TimeZoneWatcher : : create ( ) ; time_zone_watcher . is_error ( ) ) {
warnln ( " Unable to monitor system time zone: {} " , time_zone_watcher . error ( ) ) ;
} else {
m_time_zone_watcher = time_zone_watcher . release_value ( ) ;
m_time_zone_watcher - > on_time_zone_changed = [ ] ( ) {
WebContentClient : : for_each_client ( [ & ] ( WebView : : WebContentClient & client ) {
client . async_system_time_zone_changed ( ) ;
return IterationDecision : : Continue ;
} ) ;
} ;
}
}
2024-06-30 04:24:01 +00:00
m_process_manager . on_process_exited = [ this ] ( Process & & process ) {
process_did_exit ( move ( process ) ) ;
} ;
}
Application : : ~ Application ( )
{
s_the = nullptr ;
}
2024-07-30 18:01:05 +00:00
void Application : : initialize ( Main : : Arguments const & arguments , URL : : URL new_tab_page_url )
{
2024-10-07 17:36:17 +00:00
// Increase the open file limit, as the default limits on Linux cause us to run out of file descriptors with around 15 tabs open.
if ( auto result = Core : : System : : set_resource_limits ( RLIMIT_NOFILE , 8192 ) ; result . is_error ( ) )
warnln ( " Unable to increase open file limit: {} " , result . error ( ) ) ;
2024-07-30 18:01:05 +00:00
Vector < ByteString > raw_urls ;
Vector < ByteString > certificates ;
bool new_window = false ;
bool force_new_process = false ;
bool allow_popups = false ;
2024-09-04 06:13:40 +00:00
bool disable_scripting = false ;
2024-07-30 18:01:05 +00:00
bool disable_sql_database = false ;
2024-08-01 11:03:03 +00:00
Optional < StringView > debug_process ;
2024-08-01 11:13:09 +00:00
Optional < StringView > profile_process ;
2024-07-30 18:01:05 +00:00
Optional < StringView > webdriver_content_ipc_path ;
2024-08-28 14:26:11 +00:00
Optional < StringView > user_agent_preset ;
2024-07-30 18:01:05 +00:00
bool log_all_js_exceptions = false ;
bool enable_idl_tracing = false ;
bool enable_http_cache = false ;
2024-09-22 22:12:36 +00:00
bool enable_autoplay = false ;
2024-07-30 18:01:05 +00:00
bool expose_internals_object = false ;
2024-08-01 17:49:24 +00:00
bool force_cpu_painting = false ;
2024-08-19 18:35:49 +00:00
bool force_fontconfig = false ;
2024-10-31 15:06:01 +00:00
bool collect_garbage_on_every_allocation = false ;
2024-07-30 18:01:05 +00:00
Core : : ArgsParser args_parser ;
args_parser . set_general_help ( " The Ladybird web browser :^) " ) ;
args_parser . add_positional_argument ( raw_urls , " URLs to open " , " url " , Core : : ArgsParser : : Required : : No ) ;
args_parser . add_option ( certificates , " Path to a certificate file " , " certificate " , ' C ' , " certificate " ) ;
args_parser . add_option ( new_window , " Force opening in a new window " , " new-window " , ' n ' ) ;
args_parser . add_option ( force_new_process , " Force creation of new browser/chrome process " , " force-new-process " ) ;
args_parser . add_option ( allow_popups , " Disable popup blocking by default " , " allow-popups " ) ;
2024-09-04 06:13:40 +00:00
args_parser . add_option ( disable_scripting , " Disable scripting by default " , " disable-scripting " ) ;
2024-07-30 18:01:05 +00:00
args_parser . add_option ( disable_sql_database , " Disable SQL database " , " disable-sql-database " ) ;
2024-08-01 11:03:03 +00:00
args_parser . add_option ( debug_process , " Wait for a debugger to attach to the given process name (WebContent, RequestServer, etc.) " , " debug-process " , 0 , " process-name " ) ;
2024-08-01 11:13:09 +00:00
args_parser . add_option ( profile_process , " Enable callgrind profiling of the given process name (WebContent, RequestServer, etc.) " , " profile-process " , 0 , " process-name " ) ;
2024-07-30 18:01:05 +00:00
args_parser . add_option ( webdriver_content_ipc_path , " Path to WebDriver IPC for WebContent " , " webdriver-content-path " , 0 , " path " , Core : : ArgsParser : : OptionHideMode : : CommandLineAndMarkdown ) ;
args_parser . add_option ( log_all_js_exceptions , " Log all JavaScript exceptions " , " log-all-js-exceptions " ) ;
args_parser . add_option ( enable_idl_tracing , " Enable IDL tracing " , " enable-idl-tracing " ) ;
args_parser . add_option ( enable_http_cache , " Enable HTTP cache " , " enable-http-cache " ) ;
2024-09-22 22:12:36 +00:00
args_parser . add_option ( enable_autoplay , " Enable multimedia autoplay " , " enable-autoplay " ) ;
2024-07-30 18:01:05 +00:00
args_parser . add_option ( expose_internals_object , " Expose internals object " , " expose-internals-object " ) ;
2024-08-01 17:49:24 +00:00
args_parser . add_option ( force_cpu_painting , " Force CPU painting " , " force-cpu-painting " ) ;
2024-08-19 18:35:49 +00:00
args_parser . add_option ( force_fontconfig , " Force using fontconfig for font loading " , " force-fontconfig " ) ;
2024-10-31 15:06:01 +00:00
args_parser . add_option ( collect_garbage_on_every_allocation , " Collect garbage after every JS heap allocation " , " collect-garbage-on-every-allocation " , ' g ' ) ;
2024-08-28 14:26:11 +00:00
args_parser . add_option ( Core : : ArgsParser : : Option {
. argument_mode = Core : : ArgsParser : : OptionArgumentMode : : Required ,
. help_string = " Name of the User-Agent preset to use in place of the default User-Agent " ,
. long_name = " user-agent-preset " ,
. value_name = " name " ,
. accept_value = [ & ] ( StringView value ) {
user_agent_preset = normalize_user_agent_name ( value ) ;
return user_agent_preset . has_value ( ) ;
} ,
} ) ;
2024-07-30 18:01:05 +00:00
create_platform_arguments ( args_parser ) ;
args_parser . parse ( arguments ) ;
2024-09-18 17:36:45 +00:00
// Our persisted SQL storage assumes it runs in a singleton process. If we have multiple UI processes accessing
// the same underlying database, one of them is likely to fail.
if ( force_new_process )
disable_sql_database = true ;
2024-08-01 11:03:03 +00:00
Optional < ProcessType > debug_process_type ;
2024-08-01 11:13:09 +00:00
Optional < ProcessType > profile_process_type ;
2024-08-01 11:03:03 +00:00
if ( debug_process . has_value ( ) )
debug_process_type = process_type_from_name ( * debug_process ) ;
2024-08-01 11:13:09 +00:00
if ( profile_process . has_value ( ) )
profile_process_type = process_type_from_name ( * profile_process ) ;
2024-08-01 11:03:03 +00:00
2024-07-30 18:01:05 +00:00
m_chrome_options = {
. urls = sanitize_urls ( raw_urls , new_tab_page_url ) ,
. raw_urls = move ( raw_urls ) ,
. new_tab_page_url = move ( new_tab_page_url ) ,
. certificates = move ( certificates ) ,
. new_window = new_window ? NewWindow : : Yes : NewWindow : : No ,
. force_new_process = force_new_process ? ForceNewProcess : : Yes : ForceNewProcess : : No ,
. allow_popups = allow_popups ? AllowPopups : : Yes : AllowPopups : : No ,
2024-09-04 06:13:40 +00:00
. disable_scripting = disable_scripting ? DisableScripting : : Yes : DisableScripting : : No ,
2024-07-30 18:01:05 +00:00
. disable_sql_database = disable_sql_database ? DisableSQLDatabase : : Yes : DisableSQLDatabase : : No ,
2024-08-01 11:03:03 +00:00
. debug_helper_process = move ( debug_process_type ) ,
2024-08-01 11:13:09 +00:00
. profile_helper_process = move ( profile_process_type ) ,
2024-07-30 18:01:05 +00:00
} ;
if ( webdriver_content_ipc_path . has_value ( ) )
m_chrome_options . webdriver_content_ipc_path = * webdriver_content_ipc_path ;
m_web_content_options = {
. command_line = MUST ( String : : join ( ' ' , arguments . strings ) ) ,
. executable_path = MUST ( String : : from_byte_string ( MUST ( Core : : System : : current_executable_path ( ) ) ) ) ,
2024-08-28 14:26:11 +00:00
. user_agent_preset = move ( user_agent_preset ) ,
2024-07-30 18:01:05 +00:00
. log_all_js_exceptions = log_all_js_exceptions ? LogAllJSExceptions : : Yes : LogAllJSExceptions : : No ,
. enable_idl_tracing = enable_idl_tracing ? EnableIDLTracing : : Yes : EnableIDLTracing : : No ,
. enable_http_cache = enable_http_cache ? EnableHTTPCache : : Yes : EnableHTTPCache : : No ,
. expose_internals_object = expose_internals_object ? ExposeInternalsObject : : Yes : ExposeInternalsObject : : No ,
2024-08-01 17:49:24 +00:00
. force_cpu_painting = force_cpu_painting ? ForceCPUPainting : : Yes : ForceCPUPainting : : No ,
2024-08-19 18:35:49 +00:00
. force_fontconfig = force_fontconfig ? ForceFontconfig : : Yes : ForceFontconfig : : No ,
2024-09-22 22:12:36 +00:00
. enable_autoplay = enable_autoplay ? EnableAutoplay : : Yes : EnableAutoplay : : No ,
2024-10-31 15:06:01 +00:00
. collect_garbage_on_every_allocation = collect_garbage_on_every_allocation ? CollectGarbageOnEveryAllocation : : Yes : CollectGarbageOnEveryAllocation : : No ,
2024-07-30 18:01:05 +00:00
} ;
create_platform_options ( m_chrome_options , m_web_content_options ) ;
2024-09-05 22:19:51 +00:00
if ( m_chrome_options . disable_sql_database = = DisableSQLDatabase : : No ) {
m_database = Database : : create ( ) . release_value_but_fixme_should_propagate_errors ( ) ;
m_cookie_jar = CookieJar : : create ( * m_database ) . release_value_but_fixme_should_propagate_errors ( ) ;
} else {
m_cookie_jar = CookieJar : : create ( ) ;
}
2024-07-30 18:01:05 +00:00
}
2024-11-13 20:33:02 +00:00
ErrorOr < void > Application : : launch_services ( )
{
TRY ( launch_request_server ( ) ) ;
TRY ( launch_image_decoder_server ( ) ) ;
return { } ;
}
ErrorOr < void > Application : : launch_request_server ( )
{
// FIXME: Create an abstraction to re-spawn the RequestServer and re-hook up its client hooks to each tab on crash
2024-11-13 21:04:33 +00:00
m_request_server_client = TRY ( launch_request_server_process ( ) ) ;
2024-11-13 20:33:02 +00:00
return { } ;
}
ErrorOr < void > Application : : launch_image_decoder_server ( )
{
2024-11-13 21:04:33 +00:00
m_image_decoder_client = TRY ( launch_image_decoder_process ( ) ) ;
2024-11-13 20:33:02 +00:00
m_image_decoder_client - > on_death = [ this ] ( ) {
m_image_decoder_client = nullptr ;
if ( auto result = launch_image_decoder_server ( ) ; result . is_error ( ) ) {
dbgln ( " Failed to restart image decoder: {} " , result . error ( ) ) ;
VERIFY_NOT_REACHED ( ) ;
}
auto client_count = WebContentClient : : client_count ( ) ;
auto new_sockets = m_image_decoder_client - > send_sync_but_allow_failure < Messages : : ImageDecoderServer : : ConnectNewClients > ( client_count ) ;
if ( ! new_sockets | | new_sockets - > sockets ( ) . is_empty ( ) ) {
dbgln ( " Failed to connect {} new clients to ImageDecoder " , client_count ) ;
VERIFY_NOT_REACHED ( ) ;
}
WebContentClient : : for_each_client ( [ sockets = new_sockets - > take_sockets ( ) ] ( WebContentClient & client ) mutable {
client . async_connect_to_image_decoder ( sockets . take_last ( ) ) ;
return IterationDecision : : Continue ;
} ) ;
} ;
return { } ;
}
2024-07-30 18:01:05 +00:00
int Application : : execute ( )
2024-06-30 04:24:01 +00:00
{
int ret = m_event_loop . exec ( ) ;
m_in_shutdown = true ;
return ret ;
}
void Application : : add_child_process ( WebView : : Process & & process )
{
m_process_manager . add_process ( move ( process ) ) ;
}
# if defined(AK_OS_MACH)
void Application : : set_process_mach_port ( pid_t pid , Core : : MachPort & & port )
{
m_process_manager . set_process_mach_port ( pid , move ( port ) ) ;
}
# endif
Optional < Process & > Application : : find_process ( pid_t pid )
{
return m_process_manager . find_process ( pid ) ;
}
void Application : : update_process_statistics ( )
{
m_process_manager . update_all_process_statistics ( ) ;
}
String Application : : generate_process_statistics_html ( )
{
return m_process_manager . generate_html ( ) ;
}
void Application : : process_did_exit ( Process & & process )
{
if ( m_in_shutdown )
return ;
dbgln_if ( WEBVIEW_PROCESS_DEBUG , " Process {} died, type: {} " , process . pid ( ) , process_name_from_type ( process . type ( ) ) ) ;
switch ( process . type ( ) ) {
case ProcessType : : ImageDecoder :
if ( auto client = process . client < ImageDecoderClient : : Client > ( ) ; client . has_value ( ) ) {
dbgln_if ( WEBVIEW_PROCESS_DEBUG , " Restart ImageDecoder process " ) ;
if ( auto on_death = move ( client - > on_death ) ) {
on_death ( ) ;
}
}
break ;
case ProcessType : : RequestServer :
dbgln_if ( WEBVIEW_PROCESS_DEBUG , " FIXME: Restart request server " ) ;
break ;
case ProcessType : : WebContent :
if ( auto client = process . client < WebContentClient > ( ) ; client . has_value ( ) ) {
dbgln_if ( WEBVIEW_PROCESS_DEBUG , " Restart WebContent process " ) ;
if ( auto on_web_content_process_crash = move ( client - > on_web_content_process_crash ) )
on_web_content_process_crash ( ) ;
}
break ;
case ProcessType : : WebWorker :
dbgln_if ( WEBVIEW_PROCESS_DEBUG , " WebWorker {} died, not sure what to do. " , process . pid ( ) ) ;
break ;
case ProcessType : : Chrome :
dbgln ( " Invalid process type to be dying: Chrome " ) ;
VERIFY_NOT_REACHED ( ) ;
}
}
2024-08-28 19:29:51 +00:00
ErrorOr < LexicalPath > Application : : path_for_downloaded_file ( StringView file ) const
{
auto downloads_directory = Core : : StandardPaths : : downloads_directory ( ) ;
if ( ! FileSystem : : is_directory ( downloads_directory ) ) {
auto maybe_downloads_directory = ask_user_for_download_folder ( ) ;
if ( ! maybe_downloads_directory . has_value ( ) )
return Error : : from_errno ( ECANCELED ) ;
downloads_directory = maybe_downloads_directory . release_value ( ) ;
}
if ( ! FileSystem : : is_directory ( downloads_directory ) )
return Error : : from_errno ( ENOENT ) ;
return LexicalPath : : join ( downloads_directory , file ) ;
}
2024-06-30 04:24:01 +00:00
}