chromium/chrome/browser/ui/ash/shell_delegate/chrome_shell_delegate.cc

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

#include "chrome/browser/ui/ash/shell_delegate/chrome_shell_delegate.h"

#include <memory>
#include <utility>

#include "ash/accelerators/accelerator_prefs_delegate.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "ash/game_dashboard/game_dashboard_delegate.h"
#include "ash/public/cpp/app_types_util.h"
#include "ash/public/cpp/assistant/assistant_state.h"
#include "ash/public/cpp/new_window_delegate.h"
#include "ash/public/cpp/system_sounds_delegate.h"
#include "ash/public/cpp/tab_strip_delegate.h"
#include "ash/shell_delegate.h"
#include "ash/webui/settings/public/constants/routes.mojom.h"
#include "ash/webui/settings/public/constants/setting.mojom-shared.h"
#include "ash/wm/window_state.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "cc/input/touch_action.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/ash/api/tasks/chrome_tasks_delegate.h"
#include "chrome/browser/ash/arc/arc_util.h"
#include "chrome/browser/ash/arc/session/arc_session_manager.h"
#include "chrome/browser/ash/assistant/assistant_util.h"
#include "chrome/browser/ash/crosapi/browser_manager.h"
#include "chrome/browser/ash/crosapi/crosapi_ash.h"
#include "chrome/browser/ash/crosapi/crosapi_manager.h"
#include "chrome/browser/ash/crosapi/desk_profiles_ash.h"
#include "chrome/browser/ash/crosapi/fullscreen_controller_ash.h"
#include "chrome/browser/ash/file_manager/path_util.h"
#include "chrome/browser/ash/multidevice_setup/multidevice_setup_service_factory.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part_ash.h"
#include "chrome/browser/nearby_sharing/nearby_share_delegate_impl.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/sessions/session_restore.h"
#include "chrome/browser/ui/ash/accelerator/chrome_accelerator_prefs_delegate.h"
#include "chrome/browser/ui/ash/accessibility/chrome_accessibility_delegate.h"
#include "chrome/browser/ui/ash/back_gesture/back_gesture_contextual_nudge_delegate.h"
#include "chrome/browser/ui/ash/boca/chrome_tab_strip_delegate.h"
#include "chrome/browser/ui/ash/capture_mode/chrome_capture_mode_delegate.h"
#include "chrome/browser/ui/ash/clipboard/clipboard_history_controller_delegate_impl.h"
#include "chrome/browser/ui/ash/desks/chrome_saved_desk_delegate.h"
#include "chrome/browser/ui/ash/focus_mode/chrome_focus_mode_delegate.h"
#include "chrome/browser/ui/ash/game_dashboard/chrome_game_dashboard_delegate.h"
#include "chrome/browser/ui/ash/global_media_controls/media_notification_provider_impl.h"
#include "chrome/browser/ui/ash/keyboard/chrome_keyboard_ui.h"
#include "chrome/browser/ui/ash/session/session_util.h"
#include "chrome/browser/ui/ash/system_sounds_delegate_impl.h"
#include "chrome/browser/ui/ash/user_education/chrome_user_education_delegate.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_command_controller.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/chromeos/window_pin_util.h"
#include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
#include "chrome/browser/ui/settings_window_manager_chromeos.h"
#include "chrome/browser/ui/views/chrome_browser_main_extra_parts_views.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/tabs/tab_scrubber_chromeos.h"
#include "chrome/browser/ui/webui/tab_strip/tab_strip_ui_layout.h"
#include "chrome/browser/ui/webui/tab_strip/tab_strip_ui_util.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/common/channel_info.h"
#include "chrome/common/chrome_switches.h"
#include "chromeos/ash/components/browser_context_helper/browser_context_helper.h"
#include "chromeos/ash/services/multidevice_setup/multidevice_setup_service.h"
#include "components/ui_devtools/devtools_server.h"
#include "components/user_manager/user_manager.h"
#include "components/version_info/channel.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/chromeos/multi_capture_service.h"
#include "content/public/browser/device_service.h"
#include "content/public/browser/devtools_agent_host.h"
#include "content/public/browser/media_session_service.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "extensions/browser/app_window/app_window.h"
#include "extensions/browser/app_window/app_window_registry.h"
#include "extensions/common/constants.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/clipboard/clipboard_buffer.h"
#include "url/gurl.h"

