chromium/fuchsia_web/webengine/browser/theme_manager.cc

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "fuchsia_web/webengine/browser/theme_manager.h"

#include "base/check.h"
#include "base/fuchsia/fuchsia_logging.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.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/webpreferences/web_preferences.mojom.h"

namespace {

using blink::mojom::PreferredColorScheme;
using fuchsia::settings::ThemeType;

constexpr PreferredColorScheme kFallbackColorScheme =
    PreferredColorScheme::kLight;

PreferredColorScheme ThemeTypeToBlinkScheme(ThemeType type) {
  switch (type) {
    case ThemeType::LIGHT:
      return PreferredColorScheme::kLight;
    case ThemeType::DARK:
      return PreferredColorScheme::kDark;
    default:
      NOTREACHED_IN_MIGRATION();
      return kFallbackColorScheme;
  }
}

}  // namespace

ThemeManager::ThemeManager(content::WebContents* web_contents,
                           base::OnceClosure on_display_error)
    : web_contents_(web_contents),
      on_display_error_(std::move(on_display_error)) {
  DCHECK(web_contents_);

  // Per the FIDL API, the default theme is LIGHT.
  SetTheme(ThemeType::LIGHT);
}

ThemeManager::~ThemeManager() = default;

void ThemeManager::SetTheme(ThemeType theme) {
  requested_theme_ = theme;

  if (theme == ThemeType::DEFAULT) {
    if (!EnsureDisplayService()) {
      OnDisplayServiceMissing();
      return;
    }
  }
}

bool ThemeManager::EnsureDisplayService() {
  if (observed_display_service_error_)
    return false;

  if (display_service_)
    return true;

  display_service_ = base::ComponentContextForProcess()
                         ->svc()
                         ->Connect<fuchsia::settings::Display>();

  display_service_.set_error_handler([this](zx_status_t status) {
    ZX_LOG_IF(ERROR, status != ZX_ERR_PEER_CLOSED, status)
        << "fuchsia.settings.Display disconnected.";

    observed_display_service_error_ = true;

    // If the channel to the Display service was dropped before we received a
    // response from WatchForDisplayChanges, then it's likely that the service
    // isn't available in the namespace at all, which should be reported as
    // an error on the Frame.
    // Otherwise, if a failure was detected for a Display that was previously
    // functioning, it should be treated as a transient issue and the last known
    // system theme should be used.
    if (requested_theme_ && (*requested_theme_ == ThemeType::DEFAULT) &&
        !did_receive_first_watch_result_) {
      OnDisplayServiceMissing();
    }
  });

  WatchForDisplayChanges();
  return true;
}

void ThemeManager::OnDisplayServiceMissing() {
  LOG(ERROR) << "DEFAULT theme requires access to the "
                "`fuchsia.settings.Display` service to work.";

  if (on_display_error_)
    std::move(on_display_error_).Run();
}

void ThemeManager::ApplyThemeToWebPreferences(
    blink::web_pref::WebPreferences* web_prefs) {
  DCHECK(requested_theme_);

  if (requested_theme_ == ThemeType::DEFAULT) {
    if (!system_theme_) {
      // Defer theme application until we receive a system theme.
      return;
    }

    web_prefs->preferred_color_scheme = ThemeTypeToBlinkScheme(*system_theme_);
  } else {
    DCHECK(requested_theme_ == ThemeType::LIGHT ||
           requested_theme_ == ThemeType::DARK);

    web_prefs->preferred_color_scheme =
        ThemeTypeToBlinkScheme(*requested_theme_);
  }
}

void ThemeManager::WatchForDisplayChanges() {
  DCHECK(display_service_);

  // Will reply immediately for the first call of Watch(). Subsequent calls to
  // Watch() will be replied to as changes occur.
  display_service_->Watch(
      fit::bind_member(this, &ThemeManager::OnWatchResultReceived));
}

void ThemeManager::OnWatchResultReceived(
    fuchsia::settings::DisplaySettings settings) {
  did_receive_first_watch_result_ = true;

  if (settings.has_theme() && settings.theme().has_theme_type() &&
      (settings.theme().theme_type() == ThemeType::DARK ||
       settings.theme().theme_type() == ThemeType::LIGHT)) {
    system_theme_ = settings.theme().theme_type();
  } else {
    system_theme_ = std::nullopt;
  }

  web_contents_->OnWebPreferencesChanged();
  WatchForDisplayChanges();
}