diff --git a/src/dao/browser/dao_pref_names.cc b/src/dao/browser/dao_pref_names.cc
index 82b03fe4..0effa82c 100644
--- a/src/dao/browser/dao_pref_names.cc
+++ b/src/dao/browser/dao_pref_names.cc
@@ -4,7 +4,17 @@
#include "dao/browser/dao_pref_names.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
+#include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/browser/web_contents.h"
+#include "third_party/blink/public/common/web_preferences/web_preferences.h"
+#include "third_party/blink/public/mojom/css/preferred_color_scheme.mojom.h"
+#include "ui/native_theme/native_theme.h"
namespace dao::prefs {
@@ -15,6 +25,7 @@ void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterBooleanPref(kDaoEnhancedPipEnabled, true);
registry->RegisterBooleanPref(kDaoWelcomeShown, false);
registry->RegisterBooleanPref(kDaoLittleDaoEnabled, true);
+ registry->RegisterBooleanPref(kDaoForceDarkModeEnabled, false);
registry->RegisterBooleanPref(kDaoEnhancedCommandBarSuggestionsEnabled,
false);
registry->RegisterDictionaryPref(kDaoLittleDaoWindowSize);
@@ -24,3 +35,97 @@ void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
}
} // namespace dao::prefs
+
+namespace dao {
+
+namespace {
+
+Profile* GetStorageProfile(Profile* profile) {
+ return profile ? profile->GetOriginalProfile() : nullptr;
+}
+
+bool UsesSameStorageProfile(Profile* lhs, Profile* rhs) {
+ return GetStorageProfile(lhs) == GetStorageProfile(rhs);
+}
+
+} // namespace
+
+bool IsSystemDarkMode() {
+ ui::NativeTheme* theme = ui::NativeTheme::GetInstanceForNativeUi();
+ return theme && theme->preferred_color_scheme() ==
+ ui::NativeTheme::PreferredColorScheme::kDark;
+}
+
+bool IsForceDarkModeUserEnabled(Profile* profile) {
+ Profile* storage_profile = GetStorageProfile(profile);
+ return storage_profile && storage_profile->GetPrefs()->GetBoolean(
+ prefs::kDaoForceDarkModeEnabled);
+}
+
+bool IsForceDarkModeAvailable() {
+ return IsSystemDarkMode();
+}
+
+bool IsForceDarkModeEffective(Profile* profile) {
+ return IsForceDarkModeAvailable() && IsForceDarkModeUserEnabled(profile);
+}
+
+void SetForceDarkModeUserEnabled(Profile* profile, bool enabled) {
+ Profile* storage_profile = GetStorageProfile(profile);
+ if (!storage_profile) {
+ return;
+ }
+ storage_profile->GetPrefs()->SetBoolean(prefs::kDaoForceDarkModeEnabled,
+ enabled);
+ NotifyForceDarkModeChanged(storage_profile);
+}
+
+void ApplyForceDarkModePreferences(
+ Profile* profile,
+ blink::web_pref::WebPreferences* web_preferences) {
+ if (!web_preferences) {
+ return;
+ }
+
+ const bool force_dark_mode = IsForceDarkModeEffective(profile);
+ web_preferences->force_dark_mode_enabled = force_dark_mode;
+ if (!force_dark_mode) {
+ return;
+ }
+
+ web_preferences->preferred_color_scheme =
+ blink::mojom::PreferredColorScheme::kDark;
+ web_preferences->preferred_root_scrollbar_color_scheme =
+ blink::mojom::PreferredColorScheme::kDark;
+}
+
+void NotifyForceDarkModeChanged(Profile* profile) {
+ Profile* storage_profile = GetStorageProfile(profile);
+ if (!storage_profile) {
+ return;
+ }
+
+ for (BrowserWindowInterface* browser_window :
+ GetAllBrowserWindowInterfaces()) {
+ Browser* browser =
+ browser_window ? browser_window->GetBrowserForMigrationOnly() : nullptr;
+ if (!browser ||
+ !UsesSameStorageProfile(browser->profile(), storage_profile)) {
+ continue;
+ }
+
+ TabStripModel* tab_strip_model = browser->tab_strip_model();
+ if (!tab_strip_model) {
+ continue;
+ }
+
+ for (int i = 0; i < tab_strip_model->count(); ++i) {
+ content::WebContents* contents = tab_strip_model->GetWebContentsAt(i);
+ if (contents) {
+ contents->OnWebPreferencesChanged();
+ }
+ }
+ }
+}
+
+} // namespace dao
diff --git a/src/dao/browser/dao_pref_names.h b/src/dao/browser/dao_pref_names.h
index 73969e05..16663189 100644
--- a/src/dao/browser/dao_pref_names.h
+++ b/src/dao/browser/dao_pref_names.h
@@ -5,6 +5,12 @@
#ifndef DAO_BROWSER_DAO_PREF_NAMES_H_
#define DAO_BROWSER_DAO_PREF_NAMES_H_
+class Profile;
+
+namespace blink::web_pref {
+struct WebPreferences;
+}
+
namespace user_prefs {
class PrefRegistrySyncable;
}
@@ -40,6 +46,12 @@ inline constexpr char kDaoWelcomeShown[] = "dao.welcome_shown";
// When false, external links use the regular full browser window.
inline constexpr char kDaoLittleDaoEnabled[] = "dao.little_dao_enabled";
+// Boolean pref that remembers whether Dao should use Chromium's Auto Dark Mode
+// pipeline for web contents. It only takes effect while the system appearance
+// is dark.
+inline constexpr char kDaoForceDarkModeEnabled[] =
+ "dao.force_dark_mode_enabled";
+
// Boolean pref that enables the richer Arc-style command bar suggestion
// pipeline. Kept off by default while the ranking model is still maturing.
inline constexpr char kDaoEnhancedCommandBarSuggestionsEnabled[] =
@@ -68,4 +80,19 @@ inline constexpr char kDaoDreamExcludedDomains[] =
} // namespace dao::prefs
+namespace dao {
+
+bool IsSystemDarkMode();
+bool IsForceDarkModeUserEnabled(Profile* profile);
+bool IsForceDarkModeAvailable();
+bool IsForceDarkModeEffective(Profile* profile);
+
+void SetForceDarkModeUserEnabled(Profile* profile, bool enabled);
+void ApplyForceDarkModePreferences(
+ Profile* profile,
+ blink::web_pref::WebPreferences* web_preferences);
+void NotifyForceDarkModeChanged(Profile* profile);
+
+} // namespace dao
+
#endif // DAO_BROWSER_DAO_PREF_NAMES_H_
diff --git a/src/dao/browser/strings/dao_strings.grd b/src/dao/browser/strings/dao_strings.grd
index b8a1a6d0..19ae94bf 100644
--- a/src/dao/browser/strings/dao_strings.grd
+++ b/src/dao/browser/strings/dao_strings.grd
@@ -336,6 +336,9 @@
Back
+
+ Share
+
Clear Cache
@@ -345,6 +348,18 @@
Clearing...
+
+ Force dark mode
+
+
+ Dark
+
+
+ Darken websites with Auto Dark Mode while the system is in dark mode.
+
+
+ Available when the system is in dark mode.
+
diff --git a/src/dao/browser/ui/views/dao_browser_browsertest.cc b/src/dao/browser/ui/views/dao_browser_browsertest.cc
index 456c5fc4..002f68ba 100644
--- a/src/dao/browser/ui/views/dao_browser_browsertest.cc
+++ b/src/dao/browser/ui/views/dao_browser_browsertest.cc
@@ -4,6 +4,7 @@
#include
#include
+#include
#include "base/containers/span.h"
#include "base/files/file_path.h"
@@ -117,6 +118,7 @@
#include "dao/browser/ui/views/dao_control_center_more_menu.h"
#include "dao/browser/ui/views/dao_control_center_popup.h"
#include "dao/browser/ui/views/dao_control_center_qr_view.h"
+#include "dao/browser/ui/views/dao_control_center_utility_section.h"
#include "dao/browser/ui/views/dao_corner_overlay_view.h"
#include "dao/browser/ui/views/dao_load_progress_view.h"
#include "dao/browser/ui/views/dao_pinned_extensions_container.h"
@@ -146,6 +148,8 @@
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/common/web_preferences/web_preferences.h"
+#include "third_party/blink/public/mojom/css/preferred_color_scheme.mojom.h"
#include "third_party/blink/public/mojom/devtools/console_message.mojom-shared.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
@@ -164,6 +168,7 @@
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia_rep.h"
#include "ui/native_theme/native_theme.h"
+#include "ui/views/controls/button/checkbox.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/button/label_button_label.h"
@@ -365,6 +370,36 @@ views::Button* FindButtonWithAccessibleName(
return nullptr;
}
+std::vector GetDirectButtonAccessibleNames(views::View* root) {
+ std::vector names;
+ if (!root) {
+ return names;
+ }
+ for (views::View* child : root->children()) {
+ if (auto* button = views::AsViewClass(child)) {
+ names.push_back(button->GetAccessibleName());
+ }
+ }
+ return names;
+}
+
+views::LabelButton* FindLabelButtonWithText(views::View* root,
+ std::u16string_view text) {
+ if (!root) {
+ return nullptr;
+ }
+ if (auto* button = views::AsViewClass(root);
+ button && button->GetText() == text) {
+ return button;
+ }
+ for (views::View* child : root->children()) {
+ if (auto* button = FindLabelButtonWithText(child, text)) {
+ return button;
+ }
+ }
+ return nullptr;
+}
+
views::LabelButton* FindLabelButtonExceptText(views::View* root,
std::u16string_view text) {
if (!root) {
@@ -5247,6 +5282,147 @@ IN_PROC_BROWSER_TEST_F(DaoToastViewBrowserTest, ShowToastMakesVisible) {
using DaoControlCenterPopupBrowserTest = InProcessBrowserTest;
+IN_PROC_BROWSER_TEST_F(DaoControlCenterPopupBrowserTest,
+ ForceDarkModeDefaultsOffAndRequiresSystemDark) {
+ Profile* profile = browser()->profile();
+ PrefService* prefs = profile->GetPrefs();
+ ASSERT_TRUE(prefs);
+ EXPECT_FALSE(prefs->GetBoolean(prefs::kDaoForceDarkModeEnabled));
+
+ ui::NativeTheme* theme = ui::NativeTheme::GetInstanceForNativeUi();
+ ASSERT_TRUE(theme);
+ theme->set_preferred_color_scheme(
+ ui::NativeTheme::PreferredColorScheme::kDark);
+ EXPECT_FALSE(IsForceDarkModeEffective(profile));
+ blink::web_pref::WebPreferences web_preferences;
+ web_preferences.force_dark_mode_enabled = true;
+ ApplyForceDarkModePreferences(profile, &web_preferences);
+ EXPECT_FALSE(web_preferences.force_dark_mode_enabled);
+
+ prefs->SetBoolean(prefs::kDaoForceDarkModeEnabled, true);
+ EXPECT_TRUE(IsForceDarkModeEffective(profile));
+ ApplyForceDarkModePreferences(profile, &web_preferences);
+ EXPECT_TRUE(web_preferences.force_dark_mode_enabled);
+ EXPECT_EQ(blink::mojom::PreferredColorScheme::kDark,
+ web_preferences.preferred_color_scheme);
+ EXPECT_EQ(blink::mojom::PreferredColorScheme::kDark,
+ web_preferences.preferred_root_scrollbar_color_scheme);
+
+ theme->set_preferred_color_scheme(
+ ui::NativeTheme::PreferredColorScheme::kLight);
+ EXPECT_FALSE(IsForceDarkModeEffective(profile));
+ ApplyForceDarkModePreferences(profile, &web_preferences);
+ EXPECT_FALSE(web_preferences.force_dark_mode_enabled);
+ EXPECT_TRUE(prefs->GetBoolean(prefs::kDaoForceDarkModeEnabled));
+ theme->NotifyOnNativeThemeUpdated();
+}
+
+IN_PROC_BROWSER_TEST_F(DaoControlCenterPopupBrowserTest,
+ ForceDarkModeButtonLivesInMainUtilityRow) {
+ auto* theme = ui::NativeTheme::GetInstanceForNativeUi();
+ theme->set_preferred_color_scheme(
+ ui::NativeTheme::PreferredColorScheme::kLight);
+ theme->NotifyOnNativeThemeUpdated();
+
+ auto* popup = GetBrowserView(browser())->dao_control_center_popup();
+ ASSERT_NE(nullptr, popup);
+ popup->ShowAt(gfx::Point(100, 100));
+
+ const std::u16string label =
+ l10n_util::GetStringUTF16(IDS_DAO_FORCE_DARK_MODE_LABEL);
+ auto* utility_section =
+ FindDescendantViewOfClass(popup);
+ ASSERT_NE(nullptr, utility_section);
+ EXPECT_EQ((std::vector{
+ u"QR Code",
+ l10n_util::GetStringUTF16(IDS_DAO_CONTROL_CENTER_MINI_DAO),
+ u"Security",
+ label,
+ u"More",
+ }),
+ GetDirectButtonAccessibleNames(utility_section));
+ views::Button* button = FindButtonWithAccessibleName(popup, label);
+ ASSERT_NE(nullptr, button);
+ EXPECT_TRUE(button->IsDrawn());
+ EXPECT_FALSE(button->GetEnabled());
+ EXPECT_EQ(views::Button::STATE_DISABLED, button->GetState());
+ EXPECT_EQ(l10n_util::GetStringUTF16(
+ IDS_DAO_FORCE_DARK_MODE_DISABLED_TOOLTIP),
+ button->GetTooltipText());
+ auto* button_label = FindDescendantLabelWithText(
+ button, l10n_util::GetStringUTF16(
+ IDS_DAO_FORCE_DARK_MODE_SHORT_LABEL));
+ ASSERT_NE(nullptr, button_label);
+ const SkColor disabled_label_color = button_label->GetEnabledColor();
+ const int disabled_label_red = static_cast(
+ SkColorGetR(disabled_label_color));
+ EXPECT_NE(ControlCenterLabelColor(), disabled_label_color);
+ EXPECT_NEAR(SkColorGetR(disabled_label_color),
+ SkColorGetG(disabled_label_color), 2);
+ EXPECT_NEAR(SkColorGetG(disabled_label_color),
+ SkColorGetB(disabled_label_color), 2);
+ EXPECT_GT(disabled_label_red,
+ static_cast(SkColorGetR(ControlCenterLabelColor())));
+ EXPECT_LT(disabled_label_red, 200);
+ EXPECT_EQ(nullptr, FindDescendantViewOfClass(popup));
+
+ popup->Hide();
+}
+
+IN_PROC_BROWSER_TEST_F(DaoControlCenterPopupBrowserTest,
+ MoreMenuContainsShareAction) {
+ auto* popup = GetBrowserView(browser())->dao_control_center_popup();
+ ASSERT_NE(nullptr, popup);
+ popup->ShowAt(gfx::Point(100, 100));
+ popup->ShowMoreMenu();
+
+ auto* more_menu = FindDescendantViewOfClass(popup);
+ ASSERT_NE(nullptr, more_menu);
+ ASSERT_TRUE(more_menu->GetVisible());
+ views::LabelButton* share_button = FindLabelButtonWithText(
+ more_menu, l10n_util::GetStringUTF16(IDS_DAO_CONTROL_CENTER_SHARE));
+ ASSERT_NE(nullptr, share_button);
+ EXPECT_TRUE(share_button->IsDrawn());
+
+ popup->Hide();
+}
+
+IN_PROC_BROWSER_TEST_F(DaoControlCenterPopupBrowserTest,
+ ForceDarkModeUtilityButtonTogglesPreference) {
+ auto* theme = ui::NativeTheme::GetInstanceForNativeUi();
+ theme->set_preferred_color_scheme(
+ ui::NativeTheme::PreferredColorScheme::kDark);
+ theme->NotifyOnNativeThemeUpdated();
+
+ PrefService* prefs = browser()->profile()->GetPrefs();
+ ASSERT_TRUE(prefs);
+ prefs->SetBoolean(prefs::kDaoForceDarkModeEnabled, false);
+
+ auto* popup = GetBrowserView(browser())->dao_control_center_popup();
+ ASSERT_NE(nullptr, popup);
+ popup->ShowAt(gfx::Point(100, 100));
+
+ views::Button* button = FindButtonWithAccessibleName(
+ popup, l10n_util::GetStringUTF16(IDS_DAO_FORCE_DARK_MODE_LABEL));
+ ASSERT_NE(nullptr, button);
+ ASSERT_TRUE(button->GetEnabled());
+
+ views::test::ButtonTestApi(button).NotifyClick(ui::MouseEvent(
+ ui::EventType::kMousePressed, gfx::Point(), gfx::Point(),
+ ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
+ ui::EF_LEFT_MOUSE_BUTTON));
+ EXPECT_TRUE(prefs->GetBoolean(prefs::kDaoForceDarkModeEnabled));
+ EXPECT_TRUE(IsForceDarkModeEffective(browser()->profile()));
+
+ views::test::ButtonTestApi(button).NotifyClick(ui::MouseEvent(
+ ui::EventType::kMousePressed, gfx::Point(), gfx::Point(),
+ ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
+ ui::EF_LEFT_MOUSE_BUTTON));
+ EXPECT_FALSE(prefs->GetBoolean(prefs::kDaoForceDarkModeEnabled));
+
+ popup->Hide();
+}
+
IN_PROC_BROWSER_TEST_F(DaoControlCenterPopupBrowserTest, PopupExists) {
auto* popup = GetBrowserView(browser())->dao_control_center_popup();
ASSERT_NE(nullptr, popup);
@@ -5751,6 +5927,37 @@ IN_PROC_BROWSER_TEST_F(DaoLittleDaoViewBrowserTest,
removed.Wait();
}
+IN_PROC_BROWSER_TEST_F(DaoLittleDaoViewBrowserTest,
+ SiteCenterDoesNotContainForceDarkModeControl) {
+ auto* theme = ui::NativeTheme::GetInstanceForNativeUi();
+ theme->set_preferred_color_scheme(
+ ui::NativeTheme::PreferredColorScheme::kLight);
+ theme->NotifyOnNativeThemeUpdated();
+
+ Browser* little_dao_browser = dao::DaoLittleDaoController::OpenInLittleDao(
+ browser()->profile(), GURL("data:text/html,site-center-force-dark"));
+ ASSERT_NE(nullptr, little_dao_browser);
+
+ BrowserView* little_browser_view = GetBrowserView(little_dao_browser);
+ ASSERT_NE(nullptr, little_browser_view);
+ little_browser_view->DeprecatedLayoutImmediately();
+
+ auto* little_view = little_browser_view->dao_little_dao_view();
+ ASSERT_NE(nullptr, little_view);
+ auto* popup = little_browser_view->dao_mini_dao_site_center_popup();
+ ASSERT_NE(nullptr, popup);
+
+ little_view->ShowMiniDaoSiteCenterForTesting();
+ EXPECT_EQ(nullptr, FindButtonWithAccessibleName(
+ popup, l10n_util::GetStringUTF16(
+ IDS_DAO_FORCE_DARK_MODE_LABEL)));
+ EXPECT_EQ(nullptr, FindDescendantViewOfClass(popup));
+
+ BrowserRemovedWaiter removed(little_dao_browser);
+ little_dao_browser->window()->Close();
+ removed.Wait();
+}
+
IN_PROC_BROWSER_TEST_F(DaoLittleDaoViewBrowserTest,
SiteCenterHideClearsHoveredActionBackground) {
Browser* little_dao_browser = dao::DaoLittleDaoController::OpenInLittleDao(
diff --git a/src/dao/browser/ui/views/dao_control_center_more_menu.cc b/src/dao/browser/ui/views/dao_control_center_more_menu.cc
index 01f595dd..b4c8566a 100644
--- a/src/dao/browser/ui/views/dao_control_center_more_menu.cc
+++ b/src/dao/browser/ui/views/dao_control_center_more_menu.cc
@@ -91,6 +91,16 @@ DaoControlCenterMoreMenu::DaoControlCenterMoreMenu(
back_btn->SetBorder(views::CreateEmptyBorder(gfx::Insets::VH(4, 12)));
AddChildView(std::move(back_btn));
+ // Share button
+ {
+ auto btn = std::make_unique(
+ l10n_util::GetStringUTF16(IDS_DAO_CONTROL_CENTER_SHARE),
+ base::BindRepeating(&DaoControlCenterMoreMenu::OnShareClicked,
+ base::Unretained(this)));
+ share_button_ = btn.get();
+ AddChildView(static_cast(btn.release()));
+ }
+
// Clear Cache button
{
auto btn = std::make_unique(
@@ -131,6 +141,13 @@ void DaoControlCenterMoreMenu::OnBackClicked() {
}
}
+void DaoControlCenterMoreMenu::OnShareClicked() {
+ if (popup_) {
+ popup_->ShareCurrentPage(share_button_ ? share_button_->GetBoundsInScreen()
+ : GetBoundsInScreen());
+ }
+}
+
void DaoControlCenterMoreMenu::OnClearCacheClicked() {
if (!popup_ || !popup_->browser()) {
return;
diff --git a/src/dao/browser/ui/views/dao_control_center_more_menu.h b/src/dao/browser/ui/views/dao_control_center_more_menu.h
index e27a9a63..1bbce37f 100644
--- a/src/dao/browser/ui/views/dao_control_center_more_menu.h
+++ b/src/dao/browser/ui/views/dao_control_center_more_menu.h
@@ -17,7 +17,7 @@ namespace dao {
class DaoControlCenterPopup;
-// Sub-panel with additional actions: clear cache, clear cookies.
+// Sub-panel with additional actions: share, clear cache, clear cookies.
class DaoControlCenterMoreMenu : public views::View {
METADATA_HEADER(DaoControlCenterMoreMenu, views::View)
@@ -32,10 +32,12 @@ class DaoControlCenterMoreMenu : public views::View {
private:
void OnBackClicked();
+ void OnShareClicked();
void OnClearCacheClicked();
void OnClearCookiesClicked();
raw_ptr popup_;
+ raw_ptr share_button_ = nullptr;
raw_ptr clear_cache_button_ = nullptr;
raw_ptr clear_cookies_button_ = nullptr;
diff --git a/src/dao/browser/ui/views/dao_control_center_popup.cc b/src/dao/browser/ui/views/dao_control_center_popup.cc
index cd838e4a..0af386d0 100644
--- a/src/dao/browser/ui/views/dao_control_center_popup.cc
+++ b/src/dao/browser/ui/views/dao_control_center_popup.cc
@@ -4,7 +4,7 @@
#include "dao/browser/ui/views/dao_control_center_popup.h"
-#include "third_party/blink/public/common/input/web_input_event.h"
+#include "build/build_config.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "content/public/browser/web_contents.h"
@@ -25,6 +25,15 @@
#include "ui/views/layout/box_layout.h"
#include "ui/views/view_utils.h"
+#if BUILDFLAG(IS_MAC)
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
+#include "dao/browser/ui/views/dao_native_share_mac.h"
+#include "ui/views/widget/widget.h"
+#endif
+
+#include "third_party/blink/public/common/input/web_input_event.h"
+
namespace dao {
namespace {
@@ -147,6 +156,9 @@ void DaoControlCenterPopup::ApplyTheme() {
separator_->SetBackground(
views::CreateSolidBackground(SeparatorColor()));
}
+ if (utility_section_) {
+ utility_section_->Refresh();
+ }
if (more_menu_) {
more_menu_->ApplyTheme();
}
@@ -245,10 +257,40 @@ void DaoControlCenterPopup::ShowMainPanel() {
if (card_->children().size() > 1) {
card_->children()[1]->SetVisible(true);
}
+ if (utility_section_) {
+ utility_section_->Refresh();
+ }
InvalidateLayout();
SchedulePaint();
}
+void DaoControlCenterPopup::ShareCurrentPage(const gfx::Rect& anchor_rect) {
+#if BUILDFLAG(IS_MAC)
+ if (!browser_) {
+ return;
+ }
+ auto* web_contents = browser_->tab_strip_model()->GetActiveWebContents();
+ if (!web_contents) {
+ return;
+ }
+
+ std::string url = web_contents->GetVisibleURL().spec();
+ std::string title = web_contents->GetTitle().empty()
+ ? url
+ : base::UTF16ToUTF8(web_contents->GetTitle());
+
+ BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser_);
+ if (!browser_view || !browser_view->GetWidget()) {
+ return;
+ }
+
+ dao::ShowNativeShareMac(url, title, browser_view->GetWidget()->GetNativeView(),
+ anchor_rect);
+#else
+ (void)anchor_rect;
+#endif
+}
+
void DaoControlCenterPopup::OnTabStripModelChanged(
TabStripModel* tab_strip_model,
const TabStripModelChange& change,
diff --git a/src/dao/browser/ui/views/dao_control_center_popup.h b/src/dao/browser/ui/views/dao_control_center_popup.h
index 0582f4e8..391a9e69 100644
--- a/src/dao/browser/ui/views/dao_control_center_popup.h
+++ b/src/dao/browser/ui/views/dao_control_center_popup.h
@@ -16,6 +16,10 @@
class Browser;
+namespace gfx {
+class Rect;
+}
+
namespace dao {
class DaoControlCenterExtensionsSection;
@@ -47,6 +51,8 @@ class DaoControlCenterPopup : public views::View,
void ShowMoreMenu();
// Return to the main panel from a sub-panel.
void ShowMainPanel();
+ // Show the native share picker for the active page.
+ void ShareCurrentPage(const gfx::Rect& anchor_rect);
Browser* browser() const { return browser_; }
diff --git a/src/dao/browser/ui/views/dao_control_center_utility_section.cc b/src/dao/browser/ui/views/dao_control_center_utility_section.cc
index ed80534f..e82750d4 100644
--- a/src/dao/browser/ui/views/dao_control_center_utility_section.cc
+++ b/src/dao/browser/ui/views/dao_control_center_utility_section.cc
@@ -6,6 +6,8 @@
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
+#include "cc/paint/paint_flags.h"
+#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
@@ -14,6 +16,7 @@
#include "chrome/browser/ui/views/page_info/page_info_bubble_view.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/web_contents.h"
+#include "dao/browser/dao_pref_names.h"
#include "dao/browser/strings/grit/dao_strings.h"
#include "dao/browser/ui/views/dao_colors.h"
#include "dao/browser/ui/views/dao_control_center_popup.h"
@@ -24,6 +27,9 @@
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/rect_f.h"
+#include "third_party/skia/include/core/SkMatrix.h"
+#include "third_party/skia/include/core/SkPath.h"
+#include "third_party/skia/include/utils/SkParsePath.h"
#include "ui/views/background.h"
#include "ui/views/bubble/bubble_border.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
@@ -33,19 +39,37 @@
#include "ui/views/widget/widget.h"
#include "url/gurl.h"
-#if BUILDFLAG(IS_MAC)
-#include "dao/browser/ui/views/dao_native_share_mac.h"
-#endif
-
namespace dao {
namespace {
-constexpr int kUtilButtonSize = 56;
+constexpr int kUtilButtonSize = 48;
constexpr int kUtilIconSize = 18;
constexpr int kUtilCornerRadius = 10;
constexpr int kUtilFontSize = 10;
+void DrawFilledMoonIcon(gfx::Canvas* canvas,
+ const gfx::RectF& rect,
+ SkColor color) {
+ const float scale = rect.width() / 24.0f;
+ SkPath path;
+ SkParsePath::FromSVGString(
+ "M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46."
+ "402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803."
+ "401",
+ &path);
+ SkMatrix matrix;
+ matrix.setScale(scale, scale);
+ matrix.postTranslate(rect.x(), rect.y());
+ path = path.makeTransform(matrix);
+
+ cc::PaintFlags flags;
+ flags.setAntiAlias(true);
+ flags.setStyle(cc::PaintFlags::kFill_Style);
+ flags.setColor(color);
+ canvas->DrawPath(path, flags);
+}
+
// A utility button with a Lucide icon and a text label.
class UtilityButton : public views::Button {
public:
@@ -66,29 +90,49 @@ class UtilityButton : public views::Button {
icon_spacer->SetCanProcessEventsWithinSubtree(false);
AddChildView(icon_spacer.release());
- auto* label =
- AddChildView(std::make_unique(label_text));
- label->SetFontList(gfx::FontList({"system-ui"}, gfx::Font::NORMAL,
- kUtilFontSize,
- gfx::Font::Weight::NORMAL));
- label->SetEnabledColor(ControlCenterLabelColor());
- label->SetCanProcessEventsWithinSubtree(false);
+ label_ = AddChildView(std::make_unique(label_text));
+ label_->SetFontList(gfx::FontList({"system-ui"}, gfx::Font::NORMAL,
+ kUtilFontSize,
+ gfx::Font::Weight::NORMAL));
+ label_->SetEnabledColor(ControlCenterLabelColor());
+ label_->SetCanProcessEventsWithinSubtree(false);
SetPreferredSize(gfx::Size(kUtilButtonSize, kUtilButtonSize));
SetInstallFocusRingOnFocus(false);
SetAccessibleName(label_text);
}
+ void SetSelected(bool selected) {
+ if (selected_ == selected) {
+ return;
+ }
+ selected_ = selected;
+ RefreshBackground();
+ SchedulePaint();
+ }
+
+ void SetVisualEnabled(bool enabled) {
+ SetEnabled(enabled);
+ label_->SetEnabled(enabled);
+ label_->SetEnabledColor(enabled ? ControlCenterLabelColor()
+ : ControlCenterSecondaryTextColor());
+ RefreshBackground();
+ SchedulePaint();
+ }
+
void OnMouseEntered(const ui::MouseEvent& event) override {
Button::OnMouseEntered(event);
- SetBackground(views::CreateRoundedRectBackground(
- SuggestionHover(), kUtilCornerRadius));
- SchedulePaint();
+ if (GetEnabled()) {
+ hovered_ = true;
+ RefreshBackground();
+ SchedulePaint();
+ }
}
void OnMouseExited(const ui::MouseEvent& event) override {
Button::OnMouseExited(event);
- SetBackground(nullptr);
+ hovered_ = false;
+ RefreshBackground();
SchedulePaint();
}
@@ -96,15 +140,43 @@ class UtilityButton : public views::Button {
// Draw the icon centered in the spacer area (first child).
if (!children().empty()) {
gfx::Rect icon_bounds = children().front()->bounds();
- DrawLucideIcon(canvas, icon_,
- gfx::RectF(icon_bounds.x(), icon_bounds.y(),
- kUtilIconSize, kUtilIconSize),
- ControlCenterIconMuted());
+ const gfx::RectF icon_rect(icon_bounds.x(), icon_bounds.y(),
+ kUtilIconSize, kUtilIconSize);
+ const SkColor icon_color =
+ GetEnabled() ? (selected_ ? ControlCenterIconDefault()
+ : ControlCenterIconMuted())
+ : ControlCenterSecondaryTextColor();
+ if (selected_ && icon_ == LucideIcon::kMoon) {
+ DrawFilledMoonIcon(canvas, icon_rect, icon_color);
+ } else {
+ DrawLucideIcon(canvas, icon_, icon_rect, icon_color);
+ }
}
}
private:
+ void RefreshBackground() {
+ if (!GetEnabled()) {
+ SetBackground(nullptr);
+ return;
+ }
+ if (hovered_) {
+ SetBackground(views::CreateRoundedRectBackground(
+ SuggestionHover(), kUtilCornerRadius));
+ return;
+ }
+ if (selected_) {
+ SetBackground(views::CreateRoundedRectBackground(
+ ControlCenterActiveBg(), kUtilCornerRadius));
+ return;
+ }
+ SetBackground(nullptr);
+ }
+
LucideIcon icon_;
+ raw_ptr label_ = nullptr;
+ bool selected_ = false;
+ bool hovered_ = false;
};
} // namespace
@@ -120,14 +192,6 @@ DaoControlCenterUtilitySection::DaoControlCenterUtilitySection(
layout->set_main_axis_alignment(
views::BoxLayout::MainAxisAlignment::kCenter);
- AddChildView(static_cast(
- std::make_unique(
- u"Share", LucideIcon::kShare,
- base::BindRepeating(
- &DaoControlCenterUtilitySection::OnShareClicked,
- base::Unretained(this)))
- .release()));
-
AddChildView(static_cast(
std::make_unique(
u"QR Code", LucideIcon::kQrCode,
@@ -153,6 +217,19 @@ DaoControlCenterUtilitySection::DaoControlCenterUtilitySection(
base::Unretained(this)))
.release()));
+ {
+ auto button = std::make_unique(
+ l10n_util::GetStringUTF16(IDS_DAO_FORCE_DARK_MODE_SHORT_LABEL),
+ LucideIcon::kMoon,
+ base::BindRepeating(
+ &DaoControlCenterUtilitySection::OnForceDarkModeClicked,
+ base::Unretained(this)));
+ button->SetAccessibleName(
+ l10n_util::GetStringUTF16(IDS_DAO_FORCE_DARK_MODE_LABEL));
+ force_dark_mode_button_ = button.get();
+ AddChildView(static_cast(button.release()));
+ }
+
AddChildView(static_cast(
std::make_unique(
u"More", LucideIcon::kEllipsis,
@@ -160,34 +237,14 @@ DaoControlCenterUtilitySection::DaoControlCenterUtilitySection(
&DaoControlCenterUtilitySection::OnMoreClicked,
base::Unretained(this)))
.release()));
+
+ RefreshForceDarkModeState();
}
DaoControlCenterUtilitySection::~DaoControlCenterUtilitySection() = default;
-void DaoControlCenterUtilitySection::OnShareClicked() {
-#if BUILDFLAG(IS_MAC)
- if (!popup_ || !popup_->browser()) {
- return;
- }
- auto* web_contents =
- popup_->browser()->tab_strip_model()->GetActiveWebContents();
- if (!web_contents) {
- return;
- }
- std::string url = web_contents->GetVisibleURL().spec();
- std::string title = web_contents->GetTitle().empty()
- ? url
- : base::UTF16ToUTF8(web_contents->GetTitle());
-
- BrowserView* browser_view =
- BrowserView::GetBrowserViewForBrowser(popup_->browser());
- if (!browser_view || !browser_view->GetWidget()) {
- return;
- }
- gfx::NativeView native_view = browser_view->GetWidget()->GetNativeView();
- gfx::Rect anchor_rect = GetBoundsInScreen();
- dao::ShowNativeShareMac(url, title, native_view, anchor_rect);
-#endif
+void DaoControlCenterUtilitySection::Refresh() {
+ RefreshForceDarkModeState();
}
void DaoControlCenterUtilitySection::OnQrClicked() {
@@ -219,6 +276,33 @@ void DaoControlCenterUtilitySection::OnMiniDaoClicked() {
IDS_DAO_CONTROL_CENTER_MINI_DAO_FAILED_TOAST));
}
+void DaoControlCenterUtilitySection::OnForceDarkModeClicked() {
+ if (!popup_ || !popup_->browser() || !IsForceDarkModeAvailable()) {
+ RefreshForceDarkModeState();
+ return;
+ }
+
+ Profile* profile = popup_->browser()->profile();
+ SetForceDarkModeUserEnabled(profile, !IsForceDarkModeUserEnabled(profile));
+ RefreshForceDarkModeState();
+}
+
+void DaoControlCenterUtilitySection::RefreshForceDarkModeState() {
+ if (!force_dark_mode_button_) {
+ return;
+ }
+
+ auto* button = static_cast(force_dark_mode_button_.get());
+ Profile* profile =
+ popup_ && popup_->browser() ? popup_->browser()->profile() : nullptr;
+ const bool available = IsForceDarkModeAvailable();
+ button->SetSelected(IsForceDarkModeUserEnabled(profile));
+ button->SetVisualEnabled(available);
+ button->SetTooltipText(l10n_util::GetStringUTF16(
+ available ? IDS_DAO_FORCE_DARK_MODE_TOOLTIP
+ : IDS_DAO_FORCE_DARK_MODE_DISABLED_TOOLTIP));
+}
+
void DaoControlCenterUtilitySection::OnLockClicked() {
if (!popup_ || !popup_->browser()) {
return;
diff --git a/src/dao/browser/ui/views/dao_control_center_utility_section.h b/src/dao/browser/ui/views/dao_control_center_utility_section.h
index a9a8d18c..c5d3be77 100644
--- a/src/dao/browser/ui/views/dao_control_center_utility_section.h
+++ b/src/dao/browser/ui/views/dao_control_center_utility_section.h
@@ -8,11 +8,15 @@
#include "base/memory/raw_ptr.h"
#include "ui/views/view.h"
+namespace views {
+class Button;
+} // namespace views
+
namespace dao {
class DaoControlCenterPopup;
-// A row of utility buttons: Share, QR Code, Mini Dao, Lock (Page Info), More.
+// A row of utility buttons: QR Code, Mini Dao, Security, dark mode, More.
class DaoControlCenterUtilitySection : public views::View {
METADATA_HEADER(DaoControlCenterUtilitySection, views::View)
@@ -24,14 +28,18 @@ class DaoControlCenterUtilitySection : public views::View {
const DaoControlCenterUtilitySection&) = delete;
~DaoControlCenterUtilitySection() override;
+ void Refresh();
+
private:
- void OnShareClicked();
void OnQrClicked();
void OnMiniDaoClicked();
+ void OnForceDarkModeClicked();
+ void RefreshForceDarkModeState();
void OnLockClicked();
void OnMoreClicked();
raw_ptr popup_;
+ raw_ptr force_dark_mode_button_ = nullptr;
};
} // namespace dao
diff --git a/src/dao/browser/ui/views/dao_lucide_icons.cc b/src/dao/browser/ui/views/dao_lucide_icons.cc
index f7b9e186..f30fa05f 100644
--- a/src/dao/browser/ui/views/dao_lucide_icons.cc
+++ b/src/dao/browser/ui/views/dao_lucide_icons.cc
@@ -348,6 +348,20 @@ void DrawSparkles(gfx::Canvas* canvas,
canvas->DrawCircle(gfx::PointF(ox + 4 * s, oy + 20 * s), 2.0f * s, flags);
}
+// Lucide "moon"
+void DrawMoon(gfx::Canvas* canvas,
+ float s,
+ float ox,
+ float oy,
+ const cc::PaintFlags& flags) {
+ DrawSvgPath(
+ canvas,
+ "M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46."
+ "402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803."
+ "401",
+ s, ox, oy, flags);
+}
+
} // namespace
void DrawLucideIcon(gfx::Canvas* canvas,
@@ -445,6 +459,12 @@ void DrawLucideIcon(gfx::Canvas* canvas,
case LucideIcon::kSparkles:
DrawSparkles(canvas, s, ox, oy, flags);
break;
+ case LucideIcon::kMoon: {
+ cc::PaintFlags moon_flags = flags;
+ moon_flags.setStrokeWidth(2.0f * s);
+ DrawMoon(canvas, s, ox, oy, moon_flags);
+ break;
+ }
}
}
diff --git a/src/dao/browser/ui/views/dao_lucide_icons.h b/src/dao/browser/ui/views/dao_lucide_icons.h
index 61d04bfe..7938936f 100644
--- a/src/dao/browser/ui/views/dao_lucide_icons.h
+++ b/src/dao/browser/ui/views/dao_lucide_icons.h
@@ -43,6 +43,7 @@ enum class LucideIcon {
kSquareArrowDownLeft,
kBot,
kSparkles,
+ kMoon,
};
// Draw a Lucide icon into |rect| on |canvas| using |color|.
diff --git a/src/patches/chrome/browser/chrome_content_browser_client_force_dark_mode.cc.patch b/src/patches/chrome/browser/chrome_content_browser_client_force_dark_mode.cc.patch
new file mode 100644
index 00000000..0670620c
--- /dev/null
+++ b/src/patches/chrome/browser/chrome_content_browser_client_force_dark_mode.cc.patch
@@ -0,0 +1,54 @@
+diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
+index 0000000000..0000000000 100644
+--- a/chrome/browser/chrome_content_browser_client.cc
++++ b/chrome/browser/chrome_content_browser_client.cc
+@@ -355,6 +355,7 @@
+ #include "content/public/common/content_switches.h"
+ #include "content/public/common/origin_util.h"
+ #include "content/public/common/url_utils.h"
++#include "dao/browser/dao_pref_names.h"
+ #include "content/public/common/window_container_type.mojom-shared.h"
+ #include "device/vr/buildflags/buildflags.h"
+ #include "extensions/browser/browser_frame_context_data.h"
+@@ -4851,6 +4852,8 @@ void ChromeContentBrowserClient::OverrideWebPreferences(
+ web_prefs->root_scrollbar_theme_color =
+ GetRootScrollbarThemeColor(web_contents);
+
++ dao::ApplyForceDarkModePreferences(profile, web_prefs);
++
+ web_prefs->translate_service_available = TranslateService::IsAvailable(prefs);
+
+ std::optional style =
+@@ -4977,6 +4980,21 @@ bool ChromeContentBrowserClient::OverrideWebPreferencesAfterNavigation(
+ }
+ #endif
+
++ const bool old_force_dark_mode_enabled =
++ web_prefs->force_dark_mode_enabled;
++ const auto old_dao_preferred_color_scheme =
++ web_prefs->preferred_color_scheme;
++ const auto old_dao_preferred_root_scrollbar_color_scheme =
++ web_prefs->preferred_root_scrollbar_color_scheme;
++ dao::ApplyForceDarkModePreferences(
++ Profile::FromBrowserContext(web_contents->GetBrowserContext()),
++ web_prefs);
++ prefs_changed |=
++ web_prefs->force_dark_mode_enabled != old_force_dark_mode_enabled ||
++ web_prefs->preferred_color_scheme != old_dao_preferred_color_scheme ||
++ web_prefs->preferred_root_scrollbar_color_scheme !=
++ old_dao_preferred_root_scrollbar_color_scheme;
++
+ #if BUILDFLAG(IS_CHROMEOS)
+ const bool subapps_apis_require_user_gesture_and_authorization =
+ SubAppsAPIsRequireUserGestureAndAuthorization(web_contents);
+@@ -5006,6 +5024,9 @@ bool ChromeContentBrowserClient::
+ GetPreferredColorScheme(prefs, main_frame_site.GetSiteURL(),
+ &web_contents) !=
+ std::tie(prefs.preferred_color_scheme,
+ prefs.preferred_root_scrollbar_color_scheme) ||
++ dao::IsForceDarkModeEffective(Profile::FromBrowserContext(
++ web_contents.GetBrowserContext())) !=
++ prefs.force_dark_mode_enabled ||
+ GetRootScrollbarThemeColor(&web_contents) !=
+ prefs.root_scrollbar_theme_color;
+ }
diff --git a/website/components/FeatureGrid.module.css b/website/components/FeatureGrid.module.css
index 38ba40c0..e279a56e 100644
--- a/website/components/FeatureGrid.module.css
+++ b/website/components/FeatureGrid.module.css
@@ -48,6 +48,16 @@
background: var(--surface-hover);
}
+.highlight {
+ background: var(--accent-subtle);
+ border-color: var(--accent-dim);
+ box-shadow: inset 0 0 0 1px var(--accent-dim);
+}
+
+.highlight:hover {
+ background: var(--accent-subtle);
+}
+
.icon {
color: var(--accent);
}
@@ -65,3 +75,15 @@
color: var(--text-secondary);
margin: 0;
}
+
+@media (min-width: 641px) {
+ .highlight {
+ grid-column: span 2;
+ }
+}
+
+@media (max-width: 640px) {
+ .highlight {
+ grid-column: auto;
+ }
+}
diff --git a/website/components/FeatureGrid.tsx b/website/components/FeatureGrid.tsx
index 269e27d6..c6c10e29 100644
--- a/website/components/FeatureGrid.tsx
+++ b/website/components/FeatureGrid.tsx
@@ -5,9 +5,16 @@ interface GridItem {
icon: IconName;
title: string;
body: string;
+ highlight?: boolean;
}
const ITEMS: GridItem[] = [
+ {
+ icon: 'moon',
+ title: 'Force Dark Mode',
+ body: 'Darken bright sites with Chromium Auto Dark Mode, only when your system is already dark.',
+ highlight: true,
+ },
{
icon: 'picture-in-picture-2',
title: 'Picture-in-Picture',
@@ -46,7 +53,7 @@ export function FeatureGrid() {
And there's more.
{ITEMS.map((it) => (
- -
+
-
{it.title}
{it.body}
diff --git a/website/components/ui/LucideIcon.tsx b/website/components/ui/LucideIcon.tsx
index 06dd0e6b..113ae636 100644
--- a/website/components/ui/LucideIcon.tsx
+++ b/website/components/ui/LucideIcon.tsx
@@ -25,6 +25,7 @@ export type IconName =
| 'sliders-horizontal'
| 'square'
| 'palette'
+ | 'moon'
| 'globe'
| 'menu'
| 'x'
@@ -151,6 +152,11 @@ const ICON_PATHS: Record = {
>
),
+ moon: (
+ <>
+
+ >
+ ),
globe: (
<>
diff --git a/website/lib/__tests__/homepage-features.test.ts b/website/lib/__tests__/homepage-features.test.ts
new file mode 100644
index 00000000..95fa15c5
--- /dev/null
+++ b/website/lib/__tests__/homepage-features.test.ts
@@ -0,0 +1,18 @@
+import { readFileSync } from 'node:fs';
+import { dirname, resolve } from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+import { describe, expect, it } from 'vitest';
+
+const featureGridSource = readFileSync(
+ resolve(dirname(fileURLToPath(import.meta.url)), '../../components/FeatureGrid.tsx'),
+ 'utf8',
+);
+
+describe('homepage feature grid', () => {
+ it('highlights Force Dark Mode as a feature', () => {
+ expect(featureGridSource).toContain('Force Dark Mode');
+ expect(featureGridSource).toContain('Chromium Auto Dark Mode');
+ expect(featureGridSource).toContain("icon: 'moon'");
+ });
+});