namespace {

const char kKeyboardShortcutHelpPageUrl[] =
    "https://support.google.com/chromebook/answer/183101";

// Browser tests are always started with --disable-logging-redirect, so we need
// independent option here.
std::optional<bool> disable_logging_redirect_for_testing;

// Returns the TabStripModel that associates with |window| if the given |window|
// contains a browser frame, otherwise returns nullptr.
TabStripModel* GetTabstripModelForWindowIfAny(aura::Window* window) {
  BrowserView* browser_view =
      BrowserView::GetBrowserViewForNativeWindow(window);
  return browser_view ? browser_view->browser()->tab_strip_model() : nullptr;
}

content::WebContents* GetActiveWebContentsForNativeBrowserWindow(
    gfx::NativeWindow window) {
  if (!window)
    return nullptr;

  TabStripModel* tab_strip_model = GetTabstripModelForWindowIfAny(window);
  return tab_strip_model ? tab_strip_model->GetActiveWebContents() : nullptr;
}

feedback::FeedbackSource ToChromeFeedbackSource(
    ash::ShellDelegate::FeedbackSource source) {
  switch (source) {
    case ash::ShellDelegate::FeedbackSource::kGameDashboard:
      return feedback::FeedbackSource::kFeedbackSourceGameDashboard;
    case ash::ShellDelegate::FeedbackSource::kOverview:
      return feedback::FeedbackSource::kFeedbackSourceOverview;
    case ash::ShellDelegate::FeedbackSource::kWindowLayoutMenu:
      return feedback::FeedbackSource::kFeedbackSourceWindowLayoutMenu;
  }
  NOTREACHED() << "Unable to retrieve feedback::FeedbackSource due to "
                  "unknown source type.";
}

}  // namespace

ChromeShellDelegate::ChromeShellDelegate() = default;

ChromeShellDelegate::~ChromeShellDelegate() = default;

bool ChromeShellDelegate::CanShowWindowForUser(
    const aura::Window* window) const {
  return ::CanShowWindowForUser(window,
                                base::BindRepeating(&GetActiveBrowserContext));
}

std::unique_ptr<ash::CaptureModeDelegate>
ChromeShellDelegate::CreateCaptureModeDelegate() const {
  return std::make_unique<ChromeCaptureModeDelegate>();
}

std::unique_ptr<ash::ClipboardHistoryControllerDelegate>
ChromeShellDelegate::CreateClipboardHistoryControllerDelegate() const {
  return std::make_unique<ClipboardHistoryControllerDelegateImpl>();
}

std::unique_ptr<ash::GameDashboardDelegate>
ChromeShellDelegate::CreateGameDashboardDelegate() const {
  return std::make_unique<ChromeGameDashboardDelegate>();
}

std::unique_ptr<ash::AcceleratorPrefsDelegate>
ChromeShellDelegate::CreateAcceleratorPrefsDelegate() const {
  return std::make_unique<ChromeAcceleratorPrefsDelegate>();
}

ash::AccessibilityDelegate* ChromeShellDelegate::CreateAccessibilityDelegate() {
  return new ChromeAccessibilityDelegate;
}

std::unique_ptr<ash::BackGestureContextualNudgeDelegate>
ChromeShellDelegate::CreateBackGestureContextualNudgeDelegate(
    ash::BackGestureContextualNudgeController* controller) {
  return std::make_unique<BackGestureContextualNudgeDelegate>(controller);
}

std::unique_ptr<ash::MediaNotificationProvider>
ChromeShellDelegate::CreateMediaNotificationProvider() {
  return std::make_unique<ash::MediaNotificationProviderImpl>(
      GetMediaSessionService());
}

std::unique_ptr<ash::NearbyShareDelegate>
ChromeShellDelegate::CreateNearbyShareDelegate(
    ash::NearbyShareController* controller) const {
  return std::make_unique<NearbyShareDelegateImpl>(controller);
}

