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

#include "content/browser/web_contents/slow_web_preference_cache.h"

#include "base/command_line.h"
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "base/system/sys_info.h"
#include "base/trace_event/optional_trace_event.h"
#include "build/blink_buildflags.h"
#include "content/public/common/content_switches.h"
#include "third_party/blink/public/common/web_preferences/web_preferences.h"
#include "ui/base/pointer/pointer_device.h"
#include "ui/gl/gpu_switching_manager.h"

#if BUILDFLAG(IS_WIN)
#include "ui/events/devices/input_device_observer_win.h"
#elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
#include "ui/events/devices/device_data_manager.h"
#elif BUILDFLAG(IS_ANDROID)
#include "ui/base/device_form_factor.h"
#include "ui/events/devices/input_device_observer_android.h"
#elif BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_IOS_TVOS) && BUILDFLAG(USE_BLINK)
#include "ui/events/devices/input_device_observer_ios.h"
#endif

namespace content {

namespace {

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class AllPointerTypes {
  kNone = 0,
  kCoarse = 1,
  kFine = 2,
  kBoth = 3,
  kMaxValue = kBoth
};

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class PrimaryPointerType {
  kNone = 0,
  kCoarse = 1,
  kFine = 2,
  kMaxValue = kFine
};
}  // namespace

SlowWebPreferenceCacheObserver::~SlowWebPreferenceCacheObserver() = default;

SlowWebPreferenceCache::SlowWebPreferenceCache() {
  gpu_switch_observation_.Observe(ui::GpuSwitchingManager::GetInstance());

#if BUILDFLAG(IS_WIN)
  ui::InputDeviceObserverWin::GetInstance()->AddObserver(this);
#elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
  ui::DeviceDataManager::GetInstance()->AddObserver(this);
#elif BUILDFLAG(IS_ANDROID)
  ui::InputDeviceObserverAndroid::GetInstance()->AddObserver(this);
#elif BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_IOS_TVOS) && BUILDFLAG(USE_BLINK)
  ui::InputDeviceObserverIOS::GetInstance()->AddObserver(this);
#endif
}

SlowWebPreferenceCache::~SlowWebPreferenceCache() {
#if BUILDFLAG(IS_WIN)
  ui::InputDeviceObserverWin::GetInstance()->RemoveObserver(this);
#elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
  ui::DeviceDataManager::GetInstance()->RemoveObserver(this);
#elif BUILDFLAG(IS_ANDROID)
  ui::InputDeviceObserverAndroid::GetInstance()->RemoveObserver(this);
#elif BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_IOS_TVOS) && BUILDFLAG(USE_BLINK)
  ui::InputDeviceObserverIOS::GetInstance()->RemoveObserver(this);
#endif
}

// static
SlowWebPreferenceCache* SlowWebPreferenceCache::GetInstance() {
  static base::NoDestructor<SlowWebPreferenceCache> instance;
  return instance.get();
}

void SlowWebPreferenceCache::AddObserver(
    SlowWebPreferenceCacheObserver* observer) {
  observers_.AddObserver(observer);
}

void SlowWebPreferenceCache::RemoveObserver(
    SlowWebPreferenceCacheObserver* observer) {
  observers_.RemoveObserver(observer);
}

void SlowWebPreferenceCache::Load(blink::web_pref::WebPreferences* prefs) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!is_initialized_) {
    Update();
  }

#define SET_FROM_CACHE(prefs, field) prefs->field = field##_
  SET_FROM_CACHE(prefs, touch_event_feature_detection_enabled);
  SET_FROM_CACHE(prefs, available_pointer_types);
  SET_FROM_CACHE(prefs, available_hover_types);
  SET_FROM_CACHE(prefs, primary_pointer_type);
  SET_FROM_CACHE(prefs, primary_hover_type);
  SET_FROM_CACHE(prefs, pointer_events_max_touch_points);
  SET_FROM_CACHE(prefs, number_of_cpu_cores);

#if BUILDFLAG(IS_ANDROID)
  SET_FROM_CACHE(prefs, video_fullscreen_orientation_lock_enabled);
  SET_FROM_CACHE(prefs, video_rotate_to_fullscreen_enabled);
#endif

#undef SET_FROM_CACHE
}

void SlowWebPreferenceCache::OnInputDeviceConfigurationChanged(uint8_t) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (Update()) {
    observers_.Notify(
        &SlowWebPreferenceCacheObserver::OnSlowWebPreferenceChanged);
  }
}

void SlowWebPreferenceCache::OnGpuSwitched() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (Update()) {
    observers_.Notify(
        &SlowWebPreferenceCacheObserver::OnSlowWebPreferenceChanged);
  }
}

