|  | // Copyright 2019 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/web_applications/app_browser_controller.h" | 
|  |  | 
|  | #include <string_view> | 
|  |  | 
|  | #include "base/containers/contains.h" | 
|  | #include "base/feature_list.h" | 
|  | #include "base/functional/bind.h" | 
|  | #include "base/memory/values_equivalent.h" | 
|  | #include "base/strings/escape.h" | 
|  | #include "base/strings/utf_string_conversions.h" | 
|  | #include "base/time/time.h" | 
|  | #include "base/trace_event/trace_event.h" | 
|  | #include "build/build_config.h" | 
|  | #include "chrome/browser/profiles/profile.h" | 
|  | #include "chrome/browser/themes/browser_theme_pack.h" | 
|  | #include "chrome/browser/themes/theme_properties.h" | 
|  | #include "chrome/browser/themes/theme_service.h" | 
|  | #include "chrome/browser/ui/actions/chrome_action_id.h" | 
|  | #include "chrome/browser/ui/browser.h" | 
|  | #include "chrome/browser/ui/browser_list.h" | 
|  | #include "chrome/browser/ui/browser_window.h" | 
|  | #include "chrome/browser/ui/browser_window/public/browser_window_features.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/browser_window_state.h" | 
|  | #include "chrome/browser/ui/color/chrome_color_id.h" | 
|  | #include "chrome/browser/ui/tabs/tab_menu_model_factory.h" | 
|  | #include "chrome/browser/ui/tabs/tab_strip_model.h" | 
|  | #include "chrome/browser/ui/ui_features.h" | 
|  | #include "chrome/browser/ui/views/page_action/action_ids.h" | 
|  | #include "chrome/browser/web_applications/web_app_provider.h" | 
|  | #include "chrome/browser/web_applications/web_app_registrar.h" | 
|  | #include "chrome/browser/web_applications/web_app_tab_helper.h" | 
|  | #include "chrome/common/chrome_features.h" | 
|  | #include "chrome/common/chrome_render_frame.mojom.h" | 
|  | #include "chrome/common/pref_names.h" | 
|  | #include "chrome/common/themes/autogenerated_theme_util.h" | 
|  | #include "chrome/grit/generated_resources.h" | 
|  | #include "components/security_state/content/security_state_tab_helper.h" | 
|  | #include "components/security_state/core/security_state.h" | 
|  | #include "components/url_formatter/url_formatter.h" | 
|  | #include "components/webapps/browser/installable/installable_evaluator.h" | 
|  | #include "content/public/browser/navigation_controller.h" | 
|  | #include "content/public/browser/navigation_entry.h" | 
|  | #include "content/public/browser/navigation_handle.h" | 
|  | #include "content/public/browser/page.h" | 
|  | #include "content/public/browser/render_frame_host.h" | 
|  | #include "content/public/browser/web_contents.h" | 
|  | #include "content/public/common/url_constants.h" | 
|  | #include "extensions/common/constants.h" | 
|  | #include "mojo/public/cpp/bindings/associated_remote.h" | 
|  | #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h" | 
|  | #include "third_party/blink/public/common/features.h" | 
|  | #include "third_party/blink/public/common/renderer_preferences/renderer_preferences.h" | 
|  | #include "third_party/blink/public/mojom/page/draggable_region.mojom.h" | 
|  | #include "third_party/skia/include/core/SkBitmap.h" | 
|  | #include "ui/base/models/image_model.h" | 
|  | #include "ui/color/color_id.h" | 
|  | #include "ui/color/color_provider.h" | 
|  | #include "ui/color/color_recipe.h" | 
|  | #include "ui/color/color_transform.h" | 
|  | #include "ui/display/display.h" | 
|  | #include "ui/display/screen.h" | 
|  | #include "ui/gfx/color_palette.h" | 
|  | #include "ui/gfx/color_utils.h" | 
|  | #include "ui/gfx/favicon_size.h" | 
|  | #include "ui/gfx/geometry/rect.h" | 
|  | #include "ui/gfx/geometry/resize_utils.h" | 
|  | #include "ui/gfx/image/image_skia.h" | 
|  | #include "ui/native_theme/native_theme.h" | 
|  | #include "ui/native_theme/os_settings_provider.h" | 
|  | #include "url/gurl.h" | 
|  | #include "url/origin.h" | 
|  | #include "url/url_constants.h" | 
|  |  | 
|  | #if BUILDFLAG(IS_CHROMEOS) | 
|  | #include "chrome/browser/apps/icon_standardizer.h" | 
|  | #include "chromeos/ash/experiences/system_web_apps/types/system_web_app_delegate.h" | 
|  | #include "chromeos/ui/base/chromeos_ui_constants.h" | 
|  | #endif | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | SkColor GetAltColor(SkColor color) { | 
|  | return color_utils::BlendForMinContrast( | 
|  | color, color, std::nullopt, | 
|  | kAutogeneratedThemeActiveTabMinContrast) | 
|  | .color; | 
|  | } | 
|  |  | 
|  | void SetWebContentsCanAcceptLoadDrops(content::WebContents* contents, | 
|  | bool can_accept) { | 
|  | contents->GetMutableRendererPrefs()->can_accept_load_drops = can_accept; | 
|  | contents->SyncRendererPrefs(); | 
|  | contents->NotifyPreferencesChanged(); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | namespace web_app { | 
|  |  | 
|  | // static | 
|  | bool AppBrowserController::IsWebApp(const BrowserWindowInterface* browser) { | 
|  | return browser && browser->GetFeatures().app_browser_controller(); | 
|  | } | 
|  |  | 
|  | // static | 
|  | bool AppBrowserController::IsForWebApp(const BrowserWindowInterface* browser, | 
|  | const webapps::AppId& app_id) { | 
|  | return IsWebApp(browser) && | 
|  | browser->GetFeatures().app_browser_controller()->app_id() == app_id; | 
|  | } | 
|  |  | 
|  | // static | 
|  | BrowserWindowInterface* AppBrowserController::FindForWebApp( | 
|  | const Profile& profile, | 
|  | const webapps::AppId& app_id) { | 
|  | BrowserWindowInterface* browser_for_web_app = nullptr; | 
|  | ForEachCurrentBrowserWindowInterfaceOrderedByActivation( | 
|  | [&](BrowserWindowInterface* browser) { | 
|  | if (browser->GetBrowserForMigrationOnly() | 
|  | ->IsAttemptingToCloseBrowser()) { | 
|  | return true;  // continue iterating | 
|  | } | 
|  | if (browser->GetType() != BrowserWindowInterface::TYPE_APP) { | 
|  | return true;  // continue iterating | 
|  | } | 
|  | if (browser->GetProfile() != &profile) { | 
|  | return true;  // continue iterating | 
|  | } | 
|  | if (!IsForWebApp(browser, app_id)) { | 
|  | return true;  // continue iterating | 
|  | } | 
|  | browser_for_web_app = browser; | 
|  | return false;  // stop iterating | 
|  | }); | 
|  | return browser_for_web_app; | 
|  | } | 
|  |  | 
|  | // static | 
|  | std::optional<int> AppBrowserController::FindTabIndexForApp( | 
|  | BrowserWindowInterface* browser, | 
|  | const webapps::AppId& app_id, | 
|  | bool for_focus_existing, | 
|  | HomeTabScope home_tab_scope) { | 
|  | auto is_valid_tab = [&app_id, home_tab_scope, for_focus_existing, | 
|  | browser](content::WebContents* contents) { | 
|  | WebAppTabHelper* tab_helper = WebAppTabHelper::FromWebContents(contents); | 
|  | if (app_id != tab_helper->app_id() || contents->HasOpener()) { | 
|  | return false; | 
|  | } | 
|  | if (for_focus_existing && !tab_helper->CanBeUsedForFocusExisting()) { | 
|  | return false; | 
|  | } | 
|  | if (home_tab_scope == HomeTabScope::kDontCare) { | 
|  | return true; | 
|  | } | 
|  | return (home_tab_scope == HomeTabScope::kInScope) == | 
|  | (browser->GetAppBrowserController()->GetPinnedHomeTab() == contents); | 
|  | }; | 
|  | // The active web contents should have preference if it is in scope. | 
|  | if (browser->GetFeatures().tab_strip_model()->active_index() != | 
|  | TabStripModel::kNoTab) { | 
|  | if (is_valid_tab( | 
|  | browser->GetFeatures().tab_strip_model()->GetActiveWebContents())) { | 
|  | return {browser->GetFeatures().tab_strip_model()->active_index()}; | 
|  | } | 
|  | } | 
|  | // Otherwise, use the first one for the app. | 
|  | for (int i = 0; i < browser->GetFeatures().tab_strip_model()->count(); ++i) { | 
|  | if (is_valid_tab( | 
|  | browser->GetFeatures().tab_strip_model()->GetWebContentsAt(i))) { | 
|  | return {i}; | 
|  | } | 
|  | } | 
|  | return std::nullopt; | 
|  | } | 
|  |  | 
|  | // static | 
|  | std::optional<AppBrowserController::BrowserAndTabIndex> | 
|  | AppBrowserController::FindTopLevelBrowsingContextForWebApp( | 
|  | const Profile& profile, | 
|  | const webapps::AppId& app_id, | 
|  | Browser::Type browser_type, | 
|  | bool for_focus_existing, | 
|  | HomeTabScope home_tab_scope) { | 
|  | std::optional<AppBrowserController::BrowserAndTabIndex> | 
|  | browser_and_tab_index = std::nullopt; | 
|  | ForEachCurrentBrowserWindowInterfaceOrderedByActivation( | 
|  | [&](BrowserWindowInterface* browser) { | 
|  | if (browser->GetBrowserForMigrationOnly() | 
|  | ->IsAttemptingToCloseBrowser()) { | 
|  | return true;  // continue iterating | 
|  | } | 
|  | if (browser->GetType() != browser_type) { | 
|  | return true;  // continue iterating | 
|  | } | 
|  | if (browser->GetProfile() != &profile) { | 
|  | return true;  // continue iterating | 
|  | } | 
|  | if (IsWebApp(browser) && !IsForWebApp(browser, app_id)) { | 
|  | return true;  // continue iterating | 
|  | } | 
|  | std::optional<int> tab_index = FindTabIndexForApp( | 
|  | browser, app_id, for_focus_existing, home_tab_scope); | 
|  | if (tab_index.has_value()) { | 
|  | browser_and_tab_index = {{browser, *tab_index}}; | 
|  | return false;  // stop iterating | 
|  | } | 
|  | return true;  // continue iterating | 
|  | }); | 
|  | return browser_and_tab_index; | 
|  | } | 
|  |  | 
|  | // static | 
|  | std::u16string AppBrowserController::FormatUrlOrigin( | 
|  | const GURL& url, | 
|  | url_formatter::FormatUrlTypes format_types) { | 
|  | auto origin = url::Origin::Create(url); | 
|  | return url_formatter::FormatUrl(origin.opaque() ? url : origin.GetURL(), | 
|  | format_types, base::UnescapeRule::SPACES, | 
|  | nullptr, nullptr, nullptr); | 
|  | } | 
|  |  | 
|  | const ui::ThemeProvider* AppBrowserController::GetThemeProvider() const { | 
|  | return theme_provider_.get(); | 
|  | } | 
|  |  | 
|  | AppBrowserController::AppBrowserController(Browser* browser, | 
|  | webapps::AppId app_id, | 
|  | bool has_tab_strip) | 
|  | : content::WebContentsObserver(nullptr), | 
|  | browser_(browser), | 
|  | app_id_(std::move(app_id)), | 
|  | has_tab_strip_(has_tab_strip), | 
|  | theme_provider_( | 
|  | ThemeService::CreateBoundThemeProvider(browser_->profile(), this)) { | 
|  | CHECK(browser->tab_strip_model()->empty()); | 
|  | browser->tab_strip_model()->AddObserver(this); | 
|  | } | 
|  |  | 
|  | AppBrowserController::AppBrowserController(Browser* browser, | 
|  | webapps::AppId app_id) | 
|  | : AppBrowserController(browser, std::move(app_id), false) {} | 
|  |  | 
|  | void AppBrowserController::Init() { | 
|  | UpdateThemePack(); | 
|  | } | 
|  |  | 
|  | AppBrowserController::~AppBrowserController() { | 
|  | browser()->tab_strip_model()->RemoveObserver(this); | 
|  | } | 
|  |  | 
|  | bool AppBrowserController::ShouldShowCustomTabBar() const { | 
|  | if (!IsInstalled()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | content::WebContents* web_contents = | 
|  | browser()->tab_strip_model()->GetActiveWebContents(); | 
|  |  | 
|  | if (!web_contents) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | GURL start_url = GetAppStartUrl(); | 
|  | std::string_view start_url_scheme = start_url.scheme(); | 
|  |  | 
|  | bool is_internal_start_url_scheme = | 
|  | start_url_scheme == extensions::kExtensionScheme || | 
|  | start_url_scheme == content::kChromeUIScheme || | 
|  | start_url_scheme == content::kChromeUIUntrustedScheme; | 
|  |  | 
|  | auto should_show_toolbar_for_url = [&](const GURL& url) -> bool { | 
|  | // If the url is unset, it doesn't give a signal as to whether the toolbar | 
|  | // should be shown or not. In lieu of more information, do not show the | 
|  | // toolbar. | 
|  | if (url.is_empty()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Show toolbar when not using 'https', unless this is an internal app, | 
|  | // or origin is secure (e.g. localhost). | 
|  | if (!is_internal_start_url_scheme && !url.SchemeIs(url::kHttpsScheme) && | 
|  | !webapps::InstallableEvaluator::IsOriginConsideredSecure(url)) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Page URLs that are not within scope | 
|  | // (https://www.w3.org/TR/appmanifest/#dfn-within-scope) of the app | 
|  | // corresponding to |start_url| show the toolbar. | 
|  | return !IsUrlInAppScope(url); | 
|  | }; | 
|  |  | 
|  | GURL visible_url = web_contents->GetVisibleURL(); | 
|  | GURL last_committed_url = web_contents->GetLastCommittedURL(); | 
|  |  | 
|  | if (last_committed_url.is_empty() && visible_url.is_empty()) { | 
|  | return should_show_toolbar_for_url(initial_url()); | 
|  | } | 
|  |  | 
|  | // Special case for about:blank app popup windows. If an app window creates a | 
|  | // popup window to about:blank from a document within app scope, the toolbar | 
|  | // should not be shown. | 
|  | if (last_committed_url.spec() == url::kAboutBlankURL) { | 
|  | auto* primary_main_frame = web_contents->GetPrimaryMainFrame(); | 
|  | if (primary_main_frame && | 
|  | primary_main_frame->GetLastCommittedOrigin().IsSameOriginWith( | 
|  | start_url) && | 
|  | browser()->is_type_app_popup()) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (should_show_toolbar_for_url(visible_url) || | 
|  | should_show_toolbar_for_url(last_committed_url)) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Insecure external web sites show the toolbar. | 
|  | // Note: IsContentSecure is false until a navigation is committed. | 
|  | if (!last_committed_url.is_empty() && !is_internal_start_url_scheme && | 
|  | !webapps::InstallableEvaluator::IsContentSecure(web_contents)) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool AppBrowserController::has_tab_strip() const { | 
|  | return has_tab_strip_; | 
|  | } | 
|  |  | 
|  | bool AppBrowserController::HasTitlebarMenuButton() const { | 
|  | #if BUILDFLAG(IS_CHROMEOS) | 
|  | // Hide for system apps. | 
|  | return !system_app(); | 
|  | #else | 
|  | return true; | 
|  | #endif  // BUILDFLAG(IS_CHROMEOS) | 
|  | } | 
|  |  | 
|  | bool AppBrowserController::HasTitlebarAppOriginText() const { | 
|  | #if BUILDFLAG(IS_CHROMEOS) | 
|  | // Do not show origin text for System Apps. | 
|  | if (system_app()) { | 
|  | return false; | 
|  | } | 
|  | #endif  // BUILDFLAG(IS_CHROMEOS) | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool AppBrowserController::HasTitlebarContentSettings() const { | 
|  | #if BUILDFLAG(IS_CHROMEOS) | 
|  | // Do not show content settings for System Apps. | 
|  | return !system_app(); | 
|  | #else | 
|  | return true; | 
|  | #endif  // BUILDFLAG(IS_CHROMEOS) | 
|  | } | 
|  |  | 
|  | std::vector<actions::ActionId> AppBrowserController::GetTitleBarPageActions() | 
|  | const { | 
|  | if (!base::FeatureList::IsEnabled(features::kPageActionsMigration)) { | 
|  | return {}; | 
|  | } | 
|  | #if BUILDFLAG(IS_CHROMEOS) | 
|  | if (system_app()) { | 
|  | return { | 
|  | kActionFind, | 
|  | kActionZoomNormal, | 
|  | }; | 
|  | } | 
|  | #endif  // BUILDFLAG(IS_CHROMEOS) | 
|  |  | 
|  | std::vector<actions::ActionId> types_enabled = { | 
|  | kActionFind, | 
|  | kActionShowPasswordsBubbleOrPage, | 
|  | kActionShowTranslate, | 
|  | kActionZoomNormal, | 
|  | kActionShowFileSystemAccess, | 
|  | kActionShowCookieControls, | 
|  | }; | 
|  |  | 
|  | #if DCHECK_IS_ON() | 
|  | for (auto action_id : types_enabled) { | 
|  | DCHECK(base::Contains(page_actions::kActionIds, action_id)); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | return types_enabled; | 
|  | } | 
|  |  | 
|  | std::vector<PageActionIconType> | 
|  | AppBrowserController::GetTitleBarPageActionTypes() const { | 
|  | #if BUILDFLAG(IS_CHROMEOS) | 
|  | if (system_app()) { | 
|  | return {PageActionIconType::kFind, PageActionIconType::kZoom}; | 
|  | } | 
|  | #endif  // BUILDFLAG(IS_CHROMEOS) | 
|  |  | 
|  | std::vector<PageActionIconType> types_enabled; | 
|  | types_enabled.push_back(PageActionIconType::kFind); | 
|  | types_enabled.push_back(PageActionIconType::kManagePasswords); | 
|  | types_enabled.push_back(PageActionIconType::kTranslate); | 
|  | types_enabled.push_back(PageActionIconType::kZoom); | 
|  | types_enabled.push_back(PageActionIconType::kFileSystemAccess); | 
|  | types_enabled.push_back(PageActionIconType::kCookieControls); | 
|  | types_enabled.push_back(PageActionIconType::kSaveCard); | 
|  |  | 
|  | return types_enabled; | 
|  | } | 
|  |  | 
|  | bool AppBrowserController::IsInstalled() const { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<TabMenuModelFactory> | 
|  | AppBrowserController::GetTabMenuModelFactory() const { | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | bool AppBrowserController::AppUsesWindowControlsOverlay() const { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool AppBrowserController::AppUsesBorderlessMode() const { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool AppBrowserController::AppUsesTabbed() const { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool AppBrowserController::IsIsolatedWebApp() const { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | void AppBrowserController::SetIsolatedWebAppTrueForTesting() {} | 
|  |  | 
|  | bool AppBrowserController::IsWindowControlsOverlayEnabled() const { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | void AppBrowserController::ToggleWindowControlsOverlayEnabled( | 
|  | base::OnceClosure on_complete) { | 
|  | std::move(on_complete).Run(); | 
|  | } | 
|  |  | 
|  | gfx::Rect AppBrowserController::GetDefaultBounds() const { | 
|  | return gfx::Rect(); | 
|  | } | 
|  |  | 
|  | bool AppBrowserController::HasReloadButton() const { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool AppBrowserController::HasPendingUpdate() const { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool AppBrowserController::HasPendingUpdateNotIgnoredByUser() const { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | void AppBrowserController::CreateMetadataAndTriggerAppUpdateDialog( | 
|  | base::TimeTicks start_time) const {} | 
|  |  | 
|  | bool AppBrowserController::IsPreventCloseEnabled() const { | 
|  | auto* provider = WebAppProvider::GetForWebApps(browser()->profile()); | 
|  | if (!provider) { | 
|  | return false; | 
|  | } | 
|  | return provider->registrar_unsafe().IsPreventCloseEnabled(app_id()); | 
|  | } | 
|  |  | 
|  | #if !BUILDFLAG(IS_CHROMEOS) | 
|  | bool AppBrowserController::HasProfileMenuButton() const { | 
|  | return false; | 
|  | } | 
|  | bool AppBrowserController::IsProfileMenuButtonVisible() const { | 
|  | return false; | 
|  | } | 
|  | #endif  // !BUILDFLAG(IS_CHROMEOS) | 
|  |  | 
|  | #if BUILDFLAG(IS_CHROMEOS) | 
|  | const ash::SystemWebAppDelegate* AppBrowserController::system_app() const { | 
|  | return nullptr; | 
|  | } | 
|  | #endif  // BUILDFLAG(IS_CHROMEOS) | 
|  |  | 
|  | std::u16string AppBrowserController::GetLaunchFlashText() const { | 
|  | // Isolated Web Apps should show the app's name instead of the origin. | 
|  | // App Short Name is considered trustworthy because manifest comes from signed | 
|  | // web bundle. The flash text is not needed on platforms that already display | 
|  | // the app name in the title bar (e.g. Mac, Windows, and Linux). | 
|  | if (IsIsolatedWebApp()) { | 
|  | #if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) | 
|  | return std::u16string(); | 
|  | #else   // !(BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX)) | 
|  | return GetAppShortName(); | 
|  | #endif  // BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) | 
|  | } | 
|  | return GetFormattedUrlOrigin(); | 
|  | } | 
|  |  | 
|  | WebAppBrowserController* AppBrowserController::AsWebAppBrowserController() { | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | bool AppBrowserController::CanUserUninstall() const { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | void AppBrowserController::Uninstall( | 
|  | webapps::WebappUninstallSource webapp_uninstall_source) { | 
|  | NOTREACHED(); | 
|  | } | 
|  |  | 
|  | void AppBrowserController::UpdateCustomTabBarVisibility(bool animate) const { | 
|  | browser()->window()->UpdateCustomTabBarVisibility(ShouldShowCustomTabBar(), | 
|  | animate); | 
|  | } | 
|  |  | 
|  | void AppBrowserController::DidStartNavigation( | 
|  | content::NavigationHandle* navigation_handle) { | 
|  | if (!initial_url().is_empty()) { | 
|  | return; | 
|  | } | 
|  | if (!navigation_handle->IsInPrimaryMainFrame()) { | 
|  | return; | 
|  | } | 
|  | if (navigation_handle->GetURL().is_empty()) { | 
|  | return; | 
|  | } | 
|  | SetInitialURL(navigation_handle->GetURL()); | 
|  | } | 
|  |  | 
|  | void AppBrowserController::DOMContentLoaded( | 
|  | content::RenderFrameHost* render_frame_host) { | 
|  | // We hold off changing theme color for a new tab until the page is loaded. | 
|  | UpdateThemePack(); | 
|  | } | 
|  |  | 
|  | void AppBrowserController::DidChangeThemeColor() { | 
|  | UpdateThemePack(); | 
|  | } | 
|  |  | 
|  | void AppBrowserController::OnBackgroundColorChanged() { | 
|  | UpdateThemePack(); | 
|  | } | 
|  |  | 
|  | void AppBrowserController::PrimaryPageChanged(content::Page& page) { | 
|  | // Reset the draggable regions for window controls overlay apps so they are | 
|  | // not cached on navigation. Note that these are not cleared for borderless | 
|  | // apps because when we navigate out of scope and then back to scope, the | 
|  | // draggable regions stay same and nothing triggers to re-initialize them. | 
|  | // So if they are cleared, they don't work anymore when coming back to scope. | 
|  | if (AppUsesWindowControlsOverlay()) { | 
|  | draggable_region_ = std::nullopt; | 
|  | } | 
|  |  | 
|  | // Collect draggable app regions if the app supports Window Controls Overlay | 
|  | // or Borderless mode. | 
|  | if (AppUsesWindowControlsOverlay() || AppUsesBorderlessMode()) { | 
|  | content::RenderFrameHost& host = page.GetMainDocument(); | 
|  | UpdateSupportsDraggableRegions(/*supports_draggable_regions=*/true, &host); | 
|  | } | 
|  | } | 
|  |  | 
|  | std::optional<SkColor> AppBrowserController::GetThemeColor() const { | 
|  | if (ui::NativeTheme::GetInstanceForNativeUi()->preferred_contrast() == | 
|  | ui::NativeTheme::PreferredContrast::kMore) { | 
|  | if (const std::optional<SkColor> window_color = | 
|  | ui::OsSettingsProvider::Get().Color( | 
|  | ui::OsSettingsProvider::ColorId::kWindow)) { | 
|  | return window_color; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (content::WebContents* const web_contents = | 
|  | browser()->tab_strip_model()->GetActiveWebContents()) { | 
|  | // HTML meta theme-color tag overrides manifest theme_color, see spec: | 
|  | // https://www.w3.org/TR/appmanifest/#theme_color-member | 
|  | if (const std::optional<SkColor> color = web_contents->GetThemeColor()) { | 
|  | // The frame/tabstrip code expects an opaque color. | 
|  | return SkColorSetA(*color, SK_AlphaOPAQUE); | 
|  | } | 
|  | } | 
|  |  | 
|  | return std::nullopt; | 
|  | } | 
|  |  | 
|  | std::optional<SkColor> AppBrowserController::GetBackgroundColor() const { | 
|  | std::optional<SkColor> color; | 
|  | if (auto* web_contents = | 
|  | browser()->tab_strip_model()->GetActiveWebContents()) { | 
|  | color = web_contents->GetBackgroundColor(); | 
|  | } | 
|  | return color ? SkColorSetA(*color, SK_AlphaOPAQUE) : color; | 
|  | } | 
|  |  | 
|  | std::u16string AppBrowserController::GetTitle() const { | 
|  | content::WebContents* web_contents = | 
|  | browser()->tab_strip_model()->GetActiveWebContents(); | 
|  | if (!web_contents) { | 
|  | return std::u16string(); | 
|  | } | 
|  |  | 
|  | content::NavigationEntry* entry = | 
|  | web_contents->GetController().GetVisibleEntry(); | 
|  | return entry ? entry->GetTitle() : std::u16string(); | 
|  | } | 
|  |  | 
|  | std::string AppBrowserController::GetTitleForMediaControls() const { | 
|  | #if BUILDFLAG(IS_CHROMEOS) | 
|  | // Only return the app name if we're a System Web App. | 
|  | if (system_app()) { | 
|  | return base::UTF16ToUTF8(GetAppShortName()); | 
|  | } | 
|  | #endif  // BUILDFLAG(IS_CHROMEOS) | 
|  | return std::string(); | 
|  | } | 
|  |  | 
|  | const GURL& AppBrowserController::GetAppNewTabUrl() const { | 
|  | return GetAppStartUrl(); | 
|  | } | 
|  |  | 
|  | content::WebContents* AppBrowserController::GetPinnedHomeTab() const { | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | bool AppBrowserController::ShouldHideNewTabButton() const { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool AppBrowserController::IsUrlInHomeTabScope(const GURL& url) const { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool AppBrowserController::ShouldShowAppIconOnTab(int index) const { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | #if BUILDFLAG(IS_MAC) | 
|  | bool AppBrowserController::AlwaysShowToolbarInFullscreen() const { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void AppBrowserController::ToggleAlwaysShowToolbarInFullscreen() {} | 
|  | #endif | 
|  |  | 
|  | void AppBrowserController::OnTabStripModelChanged( | 
|  | TabStripModel* tab_strip_model, | 
|  | const TabStripModelChange& change, | 
|  | const TabStripSelectionChange& selection) { | 
|  | if (selection.active_tab_changed()) { | 
|  | content::WebContentsObserver::Observe(selection.new_contents); | 
|  | // Update theme when tabs change unless there are no tabs, or if the tab has | 
|  | // not finished loading, we will update later in DOMContentLoaded(). | 
|  | if (tab_strip_model->count() > 0 && | 
|  | selection.new_contents->IsDocumentOnLoadCompletedInPrimaryMainFrame()) { | 
|  | UpdateThemePack(); | 
|  | } | 
|  | } | 
|  | if (change.type() == TabStripModelChange::kInserted) { | 
|  | for (const auto& contents : change.GetInsert()->contents) { | 
|  | OnTabInserted(contents.contents); | 
|  | } | 
|  | } else if (change.type() == TabStripModelChange::kRemoved) { | 
|  | for (const auto& contents : change.GetRemove()->contents) { | 
|  | OnTabRemoved(contents.contents); | 
|  | } | 
|  | // WebContents should be null when the last tab is closed. | 
|  | DCHECK_EQ(web_contents() == nullptr, tab_strip_model->empty()); | 
|  | } | 
|  |  | 
|  | // Do not update the UI during window shutdown. | 
|  | if (!selection.new_contents) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | UpdateCustomTabBarVisibility(/*animate=*/false); | 
|  | } | 
|  |  | 
|  | CustomThemeSupplier* AppBrowserController::GetThemeSupplier() const { | 
|  | return theme_pack_.get(); | 
|  | } | 
|  |  | 
|  | bool AppBrowserController::ShouldUseCustomFrame() const { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void AppBrowserController::AddColorMixers( | 
|  | ui::ColorProvider* provider, | 
|  | const ui::ColorProviderKey& key) const { | 
|  | constexpr SkAlpha kSeparatorOpacity = 0.15f * 255.0f; | 
|  | #if !BUILDFLAG(IS_CHROMEOS) | 
|  | // This color is the same as the default active frame color. | 
|  | const std::optional<SkColor> theme_color = GetThemeColor(); | 
|  | ui::ColorTransform default_background = | 
|  | key.color_mode == ui::ColorProviderKey::ColorMode::kLight | 
|  | ? ui::ColorTransform(ui::kColorFrameActiveUnthemed) | 
|  | : ui::HSLShift(ui::kColorFrameActiveUnthemed, | 
|  | ThemeProperties::GetDefaultTint( | 
|  | ThemeProperties::TINT_FRAME, true)); | 
|  | #endif | 
|  | ui::ColorMixer& mixer = provider->AddMixer(); | 
|  | std::optional<SkColor> bg_color = GetBackgroundColor(); | 
|  | // TODO(kylixrd): The definition of kColorPwaBackground isn't fully fleshed | 
|  | // out yet. Whether or not the PWA background color is set is used in many | 
|  | // locations to derive other colors. Those specific locations would need to be | 
|  | // addressed in their own context. | 
|  | if (bg_color) { | 
|  | mixer[kColorPwaBackground] = {bg_color.value()}; | 
|  | } | 
|  | mixer[kColorPwaMenuButtonIcon] = {kColorToolbarButtonIcon}; | 
|  | mixer[kColorPwaSecurityChipForeground] = {ui::kColorSecondaryForeground}; | 
|  | mixer[kColorPwaSecurityChipForegroundDangerous] = { | 
|  | ui::kColorAlertHighSeverity}; | 
|  | mixer[kColorPwaSecurityChipForegroundSecure] = { | 
|  | kColorPwaSecurityChipForeground}; | 
|  | auto separator_color = | 
|  | ui::GetColorWithMaxContrast(kColorPwaToolbarBackground); | 
|  | mixer[kColorPwaTabBarBottomSeparator] = ui::AlphaBlend( | 
|  | separator_color, kColorPwaToolbarBackground, kSeparatorOpacity); | 
|  | mixer[kColorPwaTabBarTopSeparator] = | 
|  | ui::AlphaBlend(separator_color, kColorPwaTheme, kSeparatorOpacity); | 
|  | #if BUILDFLAG(IS_CHROMEOS) | 
|  | // Ash system frames differ from ChromeOS browser frames. | 
|  | mixer[kColorPwaTheme] = {chromeos::kDefaultFrameColor}; | 
|  | #else | 
|  | mixer[kColorPwaTheme] = theme_color ? ui::ColorTransform(theme_color.value()) | 
|  | : default_background; | 
|  | #endif | 
|  | mixer[kColorPwaToolbarBackground] = {ui::kColorEndpointBackground}; | 
|  | mixer[kColorPwaToolbarButtonIcon] = | 
|  | ui::DeriveDefaultIconColor(ui::kColorEndpointForeground); | 
|  | mixer[kColorPwaToolbarButtonIconDisabled] = | 
|  | ui::SetAlpha(kColorPwaToolbarButtonIcon, gfx::kDisabledControlAlpha); | 
|  | if (bg_color) { | 
|  | mixer[kColorWebContentsBackground] = {kColorPwaBackground}; | 
|  | } | 
|  |  | 
|  | mixer[kColorInfoBarBackground] = {kColorPwaToolbarBackground}; | 
|  | mixer[kColorInfoBarForeground] = {kColorPwaToolbarButtonIcon}; | 
|  | mixer[kColorInfoBarButtonIcon] = {kColorPwaToolbarButtonIcon}; | 
|  | mixer[kColorInfoBarButtonIconDisabled] = {kColorPwaToolbarButtonIconDisabled}; | 
|  |  | 
|  | // Omnibox icon colors in PWA windows are used both for the LocationIconView | 
|  | // in the CustomTabBarView as well as page action and info icons in the title | 
|  | // bar. In case of LocationIconView, CustomTabBarView overrides the color ID | 
|  | // to use for its background, so here we define the colors to use for the | 
|  | // icons that appear in the title bar. Note that all three of these colors are | 
|  | // "background" colors, i.e. the color that is shown behind the icon/text. | 
|  | // Making them equal to the toolbar ink drop colors will make these icons look | 
|  | // similar to other icons in the PWA title bar. | 
|  | mixer[kColorOmniboxIconBackground] = {kColorToolbarInkDropHover}; | 
|  | mixer[kColorOmniboxIconHover] = {kColorToolbarInkDropHover}; | 
|  | mixer[kColorOmniboxIconPressed] = {kColorToolbarInkDropRipple}; | 
|  |  | 
|  | // The Material Design color mixer hardcodes various toolbar colors to certain | 
|  | // colors, ignoring the toolbar colors set in the BrowserThemePack. Since in | 
|  | // web apps the toolbar is part of the frame/titlebar, we set them to match | 
|  | // the frame colors here. Because BrowserFrameViewWin overrides | 
|  | // GetCaptionColor, special handling is needed to ensure ToolbarButton | 
|  | // foreground color matches the rest of the title bar elements on Windows. | 
|  | #if BUILDFLAG(IS_WIN) | 
|  | mixer[kColorFrameCaptionActive] = {kColorCaptionForegroundActive}; | 
|  | mixer[kColorFrameCaptionInactive] = {kColorCaptionForegroundInactive}; | 
|  | #endif  // BUILDFLAG(IS_WIN) | 
|  | mixer[kColorToolbar] = {ui::kColorFrameActive}; | 
|  | mixer[kColorToolbarTextDefault] = {kColorFrameCaptionActive}; | 
|  | mixer[kColorToolbarTextDisabledDefault] = {kColorFrameCaptionInactive}; | 
|  | mixer[kColorToolbarButtonIconDefault] = {kColorFrameCaptionActive}; | 
|  | mixer[kColorToolbarButtonIcon] = {kColorToolbarButtonIconDefault}; | 
|  | mixer[kColorToolbarButtonIconHovered] = {kColorToolbarButtonIcon}; | 
|  | mixer[kColorToolbarButtonIconPressed] = {kColorToolbarButtonIcon}; | 
|  | // While there are separate color IDs for disabled in inactive toolbar button | 
|  | // icons, in reality toolbar buttons don't distinguish between disabled and | 
|  | // inactive states. We want to make sure that the disabled state if visually | 
|  | // distinct from the active state. On windows this is always the case for | 
|  | // kColorFrameCaptionInactive, however on other platforms this might be the | 
|  | // same color as the active caption color. So on non-windows we derive the | 
|  | // disabled color from the regular toolbar color. | 
|  | #if BUILDFLAG(IS_WIN) | 
|  | mixer[kColorToolbarButtonIconDisabled] = {kColorFrameCaptionInactive}; | 
|  | #else | 
|  | mixer[kColorToolbarButtonIconDisabled] = {ui::GetResultingPaintColor( | 
|  | {ui::kColorSysStateDisabled}, {kColorToolbar})}; | 
|  | #endif | 
|  | mixer[kColorToolbarButtonIconInactive] = {kColorToolbarButtonIconDisabled}; | 
|  |  | 
|  | // App menu highlight colors in PWA window should be derived from the (active) | 
|  | // frame color, as that is what it is drawn on top of. | 
|  | mixer[kColorAppMenuHighlightDefault] = | 
|  | ui::PickGoogleColor(ui::kColorFrameActiveUnthemed, kColorToolbar, | 
|  | color_utils::kMinimumVisibleContrastRatio); | 
|  | mixer[kColorAppMenuExpandedForegroundDefault] = | 
|  | ui::GetColorWithMaxContrast(kColorAppMenuHighlightDefault); | 
|  | } | 
|  |  | 
|  | void AppBrowserController::OnReceivedInitialURL() { | 
|  | UpdateCustomTabBarVisibility(/*animate=*/false); | 
|  |  | 
|  | // Browsers of picture in picture type already take care of setting the proper | 
|  | // window bounds. | 
|  | if (browser()->is_type_picture_in_picture()) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | // If the window bounds have not been overridden, there is no need to resize | 
|  | // the window. | 
|  | if (!browser()->bounds_overridden()) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | // The saved bounds will only be wrong if they are content bounds. | 
|  | if (!chrome::SavedBoundsAreContentBounds(browser())) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | // TODO(crbug.com/41459774): Correctly set the window size at creation time. | 
|  | // This is currently not possible because the current url is not easily known | 
|  | // at popup construction time. | 
|  | // | 
|  | // Note that any potential fix should take into account that | 
|  | // `override_bounds()` represent the outer window bounds, not the content | 
|  | // size. | 
|  | browser()->window()->SetContentsSize(browser()->override_bounds().size()); | 
|  | } | 
|  |  | 
|  | void AppBrowserController::OnTabInserted(content::WebContents* contents) { | 
|  | if (!contents->GetVisibleURL().is_empty() && initial_url_.is_empty()) { | 
|  | SetInitialURL(contents->GetVisibleURL()); | 
|  | } | 
|  |  | 
|  | // Collect draggable app regions if the app supports Window Controls Overlay | 
|  | // or Borderless mode. This is required in addition to the use in | 
|  | // RenderFrameCreated to handle existing web contents being reparented into an | 
|  | // app window. | 
|  | if (AppUsesWindowControlsOverlay() || AppUsesBorderlessMode()) { | 
|  | content::RenderFrameHost* host = contents->GetPrimaryMainFrame(); | 
|  | UpdateSupportsDraggableRegions(/*supports_draggable_regions=*/true, host); | 
|  | } | 
|  |  | 
|  | SetWebContentsCanAcceptLoadDrops(contents, false); | 
|  | } | 
|  |  | 
|  | void AppBrowserController::OnTabRemoved(content::WebContents* contents) { | 
|  | // Stop collecting draggable app regions when the web contents is removed | 
|  | // since it may be reparented to a tab in the browser. | 
|  | content::RenderFrameHost* host = contents->GetPrimaryMainFrame(); | 
|  | UpdateSupportsDraggableRegions(/*supports_draggable_regions=*/false, host); | 
|  |  | 
|  | SetWebContentsCanAcceptLoadDrops(contents, true); | 
|  | } | 
|  |  | 
|  | ui::ImageModel AppBrowserController::GetFallbackAppIcon() const { | 
|  | TRACE_EVENT0("ui", "TaskManagerView::GetFallbackAppIcon"); | 
|  | gfx::ImageSkia page_icon = browser()->GetCurrentPageIcon().AsImageSkia(); | 
|  | if (!page_icon.isNull()) { | 
|  | #if BUILDFLAG(IS_CHROMEOS) | 
|  | return ui::ImageModel::FromImageSkia( | 
|  | apps::CreateStandardIconImage(page_icon)); | 
|  | #else | 
|  | return ui::ImageModel::FromImageSkia(page_icon); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | // The icon may be loading still. Return a transparent icon rather | 
|  | // than using a placeholder to avoid flickering. | 
|  | SkBitmap bitmap; | 
|  | bitmap.allocN32Pixels(gfx::kFaviconSize, gfx::kFaviconSize); | 
|  | bitmap.eraseColor(SK_ColorTRANSPARENT); | 
|  | return ui::ImageModel::FromImageSkia( | 
|  | gfx::ImageSkia::CreateFrom1xBitmap(bitmap)); | 
|  | } | 
|  |  | 
|  | void AppBrowserController::DraggableRegionsChanged( | 
|  | const std::vector<blink::mojom::DraggableRegionPtr>& regions, | 
|  | content::WebContents* contents) { | 
|  | content::WebContents* active_contents = | 
|  | browser()->tab_strip_model()->GetActiveWebContents(); | 
|  |  | 
|  | if (contents != active_contents) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | SkRegion sk_region; | 
|  | for (const blink::mojom::DraggableRegionPtr& region : regions) { | 
|  | sk_region.op( | 
|  | SkIRect::MakeLTRB(region->bounds.x(), region->bounds.y(), | 
|  | region->bounds.x() + region->bounds.width(), | 
|  | region->bounds.y() + region->bounds.height()), | 
|  | region->draggable ? SkRegion::kUnion_Op : SkRegion::kDifference_Op); | 
|  | } | 
|  |  | 
|  | draggable_region_ = sk_region; | 
|  |  | 
|  | if (on_draggable_region_set_for_testing_) { | 
|  | std::move(on_draggable_region_set_for_testing_).Run(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void AppBrowserController::SetOnUpdateDraggableRegionForTesting( | 
|  | base::OnceClosure done) { | 
|  | on_draggable_region_set_for_testing_ = std::move(done); | 
|  | } | 
|  |  | 
|  | void AppBrowserController::MaybeSetInitialUrlOnReparentTab() { | 
|  | if (initial_url_.is_empty() || !IsUrlInAppScope(initial_url_)) { | 
|  | initial_url_ = GURL(); | 
|  | SetInitialURL(GetAppStartUrl()); | 
|  | } | 
|  | } | 
|  |  | 
|  | void AppBrowserController::UpdateThemePack() { | 
|  | std::optional<SkColor> theme_color = GetThemeColor(); | 
|  |  | 
|  | // TODO(crbug.com/40119262): Add tests for theme properties being set in this | 
|  | // branch. | 
|  | std::optional<SkColor> background_color = GetBackgroundColor(); | 
|  | if (theme_color == last_theme_color_ && | 
|  | background_color == last_background_color_) { | 
|  | return; | 
|  | } | 
|  | last_theme_color_ = theme_color; | 
|  | last_background_color_ = background_color; | 
|  |  | 
|  | bool ignore_custom_colors = false; | 
|  | #if BUILDFLAG(IS_CHROMEOS) | 
|  | // Some system web apps use the system theme color, and should not update | 
|  | // the theme pack here. Otherwise the colorIds for the window caption bar will | 
|  | // be remapped through `BrowserThemePack::BuildFromColors`, and colors will be | 
|  | // resolved differently than the colors set in the function `AddUiColorMixer`. | 
|  | if (system_app() && system_app()->UseSystemThemeColor()) { | 
|  | ignore_custom_colors = true; | 
|  | } | 
|  | #endif  // BUILDFLAG(IS_CHROMEOS) | 
|  |  | 
|  | bool no_custom_colors = !theme_color && !background_color; | 
|  | bool non_tabbed_no_frame_color = !has_tab_strip_ && !theme_color; | 
|  |  | 
|  | if (ignore_custom_colors || no_custom_colors || non_tabbed_no_frame_color) { | 
|  | theme_pack_ = nullptr; | 
|  | if (browser_->window()) { | 
|  | browser_->window()->UserChangedTheme( | 
|  | BrowserThemeChangeType::kWebAppTheme); | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!theme_color) { | 
|  | theme_color = GetAltColor(*background_color); | 
|  | } else if (!background_color) { | 
|  | background_color = | 
|  | (ui::NativeTheme::GetInstanceForNativeUi()->preferred_color_scheme() == | 
|  | ui::NativeTheme::PreferredColorScheme::kDark) | 
|  | ? gfx::kGoogleGrey900 | 
|  | : SK_ColorWHITE; | 
|  | } | 
|  |  | 
|  | theme_pack_ = base::MakeRefCounted<BrowserThemePack>( | 
|  | ui::ColorProviderKey::ThemeInitializerSupplier::ThemeType:: | 
|  | kAutogenerated); | 
|  | BrowserThemePack::BuildFromWebAppColors(*theme_color, *background_color, | 
|  | theme_pack_.get()); | 
|  | if (browser_->window()) { | 
|  | browser_->window()->UserChangedTheme(BrowserThemeChangeType::kWebAppTheme); | 
|  | } | 
|  | } | 
|  |  | 
|  | void AppBrowserController::SetInitialURL(const GURL& initial_url) { | 
|  | DCHECK(initial_url_.is_empty()); | 
|  | initial_url_ = initial_url; | 
|  |  | 
|  | OnReceivedInitialURL(); | 
|  | } | 
|  |  | 
|  | void AppBrowserController::UpdateSupportsDraggableRegions( | 
|  | bool supports_draggable_regions, | 
|  | content::RenderFrameHost* host) { | 
|  | CHECK(host); | 
|  |  | 
|  | // App regions are only supported in the main frame. | 
|  | if (!host->IsInPrimaryMainFrame()) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | mojo::AssociatedRemote<chrome::mojom::ChromeRenderFrame> client; | 
|  | host->GetRemoteAssociatedInterfaces()->GetInterface(&client); | 
|  | client->SetSupportsDraggableRegions(supports_draggable_regions); | 
|  | } | 
|  |  | 
|  | }  // namespace web_app |