std::unique_ptr<ash::SavedDeskDelegate>
ChromeShellDelegate::CreateSavedDeskDelegate() const {
  return std::make_unique<ChromeSavedDeskDelegate>();
}

std::unique_ptr<ash::SystemSoundsDelegate>
ChromeShellDelegate::CreateSystemSoundsDelegate() const {
  return std::make_unique<SystemSoundsDelegateImpl>();
}

std::unique_ptr<ash::TabStripDelegate>
ChromeShellDelegate::CreateTabStripDelegate() const {
  return std::make_unique<ChromeTabStripDelegate>();
}

std::unique_ptr<ash::api::TasksDelegate>
ChromeShellDelegate::CreateTasksDelegate() const {
  return std::make_unique<ash::api::ChromeTasksDelegate>();
}

std::unique_ptr<ash::FocusModeDelegate>
ChromeShellDelegate::CreateFocusModeDelegate() const {
  return std::make_unique<ChromeFocusModeDelegate>();
}

std::unique_ptr<ash::UserEducationDelegate>
ChromeShellDelegate::CreateUserEducationDelegate() const {
  return std::make_unique<ChromeUserEducationDelegate>();
}

scoped_refptr<network::SharedURLLoaderFactory>
ChromeShellDelegate::GetBrowserProcessUrlLoaderFactory() const {
  return g_browser_process->shared_url_loader_factory();
}

void ChromeShellDelegate::OpenKeyboardShortcutHelpPage() const {
  ash::NewWindowDelegate::GetPrimary()->OpenUrl(
      GURL(kKeyboardShortcutHelpPageUrl),
      ash::NewWindowDelegate::OpenUrlFrom::kUserInteraction,
      ash::NewWindowDelegate::Disposition::kNewForegroundTab);
}

bool ChromeShellDelegate::CanGoBack(gfx::NativeWindow window) const {
  content::WebContents* contents =
      GetActiveWebContentsForNativeBrowserWindow(window);
  return contents ? contents->GetController().CanGoBack() : false;
}

void ChromeShellDelegate::SetTabScrubberChromeOSEnabled(bool enabled) {
  TabScrubberChromeOS::GetInstance()->SetEnabled(enabled);
}

bool ChromeShellDelegate::AllowDefaultTouchActions(gfx::NativeWindow window) {
  content::WebContents* contents =
      GetActiveWebContentsForNativeBrowserWindow(window);
  if (!contents)
    return true;
  content::RenderWidgetHostView* render_widget_host_view =
      contents->GetRenderWidgetHostView();
  if (!render_widget_host_view)
    return true;
  content::RenderWidgetHost* render_widget_host =
      render_widget_host_view->GetRenderWidgetHost();
  if (!render_widget_host)
    return true;
  std::optional<cc::TouchAction> allowed_touch_action =
      render_widget_host->GetAllowedTouchAction();
  return allowed_touch_action.has_value()
             ? *allowed_touch_action != cc::TouchAction::kNone
             : true;
}

bool ChromeShellDelegate::ShouldWaitForTouchPressAck(gfx::NativeWindow window) {
  content::WebContents* contents =
      GetActiveWebContentsForNativeBrowserWindow(window);
  if (!contents)
    return false;
  content::RenderWidgetHostView* render_widget_host_view =
      contents->GetRenderWidgetHostView();
  if (!render_widget_host_view)
    return false;
  return !!render_widget_host_view->GetRenderWidgetHost();
}

bool ChromeShellDelegate::IsTabDrag(const ui::OSExchangeData& drop_data) {
  return tab_strip_ui::IsDraggedTab(drop_data);
}

int ChromeShellDelegate::GetBrowserWebUITabStripHeight() {
  return TabStripUILayout::GetContainerHeight();
}

void ChromeShellDelegate::BindFingerprint(
    mojo::PendingReceiver<device::mojom::Fingerprint> receiver) {
  content::GetDeviceService().BindFingerprint(std::move(receiver));
}