bool SlowWebPreferenceCache::Update() {
  OPTIONAL_TRACE_EVENT0("content", "SlowWebPreferenceCache::Update");
  bool prev_touch_event_feature_detection_enabled =
      touch_event_feature_detection_enabled_;
  int prev_available_pointer_types = available_pointer_types_;
  int prev_available_hover_types = available_hover_types_;
  blink::mojom::PointerType prev_primary_pointer_type = primary_pointer_type_;
  blink::mojom::HoverType prev_primary_hover_type = primary_hover_type_;
  int prev_pointer_events_max_touch_points = pointer_events_max_touch_points_;
  int prev_number_of_cpu_cores = number_of_cpu_cores_;
#if BUILDFLAG(IS_ANDROID)
  bool prev_video_fullscreen_orientation_lock_enabled =
      video_fullscreen_orientation_lock_enabled_;
  bool prev_video_rotate_to_fullscreen_enabled =
      video_rotate_to_fullscreen_enabled_;
#endif  // BUILDFLAG(IS_ANDROID)

  is_initialized_ = true;

  const base::CommandLine& command_line =
      *base::CommandLine::ForCurrentProcess();

  // On Android and iOS, Touch event feature detection is enabled by default,
  // Otherwise default is disabled.
  std::string touch_enabled_default_switch =
      switches::kTouchEventFeatureDetectionDisabled;
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
  touch_enabled_default_switch = switches::kTouchEventFeatureDetectionEnabled;
#endif  // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
  const std::string touch_enabled_switch =
      command_line.HasSwitch(switches::kTouchEventFeatureDetection)
          ? command_line.GetSwitchValueASCII(
                switches::kTouchEventFeatureDetection)
          : touch_enabled_default_switch;

  touch_event_feature_detection_enabled_ =
      (touch_enabled_switch == switches::kTouchEventFeatureDetectionAuto)
          ? (ui::GetTouchScreensAvailability() ==
             ui::TouchScreensAvailability::ENABLED)
          : (touch_enabled_switch.empty() ||
             touch_enabled_switch ==
                 switches::kTouchEventFeatureDetectionEnabled);

  std::tie(available_pointer_types_, available_hover_types_) =
      GetAvailablePointerAndHoverTypes();
  primary_pointer_type_ =
      static_cast<blink::mojom::PointerType>(ui::GetPrimaryPointerType());
  primary_hover_type_ =
      static_cast<blink::mojom::HoverType>(ui::GetPrimaryHoverType());

  pointer_events_max_touch_points_ = ui::MaxTouchPoints();

  number_of_cpu_cores_ = base::SysInfo::NumberOfProcessors();

  AllPointerTypes all_pointer_types = AllPointerTypes::kNone;
  if (available_pointer_types_ & ui::POINTER_TYPE_COARSE &&
      available_pointer_types_ & ui::POINTER_TYPE_FINE) {
    all_pointer_types = AllPointerTypes::kBoth;
  } else if (available_pointer_types_ & ui::POINTER_TYPE_COARSE) {
    all_pointer_types = AllPointerTypes::kCoarse;
  } else if (available_pointer_types_ & ui::POINTER_TYPE_FINE) {
    all_pointer_types = AllPointerTypes::kFine;
  }
  PrimaryPointerType primary_pointer = PrimaryPointerType::kNone;
  if (primary_pointer_type_ == blink::mojom::PointerType::kPointerCoarseType) {
    primary_pointer = PrimaryPointerType::kCoarse;
  } else if (primary_pointer_type_ ==
             blink::mojom::PointerType::kPointerFineType) {
    primary_pointer = PrimaryPointerType::kFine;
  }
  base::UmaHistogramEnumeration("Input.PointerTypesAll", all_pointer_types);
  base::UmaHistogramEnumeration("Input.PointerTypePrimary", primary_pointer);

#if BUILDFLAG(IS_ANDROID)
  const bool device_is_phone =
      ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_PHONE;
  video_fullscreen_orientation_lock_enabled_ = device_is_phone;
  video_rotate_to_fullscreen_enabled_ = device_is_phone;
  if (video_fullscreen_orientation_lock_enabled_ !=
          prev_video_fullscreen_orientation_lock_enabled ||
      video_rotate_to_fullscreen_enabled_ !=
          prev_video_rotate_to_fullscreen_enabled) {
    return true;
  }
#endif

  return touch_event_feature_detection_enabled_ !=
             prev_touch_event_feature_detection_enabled ||
         available_pointer_types_ != prev_available_pointer_types ||
         available_hover_types_ != prev_available_hover_types ||
         primary_pointer_type_ != prev_primary_pointer_type ||
         primary_hover_type_ != prev_primary_hover_type ||
         pointer_events_max_touch_points_ !=
             prev_pointer_events_max_touch_points ||
         number_of_cpu_cores_ != prev_number_of_cpu_cores;
}

// static
std::pair<int, int> SlowWebPreferenceCache::GetAvailablePointerAndHoverTypes() {
  // On Windows we have to temporarily allow blocking calls since
  // ui::GetAvailablePointerAndHoverTypes needs to call some in order to
  // figure out tablet device details in base::win::IsDeviceUsedAsATablet,
  // see https://crbug.com/1262162.
#if BUILDFLAG(IS_WIN)
  base::ScopedAllowBlocking scoped_allow_blocking;
#endif
  return ui::GetAvailablePointerAndHoverTypes();
}

}  // namespace content
