blob: e076e4d6eb6706c85341e832f9ee235285bb5044 [file] [log] [blame]
// Copyright 2018 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/web_applications/web_app.h"
#include <array>
#include <bitset>
#include <optional>
#include <ostream>
#include <string>
#include <tuple>
#include <utility>
#include <variant>
#include "base/check.h"
#include "base/check_is_test.h"
#include "base/check_op.h"
#include "base/containers/flat_tree.h"
#include "base/containers/to_value_list.h"
#include "base/not_fatal_until.h"
#include "base/notreached.h"
#include "base/numerics/clamped_math.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/to_string.h"
#include "base/types/optional_util.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/web_applications/generated_icon_fix_util.h"
#include "chrome/browser/web_applications/mojom/user_display_mode.mojom-shared.h"
#include "chrome/browser/web_applications/proto/web_app.equal.h"
#include "chrome/browser/web_applications/proto/web_app.ostream.h"
#include "chrome/browser/web_applications/proto/web_app.pb.h"
#include "chrome/browser/web_applications/proto/web_app.to_value.h"
#include "chrome/browser/web_applications/proto/web_app_install_state.ostream.h"
#include "chrome/browser/web_applications/proto/web_app_install_state.pb.h"
#include "chrome/browser/web_applications/proto/web_app_install_state.to_value.h"
#include "chrome/browser/web_applications/proto/web_app_os_integration_state.equal.h"
#include "chrome/browser/web_applications/proto/web_app_os_integration_state.pb.h"
#include "chrome/browser/web_applications/proto/web_app_os_integration_state.to_value.h"
#include "chrome/browser/web_applications/tabbed_mode_scope_matcher.h"
#include "chrome/browser/web_applications/user_display_mode.h"
#include "chrome/browser/web_applications/web_app_chromeos_data.h"
#include "chrome/browser/web_applications/web_app_constants.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/browser/web_applications/web_app_management_type.h"
#include "chrome/browser/web_applications/web_app_proto_utils.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "chrome/common/url_constants.h"
#include "components/sync/base/time.h"
#include "components/sync/protocol/proto_value_conversions.h"
#include "components/sync/protocol/web_app_specifics.equal.h"
#include "components/sync/protocol/web_app_specifics.pb.h"
#include "components/sync/protocol/web_app_specifics.to_value.h"
#include "components/webapps/browser/installable/installable_metrics.h"
#include "components/webapps/isolated_web_apps/types/storage_location.h"
#include "services/network/public/cpp/permissions_policy/permissions_policy_declaration.h"
#include "third_party/blink/public/common/manifest/manifest_util.h"
#include "third_party/blink/public/common/permissions_policy/policy_helper_public.h"
#include "third_party/blink/public/common/safe_url_pattern.h"
#include "third_party/blink/public/mojom/manifest/manifest.mojom-shared.h"
#include "third_party/blink/public/mojom/manifest/manifest_launch_handler.mojom-shared.h"
#include "third_party/liburlpattern/options.h"
#include "third_party/liburlpattern/pattern.h"
#include "third_party/protobuf/src/google/protobuf/repeated_field.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/geometry/size.h"
#include "url/origin.h"
namespace web_app {
namespace {
// Converts an optional to a string wrapped in a `Value`, or an empty `Value` if
// absent.
template <typename T>
base::Value OptionalToStringValue(const std::optional<T>& optional) {
if (optional.has_value()) {
return base::Value(base::ToString(optional.value()));
}
return base::Value();
}
// Converts an optional to a debug `Value`, or an empty `Value` if absent.
template <typename T>
base::Value OptionalAsDebugValue(const std::optional<T>& optional) {
if (optional.has_value()) {
return optional.value().AsDebugValue();
}
return base::Value();
}
std::string ColorToString(std::optional<SkColor> color) {
return color.has_value() ? color_utils::SkColorToRgbaString(color.value())
: "none";
}
std::string ApiApprovalStateToString(ApiApprovalState state) {
switch (state) {
case ApiApprovalState::kRequiresPrompt:
return "kRequiresPrompt";
case ApiApprovalState::kAllowed:
return "kAllowed";
case ApiApprovalState::kDisallowed:
return "kDisallowed";
}
}
base::Value::Dict ImageResourceDebugDict(
const blink::Manifest::ImageResource& icon) {
const auto kPurposeStrings =
std::to_array<const char*>({"Any", "Monochrome", "Maskable"});
base::Value::Dict root;
root.Set("src", icon.src.spec());
root.Set("type", icon.type);
base::Value::List sizes_json;
for (const auto& size : icon.sizes) {
std::string size_formatted = base::NumberToString(size.width()) + "x" +
base::NumberToString(size.height());
sizes_json.Append(base::Value(size_formatted));
}
root.Set("sizes", std::move(sizes_json));
base::Value::List purpose_json;
for (const auto& purpose : icon.purpose) {
purpose_json.Append(kPurposeStrings[static_cast<int>(purpose)]);
}
root.Set("purpose", std::move(purpose_json));
return root;
}
base::Value::Dict UrlPatternDebugValue(const blink::SafeUrlPattern& pattern) {
liburlpattern::Options options = {.delimiter_list = "/",
.prefix_list = "/",
.sensitive = true,
.strict = false};
liburlpattern::Pattern pathname(pattern.pathname, options, "[^/]+?");
base::Value::Dict pattern_dict;
pattern_dict.Set("pathname", pathname.GeneratePatternString());
return pattern_dict;
}
base::Value OptTabStripToDebugValue(
std::optional<blink::Manifest::TabStrip> tab_strip) {
if (!tab_strip.has_value()) {
return base::Value();
}
base::Value::Dict result;
base::Value::Dict new_tab_button_json;
new_tab_button_json.Set(
"url", base::ToString(tab_strip->new_tab_button.url.value_or(GURL(""))));
result.Set("new_tab_button", std::move(new_tab_button_json));
if (std::holds_alternative<TabStrip::Visibility>(tab_strip->home_tab)) {
result.Set(
"home_tab",
base::ToString(std::get<TabStrip::Visibility>(tab_strip->home_tab)));
} else {
base::Value::Dict home_tab_json;
const blink::Manifest::HomeTabParams& home_tab_params =
std::get<blink::Manifest::HomeTabParams>(tab_strip->home_tab);
base::Value::List icons_json;
std::optional<std::vector<blink::Manifest::ImageResource>> icons =
home_tab_params.icons;
for (auto& icon : *icons) {
icons_json.Append(ImageResourceDebugDict(icon));
}
base::Value::List scope_patterns_json;
const std::vector<blink::SafeUrlPattern>& scope_patterns =
home_tab_params.scope_patterns;
for (const auto& scope_pattern : scope_patterns) {
scope_patterns_json.Append(UrlPatternDebugValue(scope_pattern));
}
home_tab_json.Set("icons", std::move(icons_json));
home_tab_json.Set("scope_patterns", std::move(scope_patterns_json));
result.Set("home_tab", std::move(home_tab_json));
}
return base::Value(std::move(result));
}
base::Value RelatedApplicationsToDebugValue(
const std::vector<blink::Manifest::RelatedApplication>&
related_applications) {
base::Value::List related_applications_json;
for (const auto& related_application : related_applications) {
base::Value::Dict related_application_json;
related_application_json.Set("platform",
related_application.platform.value());
if (related_application.url.is_valid()) {
related_application_json.Set("url", related_application.url.spec());
}
if (related_application.id.has_value()) {
related_application_json.Set("id", related_application.id.value());
}
related_applications_json.Append(std::move(related_application_json));
}
return base::Value(std::move(related_applications_json));
}
void CheckValidPendingUpdateInfo(
const std::optional<proto::PendingUpdateInfo>& pending_update_info) {
if (pending_update_info.has_value()) {
CHECK(pending_update_info->has_name() ||
(!pending_update_info->trusted_icons().empty() &&
!pending_update_info->manifest_icons().empty()));
if (!pending_update_info->trusted_icons().empty() &&
!pending_update_info->manifest_icons().empty()) {
for (const auto& icon : pending_update_info->trusted_icons()) {
CHECK(icon.has_url() && icon.has_size_in_px() && icon.has_purpose());
}
for (const auto& icon : pending_update_info->manifest_icons()) {
CHECK(icon.has_url() && icon.has_size_in_px() && icon.has_purpose());
}
}
}
}
} // namespace
WebApp::CachedDerivedData::CachedDerivedData() = default;
WebApp::CachedDerivedData::~CachedDerivedData() = default;
WebApp::CachedDerivedData::CachedDerivedData(CachedDerivedData&&) = default;
WebApp::CachedDerivedData& WebApp::CachedDerivedData::operator=(
CachedDerivedData&&) = default;
WebApp::CachedDerivedData::CachedDerivedData(const CachedDerivedData&) {}
WebApp::CachedDerivedData& WebApp::CachedDerivedData::operator=(
const CachedDerivedData&) {
return *this;
}
WebApp::WebApp(const webapps::AppId& app_id)
: app_id_(app_id),
chromeos_data_(IsChromeOsDataMandatory()
? std::make_optional<WebAppChromeOsData>()
: std::nullopt) {}
WebApp::WebApp(const webapps::ManifestId& manifest_id,
const GURL& start_url,
const GURL& scope,
std::optional<webapps::AppId> parent_app_id,
std::optional<webapps::ManifestId> parent_manifest_id)
: app_id_(GenerateAppIdFromManifestId(manifest_id, parent_manifest_id)),
start_url_(start_url),
scope_(scope),
chromeos_data_(IsChromeOsDataMandatory()
? std::make_optional<WebAppChromeOsData>()
: std::nullopt),
manifest_id_(manifest_id),
parent_app_id_(parent_app_id) {
CHECK(manifest_id.is_valid());
CHECK(start_url.is_valid());
CHECK(scope.is_valid());
CHECK(url::IsSameOriginWith(manifest_id_, start_url_))
<< manifest_id_.spec() << " vs " << start_url_.spec();
CHECK(url::IsSameOriginWith(start_url_, scope_))
<< start_url_.spec() << " vs " << scope_.spec();
CHECK(!scope_.has_ref() && !scope_.has_query());
CHECK(!manifest_id_.has_ref());
CHECK(base::StartsWith(start_url_.spec(), scope_.spec(),
base::CompareCase::SENSITIVE))
<< "Start URL " << start_url_ << " must be nested in scope " << scope_;
if (parent_app_id_.has_value()) {
CHECK(!parent_app_id_->empty());
}
CHECK(!!parent_app_id == !!parent_manifest_id);
// Ensure sync proto is initialized.
SetSyncProto(sync_proto_);
}
WebApp::~WebApp() = default;
WebApp::WebApp(const WebApp& web_app) = default;
WebApp::WebApp(WebApp&& web_app) = default;
WebApp& WebApp::operator=(WebApp&& web_app) = default;
const SortedSizesPx& WebApp::downloaded_icon_sizes(IconPurpose purpose) const {
switch (purpose) {
case IconPurpose::ANY:
return downloaded_icon_sizes_any_;
case IconPurpose::MONOCHROME:
return downloaded_icon_sizes_monochrome_;
case IconPurpose::MASKABLE:
return downloaded_icon_sizes_maskable_;
}
}
webapps::ManifestId WebApp::manifest_id() const {
// Almost all production use-cases should have the manifest_id set, but in
// some test it is not. If the manifest id is not set, then fall back to the
// start_url, as per the algorithm in
// https://www.w3.org/TR/appmanifest/#id-member.
if (manifest_id_.is_empty()) {
CHECK_IS_TEST();
// This is why the function must return a value instead of a const ref, as
// this object would be temporary.
return GenerateManifestIdFromStartUrlOnly(start_url_);
}
return manifest_id_;
}
const SortedSizesPx& WebApp::stored_trusted_icon_sizes(
IconPurpose purpose) const {
switch (purpose) {
case IconPurpose::ANY:
return stored_trusted_icon_sizes_any_;
case IconPurpose::MASKABLE:
return stored_trusted_icon_sizes_maskable_;
case IconPurpose::MONOCHROME:
NOTREACHED();
}
}
void WebApp::AddSource(WebAppManagement::Type source) {
sources_.Put(source);
}
void WebApp::RemoveSource(WebAppManagement::Type source) {
sources_.Remove(source);
management_to_external_config_map_.erase(source);
}
bool WebApp::HasAnySources() const {
return !sources_.empty();
}
bool WebApp::HasOnlySource(WebAppManagement::Type source) const {
WebAppManagementTypes specified_sources;
specified_sources.Put(source);
return HasAnySpecifiedSourcesAndNoOtherSources(sources_, specified_sources);
}
WebAppManagementTypes WebApp::GetSources() const {
return sources_;
}
bool WebApp::IsSynced() const {
return sources_.Has(WebAppManagement::kSync);
}
bool WebApp::IsPreinstalledApp() const {
return sources_.Has(WebAppManagement::kDefault);
}
bool WebApp::IsPolicyInstalledApp() const {
return sources_.Has(WebAppManagement::kPolicy);
}
bool WebApp::IsIwaPolicyInstalledApp() const {
return sources_.Has(WebAppManagement::kIwaPolicy);
}
bool WebApp::IsIwaShimlessRmaApp() const {
return sources_.Has(WebAppManagement::kIwaShimlessRma);
}
bool WebApp::IsSystemApp() const {
return sources_.Has(WebAppManagement::kSystem);
}
bool WebApp::IsWebAppStoreInstalledApp() const {
return sources_.Has(WebAppManagement::kWebAppStore);
}
bool WebApp::IsSubAppInstalledApp() const {
return sources_.Has(WebAppManagement::kSubApp);
}
bool WebApp::IsKioskInstalledApp() const {
return sources_.Has(WebAppManagement::kKiosk);
}
bool WebApp::CanUserUninstallWebApp() const {
return web_app::CanUserUninstallWebApp(app_id_, sources_);
}
bool WebApp::WasInstalledByUser() const {
return sources_.Has(WebAppManagement::kSync) ||
sources_.Has(WebAppManagement::kUserInstalled) ||
sources_.Has(WebAppManagement::kWebAppStore) ||
sources_.Has(WebAppManagement::kOneDriveIntegration) ||
sources_.Has(WebAppManagement::kIwaUserInstalled);
}
WebAppManagement::Type WebApp::GetHighestPrioritySource() const {
// `WebAppManagementTypes` is iterated in order of priority.
// Top priority sources are iterated first.
for (WebAppManagement::Type source : WebAppManagementTypes::All()) {
if (sources_.Has(source)) {
return source;
}
}
DUMP_WILL_BE_NOTREACHED();
return WebAppManagement::kMaxValue;
}
void WebApp::SetName(const std::string& name) {
name_ = name;
}
void WebApp::SetDescription(const std::string& description) {
description_ = description;
}
void WebApp::SetStartUrl(const GURL& start_url) {
CHECK(start_url.is_valid());
if (manifest_id_.is_empty()) {
manifest_id_ = GenerateManifestIdFromStartUrlOnly(start_url);
}
CHECK(url::IsSameOriginWith(manifest_id(), start_url))
<< manifest_id().spec() << " " << start_url.spec();
start_url_ = start_url;
// Ensure sync proto is initialized and remains consistent. Logic in
// `SetSyncProto` will populate an unset `start_url` on the proto.
sync_proto_.clear_start_url();
SetSyncProto(sync_proto_);
// Ensure that scope is always set.
if (scope_.is_empty()) {
scope_ = start_url_.GetWithoutFilename();
}
}
void WebApp::SetScope(const GURL& scope) {
GURL scope_for_app = scope;
// If the given scope is empty, populate the scope from the `start_url_`.
if (scope.is_empty()) {
CHECK(start_url_.is_valid());
scope_for_app = start_url_.GetWithoutFilename();
}
CHECK(scope_for_app.is_valid());
// Ensure that the scope can never include queries or fragments, as per spec.
GURL::Replacements scope_replacements;
scope_replacements.ClearRef();
scope_replacements.ClearQuery();
scope_ = scope_for_app.ReplaceComponents(scope_replacements);
// Post-migration check: Scope should never be empty after setting.
CHECK(!scope_.is_empty());
}
void WebApp::SetThemeColor(std::optional<SkColor> theme_color) {
theme_color_ = theme_color;
}
void WebApp::SetDarkModeThemeColor(
std::optional<SkColor> dark_mode_theme_color) {
dark_mode_theme_color_ = dark_mode_theme_color;
}
void WebApp::SetBackgroundColor(std::optional<SkColor> background_color) {
background_color_ = background_color;
}
void WebApp::SetDarkModeBackgroundColor(
std::optional<SkColor> dark_mode_background_color) {
dark_mode_background_color_ = dark_mode_background_color;
}
void WebApp::SetDisplayMode(DisplayMode display_mode) {
DCHECK_NE(DisplayMode::kUndefined, display_mode);
display_mode_ = display_mode;
}
void WebApp::SetUserDisplayMode(mojom::UserDisplayMode user_display_mode) {
sync_pb::WebAppSpecifics_UserDisplayMode sync_udm =
ToWebAppSpecificsUserDisplayMode(user_display_mode);
SetPlatformSpecificUserDisplayMode(sync_udm, &sync_proto_);
}
void WebApp::SetDisplayModeOverride(
std::vector<DisplayMode> display_mode_override) {
display_mode_override_ = std::move(display_mode_override);
}
void WebApp::SetWebAppChromeOsData(
std::optional<WebAppChromeOsData> chromeos_data) {
chromeos_data_ = std::move(chromeos_data);
}
void WebApp::SetInstallState(proto::InstallState install_state) {
install_state_ = install_state;
}
void WebApp::SetIsFromSyncAndPendingInstallation(
bool is_from_sync_and_pending_installation) {
is_from_sync_and_pending_installation_ =
is_from_sync_and_pending_installation;
}
void WebApp::SetIsUninstalling(bool is_uninstalling) {
is_uninstalling_ = is_uninstalling;
}
void WebApp::SetManifestIcons(std::vector<apps::IconInfo> manifest_icons) {
manifest_icons_ = std::move(manifest_icons);
}
void WebApp::SetDownloadedIconSizes(IconPurpose purpose, SortedSizesPx sizes) {
switch (purpose) {
case IconPurpose::ANY:
downloaded_icon_sizes_any_ = std::move(sizes);
break;
case IconPurpose::MONOCHROME:
downloaded_icon_sizes_monochrome_ = std::move(sizes);
break;
case IconPurpose::MASKABLE:
downloaded_icon_sizes_maskable_ = std::move(sizes);
break;
}
}
void WebApp::SetIsGeneratedIcon(bool is_generated_icon) {
is_generated_icon_ = is_generated_icon;
}
void WebApp::SetFileHandlers(apps::FileHandlers file_handlers) {
file_handlers_ = std::move(file_handlers);
}
void WebApp::SetFileHandlerApprovalState(ApiApprovalState approval_state) {
file_handler_approval_state_ = approval_state;
}
void WebApp::SetShareTarget(std::optional<apps::ShareTarget> share_target) {
share_target_ = std::move(share_target);
}
void WebApp::SetAdditionalSearchTerms(
std::vector<std::string> additional_search_terms) {
additional_search_terms_ = std::move(additional_search_terms);
}
void WebApp::SetProtocolHandlers(
std::vector<apps::ProtocolHandlerInfo> handlers) {
protocol_handlers_ = std::move(handlers);
}
void WebApp::SetAllowedLaunchProtocols(
base::flat_set<std::string> allowed_launch_protocols) {
allowed_launch_protocols_ = std::move(allowed_launch_protocols);
}
void WebApp::SetDisallowedLaunchProtocols(
base::flat_set<std::string> disallowed_launch_protocols) {
disallowed_launch_protocols_ = std::move(disallowed_launch_protocols);
}
void WebApp::SetScopeExtensions(
base::flat_set<ScopeExtensionInfo> scope_extensions) {
scope_extensions_ = std::move(scope_extensions);
}
void WebApp::SetValidatedScopeExtensions(
base::flat_set<ScopeExtensionInfo> validated_scope_extensions) {
validated_scope_extensions_ = std::move(validated_scope_extensions);
}
void WebApp::SetLockScreenStartUrl(const GURL& lock_screen_start_url) {
DCHECK(lock_screen_start_url.is_empty() || lock_screen_start_url.is_valid());
lock_screen_start_url_ = lock_screen_start_url;
}
void WebApp::SetNoteTakingNewNoteUrl(const GURL& note_taking_new_note_url) {
DCHECK(note_taking_new_note_url.is_empty() ||
note_taking_new_note_url.is_valid());
note_taking_new_note_url_ = note_taking_new_note_url;
}
void WebApp::SetShortcutsMenuInfo(
std::vector<WebAppShortcutsMenuItemInfo> shortcuts_menu_item_infos) {
shortcuts_menu_item_infos_ = std::move(shortcuts_menu_item_infos);
}
void WebApp::SetLastBadgingTime(const base::Time& time) {
last_badging_time_ = time;
}
void WebApp::SetLastLaunchTime(const base::Time& time) {
last_launch_time_ = time;
}
void WebApp::SetFirstInstallTime(const base::Time& time) {
first_install_time_ = time;
}
void WebApp::SetManifestUpdateTime(const base::Time& time) {
manifest_update_time_ = time;
}
void WebApp::SetRunOnOsLoginMode(RunOnOsLoginMode mode) {
run_on_os_login_mode_ = mode;
}
void WebApp::SetSyncProto(sync_pb::WebAppSpecifics sync_proto) {
// Populate sync_proto's start_url from this WebApp if missing.
if (!start_url().is_empty()) {
CHECK(start_url().is_valid());
// Note: sync data may have a start_url that does not match the `WebApp`
// start_url, but it does not update the app (matching pre-M125 behaviour).
if (!sync_proto.has_start_url()) {
sync_proto.set_start_url(start_url().spec());
}
}
// Sync data must never be set on an app with mismatching manifest_id.
CHECK(manifest_id().is_valid());
std::string relative_manifest_id_path = RelativeManifestIdPath(manifest_id());
if (sync_proto.has_relative_manifest_id()) {
CHECK_EQ(sync_proto.relative_manifest_id(), relative_manifest_id_path);
} else {
sync_proto.set_relative_manifest_id(relative_manifest_id_path);
}
// Clear any invalid less-important fields.
if (sync_proto.has_scope() && !GURL(sync_proto.scope()).is_valid()) {
DLOG(ERROR) << "SetSyncProto: scope has invalid url: "
<< sync_proto.scope();
sync_proto.clear_scope();
}
if (!ParseAppIconInfos("SetSyncProto", sync_proto.icon_infos()).has_value()) {
sync_proto.clear_icon_infos();
}
if (sync_proto.has_user_launch_ordinal() &&
!syncer::StringOrdinal(sync_proto.user_launch_ordinal()).IsValid()) {
sync_proto.clear_user_launch_ordinal();
}
if (sync_proto.has_user_page_ordinal() &&
!syncer::StringOrdinal(sync_proto.user_page_ordinal()).IsValid()) {
sync_proto.clear_user_page_ordinal();
}
if (!ParseAppIconInfos("SetSyncProtoTrustedIcons", sync_proto.trusted_icons())
.has_value()) {
sync_proto.clear_trusted_icons();
}
sync_proto_ = std::move(sync_proto);
}
void WebApp::SetCaptureLinks(blink::mojom::CaptureLinks capture_links) {
capture_links_ = capture_links;
}
void WebApp::SetLaunchQueryParams(
std::optional<std::string> launch_query_params) {
launch_query_params_ = std::move(launch_query_params);
}
void WebApp::SetManifestUrl(const GURL& manifest_url) {
CHECK(manifest_url.is_valid() || manifest_url.is_empty(),
base::NotFatalUntil::M138);
manifest_url_ = manifest_url;
}
void WebApp::SetManifestId(const webapps::ManifestId& manifest_id) {
CHECK(manifest_id.is_valid());
CHECK(start_url_.is_empty() || url::IsSameOriginWith(start_url_, manifest_id))
<< start_url_.spec() << " vs " << manifest_id.spec();
CHECK(!manifest_id.has_ref());
manifest_id_ = manifest_id;
// Ensure sync proto is initialized and remains consistent. Logic in
// `SetSyncProto` will populate an unset `relative_manifest_id` on the proto.
sync_proto_.clear_relative_manifest_id();
SetSyncProto(sync_proto_);
}
void WebApp::SetWindowControlsOverlayEnabled(bool enabled) {
window_controls_overlay_enabled_ = enabled;
}
void WebApp::SetLaunchHandler(std::optional<LaunchHandler> launch_handler) {
launch_handler_ = std::move(launch_handler);
}
void WebApp::SetParentAppId(
const std::optional<webapps::AppId>& parent_app_id) {
parent_app_id_ = parent_app_id;
}
void WebApp::SetPermissionsPolicy(
network::ParsedPermissionsPolicy permissions_policy) {
permissions_policy_ = std::move(permissions_policy);
}
void WebApp::SetLatestInstallSource(
std::optional<webapps::WebappInstallSource> latest_install_source) {
latest_install_source_ = latest_install_source;
}
void WebApp::SetAppSizeInBytes(std::optional<int64_t> app_size_in_bytes) {
app_size_in_bytes_ = app_size_in_bytes;
}
void WebApp::SetDataSizeInBytes(std::optional<int64_t> data_size_in_bytes) {
data_size_in_bytes_ = data_size_in_bytes;
}
void WebApp::SetWebAppManagementExternalConfigMap(
ExternalConfigMap management_to_external_config_map) {
management_to_external_config_map_ =
std::move(management_to_external_config_map);
}
void WebApp::SetTabStrip(std::optional<blink::Manifest::TabStrip> tab_strip) {
tab_strip_ = std::move(tab_strip);
cached_derived_data_.home_tab_scope.reset();
}
void WebApp::SetCurrentOsIntegrationStates(
proto::os_state::WebAppOsIntegration current_os_integration_states) {
current_os_integration_states_ = std::move(current_os_integration_states);
}
void WebApp::SetIsolationData(IsolationData isolation_data) {
CHECK(manifest_id_.is_valid() &&
manifest_id_.SchemeIs(chrome::kIsolatedAppScheme));
if (isolation_data.pending_update_info().has_value()) {
DCHECK_EQ(isolation_data.location().dev_mode(),
isolation_data.pending_update_info()->location.dev_mode())
<< "IsolationData dev_mode mismatch between current location and "
"pending update location.";
}
isolation_data_ = isolation_data;
}
void WebApp::SetLinkCapturingUserPreference(
proto::LinkCapturingUserPreference user_link_capturing_preference) {
user_link_capturing_preference_ = user_link_capturing_preference;
}
void WebApp::SetSupportedLinksOfferIgnoreCount(int ignore_count) {
supported_links_offer_ignore_count_ = ignore_count;
}
void WebApp::SetSupportedLinksOfferDismissCount(int dismiss_count) {
supported_links_offer_dismiss_count_ = dismiss_count;
}
void WebApp::SetIsDiyApp(bool is_diy_app) {
is_diy_app_ = is_diy_app;
}
void WebApp::SetWasShortcutApp(bool was_shortcut_app) {
was_shortcut_app_ = was_shortcut_app;
}
void WebApp::SetDiyAppIconsMaskedOnMac(bool diy_app_icons_masked_on_mac) {
diy_app_icons_masked_on_mac_ = diy_app_icons_masked_on_mac;
}
void WebApp::SetRelatedApplications(
std::vector<blink::Manifest::RelatedApplication> related_applications) {
related_applications_ = std::move(related_applications);
}
void WebApp::AddPlaceholderInfoToManagementExternalConfigMap(
WebAppManagement::Type type,
bool is_placeholder) {
DCHECK_NE(type, WebAppManagement::Type::kSync);
DCHECK_NE(type, WebAppManagement::Type::kUserInstalled);
CHECK(!WebAppManagement::IsIwaType(type)) << type;
management_to_external_config_map_[type].is_placeholder = is_placeholder;
}
void WebApp::AddInstallURLToManagementExternalConfigMap(
WebAppManagement::Type type,
GURL install_url) {
DCHECK_NE(type, WebAppManagement::Type::kSync);
DCHECK_NE(type, WebAppManagement::Type::kUserInstalled);
CHECK(!WebAppManagement::IsIwaType(type)) << type;
DCHECK(install_url.is_valid());
management_to_external_config_map_[type].install_urls.emplace(
std::move(install_url));
}
void WebApp::AddPolicyIdToManagementExternalConfigMap(
WebAppManagement::Type type,
std::string policy_id) {
DCHECK_NE(type, WebAppManagement::Type::kSync);
DCHECK_NE(type, WebAppManagement::Type::kUserInstalled);
CHECK(!WebAppManagement::IsIwaType(type)) << type;
DCHECK(!policy_id.empty());
management_to_external_config_map_[type].additional_policy_ids.emplace(
std::move(policy_id));
}
void WebApp::AddExternalSourceInformation(WebAppManagement::Type type,
GURL install_url,
bool is_placeholder) {
AddInstallURLToManagementExternalConfigMap(type, std::move(install_url));
AddPlaceholderInfoToManagementExternalConfigMap(type, is_placeholder);
}
bool WebApp::RemoveInstallUrlForSource(WebAppManagement::Type type,
const GURL& install_url) {
if (!management_to_external_config_map_.count(type))
return false;
bool removed =
management_to_external_config_map_[type].install_urls.erase(install_url);
if (management_to_external_config_map_[type].install_urls.empty()) {
management_to_external_config_map_.erase(type);
}
return removed;
}
void WebApp::SetAlwaysShowToolbarInFullscreen(bool show) {
always_show_toolbar_in_fullscreen_ = show;
}
void WebApp::SetLatestInstallTime(const base::Time& latest_install_time) {
latest_install_time_ = latest_install_time;
}
void WebApp::SetGeneratedIconFix(
std::optional<proto::GeneratedIconFix> generated_icon_fix) {
CHECK(!generated_icon_fix.has_value() ||
generated_icon_fix_util::IsValid(*generated_icon_fix));
generated_icon_fix_ = generated_icon_fix;
}
void WebApp::SetPendingUpdateInfo(
std::optional<proto::PendingUpdateInfo> pending_update_info) {
CheckValidPendingUpdateInfo(pending_update_info);
pending_update_info_ = std::move(pending_update_info);
}
void WebApp::SetTrustedIcons(std::vector<apps::IconInfo> trusted_icons) {
trusted_icons_ = std::move(trusted_icons);
}
void WebApp::SetStoredTrustedIconSizes(IconPurpose purpose,
SortedSizesPx sizes) {
switch (purpose) {
case IconPurpose::ANY:
stored_trusted_icon_sizes_any_ = std::move(sizes);
break;
case IconPurpose::MASKABLE:
stored_trusted_icon_sizes_maskable_ = std::move(sizes);
break;
case IconPurpose::MONOCHROME:
NOTREACHED();
}
}
WebApp::ClientData::ClientData() = default;
WebApp::ClientData::~ClientData() = default;
WebApp::ClientData::ClientData(const ClientData& client_data) = default;
base::Value WebApp::ClientData::AsDebugValue() const {
base::Value::Dict root;
#if BUILDFLAG(IS_CHROMEOS)
root.Set("system_web_app_data", OptionalAsDebugValue(system_web_app_data));
#endif
return base::Value(std::move(root));
}
WebApp::ExternalManagementConfig::ExternalManagementConfig() = default;
WebApp::ExternalManagementConfig::ExternalManagementConfig(
bool is_placeholder,
const base::flat_set<GURL>& install_urls,
const base::flat_set<std::string>& additional_policy_ids)
: is_placeholder(is_placeholder),
install_urls(install_urls),
additional_policy_ids(additional_policy_ids) {}
WebApp::ExternalManagementConfig::~ExternalManagementConfig() = default;
WebApp::ExternalManagementConfig::ExternalManagementConfig(
const ExternalManagementConfig& external_management_config) = default;
WebApp::ExternalManagementConfig& WebApp::ExternalManagementConfig::operator=(
const ExternalManagementConfig& external_management_config) = default;
WebApp::ExternalManagementConfig& WebApp::ExternalManagementConfig::operator=(
ExternalManagementConfig&& external_management_config) = default;
base::Value::Dict WebApp::ExternalManagementConfig::AsDebugValue() const {
base::Value::Dict root;
base::Value::List urls;
for (const auto& install_url : install_urls) {
urls.Append(install_url.spec());
}
base::Value::List policy_ids;
for (const auto& policy_id : additional_policy_ids) {
policy_ids.Append(policy_id);
}
root.Set("install_urls", std::move(urls));
root.Set("additional_policy_ids", std::move(policy_ids));
root.Set("is_placeholder", is_placeholder);
return root;
}
const std::vector<TabbedModeScopeMatcher>& WebApp::GetTabbedModeHomeScope()
const {
if (!cached_derived_data_.home_tab_scope.has_value()) {
cached_derived_data_.home_tab_scope.emplace();
if (tab_strip_.has_value()) {
if (const auto* params = std::get_if<blink::Manifest::HomeTabParams>(
&tab_strip_->home_tab)) {
for (auto& pattern : params->scope_patterns) {
cached_derived_data_.home_tab_scope->emplace_back(pattern);
}
}
}
}
return *cached_derived_data_.home_tab_scope;
}
const std::optional<proto::GeneratedIconFix>& WebApp::generated_icon_fix()
const {
CHECK(!generated_icon_fix_.has_value() ||
generated_icon_fix_util::IsValid(generated_icon_fix_.value()));
return generated_icon_fix_;
}
bool WebApp::operator==(const WebApp& other) const {
auto AsTuple = [](const WebApp& app) {
// Keep in order declared in web_app.h.
return std::tie(
// Disable clang-format so diffs are clearer when fields are added.
// clang-format off
app.app_id_,
app.sources_,
app.name_,
app.description_,
app.start_url_,
app.launch_query_params_,
app.scope_,
app.theme_color_,
app.dark_mode_theme_color_,
app.background_color_,
app.dark_mode_background_color_,
app.display_mode_,
app.display_mode_override_,
app.chromeos_data_,
app.install_state_,
app.is_from_sync_and_pending_installation_,
app.is_uninstalling_,
app.manifest_icons_,
app.downloaded_icon_sizes_any_,
app.downloaded_icon_sizes_monochrome_,
app.downloaded_icon_sizes_maskable_,
app.is_generated_icon_,
app.shortcuts_menu_item_infos_,
app.file_handlers_,
app.share_target_,
app.additional_search_terms_,
app.protocol_handlers_,
app.allowed_launch_protocols_,
app.disallowed_launch_protocols_,
app.scope_extensions_,
app.validated_scope_extensions_,
app.lock_screen_start_url_,
app.note_taking_new_note_url_,
app.last_badging_time_,
app.last_launch_time_,
app.first_install_time_,
app.manifest_update_time_,
app.run_on_os_login_mode_,
app.sync_proto_,
app.capture_links_,
app.manifest_url_,
app.manifest_id_,
#if BUILDFLAG(IS_CHROMEOS)
app.client_data_.system_web_app_data,
#endif
app.file_handler_approval_state_,
app.window_controls_overlay_enabled_,
app.launch_handler_,
app.parent_app_id_,
app.permissions_policy_,
app.latest_install_source_,
app.app_size_in_bytes_,
app.data_size_in_bytes_,
app.management_to_external_config_map_,
app.tab_strip_,
app.always_show_toolbar_in_fullscreen_,
app.current_os_integration_states_,
app.isolation_data_,
app.user_link_capturing_preference_,
app.latest_install_time_,
app.generated_icon_fix_,
app.supported_links_offer_ignore_count_,
app.supported_links_offer_dismiss_count_,
app.is_diy_app_,
app.was_shortcut_app_,
app.related_applications_,
app.diy_app_icons_masked_on_mac_,
app.pending_update_info_,
app.trusted_icons_,
app.stored_trusted_icon_sizes_any_,
app.stored_trusted_icon_sizes_maskable_
// clang-format on
);
};
return AsTuple(*this) == AsTuple(other);
}
base::Value WebApp::AsDebugValueWithOnlyPlatformAgnosticFields() const {
base::Value::Dict root;
auto ConvertList = [](const auto& list) {
base::Value::List list_json;
for (const auto& item : list)
list_json.Append(item);
return list_json;
};
auto ConvertDebugValueList = [](const auto& list) {
base::Value::List list_json;
for (const auto& item : list)
list_json.Append(item.AsDebugValue());
return list_json;
};
auto ConvertOptional = [](const auto& value) {
return value ? base::Value(*value) : base::Value();
};
// Prefix with a ! so these fields appear at the top when serialized.
root.Set("!app_id", app_id_);
root.Set("!name", name_);
root.Set("additional_search_terms", ConvertList(additional_search_terms_));
root.Set("app_service_icon_url",
base::StrCat({"chrome://app-icon/", app_id_, "/32"}));
root.Set("app_size_in_bytes", OptionalToStringValue(app_size_in_bytes_));
root.Set("allowed_launch_protocols", ConvertList(allowed_launch_protocols_));
root.Set("background_color", ColorToString(background_color_));
root.Set("capture_links", base::ToString(capture_links_));
root.Set("data_size_in_bytes", OptionalToStringValue(data_size_in_bytes_));
root.Set("dark_mode_background_color",
ColorToString(dark_mode_background_color_));
root.Set("dark_mode_theme_color", ColorToString(dark_mode_theme_color_));
root.Set("disallowed_launch_protocols",
ConvertList(disallowed_launch_protocols_));
root.Set("description", description_);
root.Set("display_mode", blink::DisplayModeToString(display_mode_));
base::Value::List display_override;
for (const DisplayMode& mode : display_mode_override_)
display_override.Append(blink::DisplayModeToString(mode));
root.Set("display_override", std::move(display_override));
base::Value::Dict downloaded_icon_sizes_json;
for (IconPurpose purpose : kIconPurposes) {
downloaded_icon_sizes_json.Set(base::ToString(purpose),
ConvertList(downloaded_icon_sizes(purpose)));
}
root.Set("downloaded_icon_sizes", std::move(downloaded_icon_sizes_json));
root.Set("file_handler_approval_state",
ApiApprovalStateToString(file_handler_approval_state_));
root.Set("file_handlers", ConvertDebugValueList(file_handlers_));
root.Set("manifest_icons", ConvertDebugValueList(manifest_icons_));
root.Set("latest_install_source",
OptionalToStringValue(latest_install_source_));
base::Value::Dict external_map;
for (const auto& it : management_to_external_config_map_) {
external_map.Set(base::ToString(it.first), it.second.AsDebugValue());
}
root.Set("management_type_to_external_configuration_map",
std::move(external_map));
root.Set("first_install_time", base::ToString(first_install_time_));
root.Set("is_generated_icon", is_generated_icon_);
root.Set("is_from_sync_and_pending_installation",
is_from_sync_and_pending_installation_);
root.Set("install_state", base::ToString(install_state_));
root.Set("is_uninstalling", is_uninstalling_);
root.Set("last_badging_time", base::ToString(last_badging_time_));
root.Set("last_launch_time", base::ToString(last_launch_time_));
if (launch_handler_) {
base::Value::Dict launch_handler_json;
launch_handler_json.Set(
"client_mode", base::ToString(launch_handler_->parsed_client_mode()));
launch_handler_json.Set("client_mode_valid_and_specified",
launch_handler_->client_mode_valid_and_specified());
root.Set("launch_handler", std::move(launch_handler_json));
} else {
root.Set("launch_handler", base::Value());
}
root.Set("launch_query_params", ConvertOptional(launch_query_params_));
root.Set("manifest_update_time", base::ToString(manifest_update_time_));
root.Set("manifest_url", base::ToString(manifest_url_));
root.Set("lock_screen_start_url", base::ToString(lock_screen_start_url_));
root.Set("note_taking_new_note_url",
base::ToString(note_taking_new_note_url_));
root.Set("parent_app_id", OptionalToStringValue(parent_app_id_));
if (!permissions_policy_.empty()) {
base::Value::List policy_list;
const auto& feature_to_name_map =
blink::GetPermissionsPolicyFeatureToNameMap();
for (const auto& decl : permissions_policy_) {
base::Value::Dict json_decl;
const auto& feature_name = feature_to_name_map.find(decl.feature);
if (feature_name == feature_to_name_map.end()) {
continue;
}
json_decl.Set("feature", feature_name->second);
base::Value::List allowlist_json;
for (const auto& allowlist_item : GetSerializedAllowedOrigins(decl)) {
allowlist_json.Append(allowlist_item);
}
json_decl.Set("allowed_origins", std::move(allowlist_json));
json_decl.Set("matches_all_origins", decl.matches_all_origins);
json_decl.Set("matches_opaque_src", decl.matches_opaque_src);
policy_list.Append(std::move(json_decl));
}
root.Set("permissions_policy", std::move(policy_list));
}
root.Set("protocol_handlers", ConvertDebugValueList(protocol_handlers_));
root.Set("run_on_os_login_mode", base::ToString(run_on_os_login_mode_));
root.Set("scope", base::ToString(scope_));
root.Set("share_target", OptionalAsDebugValue(share_target_));
root.Set("shortcuts_menu_item_infos",
ConvertDebugValueList(shortcuts_menu_item_infos_));
base::Value::List sources;
for (WebAppManagement::Type source : WebAppManagementTypes::All()) {
if (sources_.Has(source)) {
sources.Append(base::ToString(source));
}
}
root.Set("sources", std::move(sources));
root.Set("start_url", base::ToString(start_url_));
root.Set("sync_proto", syncer::WebAppSpecificsToValue(sync_proto_));
root.Set("theme_color", ColorToString(theme_color_));
root.Set("manifest_id", manifest_id_.spec());
root.Set("scope_extensions", ConvertDebugValueList(scope_extensions_));
root.Set("scope_extensions_validated",
ConvertDebugValueList(validated_scope_extensions_));
root.Set("window_controls_overlay_enabled", window_controls_overlay_enabled_);
root.Set("tab_strip", OptTabStripToDebugValue(tab_strip_));
root.Set("always_show_toolbar_in_fullscreen",
always_show_toolbar_in_fullscreen_);
root.Set("current_os_integration_states",
proto::os_state::Serialize(current_os_integration_states_));
root.Set("isolation_data", OptionalAsDebugValue(isolation_data_));
root.Set("user_link_capturing_preference",
base::ToString(user_link_capturing_preference_));
root.Set("latest_install_time", base::ToString(latest_install_time_));
proto::MaybeSerialize(generated_icon_fix_, "generated_icon_fix", root);
root.Set("supported_links_offer_ignore_count",
supported_links_offer_ignore_count_);
root.Set("supported_links_offer_dismiss_count",
supported_links_offer_dismiss_count_);
root.Set("is_diy_app", is_diy_app_);
root.Set("was_shortcut_app", was_shortcut_app_);
root.Set("diy_app_icons_masked_on_mac", diy_app_icons_masked_on_mac_);
root.Set("related_applications",
RelatedApplicationsToDebugValue(related_applications_));
proto::MaybeSerialize(pending_update_info_, "pending_update_info", root);
root.Set("trusted_icons", ConvertDebugValueList(trusted_icons_));
base::Value::Dict stored_trusted_icon_sizes_json;
for (IconPurpose purpose : kIconPurposes) {
// There can never be trusted monochrome icons.
if (purpose == IconPurpose::MONOCHROME) {
continue;
}
stored_trusted_icon_sizes_json.Set(
base::ToString(purpose),
ConvertList(stored_trusted_icon_sizes(purpose)));
}
root.Set("stored_trusted_icon_sizes",
std::move(stored_trusted_icon_sizes_json));
return base::Value(std::move(root));
}
base::Value WebApp::AsDebugValue() const {
base::Value value = AsDebugValueWithOnlyPlatformAgnosticFields();
auto& root = value.GetDict();
root.Set("chromeos_data", OptionalAsDebugValue(chromeos_data_));
root.Set("client_data", client_data_.AsDebugValue());
return value;
}
std::ostream& operator<<(std::ostream& out, const WebApp& app) {
return out << app.AsDebugValue();
}
std::ostream& operator<<(
std::ostream& out,
const WebApp::ExternalManagementConfig& management_config) {
return out << management_config.AsDebugValue().DebugString();
}
std::vector<std::string> GetSerializedAllowedOrigins(
const network::ParsedPermissionsPolicyDeclaration
permissions_policy_declaration) {
std::vector<std::string> allowed_origins;
if (permissions_policy_declaration.self_if_matches) {
CHECK(!permissions_policy_declaration.self_if_matches->opaque());
allowed_origins.push_back(
permissions_policy_declaration.self_if_matches->Serialize());
}
for (const auto& origin_with_possible_wildcards :
permissions_policy_declaration.allowed_origins) {
allowed_origins.push_back(origin_with_possible_wildcards.Serialize());
}
return allowed_origins;
}
} // namespace web_app