void ChromeShellDelegate::BindMultiDeviceSetup(
    mojo::PendingReceiver<ash::multidevice_setup::mojom::MultiDeviceSetup>
        receiver) {
  ash::multidevice_setup::MultiDeviceSetupService* service =
      ash::multidevice_setup::MultiDeviceSetupServiceFactory::GetForProfile(
          ProfileManager::GetPrimaryUserProfile());
  if (service)
    service->BindMultiDeviceSetup(std::move(receiver));
}

void ChromeShellDelegate::BindMultiCaptureService(
    mojo::PendingReceiver<video_capture::mojom::MultiCaptureService> receiver) {
  content::GetMultiCaptureService().BindMultiCaptureService(
      std::move(receiver));
}

media_session::MediaSessionService*
ChromeShellDelegate::GetMediaSessionService() {
  return &content::GetMediaSessionService();
}

bool ChromeShellDelegate::IsSessionRestoreInProgress() const {
  // Must be called with an active user.
  const user_manager::User* active_user =
      user_manager::UserManager::Get()->GetActiveUser();
  CHECK(active_user);

  // User profile is not yet loaded. Consider loading user profile is part of
  // session restore.
  if (!active_user->is_profile_created()) {
    return true;
  }

  return SessionRestore::IsRestoring(Profile::FromBrowserContext(
      ash::BrowserContextHelper::Get()->GetBrowserContextByUser(active_user)));
}

void ChromeShellDelegate::SetUpEnvironmentForLockedFullscreen(
    const ash::WindowState& window_state) {
  bool locked = window_state.IsPinned();
  // Reset the clipboard and kill dev tools when entering or exiting locked
  // fullscreen (security concerns).
  ui::Clipboard::GetForCurrentThread()->Clear(ui::ClipboardBuffer::kCopyPaste);
  content::DevToolsAgentHost::DetachAllClients();

  // TODO(crbug.com/40195284): This might be interesting for DLP to change.
  // Disable both screenshots and video screen captures via the capture mode
  // feature.
  ChromeCaptureModeDelegate::Get()->SetIsScreenCaptureLocked(locked);

  // Get the primary profile as that's what ARC and Assistant are attached to.
  const Profile* profile = ProfileManager::GetPrimaryUserProfile();
  // Commands below require profile.
  if (!profile) {
    return;
  }

  // Disable ARC while in the locked fullscreen mode.
  arc::ArcSessionManager* const arc_session_manager =
      arc::ArcSessionManager::Get();
  if (!ash::IsArcWindow(window_state.window()) && arc_session_manager &&
      arc::IsArcAllowedForProfile(profile)) {
    if (locked) {
      // Disable ARC, preserve data.
      arc_session_manager->RequestDisable();
    } else {
      // Re-enable ARC if needed.
      if (arc::IsArcPlayStoreEnabledForProfile(profile))
        arc_session_manager->RequestEnable();
    }
  }

  if (assistant::IsAssistantAllowedForProfile(profile) ==
      ash::assistant::AssistantAllowedState::ALLOWED) {
    ash::AssistantState::Get()->NotifyLockedFullScreenStateChanged(locked);
  }
}

bool ChromeShellDelegate::IsUiDevToolsStarted() const {
  return ChromeBrowserMainExtraPartsViews::Get()->GetUiDevToolsServerInstance();
}

void ChromeShellDelegate::StartUiDevTools() {
  ChromeBrowserMainExtraPartsViews::Get()->CreateUiDevTools();
}

void ChromeShellDelegate::StopUiDevTools() {
  ChromeBrowserMainExtraPartsViews::Get()->DestroyUiDevTools();
}

int ChromeShellDelegate::GetUiDevToolsPort() const {
  return ChromeBrowserMainExtraPartsViews::Get()
      ->GetUiDevToolsServerInstance()
      ->port();
}

bool ChromeShellDelegate::IsLoggingRedirectDisabled() const {
  if (disable_logging_redirect_for_testing.has_value())
    return disable_logging_redirect_for_testing.value();

  return base::CommandLine::ForCurrentProcess()->HasSwitch(
      switches::kDisableLoggingRedirect);
}

