diff --git a/include/capi/cef_browser_process_handler_capi.h b/include/capi/cef_browser_process_handler_capi.h index 456e8e590..7a61de4e6 100644 --- a/include/capi/cef_browser_process_handler_capi.h +++ b/include/capi/cef_browser_process_handler_capi.h @@ -33,7 +33,7 @@ // by hand. See the translator.README.txt file in the tools directory for // more information. // -// $hash=9e91adb231d67a65ce02294a0806d7effd40d280$ +// $hash=dad764dddf92655cc580e0e7e85d3f3c34bdd46d$ // #ifndef CEF_INCLUDE_CAPI_CEF_BROWSER_PROCESS_HANDLER_CAPI_H_ @@ -122,8 +122,7 @@ typedef struct _cef_browser_process_handler_t { /// therefore check the cef_initialize() return value for early exit before /// proceeding. /// - /// This function will be called on the browser process UI thread. Currently - /// only used with the chrome runtime. + /// This function will be called on the browser process UI thread. /// int(CEF_CALLBACK* on_already_running_app_relaunch)( struct _cef_browser_process_handler_t* self, diff --git a/include/cef_browser_process_handler.h b/include/cef_browser_process_handler.h index 6a7fa355f..93daf563c 100644 --- a/include/cef_browser_process_handler.h +++ b/include/cef_browser_process_handler.h @@ -113,8 +113,7 @@ class CefBrowserProcessHandler : public virtual CefBaseRefCounted { /// therefore check the CefInitialize() return value for early exit before /// proceeding. /// - /// This method will be called on the browser process UI thread. Currently - /// only used with the chrome runtime. + /// This method will be called on the browser process UI thread. /// /*--cef(optional_param=current_directory)--*/ virtual bool OnAlreadyRunningAppRelaunch( diff --git a/include/internal/cef_types.h b/include/internal/cef_types.h index b3b10a748..a31d163ff 100644 --- a/include/internal/cef_types.h +++ b/include/internal/cef_types.h @@ -279,31 +279,46 @@ typedef struct _cef_settings_t { int command_line_args_disabled; /// - /// The location where data for the global browser cache will be stored on + /// The directory where data for the global browser cache will be stored on /// disk. If this value is non-empty then it must be an absolute path that is /// either equal to or a child directory of CefSettings.root_cache_path. If /// this value is empty then browsers will be created in "incognito mode" - /// where in-memory caches are used for storage and no data is persisted to - /// disk. HTML5 databases such as localStorage will only persist across - /// sessions if a cache path is specified. Can be overridden for individual - /// CefRequestContext instances via the CefRequestContextSettings.cache_path - /// value. When using the Chrome runtime the "default" profile will be used if - /// |cache_path| and |root_cache_path| have the same value. + /// where in-memory caches are used for storage and no profile-specific data + /// is persisted to disk (installation-specific data will still be persisted + /// in root_cache_path). HTML5 databases such as localStorage will only + /// persist across sessions if a cache path is specified. Can be overridden + /// for individual CefRequestContext instances via the + /// CefRequestContextSettings.cache_path value. When using the Chrome runtime + /// any child directory value will be ignored and the "default" profile (also + /// a child directory) will be used instead. /// cef_string_t cache_path; /// - /// The root directory that all CefSettings.cache_path and - /// CefRequestContextSettings.cache_path values must have in common. If this - /// value is empty and CefSettings.cache_path is non-empty then it will - /// default to the CefSettings.cache_path value. If both values are empty - /// then the default platform-specific directory will be used + /// The root directory for installation-specific data and the parent directory + /// for profile-specific data. All CefSettings.cache_path and + /// CefRequestContextSettings.cache_path values must have this parent + /// directory in common. If this value is empty and CefSettings.cache_path is + /// non-empty then it will default to the CefSettings.cache_path value. Any + /// non-empty value must be an absolute path. If both values are empty then + /// the default platform-specific directory will be used /// ("~/.config/cef_user_data" directory on Linux, "~/Library/Application /// Support/CEF/User Data" directory on MacOS, "AppData\Local\CEF\User Data" - /// directory under the user profile directory on Windows). If this value is - /// non-empty then it must be an absolute path. Failure to set this value - /// correctly may result in the sandbox blocking read/write access to certain - /// files. + /// directory under the user profile directory on Windows). Use of the default + /// directory is not recommended in production applications (see below). + /// + /// Multiple application instances writing to the same root_cache_path + /// directory could result in data corruption. A process singleton lock based + /// on the root_cache_path value is therefore used to protect against this. + /// This singleton behavior applies to all CEF-based applications using + /// version 120 or newer. You should customize root_cache_path for your + /// application and implement CefBrowserProcessHandler:: + /// OnAlreadyRunningAppRelaunch, which will then be called on any app relaunch + /// with the same root_cache_path value. + /// + /// Failure to set the root_cache_path value correctly may result in startup + /// crashes or other unexpected behaviors (for example, the sandbox blocking + /// read/write access to certain files). /// cef_string_t root_cache_path; @@ -506,14 +521,15 @@ typedef struct _cef_request_context_settings_t { size_t size; /// - /// The location where cache data for this request context will be stored on + /// The directory where cache data for this request context will be stored on /// disk. If this value is non-empty then it must be an absolute path that is /// either equal to or a child directory of CefSettings.root_cache_path. If /// this value is empty then browsers will be created in "incognito mode" - /// where in-memory caches are used for storage and no data is persisted to - /// disk. HTML5 databases such as localStorage will only persist across - /// sessions if a cache path is specified. To share the global browser cache - /// and related configuration set this value to match the + /// where in-memory caches are used for storage and no profile-specific data + /// is persisted to disk (installation-specific data will still be persisted + /// in root_cache_path). HTML5 databases such as localStorage will only + /// persist across sessions if a cache path is specified. To share the global + /// browser cache and related configuration set this value to match the /// CefSettings.cache_path value. /// cef_string_t cache_path; diff --git a/libcef/browser/alloy/alloy_browser_main.cc b/libcef/browser/alloy/alloy_browser_main.cc index 310b14a52..71e4e44ae 100644 --- a/libcef/browser/alloy/alloy_browser_main.cc +++ b/libcef/browser/alloy/alloy_browser_main.cc @@ -19,6 +19,7 @@ #include "libcef/browser/permission_prompt.h" #include "libcef/browser/thread_util.h" #include "libcef/common/app_manager.h" +#include "libcef/common/command_line_impl.h" #include "libcef/common/extensions/extensions_util.h" #include "libcef/common/net/net_resource_provider.h" @@ -27,6 +28,7 @@ #include "base/strings/string_number_conversions.h" #include "base/task/thread_pool.h" #include "chrome/browser/browser_process.h" +#include "chrome/browser/chrome_process_singleton.h" #include "chrome/browser/media/router/chrome_media_router_factory.h" #include "chrome/browser/net/system_network_context_manager.h" #include "chrome/browser/ui/color/chrome_color_mixers.h" @@ -141,6 +143,56 @@ ui::LinuxUi* GetLinuxUI() { #endif // BUILDFLAG(IS_LINUX) +void ProcessSingletonNotificationCallbackImpl( + const base::CommandLine& command_line, + const base::FilePath& current_directory) { + // Drop the request if the browser process is already shutting down. + if (!CONTEXT_STATE_VALID()) { + return; + } + + bool handled = false; + + if (auto app = CefAppManager::Get()->GetApplication()) { + if (auto handler = app->GetBrowserProcessHandler()) { + CefRefPtr commandLinePtr( + new CefCommandLineImpl(command_line)); + handled = handler->OnAlreadyRunningAppRelaunch(commandLinePtr.get(), + current_directory.value()); + std::ignore = commandLinePtr->Detach(nullptr); + } + } + + if (!handled) { + LOG(WARNING) << "Unhandled app relaunch; implement " + "CefBrowserProcessHandler::OnAlreadyRunningAppRelaunch."; + } +} + +// Based on ChromeBrowserMainParts::ProcessSingletonNotificationCallback. +bool ProcessSingletonNotificationCallback( + const base::CommandLine& command_line, + const base::FilePath& current_directory) { + // Drop the request if the browser process is already shutting down. + // Note that we're going to post an async task below. Even if the browser + // process isn't shutting down right now, it could be by the time the task + // starts running. So, an additional check needs to happen when it starts. + // But regardless of any future check, there is no reason to post the task + // now if we know we're already shutting down. + if (!CONTEXT_STATE_VALID()) { + return false; + } + + // In order to handle this request on Windows, there is platform specific + // code in browser_finder.cc that requires making outbound COM calls to + // cross-apartment shell objects (via IVirtualDesktopManager). That is not + // allowed within a SendMessage handler, which this function is a part of. + // So, we post a task to asynchronously finish the command line processing. + return base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( + FROM_HERE, base::BindOnce(&ProcessSingletonNotificationCallbackImpl, + command_line, current_directory)); +} + } // namespace AlloyBrowserMainParts::AlloyBrowserMainParts() = default; @@ -262,6 +314,10 @@ int AlloyBrowserMainParts::PreCreateThreads() { return 0; } +void AlloyBrowserMainParts::PostCreateThreads() { + ChromeProcessSingleton::GetInstance()->StartWatching(); +} + int AlloyBrowserMainParts::PreMainMessageLoopRun() { #if defined(USE_AURA) screen_ = views::CreateDesktopScreen(); @@ -339,6 +395,12 @@ int AlloyBrowserMainParts::PreMainMessageLoopRun() { } #endif + // Allow ProcessSingleton to process messages. + // This is done here instead of just relying on the main message loop's start + // to avoid rendezvous in RunLoops that may precede MainMessageLoopRun. + ChromeProcessSingleton::GetInstance()->Unlock( + base::BindRepeating(&ProcessSingletonNotificationCallback)); + return content::RESULT_CODE_NORMAL_EXIT; } @@ -346,6 +408,8 @@ void AlloyBrowserMainParts::PostMainMessageLoopRun() { // NOTE: Destroy objects in reverse order of creation. CefDevToolsManagerDelegate::StopHttpHandler(); + ChromeProcessSingleton::GetInstance()->Cleanup(); + // There should be no additional references to the global CefRequestContext // during shutdown. Did you forget to release a CefBrowser reference? DCHECK(global_request_context_->HasOneRef()); diff --git a/libcef/browser/alloy/alloy_browser_main.h b/libcef/browser/alloy/alloy_browser_main.h index 856d19f5e..637af2062 100644 --- a/libcef/browser/alloy/alloy_browser_main.h +++ b/libcef/browser/alloy/alloy_browser_main.h @@ -49,6 +49,7 @@ class AlloyBrowserMainParts : public content::BrowserMainParts { void PreCreateMainMessageLoop() override; void PostCreateMainMessageLoop() override; int PreCreateThreads() override; + void PostCreateThreads() override; int PreMainMessageLoopRun() override; void PostMainMessageLoopRun() override; void PostDestroyThreads() override; diff --git a/libcef/browser/net/chrome_scheme_handler.cc b/libcef/browser/net/chrome_scheme_handler.cc index 8aab53ff7..4c3451bfa 100644 --- a/libcef/browser/net/chrome_scheme_handler.cc +++ b/libcef/browser/net/chrome_scheme_handler.cc @@ -35,6 +35,7 @@ #include "chrome/browser/ui/webui/chrome_untrusted_web_ui_configs.h" #include "chrome/browser/ui/webui/chrome_web_ui_controller_factory.h" #include "chrome/browser/ui/webui/theme_source.h" +#include "chrome/common/chrome_paths.h" #include "chrome/common/url_constants.h" #include "content/browser/renderer_host/debug_urls.h" #include "content/public/browser/browser_url_handler.h" @@ -385,6 +386,9 @@ bool OnVersionUI(Profile* profile, return false; } + base::FilePath user_data_dir = + base::PathService::CheckedGet(chrome::DIR_USER_DATA); + TemplateParser parser; parser.Add("YEAR", MAKE_STRING(COPYRIGHT_YEAR)); parser.Add("CEF", CEF_VERSION); @@ -400,6 +404,7 @@ bool OnVersionUI(Profile* profile, CefAppManager::Get()->GetContentClient()->browser()->GetUserAgent()); parser.Add("COMMANDLINE", GetCommandLine()); parser.Add("MODULEPATH", GetModulePath()); + parser.Add("ROOTCACHEPATH", CefString(user_data_dir.value())); parser.Add("CACHEPATH", CefString(profile->GetPath().value())); parser.Parse(&tmpl); diff --git a/libcef/common/alloy/alloy_main_delegate.cc b/libcef/common/alloy/alloy_main_delegate.cc index 0c12ac8d5..aa30a302b 100644 --- a/libcef/common/alloy/alloy_main_delegate.cc +++ b/libcef/common/alloy/alloy_main_delegate.cc @@ -24,16 +24,22 @@ #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" +#include "base/strings/sys_string_conversions.h" +#include "base/strings/utf_string_conversions.h" #include "base/synchronization/waitable_event.h" #include "chrome/browser/browser_process.h" +#include "chrome/browser/chrome_process_singleton.h" #include "chrome/child/pdf_child_init.h" #include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_paths.h" +#include "chrome/common/chrome_result_codes.h" #include "chrome/common/chrome_switches.h" +#include "chrome/grit/generated_resources.h" #include "chrome/utility/chrome_content_utility_client.h" #include "components/component_updater/component_updater_paths.h" #include "components/content_settings/core/common/content_settings_pattern.h" #include "components/embedder_support/switches.h" +#include "components/metrics/persistent_histograms.h" #include "components/viz/common/features.h" #include "content/public/common/content_features.h" #include "content/public/common/content_switches.h" @@ -45,7 +51,9 @@ #include "services/network/public/cpp/features.h" #include "third_party/blink/public/common/features.h" #include "third_party/blink/public/common/switches.h" +#include "ui/base/l10n/l10n_util.h" #include "ui/base/resource/resource_bundle.h" +#include "ui/base/resource/scoped_startup_resource_bundle.h" #include "ui/base/ui_base_features.h" #include "ui/base/ui_base_paths.h" #include "ui/base/ui_base_switches.h" @@ -69,6 +77,46 @@ const char* const kNonWildcardDomainNonPortSchemes[] = { const size_t kNonWildcardDomainNonPortSchemesSize = std::size(kNonWildcardDomainNonPortSchemes); +absl::optional AcquireProcessSingleton( + const base::FilePath& user_data_dir) { + // Take the Chrome process singleton lock. The process can become the + // Browser process if it succeed to take the lock. Otherwise, the + // command-line is sent to the actual Browser process and the current + // process can be exited. + ChromeProcessSingleton::CreateInstance(user_data_dir); + + ProcessSingleton::NotifyResult notify_result = + ChromeProcessSingleton::GetInstance()->NotifyOtherProcessOrCreate(); + switch (notify_result) { + case ProcessSingleton::PROCESS_NONE: + break; + + case ProcessSingleton::PROCESS_NOTIFIED: { + // Ensure there is an instance of ResourceBundle that is initialized for + // localized string resource accesses. + ui::ScopedStartupResourceBundle startup_resource_bundle; + printf("%s\n", base::SysWideToNativeMB( + base::UTF16ToWide(l10n_util::GetStringUTF16( + IDS_USED_EXISTING_BROWSER))) + .c_str()); + return chrome::RESULT_CODE_NORMAL_EXIT_PROCESS_NOTIFIED; + } + + case ProcessSingleton::PROFILE_IN_USE: + return chrome::RESULT_CODE_PROFILE_IN_USE; + + case ProcessSingleton::LOCK_ERROR: + LOG(ERROR) << "Failed to create a ProcessSingleton for your profile " + "directory. This means that running multiple instances " + "would start multiple browser processes rather than " + "opening a new window in the existing process. Aborting " + "now to avoid profile corruption."; + return chrome::RESULT_CODE_PROFILE_IN_USE; + } + + return absl::nullopt; +} + } // namespace AlloyMainDelegate::AlloyMainDelegate(CefMainRunnerHandler* runner, @@ -87,6 +135,35 @@ absl::optional AlloyMainDelegate::PreBrowserMain() { return absl::nullopt; } +absl::optional AlloyMainDelegate::PostEarlyInitialization( + InvokedIn invoked_in) { + const auto* invoked_in_browser = + absl::get_if(&invoked_in); + if (!invoked_in_browser) { + return absl::nullopt; + } + + // Based on ChromeMainDelegate::PostEarlyInitialization. + // The User Data dir is guaranteed to be valid as per PreSandboxStartup. + base::FilePath user_data_dir = + base::PathService::CheckedGet(chrome::DIR_USER_DATA); + + // On platforms that support the process rendezvous, acquire the process + // singleton. In case of failure, it means there is already a running browser + // instance that handled the command-line. + if (auto process_singleton_result = AcquireProcessSingleton(user_data_dir); + process_singleton_result.has_value()) { + // To ensure that the histograms emitted in this process are reported in + // case of early exit, report the metrics accumulated this session with a + // future session's metrics. + DeferBrowserMetrics(user_data_dir); + + return process_singleton_result; + } + + return absl::nullopt; +} + absl::optional AlloyMainDelegate::BasicStartupComplete() { base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); std::string process_type = diff --git a/libcef/common/alloy/alloy_main_delegate.h b/libcef/common/alloy/alloy_main_delegate.h index 911a16167..c42bb2391 100644 --- a/libcef/common/alloy/alloy_main_delegate.h +++ b/libcef/common/alloy/alloy_main_delegate.h @@ -47,6 +47,7 @@ class AlloyMainDelegate : public content::ContentMainDelegate, // content::ContentMainDelegate overrides. absl::optional PreBrowserMain() override; + absl::optional PostEarlyInitialization(InvokedIn invoked_in) override; absl::optional BasicStartupComplete() override; void PreSandboxStartup() override; absl::variant RunProcess( diff --git a/libcef/common/resource_util.cc b/libcef/common/resource_util.cc index b0bf7fa95..b8cba46bd 100644 --- a/libcef/common/resource_util.cc +++ b/libcef/common/resource_util.cc @@ -13,6 +13,7 @@ #include "base/command_line.h" #include "base/files/file_path.h" #include "base/files/file_util.h" +#include "base/logging.h" #include "base/notreached.h" #include "base/path_service.h" #include "chrome/common/chrome_constants.h" @@ -89,6 +90,10 @@ base::FilePath GetUserDataPath(CefSettings* settings, } if (!root_cache_path.empty()) { return base::FilePath(root_cache_path); + } else { + LOG(WARNING) << "Please customize CefSettings.root_cache_path for your " + "application. Use of the default value may lead to " + "unintended process singleton behavior."; } } diff --git a/libcef/resources/about_version.html b/libcef/resources/about_version.html index 024755f31..7d479c0dc 100644 --- a/libcef/resources/about_version.html +++ b/libcef/resources/about_version.html @@ -43,6 +43,10 @@ body { content: ':'; } +.footnote { + font-size: 0.8em; +} + #logo { float: right; margin-left: 40px; @@ -109,9 +113,26 @@ body { $$MODULEPATH$$ - Cache Path + Root Cache Path [1][2] + $$ROOTCACHEPATH$$ + + + Cache Path [1] $$CACHEPATH$$ + + +
[1] In Chromium terminology, + "Root Cache Path" (CefSettings.root_cache_path) + is the "User Data Directory" and "Cache Path" (CefSettings.cache_path) + is the "Profile Path". An empty "Cache Path" value means that the browser is using an Incognito Profile. + Even in Incognito mode, some per-installation data will still be written to the "Root Cache Path" directory. +

