blob: d75100d8180d091c951ef64cac40e411f8a55e9c [file] [log] [blame]
// 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/feature_list.h"
#include "base/functional/bind.h"
#include "base/strings/escape.h"
#include "base/strings/utf_string_conversions.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/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.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/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.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_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 "url/gurl.h"
#include "url/origin.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/apps/icon_standardizer.h"
#include "chrome/browser/ash/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 Browser* browser) {
return browser && browser->app_controller();
}
// static
bool AppBrowserController::IsForWebApp(const Browser* browser,
const webapps::AppId& app_id) {
return IsWebApp(browser) && browser->app_controller()->app_id() == app_id;
}
// static
Browser* AppBrowserController::FindForWebApp(const Profile& profile,
const webapps::AppId& app_id) {
const BrowserList* browser_list = BrowserList::GetInstance();
for (auto it = browser_list->begin_browsers_ordered_by_activation();
it != browser_list->end_browsers_ordered_by_activation(); ++it) {
Browser* browser = *it;
if (browser->IsAttemptingToCloseBrowser() || browser->IsBrowserClosing()) {
continue;
}
if (browser->type() == Browser::TYPE_POPUP) {
continue;
}
if (browser->profile() != &profile) {
continue;
}
if (!IsForWebApp(browser, app_id)) {
continue;
}
return browser;
}
return nullptr;
}
// 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_piece();
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());
}
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<PageActionIconType> AppBrowserController::GetTitleBarPageActions()
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::kLocalCardMigration);
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::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.
// TODO:(crbug.com/b/1394199) Disable IWA launch flash text for OSs that
// already display name on title bar.
if (IsIsolatedWebApp()) {
return GetAppShortName();
}
return GetFormattedUrlOrigin();
}
bool AppBrowserController::IsHostedApp() const {
return false;
}
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 {
ui::NativeTheme* native_theme = ui::NativeTheme::GetInstanceForNativeUi();
if (native_theme->InForcedColorsMode()) {
// use system [Window ThemeColor] when enable high contrast
return native_theme->GetSystemThemeColor(
ui::NativeTheme::SystemThemeColor::kWindow);
}
std::optional<SkColor> result;
// HTML meta theme-color tag overrides manifest theme_color, see spec:
// https://www.w3.org/TR/appmanifest/#theme_color-member
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
if (web_contents) {
std::optional<SkColor> color = web_contents->GetThemeColor();
if (color) {
result = color;
}
}
if (!result) {
return std::nullopt;
}
// The frame/tabstrip code expects an opaque color.
return SkColorSetA(*result, SK_AlphaOPAQUE);
}
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();
}
GURL AppBrowserController::GetAppNewTabUrl() const {
return GetAppStartUrl();
}
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[kColorPwaSecurityChipForegroundPolicyCert] = {
ui::kColorDisabledForeground};
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};
}
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()->ShouldUseDarkColors()
? 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