base::FilePath ChromeShellDelegate::GetPrimaryUserDownloadsFolder() const {
  const user_manager::User* primary_user =
      user_manager::UserManager::Get()->GetPrimaryUser();
  if (!primary_user)
    return base::FilePath();

  Profile* user_profile =
      ash::ProfileHelper::Get()->GetProfileByUser(primary_user);
  if (user_profile)
    return file_manager::util::GetDownloadsFolderForProfile(user_profile);

  return base::FilePath();
}

void ChromeShellDelegate::OpenFeedbackDialog(
    ShellDelegate::FeedbackSource source,
    const std::string& description_template,
    const std::string& category_tag) {
  chrome::OpenFeedbackDialog(/*browser=*/nullptr,
                             ToChromeFeedbackSource(source),
                             description_template, category_tag);
}

void ChromeShellDelegate::OpenProfileManager() {
  if (crosapi::BrowserManager::Get()->IsRunning()) {
    crosapi::BrowserManager::Get()->OpenProfileManager();
  }
}

// static
void ChromeShellDelegate::SetDisableLoggingRedirectForTesting(bool value) {
  disable_logging_redirect_for_testing = value;
}

// static
void ChromeShellDelegate::ResetDisableLoggingRedirectForTesting() {
  disable_logging_redirect_for_testing.reset();
}

const GURL& ChromeShellDelegate::GetLastCommittedURLForWindowIfAny(
    aura::Window* window) {
  // Get the web content if the window is a browser window.
  content::WebContents* contents =
      GetActiveWebContentsForNativeBrowserWindow(window);

  if (!contents) {
    // Get the web content if the window is an app window.
    Profile* profile = ProfileManager::GetLastUsedProfile();
    if (profile) {
      const extensions::AppWindow* app_window =
          extensions::AppWindowRegistry::Get(profile)
              ->GetAppWindowForNativeWindow(window);
      if (app_window)
        contents = app_window->web_contents();
    }
  }

  return contents ? contents->GetLastCommittedURL() : GURL::EmptyGURL();
}

version_info::Channel ChromeShellDelegate::GetChannel() {
  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
          ash::switches::kForceShowReleaseTrack)) {
    // Simulate a non-stable channel so the release track UI is visible.
    return version_info::Channel::BETA;
  }
  return chrome::GetChannel();
}

void ChromeShellDelegate::ForceSkipWarningUserOnClose(
    const std::vector<raw_ptr<aura::Window, VectorExperimental>>& windows) {
  for (aura::Window* window : windows) {
    BrowserView* browser_view =
        BrowserView::GetBrowserViewForNativeWindow(window);
    if (browser_view) {
      browser_view->browser()->set_force_skip_warning_user_on_close(true);
    }
  }
}

std::string ChromeShellDelegate::GetVersionString() {
  return std::string(version_info::GetVersionNumber());
}

void ChromeShellDelegate::ShouldExitFullscreenBeforeLock(
    ChromeShellDelegate::ShouldExitFullscreenCallback callback) {
  crosapi::CrosapiManager::Get()
      ->crosapi_ash()
      ->fullscreen_controller_ash()
      ->ShouldExitFullscreenBeforeLock(std::move(callback));
}

ash::DeskProfilesDelegate* ChromeShellDelegate::GetDeskProfilesDelegate() {
  return crosapi::CrosapiManager::Get()->crosapi_ash()->desk_profiles_ash();
}

void ChromeShellDelegate::OpenMultitaskingSettings() {
  const auto& sub_page_path =
      ash::features::IsOsSettingsRevampWayfindingEnabled()
          ? chromeos::settings::mojom::kSystemPreferencesSectionPath
          : chromeos::settings::mojom::kPersonalizationSectionPath;
  chrome::SettingsWindowManager::GetInstance()->ShowOSSettings(
      ProfileManager::GetActiveUserProfile(), sub_page_path,
      chromeos::settings::mojom::Setting::kSnapWindowSuggestions);
}

bool ChromeShellDelegate::IsNoFirstRunSwitchOn() const {
  return base::CommandLine::ForCurrentProcess()->HasSwitch(
      ::switches::kNoFirstRun);
}