[Merge M73] [NTP] Refresh dark mode when system dark mode is changed

Refresh dark mode styling on system dark mode change. This allows the
NTP to update without needing to restart Chrome.

If dark mode is changed, the frontend is notified to refresh the
current theme.

Also add a dark mode delimiter to the MV tile icon URL. This allows
separate icons to be generated for dark/light mode respectively.

Bug: 925417
Change-Id: Ide1935a1cd019c9c3ff6709de690a92b1bfc4705
Reviewed-on: https://chromium-review.googlesource.com/c/1437894
Commit-Queue: Kristi Park <kristipark@chromium.org>
Reviewed-by: Kyle Milka <kmilka@chromium.org>
Reviewed-by: Chris Palmer <palmer@chromium.org>
Cr-Original-Commit-Position: refs/heads/master@{#626770}(cherry picked from commit ff3cf95ea71dc2ccc8319b5cf4b9ba4662053a6e)
Reviewed-on: https://chromium-review.googlesource.com/c/1450398
Reviewed-by: Kristi Park <kristipark@chromium.org>
Cr-Commit-Position: refs/branch-heads/3683@{#125}
Cr-Branched-From: e51029943e0a38dd794b73caaf6373d5496ae783-refs/heads/master@{#625896}
diff --git a/chrome/browser/resources/local_ntp/local_ntp.js b/chrome/browser/resources/local_ntp/local_ntp.js
index e0ac8f4..d585c33 100644
--- a/chrome/browser/resources/local_ntp/local_ntp.js
+++ b/chrome/browser/resources/local_ntp/local_ntp.js
@@ -256,6 +256,13 @@
 
 
 /**
+ * True if dark mode is enabled.
+ * @type {boolean}
+ */
+let isDarkModeEnabled = false;
+
+
+/**
  * Returns a timeout that can be executed early.
  * @param {!Function} timeout The timeout function.
  * @param {number} delay The timeout delay.
@@ -317,7 +324,7 @@
   // custom background set).
   if (!info || info.usingDefaultTheme && !info.customBackgroundConfigured) {
     // Dark mode is always considered a dark theme.
-    return configData.isDarkModeEnabled;
+    return isDarkModeEnabled;
   }
 
   // Heuristic: light text implies dark theme.
@@ -338,10 +345,15 @@
     return;
   }
 
+  const useDarkMode = !!info.usingDarkMode;
+  if (isDarkModeEnabled != useDarkMode) {
+    document.documentElement.setAttribute('darkmode', useDarkMode);
+    isDarkModeEnabled = useDarkMode;
+  }
+
   var background = [
-    (configData.isDarkModeEnabled ?
-         DARK_MODE_BACKGROUND_COLOR :
-         convertToRGBAColor(info.backgroundColorRgba)),
+    (isDarkModeEnabled ? DARK_MODE_BACKGROUND_COLOR :
+                         convertToRGBAColor(info.backgroundColorRgba)),
     info.imageUrl, info.imageTiling, info.imageHorizontalAlignment,
     info.imageVerticalAlignment
   ].join(' ').trim();
@@ -353,8 +365,8 @@
   }
 
   // Dark mode uses a white Google logo.
-  const useWhiteLogo = info.alternateLogo ||
-      (info.usingDefaultTheme && configData.isDarkModeEnabled);
+  const useWhiteLogo =
+      info.alternateLogo || (info.usingDefaultTheme && isDarkModeEnabled);
   document.body.classList.toggle(CLASSES.ALTERNATE_LOGO, useWhiteLogo);
   const isNonWhiteBackground = !WHITE_BACKGROUND_COLORS.includes(background);
   document.body.classList.toggle(CLASSES.NON_WHITE_BG, isNonWhiteBackground);
@@ -424,6 +436,7 @@
   var message = {cmd: 'updateTheme'};
   message.isThemeDark = isThemeDark;
   message.isUsingTheme = !info.usingDefaultTheme;
+  message.isDarkMode = !!info.usingDarkMode;
 
   var titleColor = NTP_DESIGN.titleColor;
   if (!info.usingDefaultTheme && info.textColorRgba) {
@@ -465,9 +478,18 @@
  * @private
  */
 function onThemeChange() {
+  // Save the current dark mode state to check if dark mode has changed.
+  const usingDarkMode = isDarkModeEnabled;
+
   renderTheme();
   renderOneGoogleBarTheme();
   sendThemeInfoToMostVisitedIframe();
+
+  // If dark mode has been changed, refresh the MV tiles to render the
+  // appropriate icon.
+  if (usingDarkMode != isDarkModeEnabled) {
+    reloadTiles();
+  }
 }
 
 
@@ -577,7 +599,7 @@
   let maxNumTiles = configData.isGooglePage ? MAX_NUM_TILES_CUSTOM_LINKS :
                                               MAX_NUM_TILES_MOST_VISITED;
   for (var i = 0; i < Math.min(maxNumTiles, pages.length); ++i) {
-    cmds.push({cmd: 'tile', rid: pages[i].rid});
+    cmds.push({cmd: 'tile', rid: pages[i].rid, darkMode: isDarkModeEnabled});
   }
   cmds.push({cmd: 'show'});
 
@@ -1020,9 +1042,6 @@
   ntpApiHandle.onthemechange = onThemeChange;
   ntpApiHandle.onmostvisitedchange = onMostVisitedChange;
 
-  if (configData.isDarkModeEnabled) {
-    document.documentElement.setAttribute('darkmode', true);
-  }
   renderTheme();
 
   var searchboxApiHandle = embeddedSearchApiHandle.searchBox;
diff --git a/chrome/browser/resources/local_ntp/most_visited_single.js b/chrome/browser/resources/local_ntp/most_visited_single.js
index 9271b92..d4752fc 100644
--- a/chrome/browser/resources/local_ntp/most_visited_single.js
+++ b/chrome/browser/resources/local_ntp/most_visited_single.js
@@ -343,6 +343,7 @@
   document.body.style.setProperty('--tile-title-color', info.tileTitleColor);
   document.body.classList.toggle('dark-theme', info.isThemeDark);
   document.body.classList.toggle('using-theme', info.isUsingTheme);
+  document.documentElement.setAttribute('darkmode', info.isDarkMode);
 
   // Reduce font weight on the default(white) background for Mac and CrOS.
   document.body.classList.toggle('mac-chromeos',
@@ -482,6 +483,9 @@
     }
 
     data.tid = data.rid;
+    // Use a dark icon if dark mode is enabled. Keep value in sync with
+    // NtpIconSource.
+    data.dark = args.darkMode ? 'dark/' : '';
     if (!data.faviconUrl) {
       data.faviconUrl = 'chrome-search://favicon/size/16@' +
           window.devicePixelRatio + 'x/' + data.renderViewId + '/' + data.tid;
@@ -729,7 +733,7 @@
     fi.title = '';
     fi.alt = '';
     fi.src = 'chrome-search://ntpicon/size/24@' + window.devicePixelRatio +
-        'x/' + data.url;
+        'x/' + data.dark + data.url;
     loadedCounter += 1;
     fi.addEventListener('load', function(ev) {
       // Store the type for a potential later navigation.
diff --git a/chrome/browser/search/instant_service.cc b/chrome/browser/search/instant_service.cc
index 03d417f..ed7ffe6 100644
--- a/chrome/browser/search/instant_service.cc
+++ b/chrome/browser/search/instant_service.cc
@@ -9,6 +9,7 @@
 #include "base/bind.h"
 #include "base/files/file_util.h"
 #include "base/path_service.h"
+#include "base/scoped_observer.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/post_task.h"
@@ -49,6 +50,7 @@
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/url_data_source.h"
 #include "ui/gfx/color_utils.h"
+#include "ui/native_theme/native_theme_observer.h"
 
 namespace {
 
@@ -162,6 +164,48 @@
   base::RepeatingCallback<void(bool)> callback_;
 };
 
+// Keeps track of any changes to system dark mode and notifies InstantService if
+// dark mode has been changed. Use this to check if dark mode is enabled.
+class InstantService::DarkModeHandler : public ui::NativeThemeObserver {
+ public:
+  explicit DarkModeHandler(ui::NativeTheme* theme,
+                           base::RepeatingCallback<void(bool)> callback)
+      : theme_(theme), callback_(std::move(callback)), observer_(this) {
+    using_dark_mode_ = IsDarkModeEnabled();
+    observer_.Add(theme_);
+  }
+
+  bool IsDarkModeEnabled() { return theme_->SystemDarkModeEnabled(); }
+
+  void SetThemeForTesting(ui::NativeTheme* theme) {
+    observer_.RemoveAll();
+
+    theme_ = theme;
+    using_dark_mode_ = IsDarkModeEnabled();
+    observer_.Add(theme_);
+  }
+
+ private:
+  // ui::NativeThemeObserver:
+  void OnNativeThemeUpdated(ui::NativeTheme* observed_theme) override {
+    DCHECK_EQ(observed_theme, theme_);
+
+    bool using_dark_mode = IsDarkModeEnabled();
+    if (using_dark_mode == using_dark_mode_)
+      return;
+
+    using_dark_mode_ = using_dark_mode;
+    callback_.Run(using_dark_mode_);
+  }
+
+  // The theme to query/watch for changes.
+  ui::NativeTheme* theme_;
+  // Whether or not the theme is using dark mode.
+  bool using_dark_mode_;
+  base::RepeatingCallback<void(bool)> callback_;
+  ScopedObserver<ui::NativeTheme, DarkModeHandler> observer_;
+};
+
 InstantService::InstantService(Profile* profile)
     : profile_(profile),
       pref_service_(profile_->GetPrefs()),
@@ -215,6 +259,11 @@
     }
   }
 
+  dark_mode_handler_ = std::make_unique<DarkModeHandler>(
+      ui::NativeTheme::GetInstanceForNativeUi(),
+      base::BindRepeating(&InstantService::OnDarkModeChanged,
+                          weak_ptr_factory_.GetWeakPtr()));
+
   background_service_ = NtpBackgroundServiceFactory::GetForProfile(profile_);
 
   // Listen for theme installation.
@@ -408,6 +457,11 @@
                      weak_ptr_factory_.GetWeakPtr()));
 }
 
+void InstantService::SetDarkModeThemeForTesting(ui::NativeTheme* theme) {
+  if (dark_mode_handler_)
+    dark_mode_handler_->SetThemeForTesting(theme);
+}
+
 void InstantService::Shutdown() {
   process_ids_.clear();
 
@@ -472,6 +526,12 @@
   most_visited_sites_->EnableCustomLinks(is_google);
 }
 
+void InstantService::OnDarkModeChanged(bool dark_mode) {
+  if (theme_info_)
+    theme_info_->using_dark_mode = dark_mode;
+  UpdateThemeInfo();
+}
+
 void InstantService::OnURLsAvailable(
     const std::map<ntp_tiles::SectionType, ntp_tiles::NTPTilesVector>&
         sections) {
@@ -533,6 +593,8 @@
   theme_info_->using_default_theme =
       theme_service->UsingDefaultTheme() || theme_service->UsingSystemTheme();
 
+  theme_info_->using_dark_mode = dark_mode_handler_->IsDarkModeEnabled();
+
   // Get theme colors.
   const ui::ThemeProvider& theme_provider =
       ThemeService::GetThemeProviderForProfile(profile_);
diff --git a/chrome/browser/search/instant_service.h b/chrome/browser/search/instant_service.h
index ca2ad8f..b1e461f 100644
--- a/chrome/browser/search/instant_service.h
+++ b/chrome/browser/search/instant_service.h
@@ -25,6 +25,7 @@
 #include "components/prefs/pref_registry_simple.h"
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
+#include "ui/native_theme/native_theme.h"
 #include "url/gurl.h"
 
 #if defined(OS_ANDROID)
@@ -131,6 +132,10 @@
   // Used for testing.
   ThemeBackgroundInfo* GetThemeInfoForTesting() { return theme_info_.get(); }
 
+  // Used for testing.
+  void SetDarkModeThemeForTesting(ui::NativeTheme* theme);
+
+  // Used for testing.
   void AddValidBackdropUrlForTesting(const GURL& url) const;
 
   // Check if a custom background has been set by the user.
@@ -139,6 +144,8 @@
  private:
   class SearchProviderObserver;
 
+  class DarkModeHandler;
+
   friend class InstantExtendedTest;
   friend class InstantUnitTestBase;
 
@@ -161,6 +168,10 @@
   // search provider is not Google.
   void OnSearchProviderChanged(bool is_google);
 
+  // Called when dark mode changes. Updates current theme info as necessary and
+  // notifies that the theme has changed.
+  void OnDarkModeChanged(bool dark_mode);
+
   // ntp_tiles::MostVisitedSites::Observer implementation.
   void OnURLsAvailable(
       const std::map<ntp_tiles::SectionType, ntp_tiles::NTPTilesVector>&
@@ -222,6 +233,9 @@
   // Keeps track of any changes in search engine provider. May be null.
   std::unique_ptr<SearchProviderObserver> search_provider_observer_;
 
+  // Keeps track of any changes to system dark mode.
+  std::unique_ptr<DarkModeHandler> dark_mode_handler_;
+
   PrefChangeRegistrar pref_change_registrar_;
 
   PrefService* pref_service_;
diff --git a/chrome/browser/search/instant_service_unittest.cc b/chrome/browser/search/instant_service_unittest.cc
index edfe9ee..ebed385 100644
--- a/chrome/browser/search/instant_service_unittest.cc
+++ b/chrome/browser/search/instant_service_unittest.cc
@@ -22,6 +22,7 @@
 #include "components/ntp_tiles/section_type.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/native_theme/test_native_theme.h"
 #include "url/gurl.h"
 
 namespace {
@@ -422,3 +423,38 @@
   EXPECT_EQ(GURL(), theme_info->custom_background_url);
   EXPECT_FALSE(instant_service_->IsCustomBackgroundSet());
 }
+
+class InstantServiceThemeTest : public InstantServiceTest {
+ public:
+  InstantServiceThemeTest() {}
+  ~InstantServiceThemeTest() override {}
+
+  ui::TestNativeTheme* theme() { return &theme_; }
+
+ private:
+  ui::TestNativeTheme theme_;
+
+  DISALLOW_COPY_AND_ASSIGN(InstantServiceThemeTest);
+};
+
+TEST_F(InstantServiceThemeTest, DarkModeHandler) {
+  theme()->SetDarkMode(false);
+  instant_service_->SetDarkModeThemeForTesting(theme());
+  thread_bundle()->RunUntilIdle();
+
+  // Enable dark mode.
+  theme()->SetDarkMode(true);
+  theme()->NotifyObservers();
+  thread_bundle()->RunUntilIdle();
+
+  ThemeBackgroundInfo* theme_info = instant_service_->GetThemeInfoForTesting();
+  EXPECT_TRUE(theme_info->using_dark_mode);
+
+  // Disable dark mode.
+  theme()->SetDarkMode(false);
+  theme()->NotifyObservers();
+  thread_bundle()->RunUntilIdle();
+
+  theme_info = instant_service_->GetThemeInfoForTesting();
+  EXPECT_FALSE(theme_info->using_dark_mode);
+}
diff --git a/chrome/browser/search/local_ntp_source.cc b/chrome/browser/search/local_ntp_source.cc
index 5440ced..33beaef 100644
--- a/chrome/browser/search/local_ntp_source.cc
+++ b/chrome/browser/search/local_ntp_source.cc
@@ -72,10 +72,7 @@
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/resource/resource_bundle.h"
-#include "ui/base/ui_base_features.h"
-#include "ui/base/ui_base_switches.h"
 #include "ui/base/webui/web_ui_util.h"
-#include "ui/native_theme/native_theme.h"
 #include "ui/resources/grit/ui_resources.h"
 #include "url/gurl.h"
 
@@ -594,9 +591,6 @@
     config_data.SetBoolean("isAccessibleBrowser",
                            content::BrowserAccessibilityState::GetInstance()
                                ->IsAccessibleBrowser());
-    config_data.SetBoolean(
-        "isDarkModeEnabled",
-        ui::NativeTheme::GetInstanceForNativeUi()->SystemDarkModeEnabled());
 
     // Serialize the dictionary.
     std::string js_text;
diff --git a/chrome/browser/search/ntp_icon_source.cc b/chrome/browser/search/ntp_icon_source.cc
index 2f0817f..be0918f 100644
--- a/chrome/browser/search/ntp_icon_source.cc
+++ b/chrome/browser/search/ntp_icon_source.cc
@@ -60,6 +60,9 @@
 // Delimiter in the url that looks for the size specification.
 const char kSizeParameter[] = "size/";
 
+// Delimiter in the url for dark mode specification.
+const char kDarkModeParameter[] = "dark/";
+
 // Size of the icon background (gray circle), in dp.
 const int kIconSizeDip = 48;
 
@@ -127,6 +130,15 @@
 
   parsed_index = slash + 1;
 
+  // Parse the dark mode spec (e.g. "dark"), if available. The value is not
+  // used, but is required to generate a new icon for dark mode.
+  if (HasSubstringAt(path, parsed_index, kDarkModeParameter)) {
+    slash = path.find("/", parsed_index);
+    if (slash == std::string::npos)
+      return parsed;
+    parsed_index = slash + 1;
+  }
+
   parsed.url = GURL(path.substr(parsed_index));
   return parsed;
 }
diff --git a/chrome/common/instant_struct_traits.h b/chrome/common/instant_struct_traits.h
index b1c17cc..5fac96a 100644
--- a/chrome/common/instant_struct_traits.h
+++ b/chrome/common/instant_struct_traits.h
@@ -60,6 +60,7 @@
 
 IPC_STRUCT_TRAITS_BEGIN(ThemeBackgroundInfo)
   IPC_STRUCT_TRAITS_MEMBER(using_default_theme)
+  IPC_STRUCT_TRAITS_MEMBER(using_dark_mode)
   IPC_STRUCT_TRAITS_MEMBER(custom_background_url)
   IPC_STRUCT_TRAITS_MEMBER(custom_background_attribution_line_1)
   IPC_STRUCT_TRAITS_MEMBER(custom_background_attribution_line_2)
diff --git a/chrome/common/search/instant_types.cc b/chrome/common/search/instant_types.cc
index d4e907d..aee253b 100644
--- a/chrome/common/search/instant_types.cc
+++ b/chrome/common/search/instant_types.cc
@@ -26,6 +26,7 @@
 
 ThemeBackgroundInfo::ThemeBackgroundInfo()
     : using_default_theme(true),
+      using_dark_mode(false),
       custom_background_url(std::string()),
       custom_background_attribution_line_1(std::string()),
       custom_background_attribution_line_2(std::string()),
@@ -47,6 +48,7 @@
 
 bool ThemeBackgroundInfo::operator==(const ThemeBackgroundInfo& rhs) const {
   return using_default_theme == rhs.using_default_theme &&
+         using_dark_mode == rhs.using_dark_mode &&
          custom_background_url == rhs.custom_background_url &&
          custom_background_attribution_line_1 ==
              rhs.custom_background_attribution_line_1 &&
diff --git a/chrome/common/search/instant_types.h b/chrome/common/search/instant_types.h
index eea2d4ef..a6ea2f1 100644
--- a/chrome/common/search/instant_types.h
+++ b/chrome/common/search/instant_types.h
@@ -67,6 +67,9 @@
   // True if the default theme is selected.
   bool using_default_theme;
 
+  // True if dark mode is enabled.
+  bool using_dark_mode;
+
   // Url of the custom background selected by the user.
   GURL custom_background_url;
 
diff --git a/chrome/renderer/searchbox/searchbox_extension.cc b/chrome/renderer/searchbox/searchbox_extension.cc
index dda712af..ea00543 100644
--- a/chrome/renderer/searchbox/searchbox_extension.cc
+++ b/chrome/renderer/searchbox/searchbox_extension.cc
@@ -196,6 +196,8 @@
 
   builder.Set("usingDefaultTheme", theme_info.using_default_theme);
 
+  builder.Set("usingDarkMode", theme_info.using_dark_mode);
+
   // The theme background color is in RGBA format "rgba(R,G,B,A)" where R, G and
   // B are between 0 and 255 inclusive, and A is a double between 0 and 1
   // inclusive.