2020-06-25 04:34:12 +02:00
|
|
|
// Copyright 2020 The Chromium Embedded Framework Authors.
|
|
|
|
// Portions copyright 2012 The Chromium Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
|
|
// found in the LICENSE file.
|
|
|
|
|
|
|
|
#include "libcef/common/chrome/chrome_main_delegate_cef.h"
|
|
|
|
|
2022-01-25 20:40:24 +01:00
|
|
|
#include <tuple>
|
|
|
|
|
2020-07-04 04:51:17 +02:00
|
|
|
#include "libcef/browser/chrome/chrome_browser_context.h"
|
2020-06-25 04:34:12 +02:00
|
|
|
#include "libcef/browser/chrome/chrome_content_browser_client_cef.h"
|
2020-09-25 03:40:47 +02:00
|
|
|
#include "libcef/common/cef_switches.h"
|
|
|
|
#include "libcef/common/command_line_impl.h"
|
2020-07-06 20:14:57 +02:00
|
|
|
#include "libcef/common/crash_reporting.h"
|
|
|
|
#include "libcef/common/resource_util.h"
|
2020-09-25 03:40:47 +02:00
|
|
|
#include "libcef/renderer/chrome/chrome_content_renderer_client_cef.h"
|
2020-07-06 20:14:57 +02:00
|
|
|
|
2022-09-29 18:37:59 +02:00
|
|
|
#include "base/base_switches.h"
|
2020-08-29 00:39:23 +02:00
|
|
|
#include "base/command_line.h"
|
2020-09-25 03:40:47 +02:00
|
|
|
#include "base/lazy_instance.h"
|
2022-09-29 18:37:59 +02:00
|
|
|
#include "base/threading/threading_features.h"
|
2023-01-19 22:20:29 +01:00
|
|
|
#include "chrome/browser/metrics/chrome_feature_list_creator.h"
|
2023-10-11 01:26:37 +02:00
|
|
|
#include "chrome/browser/policy/chrome_browser_policy_connector.h"
|
2021-04-07 00:09:45 +02:00
|
|
|
#include "chrome/common/chrome_switches.h"
|
2023-01-19 22:20:29 +01:00
|
|
|
#include "chrome/common/pref_names.h"
|
2021-04-27 18:39:09 +02:00
|
|
|
#include "components/embedder_support/switches.h"
|
2020-07-06 20:14:57 +02:00
|
|
|
#include "content/public/common/content_switches.h"
|
2020-09-25 03:40:47 +02:00
|
|
|
#include "sandbox/policy/switches.h"
|
2021-11-10 22:57:31 +01:00
|
|
|
#include "third_party/blink/public/common/switches.h"
|
2021-04-27 18:39:09 +02:00
|
|
|
#include "ui/base/ui_base_switches.h"
|
2020-07-06 20:14:57 +02:00
|
|
|
|
2022-03-15 20:42:15 +01:00
|
|
|
#if BUILDFLAG(IS_MAC)
|
|
|
|
#include "libcef/common/util_mac.h"
|
|
|
|
#elif BUILDFLAG(IS_POSIX)
|
|
|
|
#include "libcef/common/util_linux.h"
|
|
|
|
#endif
|
|
|
|
|
2020-09-25 03:40:47 +02:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
base::LazyInstance<ChromeContentRendererClientCef>::DestructorAtExit
|
|
|
|
g_chrome_content_renderer_client = LAZY_INSTANCE_INITIALIZER;
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
2020-06-28 23:05:36 +02:00
|
|
|
ChromeMainDelegateCef::ChromeMainDelegateCef(CefMainRunnerHandler* runner,
|
2020-07-06 20:14:57 +02:00
|
|
|
CefSettings* settings,
|
2020-06-28 23:05:36 +02:00
|
|
|
CefRefPtr<CefApp> application)
|
|
|
|
: ChromeMainDelegate(base::TimeTicks::Now()),
|
|
|
|
runner_(runner),
|
2020-07-06 20:14:57 +02:00
|
|
|
settings_(settings),
|
|
|
|
application_(application) {
|
2022-01-24 18:58:02 +01:00
|
|
|
#if BUILDFLAG(IS_LINUX)
|
2020-07-06 20:14:57 +02:00
|
|
|
resource_util::OverrideAssetPath();
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2020-06-25 04:34:12 +02:00
|
|
|
ChromeMainDelegateCef::~ChromeMainDelegateCef() = default;
|
|
|
|
|
2023-12-06 21:16:15 +01:00
|
|
|
std::optional<int> ChromeMainDelegateCef::BasicStartupComplete() {
|
2023-11-21 17:22:37 +01:00
|
|
|
// Returns no value if startup should proceed.
|
2022-07-25 19:49:32 +02:00
|
|
|
auto result = ChromeMainDelegate::BasicStartupComplete();
|
2023-01-02 23:59:03 +01:00
|
|
|
if (result.has_value()) {
|
2022-07-25 19:49:32 +02:00
|
|
|
return result;
|
2023-01-02 23:59:03 +01:00
|
|
|
}
|
2020-09-25 03:40:47 +02:00
|
|
|
|
|
|
|
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
|
2020-07-06 20:14:57 +02:00
|
|
|
|
2022-01-24 18:58:02 +01:00
|
|
|
#if BUILDFLAG(IS_POSIX)
|
2022-03-15 20:42:15 +01:00
|
|
|
// Read the crash configuration file. On Windows this is done from chrome_elf.
|
2020-09-25 03:40:47 +02:00
|
|
|
crash_reporting::BasicStartupComplete(command_line);
|
2020-07-06 20:14:57 +02:00
|
|
|
#endif
|
|
|
|
|
2020-09-25 03:40:47 +02:00
|
|
|
const std::string& process_type =
|
|
|
|
command_line->GetSwitchValueASCII(switches::kProcessType);
|
|
|
|
if (process_type.empty()) {
|
|
|
|
// In the browser process. Populate the global command-line object.
|
|
|
|
// TODO(chrome-runtime): Copy more settings from AlloyMainDelegate and test.
|
|
|
|
if (settings_->command_line_args_disabled) {
|
|
|
|
// Remove any existing command-line arguments.
|
|
|
|
base::CommandLine::StringVector argv;
|
|
|
|
argv.push_back(command_line->GetProgram().value());
|
|
|
|
command_line->InitFromArgv(argv);
|
|
|
|
|
|
|
|
const base::CommandLine::SwitchMap& map = command_line->GetSwitches();
|
|
|
|
const_cast<base::CommandLine::SwitchMap*>(&map)->clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool no_sandbox = settings_->no_sandbox ? true : false;
|
|
|
|
|
|
|
|
if (no_sandbox) {
|
|
|
|
command_line->AppendSwitch(sandbox::policy::switches::kNoSandbox);
|
|
|
|
}
|
|
|
|
|
2021-04-27 18:39:09 +02:00
|
|
|
if (settings_->user_agent.length > 0) {
|
2021-06-04 03:34:56 +02:00
|
|
|
command_line->AppendSwitchASCII(
|
|
|
|
embedder_support::kUserAgent,
|
|
|
|
CefString(&settings_->user_agent).ToString());
|
2021-04-27 18:39:09 +02:00
|
|
|
} else if (settings_->user_agent_product.length > 0) {
|
|
|
|
command_line->AppendSwitchASCII(
|
|
|
|
switches::kUserAgentProductAndVersion,
|
2021-06-04 03:34:56 +02:00
|
|
|
CefString(&settings_->user_agent_product).ToString());
|
2021-04-27 18:39:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (settings_->locale.length > 0) {
|
|
|
|
command_line->AppendSwitchASCII(switches::kLang,
|
2021-06-04 03:34:56 +02:00
|
|
|
CefString(&settings_->locale).ToString());
|
2021-04-27 18:39:09 +02:00
|
|
|
} else if (!command_line->HasSwitch(switches::kLang)) {
|
|
|
|
command_line->AppendSwitchASCII(switches::kLang, "en-US");
|
|
|
|
}
|
|
|
|
|
2020-09-25 03:40:47 +02:00
|
|
|
if (settings_->javascript_flags.length > 0) {
|
2021-06-04 03:34:56 +02:00
|
|
|
command_line->AppendSwitchASCII(
|
2021-11-10 22:57:31 +01:00
|
|
|
blink::switches::kJavaScriptFlags,
|
2021-06-04 03:34:56 +02:00
|
|
|
CefString(&settings_->javascript_flags).ToString());
|
2020-09-25 03:40:47 +02:00
|
|
|
}
|
|
|
|
|
2024-02-15 02:02:20 +01:00
|
|
|
if (settings_->remote_debugging_port >= 1024 &&
|
|
|
|
settings_->remote_debugging_port <= 65535) {
|
2020-09-25 03:40:47 +02:00
|
|
|
command_line->AppendSwitchASCII(
|
|
|
|
switches::kRemoteDebuggingPort,
|
|
|
|
base::NumberToString(settings_->remote_debugging_port));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (settings_->uncaught_exception_stack_size > 0) {
|
|
|
|
command_line->AppendSwitchASCII(
|
|
|
|
switches::kUncaughtExceptionStackSize,
|
|
|
|
base::NumberToString(settings_->uncaught_exception_stack_size));
|
|
|
|
}
|
2022-09-29 18:37:59 +02:00
|
|
|
|
|
|
|
std::vector<std::string> disable_features;
|
|
|
|
|
|
|
|
if (!!settings_->multi_threaded_message_loop &&
|
|
|
|
base::kEnableHangWatcher.default_state ==
|
|
|
|
base::FEATURE_ENABLED_BY_DEFAULT) {
|
|
|
|
// Disable EnableHangWatcher when running with multi-threaded-message-loop
|
|
|
|
// to avoid shutdown crashes (see issue #3403).
|
|
|
|
disable_features.push_back(base::kEnableHangWatcher.name);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!disable_features.empty()) {
|
|
|
|
DCHECK(!base::FeatureList::GetInstance());
|
|
|
|
std::string disable_features_str =
|
|
|
|
command_line->GetSwitchValueASCII(switches::kDisableFeatures);
|
|
|
|
for (auto feature_str : disable_features) {
|
2023-01-02 23:59:03 +01:00
|
|
|
if (!disable_features_str.empty()) {
|
2022-09-29 18:37:59 +02:00
|
|
|
disable_features_str += ",";
|
2023-01-02 23:59:03 +01:00
|
|
|
}
|
2022-09-29 18:37:59 +02:00
|
|
|
disable_features_str += feature_str;
|
|
|
|
}
|
|
|
|
command_line->AppendSwitchASCII(switches::kDisableFeatures,
|
|
|
|
disable_features_str);
|
|
|
|
}
|
2020-09-25 03:40:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (application_) {
|
|
|
|
// Give the application a chance to view/modify the command line.
|
|
|
|
CefRefPtr<CefCommandLineImpl> commandLinePtr(
|
|
|
|
new CefCommandLineImpl(command_line, false, false));
|
|
|
|
application_->OnBeforeCommandLineProcessing(process_type,
|
|
|
|
commandLinePtr.get());
|
2022-01-25 20:40:24 +01:00
|
|
|
std::ignore = commandLinePtr->Detach(nullptr);
|
2020-09-25 03:40:47 +02:00
|
|
|
}
|
|
|
|
|
2022-01-24 18:58:02 +01:00
|
|
|
#if BUILDFLAG(IS_MAC)
|
2020-09-25 03:40:47 +02:00
|
|
|
util_mac::BasicStartupComplete();
|
2020-07-06 20:14:57 +02:00
|
|
|
#endif
|
|
|
|
|
2023-12-06 21:16:15 +01:00
|
|
|
return std::nullopt;
|
2020-07-06 20:14:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void ChromeMainDelegateCef::PreSandboxStartup() {
|
|
|
|
const base::CommandLine* command_line =
|
|
|
|
base::CommandLine::ForCurrentProcess();
|
|
|
|
const std::string& process_type =
|
|
|
|
command_line->GetSwitchValueASCII(switches::kProcessType);
|
|
|
|
|
|
|
|
if (process_type.empty()) {
|
2022-03-15 20:42:15 +01:00
|
|
|
#if BUILDFLAG(IS_MAC)
|
2020-07-06 20:14:57 +02:00
|
|
|
util_mac::PreSandboxStartup();
|
2022-03-15 20:42:15 +01:00
|
|
|
#elif BUILDFLAG(IS_POSIX)
|
|
|
|
util_linux::PreSandboxStartup();
|
|
|
|
#endif
|
2020-07-06 20:14:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Since this may be configured via CefSettings we override the value on
|
|
|
|
// all platforms. We can't use the default implementation on macOS because
|
|
|
|
// chrome::GetDefaultUserDataDirectory expects to find the Chromium version
|
|
|
|
// number in the app bundle path.
|
|
|
|
resource_util::OverrideUserDataDir(settings_, command_line);
|
|
|
|
|
|
|
|
ChromeMainDelegate::PreSandboxStartup();
|
|
|
|
|
|
|
|
// Initialize crash reporting state for this process/module.
|
|
|
|
// chrome::DIR_CRASH_DUMPS must be configured before calling this function.
|
|
|
|
crash_reporting::PreSandboxStartup(*command_line, process_type);
|
|
|
|
}
|
|
|
|
|
2023-12-06 21:16:15 +01:00
|
|
|
std::optional<int> ChromeMainDelegateCef::PreBrowserMain() {
|
2020-06-25 04:34:12 +02:00
|
|
|
// The parent ChromeMainDelegate implementation creates the NSApplication
|
|
|
|
// instance on macOS, and we intentionally don't want to do that here.
|
2020-07-06 20:14:57 +02:00
|
|
|
// TODO(macos): Do we need l10n_util::OverrideLocaleWithCocoaLocale()?
|
2021-06-04 03:34:56 +02:00
|
|
|
runner_->PreBrowserMain();
|
2023-12-06 21:16:15 +01:00
|
|
|
return std::nullopt;
|
2020-06-25 04:34:12 +02:00
|
|
|
}
|
|
|
|
|
2023-12-06 21:16:15 +01:00
|
|
|
std::optional<int> ChromeMainDelegateCef::PostEarlyInitialization(
|
2023-01-19 22:20:29 +01:00
|
|
|
InvokedIn invoked_in) {
|
2023-10-11 01:26:37 +02:00
|
|
|
// Configure this before ChromeMainDelegate::PostEarlyInitialization triggers
|
|
|
|
// ChromeBrowserPolicyConnector creation.
|
|
|
|
if (settings_ && settings_->chrome_policy_id.length > 0) {
|
|
|
|
policy::ChromeBrowserPolicyConnector::EnablePlatformPolicySupport(
|
|
|
|
CefString(&settings_->chrome_policy_id).ToString());
|
|
|
|
}
|
|
|
|
|
2023-01-19 22:20:29 +01:00
|
|
|
const auto result = ChromeMainDelegate::PostEarlyInitialization(invoked_in);
|
|
|
|
if (!result) {
|
|
|
|
const auto* invoked_in_browser =
|
|
|
|
absl::get_if<InvokedInBrowserProcess>(&invoked_in);
|
|
|
|
if (invoked_in_browser) {
|
|
|
|
// At this point local_state has been created but ownership has not yet
|
|
|
|
// been passed to BrowserProcessImpl (g_browser_process is nullptr).
|
|
|
|
auto* local_state = chrome_content_browser_client_->startup_data()
|
|
|
|
->chrome_feature_list_creator()
|
|
|
|
->local_state();
|
|
|
|
|
|
|
|
// Don't show the profile picker on startup (see issue #3440).
|
|
|
|
local_state->SetBoolean(prefs::kBrowserShowProfilePickerOnStartup, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2021-12-16 23:35:54 +01:00
|
|
|
absl::variant<int, content::MainFunctionParams>
|
|
|
|
ChromeMainDelegateCef::RunProcess(
|
2020-06-25 04:34:12 +02:00
|
|
|
const std::string& process_type,
|
2021-12-16 23:35:54 +01:00
|
|
|
content::MainFunctionParams main_function_params) {
|
2020-06-25 04:34:12 +02:00
|
|
|
if (process_type.empty()) {
|
2021-12-16 23:35:54 +01:00
|
|
|
return runner_->RunMainProcess(std::move(main_function_params));
|
2020-06-25 04:34:12 +02:00
|
|
|
}
|
|
|
|
|
2021-12-16 23:35:54 +01:00
|
|
|
return ChromeMainDelegate::RunProcess(process_type,
|
|
|
|
std::move(main_function_params));
|
2020-06-25 04:34:12 +02:00
|
|
|
}
|
|
|
|
|
2022-01-24 18:58:02 +01:00
|
|
|
#if BUILDFLAG(IS_LINUX)
|
2020-07-06 20:14:57 +02:00
|
|
|
void ChromeMainDelegateCef::ZygoteForked() {
|
|
|
|
ChromeMainDelegate::ZygoteForked();
|
|
|
|
|
|
|
|
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
|
|
|
|
const std::string& process_type =
|
|
|
|
command_line->GetSwitchValueASCII(switches::kProcessType);
|
|
|
|
|
|
|
|
// Initialize crash reporting state for the newly forked process.
|
|
|
|
crash_reporting::ZygoteForked(command_line, process_type);
|
|
|
|
}
|
2022-01-24 18:58:02 +01:00
|
|
|
#endif // BUILDFLAG(IS_LINUX)
|
2020-07-06 20:14:57 +02:00
|
|
|
|
2020-06-28 23:05:36 +02:00
|
|
|
content::ContentClient* ChromeMainDelegateCef::CreateContentClient() {
|
|
|
|
return &chrome_content_client_cef_;
|
|
|
|
}
|
|
|
|
|
2020-06-25 04:34:12 +02:00
|
|
|
content::ContentBrowserClient*
|
|
|
|
ChromeMainDelegateCef::CreateContentBrowserClient() {
|
|
|
|
// Match the logic in the parent ChromeMainDelegate implementation, but create
|
|
|
|
// our own object type.
|
2020-12-02 23:31:49 +01:00
|
|
|
chrome_content_browser_client_ =
|
|
|
|
std::make_unique<ChromeContentBrowserClientCef>();
|
2020-06-25 04:34:12 +02:00
|
|
|
return chrome_content_browser_client_.get();
|
|
|
|
}
|
|
|
|
|
2020-09-25 03:40:47 +02:00
|
|
|
content::ContentRendererClient*
|
|
|
|
ChromeMainDelegateCef::CreateContentRendererClient() {
|
|
|
|
return g_chrome_content_renderer_client.Pointer();
|
|
|
|
}
|
|
|
|
|
2020-06-28 23:05:36 +02:00
|
|
|
CefRefPtr<CefRequestContext> ChromeMainDelegateCef::GetGlobalRequestContext() {
|
2020-07-04 04:51:17 +02:00
|
|
|
auto browser_client = content_browser_client();
|
2023-01-02 23:59:03 +01:00
|
|
|
if (browser_client) {
|
2020-07-04 04:51:17 +02:00
|
|
|
return browser_client->request_context();
|
2023-01-02 23:59:03 +01:00
|
|
|
}
|
2020-06-28 23:05:36 +02:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2020-07-01 02:57:00 +02:00
|
|
|
CefBrowserContext* ChromeMainDelegateCef::CreateNewBrowserContext(
|
2021-04-07 00:09:45 +02:00
|
|
|
const CefRequestContextSettings& settings,
|
|
|
|
base::OnceClosure initialized_cb) {
|
2020-07-04 04:51:17 +02:00
|
|
|
auto context = new ChromeBrowserContext(settings);
|
2021-04-07 00:09:45 +02:00
|
|
|
context->InitializeAsync(std::move(initialized_cb));
|
2020-07-04 04:51:17 +02:00
|
|
|
return context;
|
2020-07-01 02:57:00 +02:00
|
|
|
}
|
|
|
|
|
2020-06-25 04:34:12 +02:00
|
|
|
scoped_refptr<base::SingleThreadTaskRunner>
|
|
|
|
ChromeMainDelegateCef::GetBackgroundTaskRunner() {
|
|
|
|
auto browser_client = content_browser_client();
|
2023-01-02 23:59:03 +01:00
|
|
|
if (browser_client) {
|
2020-06-25 04:34:12 +02:00
|
|
|
return browser_client->background_task_runner();
|
2023-01-02 23:59:03 +01:00
|
|
|
}
|
2020-06-25 04:34:12 +02:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
scoped_refptr<base::SingleThreadTaskRunner>
|
|
|
|
ChromeMainDelegateCef::GetUserVisibleTaskRunner() {
|
|
|
|
auto browser_client = content_browser_client();
|
2023-01-02 23:59:03 +01:00
|
|
|
if (browser_client) {
|
2020-06-25 04:34:12 +02:00
|
|
|
return browser_client->user_visible_task_runner();
|
2023-01-02 23:59:03 +01:00
|
|
|
}
|
2020-06-25 04:34:12 +02:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
scoped_refptr<base::SingleThreadTaskRunner>
|
|
|
|
ChromeMainDelegateCef::GetUserBlockingTaskRunner() {
|
|
|
|
auto browser_client = content_browser_client();
|
2023-01-02 23:59:03 +01:00
|
|
|
if (browser_client) {
|
2020-06-25 04:34:12 +02:00
|
|
|
return browser_client->user_blocking_task_runner();
|
2023-01-02 23:59:03 +01:00
|
|
|
}
|
2020-06-25 04:34:12 +02:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
scoped_refptr<base::SingleThreadTaskRunner>
|
|
|
|
ChromeMainDelegateCef::GetRenderTaskRunner() {
|
2020-09-25 03:40:47 +02:00
|
|
|
auto renderer_client = content_renderer_client();
|
2023-01-02 23:59:03 +01:00
|
|
|
if (renderer_client) {
|
2020-09-25 03:40:47 +02:00
|
|
|
return renderer_client->render_task_runner();
|
2023-01-02 23:59:03 +01:00
|
|
|
}
|
2020-06-25 04:34:12 +02:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
scoped_refptr<base::SingleThreadTaskRunner>
|
|
|
|
ChromeMainDelegateCef::GetWebWorkerTaskRunner() {
|
2020-09-25 03:40:47 +02:00
|
|
|
auto renderer_client = content_renderer_client();
|
2023-01-02 23:59:03 +01:00
|
|
|
if (renderer_client) {
|
2020-09-25 03:40:47 +02:00
|
|
|
return renderer_client->GetCurrentTaskRunner();
|
2023-01-02 23:59:03 +01:00
|
|
|
}
|
2020-06-25 04:34:12 +02:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
ChromeContentBrowserClientCef* ChromeMainDelegateCef::content_browser_client()
|
|
|
|
const {
|
|
|
|
return static_cast<ChromeContentBrowserClientCef*>(
|
|
|
|
chrome_content_browser_client_.get());
|
|
|
|
}
|
2020-09-25 03:40:47 +02:00
|
|
|
|
|
|
|
ChromeContentRendererClientCef* ChromeMainDelegateCef::content_renderer_client()
|
|
|
|
const {
|
2023-01-02 23:59:03 +01:00
|
|
|
if (!g_chrome_content_renderer_client.IsCreated()) {
|
2020-09-25 03:40:47 +02:00
|
|
|
return nullptr;
|
2023-01-02 23:59:03 +01:00
|
|
|
}
|
2020-09-25 03:40:47 +02:00
|
|
|
return g_chrome_content_renderer_client.Pointer();
|
2023-10-11 01:26:37 +02:00
|
|
|
}
|