[2] Chromium's process singleton lock + protects against multiple app instances writing to the same "Root Cache Path" directory. + Implement CefBrowserProcessHandler:: OnAlreadyRunningAppRelaunch + to handle the case of app relaunch with the same directory. + + diff --git a/tests/cefclient/browser/client_browser.cc b/tests/cefclient/browser/client_browser.cc index 4af93a891..1fbc6ad6d 100644 --- a/tests/cefclient/browser/client_browser.cc +++ b/tests/cefclient/browser/client_browser.cc @@ -68,6 +68,21 @@ class ClientBrowserDelegate : public ClientAppBrowser::Delegate { CefRefPtr app, CefRefPtr command_line, const CefString& current_directory) override { + // Add logging for some common switches that the user may attempt to use. + static const char* kIgnoredSwitches[] = { + switches::kEnableChromeRuntime, + switches::kMultiThreadedMessageLoop, + switches::kOffScreenRenderingEnabled, + switches::kUseViews, + }; + for (size_t i = 0; + i < sizeof(kIgnoredSwitches) / sizeof(kIgnoredSwitches[0]); ++i) { + if (command_line->HasSwitch(kIgnoredSwitches[i])) { + LOG(WARNING) << "The --" << kIgnoredSwitches[i] + << " command-line switch is ignored on app relaunch."; + } + } + // Create a new root window based on |command_line|. auto config = std::make_unique(command_line->Copy()); diff --git a/tests/cefclient/browser/root_window.h b/tests/cefclient/browser/root_window.h index 59eb92e4a..bce1d9d6b 100644 --- a/tests/cefclient/browser/root_window.h +++ b/tests/cefclient/browser/root_window.h @@ -36,9 +36,9 @@ enum class WindowType { // Used to configure how a RootWindow is created. struct RootWindowConfig { - // |command_line| will be non-nullptr when used for new window creation - // via OnAlreadyRunningAppRelaunch (chrome runtime only). Otherwise, the - // global command-line will be used. + // |command_line| will be non-nullptr when used for new window creation via + // OnAlreadyRunningAppRelaunch. Otherwise, the global command-line will be + // used. RootWindowConfig(CefRefPtr command_line = nullptr); // Associated command-line.