diff --git a/CMakeLists.txt b/CMakeLists.txt index 0853ac0c2..16abf4ca3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,8 @@ target_sources( browser-app.hpp browser-client.cpp browser-client.hpp + browser-dummy-client.cpp + browser-dummy-client.hpp browser-scheme.cpp browser-scheme.hpp browser-version.h diff --git a/browser-app.cpp b/browser-app.cpp index 5d2c4dc76..9c6d34b0c 100644 --- a/browser-app.cpp +++ b/browser-app.cpp @@ -48,11 +48,62 @@ CefRefPtr BrowserApp::GetBrowserProcessHandler() return this; } +CefRefPtr BrowserApp::GetDefaultClient() +{ + return GetDummy(); +} + void BrowserApp::OnRegisterCustomSchemes(CefRawPtr registrar) { registrar->AddCustomScheme("http", CEF_SCHEME_OPTION_STANDARD | CEF_SCHEME_OPTION_CORS_ENABLED); } +void BrowserApp::OnContextInitialized() +{ + // Without a default client, CefBrowser is unmanaged, allowing full-blown Chromium windows outside of our control + // We don't actually want those, so define a dummy client which will automatically close any such windows + dummy = new BrowserDummyClient(); + + CefRefPtr requestContext = CefRequestContext::GetGlobalContext(); + CefString errorMessage; + CefRefPtr optionValue = CefValue::Create(); + + constexpr std::array kBrowserFeaturesToDisable{ + "autofill.credit_card_enabled", + "autofill.enabled", + "autofill.iban_enabled", + "autofill.payment_card_benefits", + "autofill.payment_cvc_storage", + "autofill.profile_enabled", + "autologin.enabled", + "browser_labs_enabled", + "credentials_enable_autosignin", + "credentials_enable_service", + "payments.can_make_payment_enabled", + "printing.enabled", + "search.suggest_enabled", + "shopping_list_enabled", + "side_panel.google_search_side_panel_enabled", + "side_search.enabled", + "signin.allowed", + "signin.allowed_on_next_startup", + "translate", + "url_keyed_anonymized_data_collection.enabled"}; + + constexpr std::array kBrowserFeaturesToEnable{"extensions.block_external_extensions", + "extensions.disabled"}; + + optionValue->SetBool(false); + for (std::string_view feature : kBrowserFeaturesToDisable) { + requestContext->SetPreference(feature.data(), optionValue.get(), errorMessage); + } + + optionValue->SetBool(true); + for (std::string_view feature : kBrowserFeaturesToEnable) { + requestContext->SetPreference(feature.data(), optionValue.get(), errorMessage); + } +} + void BrowserApp::OnBeforeChildProcessLaunch(CefRefPtr command_line) { #ifdef _WIN32 @@ -82,16 +133,35 @@ void BrowserApp::OnBeforeCommandLineProcessing(const CefString &, CefRefPtrAppendSwitchWithValue("disable-features", disableFeatures); } else { command_line->AppendSwitchWithValue("disable-features", "WebBluetooth," #ifdef _WIN32 "EnableWindowsGamingInputDataFetcher," #endif + "MediaRouter," + "CalculateNativeWinOcclusion," + "LiveCaption," + "StorageNotificationService," "HardwareMediaKeyHandling"); } + if (command_line->HasSwitch("disable-blink-features")) { + std::string disableBlinkFeatures = command_line->GetSwitchValue("disable-blink-features"); + disableBlinkFeatures += ",DocumentPictureInPictureAPI"; + command_line->AppendSwitchWithValue("disable-blink-features", disableBlinkFeatures); + } else { + command_line->AppendSwitchWithValue("disable-blink-features", "DocumentPictureInPictureAPI"); + } + command_line->AppendSwitchWithValue("autoplay-policy", "no-user-gesture-required"); + command_line->AppendSwitch("disable-extensions"); + command_line->AppendSwitch("hide-crash-restore-bubble"); #ifdef __APPLE__ command_line->AppendSwitch("use-mock-keychain"); #elif !defined(_WIN32) diff --git a/browser-app.hpp b/browser-app.hpp index b25d9accd..0694d0c8e 100644 --- a/browser-app.hpp +++ b/browser-app.hpp @@ -22,6 +22,7 @@ #include #include #include "cef-headers.hpp" +#include "browser-dummy-client.hpp" typedef std::function)> BrowserFunc; @@ -85,6 +86,8 @@ class BrowserApp : public CefApp, public CefRenderProcessHandler, public CefBrow virtual CefRefPtr GetRenderProcessHandler() override; virtual CefRefPtr GetBrowserProcessHandler() override; + virtual CefRefPtr GetDefaultClient() override; + virtual void OnContextInitialized() override; virtual void OnBeforeChildProcessLaunch(CefRefPtr command_line) override; virtual void OnRegisterCustomSchemes(CefRawPtr registrar) override; virtual void OnBeforeCommandLineProcessing(const CefString &process_type, @@ -106,5 +109,9 @@ class BrowserApp : public CefApp, public CefRenderProcessHandler, public CefBrow QTimer frameTimer; #endif + CefRefPtr dummy = nullptr; + + BrowserDummyClient *GetDummy() const { return dummy.get(); }; + IMPLEMENT_REFCOUNTING(BrowserApp); }; diff --git a/browser-client.cpp b/browser-client.cpp index bf7baada9..bfe7135d1 100644 --- a/browser-client.cpp +++ b/browser-client.cpp @@ -626,6 +626,17 @@ void BrowserClient::OnLoadEnd(CefRefPtr, CefRefPtr frame, } } +void BrowserClient::OnLoadError(CefRefPtr, [[maybe_unused]] CefRefPtr frame, + CefLoadHandler::ErrorCode, const CefString &, const CefString &) +{ +#if CHROME_VERSION_BUILD > 6533 + // CEF doesn't currently provide a way to properly disable/override the default Chrome error page + // https://github.com/obsproject/obs-studio/issues/13499 + // FIXME: https://github.com/chromiumembedded/cef/issues/3852 + frame->LoadURL("about:blank"); +#endif +} + bool BrowserClient::OnConsoleMessage(CefRefPtr, cef_log_severity_t level, const CefString &message, const CefString &source, int line) { diff --git a/browser-client.hpp b/browser-client.hpp index cba0be2dd..069c3ff42 100644 --- a/browser-client.hpp +++ b/browser-client.hpp @@ -146,5 +146,9 @@ class BrowserClient : public CefClient, /* CefLoadHandler */ virtual void OnLoadEnd(CefRefPtr browser, CefRefPtr frame, int httpStatusCode) override; + virtual void OnLoadError(CefRefPtr browser, CefRefPtr frame, + CefLoadHandler::ErrorCode errorCode, const CefString &errorText, + const CefString &failedUrl) override; + IMPLEMENT_REFCOUNTING(BrowserClient); }; diff --git a/browser-dummy-client.cpp b/browser-dummy-client.cpp new file mode 100644 index 000000000..ee2726b7e --- /dev/null +++ b/browser-dummy-client.cpp @@ -0,0 +1,71 @@ + +#include "browser-dummy-client.hpp" + +CefRefPtr BrowserDummyClient::GetCommandHandler() +{ + return this; +} + +CefRefPtr BrowserDummyClient::GetRequestHandler() +{ + return this; +} + +CefRefPtr BrowserDummyClient::GetLifeSpanHandler() +{ + return this; +} + +bool BrowserDummyClient::OnChromeCommand(CefRefPtr, int, cef_window_open_disposition_t) +{ + return true; +} + +bool BrowserDummyClient::IsChromeAppMenuItemVisible(CefRefPtr, int) +{ + return false; +} + +bool BrowserDummyClient::IsChromeToolbarButtonVisible(cef_chrome_toolbar_button_type_t) +{ + return false; +} + +bool BrowserDummyClient::IsChromePageActionIconVisible(cef_chrome_page_action_icon_type_t) +{ + return false; +} + +bool BrowserDummyClient::IsChromeAppMenuItemEnabled(CefRefPtr, int) +{ + return false; +} + +bool BrowserDummyClient::OnBeforePopup(CefRefPtr, CefRefPtr, +#if CHROME_VERSION_BUILD >= 6834 + int, +#endif + const CefString &, const CefString &, cef_window_open_disposition_t, bool, + const CefPopupFeatures &, CefWindowInfo &, CefRefPtr &, + CefBrowserSettings &, CefRefPtr &, bool *) +{ + return true; +} + +void BrowserDummyClient::OnAfterCreated(CefRefPtr browser) +{ + if (browser && browser->GetHost()) { + browser->GetHost()->CloseBrowser(false); + } +} + +bool BrowserDummyClient::OnOpenURLFromTab(CefRefPtr, CefRefPtr, const CefString &, + CefRequestHandler::WindowOpenDisposition, bool) +{ + return true; +} + +bool BrowserDummyClient::OnBeforeBrowse(CefRefPtr, CefRefPtr, CefRefPtr, bool, bool) +{ + return true; +} diff --git a/browser-dummy-client.hpp b/browser-dummy-client.hpp new file mode 100644 index 000000000..80c9280d6 --- /dev/null +++ b/browser-dummy-client.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include "cef-headers.hpp" + +class BrowserDummyClient : public CefClient, + public CefCommandHandler, + public CefRequestHandler, + public CefLifeSpanHandler { +public: + virtual CefRefPtr GetCommandHandler() override; + virtual CefRefPtr GetRequestHandler() override; + virtual CefRefPtr GetLifeSpanHandler() override; + + /* CefCommandHandler */ + virtual bool OnChromeCommand(CefRefPtr browser, int command_id, + cef_window_open_disposition_t disposition) override; + virtual bool IsChromeAppMenuItemVisible(CefRefPtr browser, int command_id) override; + virtual bool IsChromeToolbarButtonVisible(cef_chrome_toolbar_button_type_t button_type) override; + + virtual bool IsChromePageActionIconVisible(cef_chrome_page_action_icon_type_t icon_type) override; + + virtual bool IsChromeAppMenuItemEnabled(CefRefPtr browser, int command_id) override; + + /* CefLifeSpanHandler */ + virtual bool OnBeforePopup(CefRefPtr browser, CefRefPtr frame, +#if CHROME_VERSION_BUILD >= 6834 + int, +#endif + const CefString &target_url, const CefString &target_frame_name, + cef_window_open_disposition_t target_disposition, bool user_gesture, + const CefPopupFeatures &popupFeatures, CefWindowInfo &windowInfo, + CefRefPtr &client, CefBrowserSettings &settings, + CefRefPtr &extra_info, bool *no_javascript_access) override; + + virtual void OnAfterCreated(CefRefPtr browser) override; + + /* CefRequestHandler */ + virtual bool OnOpenURLFromTab(CefRefPtr browser, CefRefPtr frame, + const CefString &target_url, + CefRequestHandler::WindowOpenDisposition target_disposition, + bool user_gesture) override; + + virtual bool OnBeforeBrowse(CefRefPtr browser, CefRefPtr frame, + CefRefPtr request, bool user_gesture, bool is_redirect) override; + + IMPLEMENT_REFCOUNTING(BrowserDummyClient); +}; diff --git a/cmake/os-linux.cmake b/cmake/os-linux.cmake index f74797a62..55b4eca75 100644 --- a/cmake/os-linux.cmake +++ b/cmake/os-linux.cmake @@ -14,7 +14,8 @@ add_executable(OBS::browser-helper ALIAS browser-helper) target_sources( browser-helper PRIVATE # cmake-format: sortable - browser-app.cpp browser-app.hpp cef-headers.hpp obs-browser-page/obs-browser-page-main.cpp) + browser-app.cpp browser-app.hpp browser-dummy-client.hpp browser-dummy-client.cpp + cef-headers.hpp obs-browser-page/obs-browser-page-main.cpp) target_include_directories(browser-helper PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/deps" "${CMAKE_CURRENT_SOURCE_DIR}/obs-browser-page") diff --git a/cmake/os-macos.cmake b/cmake/os-macos.cmake index 6d6dc51c8..00476ed13 100644 --- a/cmake/os-macos.cmake +++ b/cmake/os-macos.cmake @@ -31,7 +31,8 @@ foreach(helper IN LISTS helper_suffixes) target_sources( ${target_name} PRIVATE # cmake-format: sortable - browser-app.cpp browser-app.hpp cef-headers.hpp obs-browser-page/obs-browser-page-main.cpp) + browser-app.cpp browser-app.hpp browser-dummy-client.hpp browser-dummy-client.cpp + cef-headers.hpp obs-browser-page/obs-browser-page-main.cpp) target_compile_definitions(${target_name} PRIVATE ENABLE_BROWSER_SHARED_TEXTURE) diff --git a/cmake/os-windows.cmake b/cmake/os-windows.cmake index 43f9d8558..b0250738f 100644 --- a/cmake/os-windows.cmake +++ b/cmake/os-windows.cmake @@ -13,8 +13,8 @@ add_executable(OBS::browser-helper ALIAS obs-browser-helper) target_sources( obs-browser-helper PRIVATE # cmake-format: sortable - browser-app.cpp browser-app.hpp cef-headers.hpp obs-browser-page.manifest - obs-browser-page/obs-browser-page-main.cpp) + browser-app.cpp browser-app.hpp browser-dummy-client.hpp browser-dummy-client.cpp + cef-headers.hpp obs-browser-page.manifest obs-browser-page/obs-browser-page-main.cpp) configure_file(cmake/windows/obs-module-helper.rc.in obs-browser-page.rc) target_sources(obs-browser-helper PRIVATE obs-browser-page.rc) diff --git a/obs-browser-plugin.cpp b/obs-browser-plugin.cpp index 95a168c6b..65d655195 100644 --- a/obs-browser-plugin.cpp +++ b/obs-browser-plugin.cpp @@ -293,6 +293,9 @@ static void BrowserInit(void) CefString(&settings.log_file) = log_path_abs; settings.windowless_rendering_enabled = true; settings.no_sandbox = true; +#if CHROME_VERSION_BUILD > 6533 && CHROME_VERSION_BUILD <= 6613 + settings.chrome_runtime = true; +#endif uint32_t obs_ver = obs_get_version(); uint32_t obs_maj = obs_ver >> 24; @@ -341,6 +344,9 @@ static void BrowserInit(void) settings.persist_user_preferences = 1; #endif CefString(&settings.cache_path) = conf_path_abs; +#if CHROME_VERSION_BUILD > 6533 + CefString(&settings.root_cache_path) = conf_path_abs; +#endif #if !defined(__APPLE__) || defined(ENABLE_BROWSER_LEGACY) char *abs_path = os_get_abs_path_ptr(path.c_str()); CefString(&settings.browser_subprocess_path) = abs_path; @@ -714,8 +720,122 @@ static void check_hwaccel_support(void) #endif #endif +#if CHROME_VERSION_BUILD > 6533 +static void MigrateProfileConfigDir(std::string oldDir, std::string newDir, bool makeDirs) +{ + namespace fs = std::filesystem; + + BPtr configPath = obs_module_config_path("."); + fs::path rootPath{configPath.Get()}; + + fs::path oldPath = rootPath / fs::path(oldDir); + fs::path newPath = rootPath / fs::path(newDir); + + if (!fs::exists(oldPath)) { + return; + } + + try { + if (makeDirs && !fs::exists(newPath)) { + fs::create_directories(newPath); + } + const auto copyOptions = fs::copy_options::recursive; + fs::copy(oldPath, newPath, copyOptions); + } catch (const fs::filesystem_error &error) { + blog(LOG_WARNING, "[obs-browser]: Error migrating cookies for '%s': %s", oldPath.c_str(), error.what()); + } +} + +static void MigrateDefaultProfileSubdirs(std::string parent, bool makeDirs) +{ + constexpr std::array kMigrationDirectories{ + "Local Storage", + "Session Storage", + "Network", + }; + constexpr std::string_view kDefaultDirectoryPrefix{"Default/"}; + + for (std::string_view directory : kMigrationDirectories) { + std::string oldDirectory{parent}; + oldDirectory.append(directory); + std::string newDirectory{parent}; + newDirectory.append(kDefaultDirectoryPrefix); + newDirectory.append(directory); + + MigrateProfileConfigDir(oldDirectory, newDirectory, makeDirs); + } +} + +static void MigrateToChromeRuntime() +{ + namespace fs = std::filesystem; + BPtr defaultConfigPath = obs_module_config_path("Default"); + fs::path defaultPath{defaultConfigPath.Get()}; + // User has already previously launched with Chrome Runtime + if (fs::exists(defaultPath)) { + blog(LOG_INFO, "[obs-browser]: Chrome Runtime Default profile detected. No migration necessary."); + return; + } + + BPtr localPrefsConfigPath = obs_module_config_path("LocalPrefs.json"); + fs::path localPrefs{localPrefsConfigPath.Get()}; + // User has never launched old OBS Browser before + if (!fs::exists(localPrefs)) { + blog(LOG_DEBUG, "[obs-browser]: Alloy Runtime preferences not found. No migration necessary."); + return; + } + + blog(LOG_INFO, "[obs-browser]: Migrating files to Chrome Runtime.."); + + try { + fs::create_directories(defaultPath); + } catch (const fs::filesystem_error &error) { + blog(LOG_WARNING, "[obs-browser]: Error creating Default profile: %s", error.what()); + } + + MigrateDefaultProfileSubdirs("", false); + + BPtr newLocalPrefsConfigPath = obs_module_config_path("Local State"); + fs::path newLocalPrefs{newLocalPrefsConfigPath.Get()}; + try { + fs::copy(localPrefs, newLocalPrefs); + } catch (const fs::filesystem_error &error) { + blog(LOG_WARNING, "[obs-browser]: Error migrating preferences: %s", error.what()); + } + + constexpr std::string_view prefix = "obs_profile_cookies"; + + BPtr serviceProfilesPath = obs_module_config_path(prefix.data()); + fs::path serviceProfiles{serviceProfilesPath.Get()}; + if (fs::exists(serviceProfiles)) { + // User has service integration cookies that also must be copied + for (auto const &dir_entry : std::filesystem::directory_iterator{serviceProfiles}) { + if (!dir_entry.is_directory()) { + continue; + } + std::string name{dir_entry.path().filename().string()}; + std::string oldDir{prefix}; + oldDir.append("/"); + oldDir.append(name); + + std::string newDir{prefix}; + newDir.append("_"); + newDir.append(name); + MigrateProfileConfigDir(oldDir, newDir, true); + + blog(LOG_INFO, "[obs-browser]: Migrated cookies for %s.", name.c_str()); + } + } + + blog(LOG_INFO, "[obs-browser]: Migration of browser cookies and session completed."); +} +#endif + bool obs_module_load(void) { +#if CHROME_VERSION_BUILD > 6533 + MigrateToChromeRuntime(); +#endif #ifdef ENABLE_BROWSER_QT_LOOP qRegisterMetaType("MessageTask"); #endif diff --git a/panel/browser-panel-client.cpp b/panel/browser-panel-client.cpp index 868f01b62..1d61f4f96 100644 --- a/panel/browser-panel-client.cpp +++ b/panel/browser-panel-client.cpp @@ -38,6 +38,13 @@ CefRefPtr QCefBrowserClient::GetDisplayHandler() return this; } +#if CHROME_VERSION_BUILD >= 6533 +CefRefPtr QCefBrowserClient::GetCommandHandler() +{ + return this; +} +#endif + CefRefPtr QCefBrowserClient::GetRequestHandler() { return this; @@ -68,10 +75,18 @@ CefRefPtr QCefBrowserClient::GetJSDialogHandler() return this; } +/* CefCommandHandler */ +#if CHROME_VERSION_BUILD >= 6533 +bool QCefBrowserClient::OnChromeCommand(CefRefPtr, int, cef_window_open_disposition_t) +{ + return true; +} +#endif + /* CefDisplayHandler */ void QCefBrowserClient::OnTitleChange(CefRefPtr browser, const CefString &title) { - if (widget && widget->cefBrowser->IsSame(browser)) { + if (widget && widget->cefBrowser && widget->cefBrowser->IsSame(browser)) { std::string str_title = title; QString qt_title = QString::fromUtf8(str_title.c_str()); QMetaObject::invokeMethod(widget, "titleChanged", Q_ARG(QString, qt_title)); diff --git a/panel/browser-panel-client.hpp b/panel/browser-panel-client.hpp index 417021fa9..ea44000b0 100644 --- a/panel/browser-panel-client.hpp +++ b/panel/browser-panel-client.hpp @@ -7,6 +7,9 @@ class QCefBrowserClient : public CefClient, public CefDisplayHandler, +#if CHROME_VERSION_BUILD >= 6533 + public CefCommandHandler, +#endif public CefRequestHandler, public CefLifeSpanHandler, public CefContextMenuHandler, @@ -26,6 +29,9 @@ class QCefBrowserClient : public CefClient, /* CefClient */ virtual CefRefPtr GetLoadHandler() override; virtual CefRefPtr GetDisplayHandler() override; +#if CHROME_VERSION_BUILD >= 6533 + virtual CefRefPtr GetCommandHandler() override; +#endif virtual CefRefPtr GetRequestHandler() override; virtual CefRefPtr GetLifeSpanHandler() override; virtual CefRefPtr GetKeyboardHandler() override; @@ -33,6 +39,12 @@ class QCefBrowserClient : public CefClient, virtual CefRefPtr GetContextMenuHandler() override; virtual CefRefPtr GetJSDialogHandler() override; + /* CefCommandHandler */ +#if CHROME_VERSION_BUILD >= 6533 + virtual bool OnChromeCommand(CefRefPtr browser, int command_id, + cef_window_open_disposition_t disposition) override; +#endif + /* CefDisplayHandler */ virtual void OnTitleChange(CefRefPtr browser, const CefString &title) override; diff --git a/panel/browser-panel.cpp b/panel/browser-panel.cpp index b3fbe289d..099d07eb3 100644 --- a/panel/browser-panel.cpp +++ b/panel/browser-panel.cpp @@ -368,6 +368,10 @@ void QCefWidgetInternal::resizeEvent(QResizeEvent *event) void QCefWidgetInternal::Resize() { + if (os_event_try(cef_started_event) != 0) { + return; + } + QSize size = this->size() * devicePixelRatioF(); bool success = QueueCEFTask([this, size]() { @@ -552,8 +556,18 @@ QCefWidget *QCefInternal::create_widget(QWidget *parent, const std::string &url, QCefCookieManager *QCefInternal::create_cookie_manager(const std::string &storage_path, bool persist_session_cookies) { + std::string path = storage_path; +#if CHROME_VERSION_BUILD > 6533 + // TODO: Update obs-studio to use the new path structure due to subdirectories not being supported by CEF + // https://github.com/obsproject/obs-studio/issues/13498 + std::string legacy = "obs_profile_cookies/"; + size_t pos = storage_path.find(legacy); + if (pos != std::string::npos) { + path.replace(pos, legacy.length(), "obs_profile_cookies_"); + } +#endif try { - return new QCefCookieManagerInternal(storage_path, persist_session_cookies); + return new QCefCookieManagerInternal(path, persist_session_cookies); } catch (const char *error) { blog(LOG_ERROR, "Failed to create cookie manager: %s", error); return nullptr; @@ -562,7 +576,16 @@ QCefCookieManager *QCefInternal::create_cookie_manager(const std::string &storag BPtr QCefInternal::get_cookie_path(const std::string &storage_path) { - BPtr rpath = obs_module_config_path(storage_path.c_str()); + std::string path = storage_path; +#if CHROME_VERSION_BUILD > 6533 + // TODO: Update obs-studio to use the new path structure due to subdirectories not being supported by CEF + std::string legacy = "obs_profile_cookies/"; + size_t pos = storage_path.find(legacy); + if (pos != std::string::npos) { + path.replace(pos, legacy.length(), "obs_profile_cookies_"); + } +#endif + BPtr rpath = obs_module_config_path(path.c_str()); return os_get_abs_path_ptr(rpath.Get()); }