blob: d80eca00880f0aa351bfcc4b94349935f15ebf61 [file] [log] [blame]
// Copyright 2025 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_database_serialization.h"
#include <set>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include "base/check.h"
#include "base/containers/contains.h"
#include "base/functional/overloaded.h"
#include "base/metrics/histogram_functions.h"
#include "base/pickle.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/web_applications/generated_icon_fix_util.h"
#include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_integrity_block_data.h"
#include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_storage_location.h"
#include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_version.h"
#include "chrome/browser/web_applications/isolated_web_apps/isolation_data.h"
#include "chrome/browser/web_applications/mojom/user_display_mode.mojom-shared.h"
#include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
#include "chrome/browser/web_applications/os_integration/web_app_file_handler_manager.h"
#include "chrome/browser/web_applications/proto/web_app.pb.h"
#include "chrome/browser/web_applications/proto/web_app_install_state.pb.h"
#include "chrome/browser/web_applications/proto/web_app_related_applications.pb.h"
#include "chrome/browser/web_applications/proto/web_app_url_pattern.pb.h"
#include "chrome/browser/web_applications/user_display_mode.h"
#include "chrome/browser/web_applications/web_app.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_install_info.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/chrome_features.h"
#include "components/services/app_service/public/cpp/file_handler.h"
#include "components/services/app_service/public/cpp/protocol_handler_info.h"
#include "components/services/app_service/public/cpp/share_target.h"
#include "components/sync/base/data_type.h"
#include "components/sync/base/time.h"
#include "components/webapps/browser/installable/installable_metrics.h"
#include "components/webapps/common/web_app_id.h"
#include "components/webapps/isolated_web_apps/update_channel.h"
#include "services/network/public/cpp/permissions_policy/origin_with_possible_wildcards.h"
#include "services/network/public/cpp/permissions_policy/permissions_policy_declaration.h"
#include "third_party/blink/public/common/manifest/manifest.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/capture_links.mojom.h"
#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
#include "third_party/blink/public/mojom/safe_url_pattern.mojom.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace web_app {
namespace {
DisplayMode ToMojomDisplayMode(proto::WebApp::DisplayMode display_mode) {
switch (display_mode) {
case proto::WebApp::DISPLAY_MODE_UNSPECIFIED:
return DisplayMode::kUndefined;
case proto::WebApp::DISPLAY_MODE_BROWSER:
return DisplayMode::kBrowser;
case proto::WebApp::DISPLAY_MODE_MINIMAL_UI:
return DisplayMode::kMinimalUi;
case proto::WebApp::DISPLAY_MODE_STANDALONE:
return DisplayMode::kStandalone;
case proto::WebApp::DISPLAY_MODE_FULLSCREEN:
return DisplayMode::kFullscreen;
case proto::WebApp::DISPLAY_MODE_WINDOW_CONTROLS_OVERLAY:
return DisplayMode::kWindowControlsOverlay;
case proto::WebApp::DISPLAY_MODE_TABBED:
return DisplayMode::kTabbed;
case proto::WebApp::DISPLAY_MODE_BORDERLESS:
return DisplayMode::kBorderless;
case proto::WebApp::DISPLAY_MODE_PICTURE_IN_PICTURE:
return DisplayMode::kPictureInPicture;
}
}
proto::WebApp::DisplayMode ToWebAppProtoDisplayMode(DisplayMode display_mode) {
switch (display_mode) {
case DisplayMode::kBrowser:
return proto::WebApp::DISPLAY_MODE_BROWSER;
case DisplayMode::kMinimalUi:
return proto::WebApp::DISPLAY_MODE_MINIMAL_UI;
case DisplayMode::kUndefined:
NOTREACHED();
case DisplayMode::kStandalone:
return proto::WebApp::DISPLAY_MODE_STANDALONE;
case DisplayMode::kFullscreen:
return proto::WebApp::DISPLAY_MODE_FULLSCREEN;
case DisplayMode::kWindowControlsOverlay:
return proto::WebApp::DISPLAY_MODE_WINDOW_CONTROLS_OVERLAY;
case DisplayMode::kTabbed:
return proto::WebApp::DISPLAY_MODE_TABBED;
case DisplayMode::kBorderless:
return proto::WebApp::DISPLAY_MODE_BORDERLESS;
case DisplayMode::kPictureInPicture:
return proto::WebApp::DISPLAY_MODE_PICTURE_IN_PICTURE;
}
}
proto::ShareTarget_Method MethodToProto(apps::ShareTarget::Method method) {
switch (method) {
case apps::ShareTarget::Method::kGet:
return proto::ShareTarget::METHOD_GET;
case apps::ShareTarget::Method::kPost:
return proto::ShareTarget::METHOD_POST;
}
}
apps::ShareTarget::Method ProtoToMethod(proto::ShareTarget_Method method) {
switch (method) {
case proto::ShareTarget::METHOD_GET:
return apps::ShareTarget::Method::kGet;
case proto::ShareTarget::METHOD_POST:
return apps::ShareTarget::Method::kPost;
}
}
proto::ShareTarget_Enctype EnctypeToProto(apps::ShareTarget::Enctype enctype) {
switch (enctype) {
case apps::ShareTarget::Enctype::kFormUrlEncoded:
return proto::ShareTarget::ENCTYPE_FORM_URL_ENCODED;
case apps::ShareTarget::Enctype::kMultipartFormData:
return proto::ShareTarget::ENCTYPE_MULTIPART_FORM_DATA;
}
}
apps::ShareTarget::Enctype ProtoToEnctype(proto::ShareTarget_Enctype enctype) {
switch (enctype) {
case proto::ShareTarget::ENCTYPE_FORM_URL_ENCODED:
return apps::ShareTarget::Enctype::kFormUrlEncoded;
case proto::ShareTarget::ENCTYPE_MULTIPART_FORM_DATA:
return apps::ShareTarget::Enctype::kMultipartFormData;
}
}
blink::mojom::CaptureLinks ProtoToCaptureLinks(
proto::WebApp::CaptureLinks capture_links) {
switch (capture_links) {
case proto::WebApp_CaptureLinks_NONE:
return blink::mojom::CaptureLinks::kNone;
case proto::WebApp_CaptureLinks_NEW_CLIENT:
return blink::mojom::CaptureLinks::kNewClient;
case proto::WebApp_CaptureLinks_EXISTING_CLIENT_NAVIGATE:
return blink::mojom::CaptureLinks::kExistingClientNavigate;
}
}
proto::WebApp::CaptureLinks CaptureLinksToProto(
blink::mojom::CaptureLinks capture_links) {
switch (capture_links) {
case blink::mojom::CaptureLinks::kUndefined:
NOTREACHED();
case blink::mojom::CaptureLinks::kNone:
return proto::WebApp_CaptureLinks_NONE;
case blink::mojom::CaptureLinks::kNewClient:
return proto::WebApp_CaptureLinks_NEW_CLIENT;
case blink::mojom::CaptureLinks::kExistingClientNavigate:
return proto::WebApp_CaptureLinks_EXISTING_CLIENT_NAVIGATE;
}
}
LaunchHandler ProtoLaunchHandlerToLaunchHandlerClientMode(
proto::LaunchHandler::DeprecatedRouteTo route_to,
proto::LaunchHandler::DeprecatedNavigateExistingClient
navigate_existing_client,
proto::LaunchHandler::ClientMode client_mode,
std::optional<bool> client_mode_valid_and_specified) {
// When migrating from a database that doesn't have the
// client_mode_valid_and_specified field saved yet, set it to `true` when the
// client mode is non-auto. If the site did set the client_mode to 'auto',
// then this is corrected on the next manifest update.
switch (client_mode) {
case proto::LaunchHandler::CLIENT_MODE_AUTO:
return LaunchHandler{LaunchHandler::ClientMode::kAuto};
case proto::LaunchHandler::CLIENT_MODE_NAVIGATE_NEW:
return LaunchHandler{LaunchHandler::ClientMode::kNavigateNew};
case proto::LaunchHandler::CLIENT_MODE_NAVIGATE_EXISTING:
return LaunchHandler{LaunchHandler::ClientMode::kNavigateExisting};
case proto::LaunchHandler::CLIENT_MODE_FOCUS_EXISTING:
return LaunchHandler{LaunchHandler::ClientMode::kFocusExisting};
case proto::LaunchHandler::CLIENT_MODE_UNSPECIFIED: {
// route_to was removed in favor of client_mode, fall back to it if client
// mode is unset.
switch (route_to) {
case proto::LaunchHandler_DeprecatedRouteTo_UNSPECIFIED_ROUTE:
case proto::LaunchHandler_DeprecatedRouteTo_AUTO_ROUTE:
return LaunchHandler{std::nullopt};
case proto::LaunchHandler_DeprecatedRouteTo_NEW_CLIENT:
return LaunchHandler{LaunchHandler::ClientMode::kNavigateNew};
case proto::LaunchHandler_DeprecatedRouteTo_EXISTING_CLIENT:
// route_to: existing-client and navigate_existing_client were
// removed in favor of existing-client-navigate and
// existing-client-retain.
if (navigate_existing_client ==
proto::LaunchHandler_DeprecatedNavigateExistingClient_NEVER) {
return LaunchHandler{LaunchHandler::ClientMode::kFocusExisting};
}
return LaunchHandler{LaunchHandler::ClientMode::kNavigateExisting};
case proto::LaunchHandler_DeprecatedRouteTo_EXISTING_CLIENT_NAVIGATE:
return LaunchHandler{LaunchHandler::ClientMode::kNavigateExisting};
case proto::LaunchHandler_DeprecatedRouteTo_EXISTING_CLIENT_RETAIN:
return LaunchHandler{LaunchHandler::ClientMode::kFocusExisting};
}
}
}
}
proto::LaunchHandler::ClientMode LaunchHandlerClientModeToProto(
LaunchHandler::ClientMode client_mode) {
switch (client_mode) {
case LaunchHandler::ClientMode::kAuto:
return proto::LaunchHandler::CLIENT_MODE_AUTO;
case LaunchHandler::ClientMode::kNavigateNew:
return proto::LaunchHandler::CLIENT_MODE_NAVIGATE_NEW;
case LaunchHandler::ClientMode::kNavigateExisting:
return proto::LaunchHandler::CLIENT_MODE_NAVIGATE_EXISTING;
case LaunchHandler::ClientMode::kFocusExisting:
return proto::LaunchHandler::CLIENT_MODE_FOCUS_EXISTING;
}
}
ApiApprovalState ProtoToApiApprovalState(
proto::WebApp::ApiApprovalState approval_state) {
switch (approval_state) {
case proto::WebApp_ApiApprovalState_REQUIRES_PROMPT:
return ApiApprovalState::kRequiresPrompt;
case proto::WebApp_ApiApprovalState_ALLOWED:
return ApiApprovalState::kAllowed;
case proto::WebApp_ApiApprovalState_DISALLOWED:
return ApiApprovalState::kDisallowed;
}
}
proto::WebApp::ApiApprovalState ApiApprovalStateToProto(
ApiApprovalState approval_state) {
switch (approval_state) {
case ApiApprovalState::kRequiresPrompt:
return proto::WebApp_ApiApprovalState_REQUIRES_PROMPT;
case ApiApprovalState::kAllowed:
return proto::WebApp_ApiApprovalState_ALLOWED;
case ApiApprovalState::kDisallowed:
return proto::WebApp_ApiApprovalState_DISALLOWED;
}
}
apps::FileHandler::LaunchType ProtoToLaunchType(
proto::WebAppFileHandler::LaunchType state) {
switch (state) {
case proto::WebAppFileHandler::LAUNCH_TYPE_SINGLE_CLIENT:
return apps::FileHandler::LaunchType::kSingleClient;
case proto::WebAppFileHandler::LAUNCH_TYPE_MULTIPLE_CLIENTS:
return apps::FileHandler::LaunchType::kMultipleClients;
case proto::WebAppFileHandler::LAUNCH_TYPE_UNSPECIFIED:
return apps::FileHandler::LaunchType::kSingleClient;
}
}
proto::WebAppFileHandler::LaunchType LaunchTypeToProto(
apps::FileHandler::LaunchType state) {
switch (state) {
case apps::FileHandler::LaunchType::kSingleClient:
return proto::WebAppFileHandler::LAUNCH_TYPE_SINGLE_CLIENT;
case apps::FileHandler::LaunchType::kMultipleClients:
return proto::WebAppFileHandler::LAUNCH_TYPE_MULTIPLE_CLIENTS;
}
}
WebAppManagement::Type ProtoToWebAppManagement(
proto::WebAppManagementType type) {
switch (type) {
case proto::WEB_APP_MANAGEMENT_TYPE_UNSPECIFIED:
NOTREACHED();
case proto::WEB_APP_MANAGEMENT_TYPE_SYSTEM:
return WebAppManagement::Type::kSystem;
case proto::WEB_APP_MANAGEMENT_TYPE_KIOSK:
return WebAppManagement::Type::kKiosk;
case proto::WEB_APP_MANAGEMENT_TYPE_POLICY:
return WebAppManagement::Type::kPolicy;
case proto::WEB_APP_MANAGEMENT_TYPE_SUB_APP:
return WebAppManagement::Type::kSubApp;
case proto::WEB_APP_MANAGEMENT_TYPE_WEB_APP_STORE:
return WebAppManagement::Type::kWebAppStore;
case proto::WEB_APP_MANAGEMENT_TYPE_SYNC:
return WebAppManagement::Type::kSync;
case proto::WEB_APP_MANAGEMENT_TYPE_USER_INSTALLED:
return WebAppManagement::Type::kUserInstalled;
case proto::WEB_APP_MANAGEMENT_TYPE_DEFAULT:
return WebAppManagement::Type::kDefault;
case proto::WEB_APP_MANAGEMENT_TYPE_IWA_SHIMLESS_RMA:
return WebAppManagement::Type::kIwaShimlessRma;
case proto::WEB_APP_MANAGEMENT_TYPE_IWA_POLICY:
return WebAppManagement::Type::kIwaPolicy;
case proto::WEB_APP_MANAGEMENT_TYPE_IWA_USER_INSTALLED:
return WebAppManagement::Type::kIwaUserInstalled;
case proto::WEB_APP_MANAGEMENT_TYPE_OEM:
return WebAppManagement::Type::kOem;
case proto::WEB_APP_MANAGEMENT_TYPE_ONE_DRIVE_INTEGRATION:
return WebAppManagement::Type::kOneDriveIntegration;
case proto::WEB_APP_MANAGEMENT_TYPE_APS_DEFAULT:
return WebAppManagement::Type::kApsDefault;
}
}
proto::WebAppManagementType WebAppManagementToProto(
WebAppManagement::Type type) {
switch (type) {
case WebAppManagement::Type::kSystem:
return proto::WEB_APP_MANAGEMENT_TYPE_SYSTEM;
case WebAppManagement::Type::kKiosk:
return proto::WEB_APP_MANAGEMENT_TYPE_KIOSK;
case WebAppManagement::Type::kPolicy:
return proto::WEB_APP_MANAGEMENT_TYPE_POLICY;
case WebAppManagement::Type::kSubApp:
return proto::WEB_APP_MANAGEMENT_TYPE_SUB_APP;
case WebAppManagement::Type::kWebAppStore:
return proto::WEB_APP_MANAGEMENT_TYPE_WEB_APP_STORE;
case WebAppManagement::Type::kSync:
return proto::WEB_APP_MANAGEMENT_TYPE_SYNC;
case WebAppManagement::Type::kUserInstalled:
return proto::WEB_APP_MANAGEMENT_TYPE_USER_INSTALLED;
case WebAppManagement::Type::kDefault:
return proto::WEB_APP_MANAGEMENT_TYPE_DEFAULT;
case WebAppManagement::Type::kIwaShimlessRma:
return proto::WEB_APP_MANAGEMENT_TYPE_IWA_SHIMLESS_RMA;
case WebAppManagement::Type::kIwaPolicy:
return proto::WEB_APP_MANAGEMENT_TYPE_IWA_POLICY;
case WebAppManagement::Type::kIwaUserInstalled:
return proto::WEB_APP_MANAGEMENT_TYPE_IWA_USER_INSTALLED;
case WebAppManagement::Type::kOem:
return proto::WEB_APP_MANAGEMENT_TYPE_OEM;
case WebAppManagement::Type::kOneDriveIntegration:
return proto::WEB_APP_MANAGEMENT_TYPE_ONE_DRIVE_INTEGRATION;
case WebAppManagement::Type::kApsDefault:
return proto::WEB_APP_MANAGEMENT_TYPE_APS_DEFAULT;
}
}
proto::TabStrip::Visibility TabStripVisibilityToProto(
TabStrip::Visibility visibility) {
switch (visibility) {
case TabStrip::Visibility::kAuto:
return proto::TabStrip::VISIBILITY_AUTO;
case TabStrip::Visibility::kAbsent:
return proto::TabStrip::VISIBILITY_ABSENT;
}
}
std::string FilePathToProto(const base::FilePath& path) {
base::Pickle pickle;
path.WriteToPickle(&pickle);
return std::string(pickle.data_as_char(), pickle.size());
}
std::optional<base::FilePath> ProtoToFilePath(const std::string& bytes) {
const base::Pickle pickle =
base::Pickle::WithUnownedBuffer(base::as_byte_span(bytes));
base::PickleIterator pickle_iterator(pickle);
base::FilePath path;
if (!path.ReadFromPickle(&pickle_iterator)) {
return std::nullopt;
}
return path;
}
template <typename T>
void IsolationDataLocationToProto(const IsolatedWebAppStorageLocation& location,
T* proto) {
std::visit(
base::Overloaded{
[&proto](const IwaStorageOwnedBundle& bundle) {
proto->mutable_owned_bundle()->set_dir_name_ascii(
bundle.dir_name_ascii());
proto->mutable_owned_bundle()->set_dev_mode(bundle.dev_mode());
},
[&proto](const IwaStorageUnownedBundle& bundle) {
proto->mutable_unowned_bundle()->set_path(
FilePathToProto(bundle.path()));
},
[&proto](const IwaStorageProxy& proxy) {
DCHECK(!proxy.proxy_url().opaque());
proto->mutable_proxy()->set_proxy_url(
proxy.proxy_url().Serialize());
},
},
location.variant());
}
template <typename T>
base::expected<IsolatedWebAppStorageLocation, std::string>
ProtoToIsolationDataLocation(const T& proto) {
switch (proto.location_case()) {
case T::LocationCase::kOwnedBundle: {
std::string folder_name = proto.owned_bundle().dir_name_ascii();
if (!base::IsStringASCII(folder_name)) {
return base::unexpected(
".owned_bundle.dir_name_ascii parse error: cannot "
"deserialize directory name");
}
return IwaStorageOwnedBundle{folder_name,
proto.owned_bundle().dev_mode()};
}
case T::LocationCase::kUnownedBundle: {
std::optional<base::FilePath> path =
ProtoToFilePath(proto.unowned_bundle().path());
if (!path.has_value()) {
return base::unexpected(
".unowned_bundle.path parse error: cannot deserialize file path");
}
return IwaStorageUnownedBundle{*path};
}
case T::LocationCase::kProxy: {
GURL gurl_proxy_url = GURL(proto.proxy().proxy_url());
url::Origin proxy_url = url::Origin::Create(gurl_proxy_url);
if (!gurl_proxy_url.is_valid() || proxy_url.opaque()) {
return base::unexpected(
".proxy.proxy_url parse error: cannot deserialize proxy "
"url. Value: " +
proto.proxy().proxy_url());
}
return IwaStorageProxy{proxy_url};
}
case T::LocationCase::LOCATION_NOT_SET:
return base::unexpected(" parse error: not set");
}
}
} // anonymous namespace
std::unique_ptr<WebApp> ParseWebAppProtoForTesting( // IN-TEST
const webapps::AppId& app_id,
const std::string& value) {
proto::WebApp proto;
const bool parsed = proto.ParseFromString(value);
if (!parsed) {
DLOG(ERROR) << "WebApps LevelDB parse error: can't parse proto.";
return nullptr;
}
auto web_app = ParseWebAppProto(proto);
if (!web_app) {
// ParseWebAppProto() already logged what went wrong here.
return nullptr;
}
if (web_app->app_id() != app_id) {
DLOG(ERROR) << "WebApps LevelDB error: app_id doesn't match storage key "
<< app_id << " vs " << web_app->app_id() << ", from "
<< web_app->manifest_id();
return nullptr;
}
return web_app;
}
std::unique_ptr<WebApp> ParseWebAppProto(const proto::WebApp& proto) {
if (!proto.has_sync_data()) {
DLOG(ERROR) << "WebApp proto parse error: no sync_data field";
return nullptr;
}
const sync_pb::WebAppSpecifics& sync_data = proto.sync_data();
GURL start_url(sync_data.start_url());
if (start_url.is_empty() || !start_url.is_valid()) {
DLOG(ERROR) << "WebApp proto start_url parse error: "
<< start_url.possibly_invalid_spec();
return nullptr;
}
// Post-migration check: Scope should not be empty.
if (!proto.has_scope() || proto.scope().empty()) {
DLOG(ERROR) << "WebApp proto parse error: scope is empty";
return nullptr;
}
webapps::ManifestId manifest_id;
if (sync_data.has_relative_manifest_id()) {
manifest_id =
GenerateManifestId(sync_data.relative_manifest_id(), start_url);
} else {
manifest_id = GenerateManifestIdFromStartUrlOnly(start_url);
}
webapps::AppId app_id = GenerateAppIdFromManifestId(manifest_id);
auto web_app = std::make_unique<WebApp>(app_id);
web_app->SetStartUrl(start_url);
web_app->SetManifestId(manifest_id);
if (!sync_data.has_user_display_mode_cros() &&
!sync_data.has_user_display_mode_default()) {
DLOG(ERROR) << "WebApp proto parse error: no user_display_mode field";
return nullptr;
}
// Post-migration check: Ensure current platform UDM is set.
if (!HasCurrentPlatformUserDisplayMode(sync_data)) {
DLOG(ERROR) << "WebApp proto parse error: missing user display mode for "
"current platform";
return nullptr;
}
// GenerateManifestId functions above strip the fragment part from the URL,
// but stored sync data may still have a fragment in relative_manifest_id.
// Per manifest spec, manifest IDs should be compared ignoring the fragment,
// so we should remove it from the sync data. Note this doesn't trigger a DB
// write or sync change - they will only happen if the app data changes for
// some other reason (eg. launch).
std::string relative_manifest_id_path = RelativeManifestIdPath(manifest_id);
if (sync_data.has_relative_manifest_id() &&
sync_data.relative_manifest_id() != relative_manifest_id_path) {
auto modified_sync_data = sync_data;
modified_sync_data.set_relative_manifest_id(relative_manifest_id_path);
web_app->SetSyncProto(modified_sync_data);
// Record when this happens. When it is rare enough we could simplify the
// logic here by just treating apps with mismatching IDs as a parse error.
base::UmaHistogramBoolean("WebApp.ParseWebAppProto.ManifestIdMatch", false);
} else {
web_app->SetSyncProto(sync_data);
// Record success for comparison.
base::UmaHistogramBoolean("WebApp.ParseWebAppProto.ManifestIdMatch", true);
}
// Required fields:
if (!proto.has_sources()) {
DLOG(ERROR) << "WebApp proto parse error: no sources field";
return nullptr;
}
WebAppManagementTypes sources;
sources.PutOrRemove(WebAppManagement::kSystem, proto.sources().system());
sources.PutOrRemove(WebAppManagement::kPolicy, proto.sources().policy());
sources.PutOrRemove(WebAppManagement::kWebAppStore,
proto.sources().web_app_store());
sources.PutOrRemove(WebAppManagement::kSync, proto.sources().sync());
sources.PutOrRemove(WebAppManagement::kUserInstalled,
proto.sources().user_installed());
sources.PutOrRemove(WebAppManagement::kDefault, proto.sources().default_());
sources.PutOrRemove(WebAppManagement::kOem, proto.sources().oem());
sources.PutOrRemove(WebAppManagement::kSubApp, proto.sources().sub_app());
sources.PutOrRemove(WebAppManagement::kKiosk, proto.sources().kiosk());
sources.PutOrRemove(WebAppManagement::kIwaShimlessRma,
proto.sources().iwa_shimless_rma());
sources.PutOrRemove(WebAppManagement::kIwaPolicy,
proto.sources().iwa_policy());
sources.PutOrRemove(WebAppManagement::kIwaUserInstalled,
proto.sources().iwa_user_installed());
sources.PutOrRemove(WebAppManagement::kOneDriveIntegration,
proto.sources().one_drive_integration());
sources.PutOrRemove(WebAppManagement::kApsDefault,
proto.sources().aps_default());
if (sources.empty() && !proto.is_uninstalling()) {
DLOG(ERROR) << "WebApp proto parse error: no source in sources field, "
"and is_uninstalling isn't true.";
return nullptr;
}
web_app->sources_ = sources;
if (!proto.has_name()) {
DLOG(ERROR) << "WebApp proto parse error: no name field";
return nullptr;
}
web_app->SetName(proto.name());
if (!proto.has_install_state()) {
DLOG(ERROR) << "WebApp proto parse error: no install_state field";
return nullptr;
}
if (!proto::InstallState_IsValid(proto.install_state())) {
DLOG(ERROR) << "WebApp proto parse error: invalid install_state field: "
<< proto.install_state();
return nullptr;
}
web_app->SetInstallState(proto.install_state());
// Because the OS integration current state is saved in a two-phase-commit
// flow, where the app is saved to the database first without os integration,
// and then after the desired integration is complete the current os
// integration state is saved, we don't reject parsing apps where the
// install_state is INSTALLED_WITH_OS_INTEGRATION but the current os
// integration state is not set.
// This is handled in
// MaybeInstallAppsFromSyncAndPendingInstallOrSyncOsIntegration.
auto& chromeos_data_proto = proto.chromeos_data();
if (IsChromeOsDataMandatory() && !proto.has_chromeos_data()) {
DLOG(ERROR) << "WebApp proto parse error: no chromeos_data field. The web "
<< "app might have been installed when running on an OS other "
<< "than Chrome OS.";
return nullptr;
}
if (!IsChromeOsDataMandatory() && proto.has_chromeos_data()) {
DLOG(ERROR) << "WebApp proto parse error: has chromeos_data field. The web "
<< "app might have been installed when running on Chrome OS.";
return nullptr;
}
if (proto.has_chromeos_data()) {
auto chromeos_data = std::make_optional<WebAppChromeOsData>();
chromeos_data->show_in_launcher = chromeos_data_proto.show_in_launcher();
chromeos_data->show_in_search_and_shelf =
chromeos_data_proto.show_in_search_and_shelf();
chromeos_data->show_in_management =
chromeos_data_proto.show_in_management();
chromeos_data->is_disabled = chromeos_data_proto.is_disabled();
chromeos_data->oem_installed = chromeos_data_proto.oem_installed();
chromeos_data->handles_file_open_intents =
chromeos_data_proto.handles_file_open_intents();
web_app->SetWebAppChromeOsData(std::move(chromeos_data));
}
#if BUILDFLAG(IS_CHROMEOS)
if (proto.client_data().has_system_web_app_data()) {
ash::SystemWebAppData& swa_data =
web_app->client_data()->system_web_app_data.emplace();
swa_data.system_app_type = static_cast<ash::SystemWebAppType>(
proto.client_data().system_web_app_data().system_app_type());
}
#endif
// Optional fields:
if (proto.has_launch_query_params()) {
web_app->SetLaunchQueryParams(proto.launch_query_params());
}
if (proto.has_display_mode()) {
web_app->SetDisplayMode(ToMojomDisplayMode(proto.display_mode()));
}
std::vector<DisplayMode> display_mode_override;
for (int i = 0; i < proto.display_mode_override_size(); i++) {
proto::WebApp::DisplayMode display_mode = proto.display_mode_override(i);
display_mode_override.push_back(ToMojomDisplayMode(display_mode));
}
web_app->SetDisplayModeOverride(std::move(display_mode_override));
if (proto.has_description()) {
web_app->SetDescription(proto.description());
}
if (proto.has_scope()) {
GURL scope(proto.scope());
if (scope.is_empty() || !scope.is_valid()) {
DLOG(ERROR) << "WebApp proto scope parse error: "
<< scope.possibly_invalid_spec();
return nullptr;
}
// WebApp::SetScope() takes care of removing the queries and fragments from
// the scope before storing it in memory.
web_app->SetScope(scope);
}
if (proto.has_theme_color()) {
web_app->SetThemeColor(proto.theme_color());
}
if (proto.has_dark_mode_theme_color()) {
web_app->SetDarkModeThemeColor(proto.dark_mode_theme_color());
}
if (proto.has_background_color()) {
web_app->SetBackgroundColor(proto.background_color());
}
if (proto.has_dark_mode_background_color()) {
web_app->SetDarkModeBackgroundColor(proto.dark_mode_background_color());
}
if (proto.has_is_from_sync_and_pending_installation()) {
web_app->SetIsFromSyncAndPendingInstallation(
proto.is_from_sync_and_pending_installation());
}
if (proto.has_is_uninstalling()) {
web_app->SetIsUninstalling(proto.is_uninstalling());
}
if (proto.has_last_badging_time()) {
web_app->SetLastBadgingTime(
syncer::ProtoTimeToTime(proto.last_badging_time()));
}
if (proto.has_last_launch_time()) {
web_app->SetLastLaunchTime(
syncer::ProtoTimeToTime(proto.last_launch_time()));
}
if (proto.has_latest_install_source()) {
int install_source = proto.latest_install_source();
if (install_source >= 0 &&
install_source <=
static_cast<int>(webapps::WebappInstallSource::kMaxValue)) {
web_app->SetLatestInstallSource(
static_cast<webapps::WebappInstallSource>(install_source));
}
}
if (proto.has_manifest_update_time()) {
web_app->SetManifestUpdateTime(
syncer::ProtoTimeToTime(proto.manifest_update_time()));
}
if (proto.has_first_install_time()) {
web_app->SetFirstInstallTime(
syncer::ProtoTimeToTime(proto.first_install_time()));
}
std::optional<std::vector<apps::IconInfo>> parsed_manifest_icons =
ParseAppIconInfos("WebApp", proto.manifest_icons());
if (!parsed_manifest_icons) {
// ParseWebAppIconInfos() reports any errors.
return nullptr;
}
web_app->SetManifestIcons(std::move(parsed_manifest_icons.value()));
std::vector<SquareSizePx> icon_sizes_any;
for (int32_t size : proto.downloaded_icon_sizes_purpose_any()) {
icon_sizes_any.push_back(size);
}
web_app->SetDownloadedIconSizes(IconPurpose::ANY,
SortedSizesPx(std::move(icon_sizes_any)));
std::vector<SquareSizePx> icon_sizes_maskable;
for (int32_t size : proto.downloaded_icon_sizes_purpose_maskable()) {
icon_sizes_maskable.push_back(size);
}
web_app->SetDownloadedIconSizes(
IconPurpose::MASKABLE, SortedSizesPx(std::move(icon_sizes_maskable)));
std::vector<SquareSizePx> icon_sizes_monochrome;
for (int32_t size : proto.downloaded_icon_sizes_purpose_monochrome()) {
icon_sizes_monochrome.push_back(size);
}
web_app->SetDownloadedIconSizes(
IconPurpose::MONOCHROME, SortedSizesPx(std::move(icon_sizes_monochrome)));
web_app->SetIsGeneratedIcon(proto.is_generated_icon());
apps::FileHandlers file_handlers;
for (const auto& file_handler_proto : proto.file_handlers()) {
if (!file_handler_proto.has_action() ||
!file_handler_proto.has_launch_type()) {
DLOG(ERROR) << "WebApp FileHandler proto parse error";
return nullptr;
}
apps::FileHandler file_handler;
file_handler.action = GURL(file_handler_proto.action());
if (file_handler.action.is_empty() || !file_handler.action.is_valid()) {
DLOG(ERROR) << "WebApp FileHandler proto action parse error";
return nullptr;
}
if (file_handler_proto.has_display_name()) {
file_handler.display_name =
base::UTF8ToUTF16(file_handler_proto.display_name());
}
file_handler.launch_type =
ProtoToLaunchType(file_handler_proto.launch_type());
for (const auto& accept_entry_proto : file_handler_proto.accept()) {
if (!accept_entry_proto.has_mimetype()) {
DLOG(ERROR) << "WebApp FileHandler proto parse error for "
<< file_handler.action;
return nullptr;
}
apps::FileHandler::AcceptEntry accept_entry;
accept_entry.mime_type = accept_entry_proto.mimetype();
for (const auto& file_extension : accept_entry_proto.file_extensions()) {
if (base::Contains(accept_entry.file_extensions, file_extension)) {
// We intentionally don't return a nullptr here; instead, duplicate
// entries are absorbed.
DLOG(ERROR) << "apps::FileHandler::AcceptEntry parsing encountered "
<< "duplicate file extension";
}
accept_entry.file_extensions.insert(file_extension);
}
file_handler.accept.push_back(std::move(accept_entry));
}
std::optional<std::vector<apps::IconInfo>> file_handler_icon_infos =
ParseAppIconInfos("WebApp", file_handler_proto.downloaded_icons());
if (!file_handler_icon_infos) {
// ParseAppIconInfos() reports any errors.
return nullptr;
}
file_handler.downloaded_icons = std::move(file_handler_icon_infos.value());
file_handlers.push_back(std::move(file_handler));
}
web_app->SetFileHandlers(std::move(file_handlers));
if (proto.has_share_target()) {
const proto::ShareTarget& local_share_target = proto.share_target();
if (!local_share_target.has_action() || !local_share_target.has_method() ||
!local_share_target.has_enctype() || !local_share_target.has_params()) {
DLOG(ERROR) << "WebApp proto Share Target parse error";
return nullptr;
}
apps::ShareTarget share_target;
const proto::ShareTargetParams& local_share_target_params =
local_share_target.params();
GURL action(local_share_target.action());
if (action.is_empty() || !action.is_valid()) {
DLOG(ERROR) << "WebApp proto action parse error: "
<< action.possibly_invalid_spec();
return nullptr;
}
share_target.action = action;
share_target.method = ProtoToMethod(local_share_target.method());
share_target.enctype = ProtoToEnctype(local_share_target.enctype());
if (local_share_target_params.has_title()) {
share_target.params.title = local_share_target_params.title();
}
if (local_share_target_params.has_text()) {
share_target.params.text = local_share_target_params.text();
}
if (local_share_target_params.has_url()) {
share_target.params.url = local_share_target_params.url();
}
for (const auto& share_target_params_file :
local_share_target_params.files()) {
if (!share_target_params_file.has_name()) {
DLOG(ERROR) << "WebApp proto Share Target files parse error for "
<< share_target.action;
return nullptr;
}
apps::ShareTarget::Files files_entry;
files_entry.name = share_target_params_file.name();
for (const auto& file_type : share_target_params_file.accept()) {
if (base::Contains(files_entry.accept, file_type)) {
// We intentionally don't return a nullptr here; instead, duplicate
// entries are absorbed.
DLOG(ERROR) << "apps::ShareTarget::Files parsing encountered "
<< "duplicate file type";
} else {
files_entry.accept.push_back(file_type);
}
}
share_target.params.files.push_back(std::move(files_entry));
}
web_app->SetShareTarget(std::move(share_target));
}
std::vector<WebAppShortcutsMenuItemInfo> shortcuts_menu_item_infos;
for (const auto& shortcut_info_proto : proto.shortcuts_menu_item_infos()) {
if (!shortcut_info_proto.has_name() || !shortcut_info_proto.has_url()) {
DLOG(ERROR) << "WebApp proto Shortcut Menu Item Info parse error";
return nullptr;
}
WebAppShortcutsMenuItemInfo shortcut_info;
shortcut_info.name = base::UTF8ToUTF16(shortcut_info_proto.name());
shortcut_info.url = GURL(shortcut_info_proto.url());
for (IconPurpose purpose : kIconPurposes) {
// This default init needed to infer the sophisticated protobuf type.
const auto* shortcut_manifest_icons =
&shortcut_info_proto.shortcut_manifest_icons();
switch (purpose) {
case IconPurpose::ANY:
shortcut_manifest_icons =
&shortcut_info_proto.shortcut_manifest_icons();
break;
case IconPurpose::MASKABLE:
shortcut_manifest_icons =
&shortcut_info_proto.shortcut_manifest_icons_maskable();
break;
case IconPurpose::MONOCHROME:
shortcut_manifest_icons =
&shortcut_info_proto.shortcut_manifest_icons_monochrome();
break;
}
std::vector<WebAppShortcutsMenuItemInfo::Icon> manifest_icons;
for (const auto& icon_info_proto : *shortcut_manifest_icons) {
WebAppShortcutsMenuItemInfo::Icon shortcut_icon_info;
shortcut_icon_info.square_size_px = icon_info_proto.size_in_px();
shortcut_icon_info.url = GURL(icon_info_proto.url());
manifest_icons.emplace_back(std::move(shortcut_icon_info));
}
shortcut_info.SetShortcutIconInfosForPurpose(purpose,
std::move(manifest_icons));
}
shortcuts_menu_item_infos.emplace_back(std::move(shortcut_info));
}
const size_t shortcut_menu_item_size = shortcuts_menu_item_infos.size();
std::vector<IconSizes> shortcuts_menu_icons_sizes;
for (const auto& shortcuts_icon_sizes_proto :
proto.downloaded_shortcuts_menu_icons_sizes()) {
IconSizes icon_sizes;
icon_sizes.SetSizesForPurpose(
IconPurpose::ANY, std::vector<SquareSizePx>(
shortcuts_icon_sizes_proto.icon_sizes().begin(),
shortcuts_icon_sizes_proto.icon_sizes().end()));
icon_sizes.SetSizesForPurpose(
IconPurpose::MASKABLE,
std::vector<SquareSizePx>(
shortcuts_icon_sizes_proto.icon_sizes_maskable().begin(),
shortcuts_icon_sizes_proto.icon_sizes_maskable().end()));
icon_sizes.SetSizesForPurpose(
IconPurpose::MONOCHROME,
std::vector<SquareSizePx>(
shortcuts_icon_sizes_proto.icon_sizes_monochrome().begin(),
shortcuts_icon_sizes_proto.icon_sizes_monochrome().end()));
shortcuts_menu_icons_sizes.push_back(std::move(icon_sizes));
}
// Due to the bitmaps possibly being not populated (see
// https://crbug.com/1427444), we just have empty bitmap data in that case.
while (shortcuts_menu_icons_sizes.size() < shortcut_menu_item_size) {
shortcuts_menu_icons_sizes.emplace_back();
}
if (shortcut_menu_item_size < shortcuts_menu_icons_sizes.size()) {
DLOG(ERROR) << "WebApp proto had more downloaded shortcut icons than infos";
return nullptr;
}
CHECK_EQ(shortcuts_menu_item_infos.size(), shortcuts_menu_icons_sizes.size());
for (size_t i = 0; i < shortcut_menu_item_size; ++i) {
shortcuts_menu_item_infos[i].downloaded_icon_sizes =
std::move(shortcuts_menu_icons_sizes[i]);
}
// All elements have been moved.
shortcuts_menu_icons_sizes.clear();
web_app->SetShortcutsMenuInfo(std::move(shortcuts_menu_item_infos));
std::vector<std::string> additional_search_terms;
for (const std::string& additional_search_term :
proto.additional_search_terms()) {
if (additional_search_term.empty()) {
DLOG(ERROR) << "WebApp AdditionalSearchTerms proto action parse error";
return nullptr;
}
additional_search_terms.push_back(additional_search_term);
}
web_app->SetAdditionalSearchTerms(std::move(additional_search_terms));
std::vector<apps::ProtocolHandlerInfo> protocol_handlers;
for (const auto& protocol_handler_proto : proto.protocol_handlers()) {
if (!protocol_handler_proto.has_protocol() ||
!protocol_handler_proto.has_url()) {
DLOG(ERROR) << "WebApp proto Protocol Handler parse error";
return nullptr;
}
apps::ProtocolHandlerInfo protocol_handler;
protocol_handler.protocol = protocol_handler_proto.protocol();
GURL protocol_handler_url(protocol_handler_proto.url());
if (protocol_handler_url.is_empty() || !protocol_handler_url.is_valid()) {
DLOG(ERROR) << "WebApp ProtocolHandler proto url parse error: "
<< protocol_handler_url.possibly_invalid_spec();
return nullptr;
}
protocol_handler.url = protocol_handler_url;
protocol_handlers.push_back(std::move(protocol_handler));
}
web_app->SetProtocolHandlers(std::move(protocol_handlers));
std::vector<std::string> allowed_launch_protocols;
for (const std::string& allowed_launch_protocol :
proto.allowed_launch_protocols()) {
if (allowed_launch_protocol.empty()) {
DLOG(ERROR) << "WebApp AllowedLaunchProtocols proto action parse error";
return nullptr;
}
allowed_launch_protocols.push_back(allowed_launch_protocol);
}
web_app->SetAllowedLaunchProtocols(std::move(allowed_launch_protocols));
std::vector<std::string> disallowed_launch_protocols;
for (const std::string& disallowed_launch_protocol :
proto.disallowed_launch_protocols()) {
if (disallowed_launch_protocol.empty()) {
DLOG(ERROR)
<< "WebApp DisallowedLaunchProtocols proto action parse error";
return nullptr;
}
disallowed_launch_protocols.push_back(disallowed_launch_protocol);
}
web_app->SetDisallowedLaunchProtocols(std::move(disallowed_launch_protocols));
base::flat_set<ScopeExtensionInfo> scope_extensions;
for (const auto& scope_extension_proto : proto.scope_extensions()) {
if (!scope_extension_proto.has_origin() ||
!scope_extension_proto.has_has_origin_wildcard()) {
DLOG(ERROR) << "WebApp Scope Extension Info proto parse error";
return nullptr;
}
url::Origin origin =
url::Origin::Create(GURL(scope_extension_proto.origin()));
if (origin.opaque()) {
DLOG(ERROR) << "WebAppScopeExtensionProto's `origin` is opaque: "
<< scope_extension_proto.origin();
return nullptr;
}
if (origin == url::Origin()) {
DLOG(ERROR) << "WebAppScopeExtensionProto's `origin` is empty";
return nullptr;
}
if (!GURL(scope_extension_proto.scope()).is_valid()) {
DLOG(ERROR) << "WebAppScopeExtensionProto's `scope` url is invalid: "
<< scope_extension_proto.scope();
return nullptr;
}
auto scope_extension =
ScopeExtensionInfo::CreateForProto(scope_extension_proto);
scope_extensions.insert(std::move(scope_extension));
}
web_app->SetScopeExtensions(std::move(scope_extensions));
base::flat_set<ScopeExtensionInfo> valid_scope_extensions;
for (const auto& scope_extension_proto : proto.scope_extensions_validated()) {
url::Origin origin =
url::Origin::Create(GURL(scope_extension_proto.origin()));
if (origin.opaque()) {
DLOG(ERROR) << "WebAppScopeExtensionProto's `origin` is opaque: "
<< scope_extension_proto.origin();
return nullptr;
}
if (origin == url::Origin()) {
DLOG(ERROR) << "WebAppScopeExtensionProto's `origin` is empty";
return nullptr;
}
if (!GURL(scope_extension_proto.scope()).is_valid()) {
DLOG(ERROR) << "WebAppScopeExtensionProto's `scope` url is invalid: "
<< scope_extension_proto.scope();
return nullptr;
}
auto scope_extension =
ScopeExtensionInfo::CreateForProto(scope_extension_proto);
if (!scope_extension.origin.IsSameOriginWith(scope_extension.scope)) {
return nullptr;
}
valid_scope_extensions.insert(std::move(scope_extension));
}
web_app->SetValidatedScopeExtensions(std::move(valid_scope_extensions));
if (proto.has_lock_screen_start_url()) {
web_app->SetLockScreenStartUrl(GURL(proto.lock_screen_start_url()));
}
if (proto.has_note_taking_new_note_url()) {
web_app->SetNoteTakingNewNoteUrl(GURL(proto.note_taking_new_note_url()));
}
if (proto.has_user_run_on_os_login_mode()) {
web_app->SetRunOnOsLoginMode(
ToRunOnOsLoginMode(proto.user_run_on_os_login_mode()));
}
if (proto.has_capture_links()) {
web_app->SetCaptureLinks(ProtoToCaptureLinks(proto.capture_links()));
} else {
web_app->SetCaptureLinks(blink::mojom::CaptureLinks::kUndefined);
}
if (proto.has_manifest_url()) {
GURL manifest_url(proto.manifest_url());
if (manifest_url.is_empty() || !manifest_url.is_valid()) {
DLOG(ERROR) << "WebApp proto manifest_url parse error: "
<< manifest_url.possibly_invalid_spec();
return nullptr;
}
web_app->SetManifestUrl(manifest_url);
}
if (proto.has_file_handler_approval_state()) {
web_app->SetFileHandlerApprovalState(
ProtoToApiApprovalState(proto.file_handler_approval_state()));
}
if (proto.has_window_controls_overlay_enabled()) {
web_app->SetWindowControlsOverlayEnabled(
proto.window_controls_overlay_enabled());
}
if (proto.has_launch_handler()) {
const proto::LaunchHandler& launch_handler_proto = proto.launch_handler();
LaunchHandler launch_handler = ProtoLaunchHandlerToLaunchHandlerClientMode(
launch_handler_proto.route_to(),
launch_handler_proto.navigate_existing_client(),
launch_handler_proto.client_mode(),
launch_handler_proto.has_client_mode_valid_and_specified()
? std::optional(
launch_handler_proto.client_mode_valid_and_specified())
: std::nullopt);
web_app->SetLaunchHandler(launch_handler);
}
if (proto.has_parent_app_id()) {
web_app->parent_app_id_ = proto.parent_app_id();
}
if (proto.permissions_policy_size()) {
network::ParsedPermissionsPolicy policy;
const auto& name_to_feature_map =
blink::GetPermissionsPolicyNameToFeatureMap();
for (const auto& decl_proto : proto.permissions_policy()) {
network::ParsedPermissionsPolicyDeclaration decl;
const auto feature_enum = name_to_feature_map.find(decl_proto.feature());
if (feature_enum == name_to_feature_map.end()) {
continue;
}
decl.feature = feature_enum->second;
for (const std::string& origin : decl_proto.allowed_origins()) {
std::optional<network::OriginWithPossibleWildcards>
maybe_origin_with_possible_wildcards =
network::OriginWithPossibleWildcards::Parse(
origin,
network::OriginWithPossibleWildcards::NodeType::kHeader);
if (maybe_origin_with_possible_wildcards.has_value()) {
decl.allowed_origins.emplace_back(
*maybe_origin_with_possible_wildcards);
}
}
decl.matches_all_origins = decl_proto.matches_all_origins();
decl.matches_opaque_src = decl_proto.matches_opaque_src();
policy.push_back(decl);
}
web_app->SetPermissionsPolicy(policy);
}
WebApp::ExternalConfigMap management_to_external_config;
for (const auto& management_proto :
proto.management_to_external_config_info()) {
WebApp::ExternalManagementConfig config;
base::flat_set<GURL> install_urls;
for (const auto& install_url_proto : management_proto.install_urls()) {
GURL install_url(install_url_proto);
if (install_url.is_empty() || !install_url.is_valid()) {
DLOG(ERROR) << "WebApp proto install_url parse error: "
<< install_url.possibly_invalid_spec();
return nullptr;
}
install_urls.emplace(install_url);
}
base::flat_set<std::string> additional_policy_ids;
for (const auto& policy_id : management_proto.additional_policy_ids()) {
if (policy_id.empty()) {
DLOG(ERROR) << "WebApp proto empty policy_id";
return nullptr;
}
additional_policy_ids.emplace(policy_id);
}
config.is_placeholder = management_proto.is_placeholder();
config.install_urls = std::move(install_urls);
config.additional_policy_ids = std::move(additional_policy_ids);
management_to_external_config.insert_or_assign(
ProtoToWebAppManagement(management_proto.management()),
std::move(config));
}
web_app->SetWebAppManagementExternalConfigMap(management_to_external_config);
if (proto.has_tab_strip()) {
web_app->SetTabStrip(ProtoToTabStrip(proto.tab_strip()));
}
if (proto.has_current_os_integration_states()) {
web_app->SetCurrentOsIntegrationStates(
proto.current_os_integration_states());
}
if (proto.has_app_size_in_bytes()) {
web_app->SetAppSizeInBytes(proto.app_size_in_bytes());
}
if (proto.has_data_size_in_bytes()) {
web_app->SetDataSizeInBytes(proto.data_size_in_bytes());
}
if (proto.has_always_show_toolbar_in_fullscreen()) {
web_app->SetAlwaysShowToolbarInFullscreen(
proto.always_show_toolbar_in_fullscreen());
}
if (proto.has_isolation_data()) {
auto version = ParseIwaVersion(proto.isolation_data().version());
if (!version.has_value()) {
DLOG(ERROR) << "WebApp proto isolation_data.version parse error: cannot "
"deserialize version: "
<< IwaVersionParseErrorToString(version.error());
return nullptr;
}
base::expected<IsolatedWebAppStorageLocation, std::string> location =
ProtoToIsolationDataLocation(proto.isolation_data());
if (!location.has_value()) {
DLOG(ERROR) << "WebApp proto isolation_data.location" << location.error();
return nullptr;
}
auto isolation_data_builder =
IsolationData::Builder(std::move(*location), std::move(*version));
const google::protobuf::RepeatedPtrField<std::string>& partitions =
proto.isolation_data().controlled_frame_partitions();
isolation_data_builder.SetControlledFramePartitions(
{partitions.begin(), partitions.end()});
if (proto.isolation_data().has_pending_update_info()) {
const auto& pending_update_info_proto =
proto.isolation_data().pending_update_info();
base::expected<IsolatedWebAppStorageLocation, std::string>
pending_location =
ProtoToIsolationDataLocation(pending_update_info_proto);
if (!pending_location.has_value()) {
DLOG(ERROR)
<< "WebApp proto isolation_data.pending_update_info.location"
<< pending_location.error();
return nullptr;
}
if (pending_location->dev_mode() != location->dev_mode()) {
DLOG(ERROR) << "WebApp proto isolation_data.pending_update_info "
"deserialization error: "
"isolation_data.pending_update_info.location and "
"isolation_data.location must both be in dev mode or "
"not in dev mode.";
return nullptr;
}
auto pending_version =
ParseIwaVersion(pending_update_info_proto.version());
if (!pending_version.has_value()) {
DLOG(ERROR)
<< "WebApp proto isolation_data.pending_update_info.version parse "
"error: cannot deserialize version: "
<< IwaVersionParseErrorToString(pending_version.error());
return nullptr;
}
std::optional<IsolatedWebAppIntegrityBlockData>
pending_integrity_block_data;
if (pending_update_info_proto.has_integrity_block_data()) {
auto result = IsolatedWebAppIntegrityBlockData::FromProto(
pending_update_info_proto.integrity_block_data());
if (!result.has_value()) {
DLOG(ERROR) << "WebApp proto "
"isolation_data.pending_update_info.integrity_block "
"data parse error: "
<< result.error();
return nullptr;
}
pending_integrity_block_data = std::move(result.value());
}
isolation_data_builder.SetPendingUpdateInfo(
IsolationData::PendingUpdateInfo(
std::move(*pending_location), std::move(*pending_version),
std::move(pending_integrity_block_data)));
}
if (proto.isolation_data().has_integrity_block_data()) {
auto result = IsolatedWebAppIntegrityBlockData::FromProto(
proto.isolation_data().integrity_block_data());
if (!result.has_value()) {
DLOG(ERROR)
<< "WebApp proto isolation_data.integrity_block_data parse error: "
<< result.error();
return nullptr;
}
isolation_data_builder.SetIntegrityBlockData(std::move(*result));
}
if (proto.isolation_data().has_update_manifest_url()) {
GURL update_manifest_url(proto.isolation_data().update_manifest_url());
if (!update_manifest_url.is_valid()) {
DLOG(ERROR) << "WebApp proto isolation_data.update_manifest_url is not "
"a valid GURL.";
return nullptr;
}
isolation_data_builder.SetUpdateManifestUrl(
std::move(update_manifest_url));
}
if (proto.isolation_data().has_update_channel()) {
auto update_channel =
UpdateChannel::Create(proto.isolation_data().update_channel());
if (!update_channel.has_value()) {
DLOG(ERROR)
<< "WebApp proto isolation_data.update_channel is not valid.";
return nullptr;
}
isolation_data_builder.SetUpdateChannel(std::move(*update_channel));
}
web_app->SetIsolationData(std::move(isolation_data_builder).Build());
}
if (proto.has_user_link_capturing_preference()) {
web_app->SetLinkCapturingUserPreference(
proto.user_link_capturing_preference());
}
if (proto.has_latest_install_time()) {
web_app->SetLatestInstallTime(
syncer::ProtoTimeToTime(proto.latest_install_time()));
} else if (proto.has_first_install_time()) {
web_app->SetLatestInstallTime(
syncer::ProtoTimeToTime(proto.first_install_time()));
}
if (proto.has_generated_icon_fix()) {
if (!generated_icon_fix_util::IsValid(proto.generated_icon_fix())) {
return nullptr;
}
web_app->SetGeneratedIconFix(proto.generated_icon_fix());
}
if (proto.has_supported_links_offer_ignore_count()) {
web_app->SetSupportedLinksOfferIgnoreCount(
proto.supported_links_offer_ignore_count());
}
if (proto.has_supported_links_offer_dismiss_count()) {
web_app->SetSupportedLinksOfferDismissCount(
proto.supported_links_offer_dismiss_count());
}
web_app->SetIsDiyApp(proto.is_diy_app());
web_app->SetWasShortcutApp(proto.was_shortcut_app());
web_app->SetDiyAppIconsMaskedOnMac(proto.diy_app_icons_masked_on_mac());
std::vector<blink::Manifest::RelatedApplication> related_applications;
for (const auto& related_application_proto : proto.related_applications()) {
blink::Manifest::RelatedApplication related_application;
if (related_application_proto.has_platform()) {
related_application.platform = std::make_optional(
base::UTF8ToUTF16(related_application_proto.platform()));
}
related_application.url = GURL(related_application_proto.url());
if (related_application_proto.has_id()) {
related_application.id =
std::make_optional(base::UTF8ToUTF16(related_application_proto.id()));
}
related_applications.push_back(std::move(related_application));
}
web_app->SetRelatedApplications(std::move(related_applications));
return web_app;
}
std::unique_ptr<proto::WebApp> WebAppToProto(const WebApp& web_app) {
auto local_data = std::make_unique<proto::WebApp>();
// Required fields:
const GURL start_url = web_app.start_url();
DCHECK(start_url.is_valid());
DCHECK(!web_app.app_id().empty());
DCHECK(web_app.manifest_id().is_valid());
// Set sync data to sync proto.
*(local_data->mutable_sync_data()) = web_app.sync_proto();
local_data->set_name(web_app.untranslated_name());
DCHECK(!web_app.sources_.empty() || web_app.is_uninstalling());
local_data->mutable_sources()->set_system(
web_app.sources_.Has(WebAppManagement::kSystem));
local_data->mutable_sources()->set_policy(
web_app.sources_.Has(WebAppManagement::kPolicy));
local_data->mutable_sources()->set_web_app_store(
web_app.sources_.Has(WebAppManagement::kWebAppStore));
local_data->mutable_sources()->set_sync(
web_app.sources_.Has(WebAppManagement::kSync));
local_data->mutable_sources()->set_user_installed(
web_app.sources_.Has(WebAppManagement::kUserInstalled));
local_data->mutable_sources()->set_default_(
web_app.sources_.Has(WebAppManagement::kDefault));
local_data->mutable_sources()->set_sub_app(
web_app.sources_.Has(WebAppManagement::kSubApp));
local_data->mutable_sources()->set_kiosk(
web_app.sources_.Has(WebAppManagement::kKiosk));
local_data->mutable_sources()->set_iwa_shimless_rma(
web_app.sources_.Has(WebAppManagement::kIwaShimlessRma));
local_data->mutable_sources()->set_iwa_policy(
web_app.sources_.Has(WebAppManagement::kIwaPolicy));
local_data->mutable_sources()->set_iwa_user_installed(
web_app.sources_.Has(WebAppManagement::kIwaUserInstalled));
local_data->mutable_sources()->set_oem(
web_app.sources_.Has(WebAppManagement::kOem));
local_data->mutable_sources()->set_one_drive_integration(
web_app.sources_.Has(WebAppManagement::kOneDriveIntegration));
local_data->mutable_sources()->set_aps_default(
web_app.sources_.Has(WebAppManagement::kApsDefault));
local_data->set_install_state(web_app.install_state());
// Optional fields:
if (web_app.launch_query_params()) {
local_data->set_launch_query_params(*web_app.launch_query_params());
}
if (web_app.display_mode() != DisplayMode::kUndefined) {
local_data->set_display_mode(
ToWebAppProtoDisplayMode(web_app.display_mode()));
}
for (const DisplayMode& display_mode : web_app.display_mode_override()) {
local_data->add_display_mode_override(
ToWebAppProtoDisplayMode(display_mode));
}
local_data->set_description(web_app.untranslated_description());
if (!web_app.scope().is_empty()) {
local_data->set_scope(web_app.scope().spec());
}
if (web_app.theme_color().has_value()) {
local_data->set_theme_color(web_app.theme_color().value());
}
if (web_app.dark_mode_theme_color().has_value()) {
local_data->set_dark_mode_theme_color(
web_app.dark_mode_theme_color().value());
}
if (web_app.background_color().has_value()) {
local_data->set_background_color(web_app.background_color().value());
}
if (web_app.dark_mode_background_color().has_value()) {
local_data->set_dark_mode_background_color(
web_app.dark_mode_background_color().value());
}
if (!web_app.last_badging_time().is_null()) {
local_data->set_last_badging_time(
syncer::TimeToProtoTime(web_app.last_badging_time()));
}
if (!web_app.last_launch_time().is_null()) {
local_data->set_last_launch_time(
syncer::TimeToProtoTime(web_app.last_launch_time()));
}
if (!web_app.first_install_time().is_null()) {
local_data->set_first_install_time(
syncer::TimeToProtoTime(web_app.first_install_time()));
}
if (!web_app.manifest_update_time().is_null()) {
local_data->set_manifest_update_time(
syncer::TimeToProtoTime(web_app.manifest_update_time()));
}
if (web_app.latest_install_source()) {
local_data->set_latest_install_source(
static_cast<int>(*web_app.latest_install_source()));
}
if (web_app.chromeos_data().has_value()) {
auto& chromeos_data = web_app.chromeos_data().value();
auto* mutable_chromeos_data = local_data->mutable_chromeos_data();
mutable_chromeos_data->set_show_in_launcher(chromeos_data.show_in_launcher);
mutable_chromeos_data->set_show_in_search_and_shelf(
chromeos_data.show_in_search_and_shelf);
mutable_chromeos_data->set_show_in_management(
chromeos_data.show_in_management);
mutable_chromeos_data->set_is_disabled(chromeos_data.is_disabled);
mutable_chromeos_data->set_oem_installed(chromeos_data.oem_installed);
mutable_chromeos_data->set_handles_file_open_intents(
chromeos_data.handles_file_open_intents);
}
#if BUILDFLAG(IS_CHROMEOS)
if (web_app.client_data().system_web_app_data.has_value()) {
auto& swa_data = web_app.client_data().system_web_app_data.value();
auto* mutable_swa_data =
local_data->mutable_client_data()->mutable_system_web_app_data();
mutable_swa_data->set_system_app_type(
static_cast<ash::SystemWebAppDataProto_SystemWebAppType>(
swa_data.system_app_type));
}
#endif
local_data->set_user_run_on_os_login_mode(
ToWebAppProtoRunOnOsLoginMode(web_app.run_on_os_login_mode()));
local_data->set_is_from_sync_and_pending_installation(
web_app.is_from_sync_and_pending_installation());
local_data->set_is_uninstalling(web_app.is_uninstalling());
for (const apps::IconInfo& icon_info : web_app.manifest_icons()) {
*(local_data->add_manifest_icons()) = AppIconInfoToSyncProto(icon_info);
}
for (SquareSizePx size : web_app.downloaded_icon_sizes(IconPurpose::ANY)) {
local_data->add_downloaded_icon_sizes_purpose_any(size);
}
for (SquareSizePx size :
web_app.downloaded_icon_sizes(IconPurpose::MASKABLE)) {
local_data->add_downloaded_icon_sizes_purpose_maskable(size);
}
for (SquareSizePx size :
web_app.downloaded_icon_sizes(IconPurpose::MONOCHROME)) {
local_data->add_downloaded_icon_sizes_purpose_monochrome(size);
}
local_data->set_is_generated_icon(web_app.is_generated_icon());
for (const auto& file_handler : web_app.file_handlers()) {
proto::WebAppFileHandler* file_handler_proto =
local_data->add_file_handlers();
DCHECK(file_handler.action.is_valid());
file_handler_proto->set_action(file_handler.action.spec());
file_handler_proto->set_display_name(
base::UTF16ToUTF8(file_handler.display_name));
file_handler_proto->set_launch_type(
LaunchTypeToProto(file_handler.launch_type));
for (const auto& accept_entry : file_handler.accept) {
proto::WebAppFileHandlerAccept* accept_entry_proto =
file_handler_proto->add_accept();
accept_entry_proto->set_mimetype(accept_entry.mime_type);
for (const auto& file_extension : accept_entry.file_extensions) {
accept_entry_proto->add_file_extensions(file_extension);
}
}
for (const apps::IconInfo& icon_info : file_handler.downloaded_icons) {
*(file_handler_proto->add_downloaded_icons()) =
AppIconInfoToSyncProto(icon_info);
}
}
if (web_app.share_target()) {
const apps::ShareTarget& share_target = *web_app.share_target();
auto* const mutable_share_target = local_data->mutable_share_target();
mutable_share_target->set_action(share_target.action.spec());
mutable_share_target->set_method(MethodToProto(share_target.method));
mutable_share_target->set_enctype(EnctypeToProto(share_target.enctype));
const apps::ShareTarget::Params& params = share_target.params;
auto* const mutable_share_target_params =
mutable_share_target->mutable_params();
if (!params.title.empty()) {
mutable_share_target_params->set_title(params.title);
}
if (!params.text.empty()) {
mutable_share_target_params->set_text(params.text);
}
if (!params.url.empty()) {
mutable_share_target_params->set_url(params.url);
}
for (const auto& files_entry : params.files) {
proto::ShareTargetParamsFile* mutable_share_target_files =
mutable_share_target_params->add_files();
mutable_share_target_files->set_name(files_entry.name);
for (const auto& file_type : files_entry.accept) {
mutable_share_target_files->add_accept(file_type);
}
}
}
for (const WebAppShortcutsMenuItemInfo& shortcut_info :
web_app.shortcuts_menu_item_infos()) {
proto::WebAppShortcutsMenuItemInfo* shortcut_info_proto =
local_data->add_shortcuts_menu_item_infos();
shortcut_info_proto->set_name(base::UTF16ToUTF8(shortcut_info.name));
shortcut_info_proto->set_url(shortcut_info.url.spec());
for (IconPurpose purpose : kIconPurposes) {
for (const WebAppShortcutsMenuItemInfo::Icon& icon_info :
shortcut_info.GetShortcutIconInfosForPurpose(purpose)) {
sync_pb::WebAppIconInfo* shortcut_icon_info_proto;
switch (purpose) {
case IconPurpose::ANY:
shortcut_icon_info_proto =
shortcut_info_proto->add_shortcut_manifest_icons();
break;
case IconPurpose::MASKABLE:
shortcut_icon_info_proto =
shortcut_info_proto->add_shortcut_manifest_icons_maskable();
break;
case IconPurpose::MONOCHROME:
shortcut_icon_info_proto =
shortcut_info_proto->add_shortcut_manifest_icons_monochrome();
break;
}
DCHECK(!icon_info.url.is_empty());
shortcut_icon_info_proto->set_url(icon_info.url.spec());
shortcut_icon_info_proto->set_size_in_px(icon_info.square_size_px);
}
}
const IconSizes& icon_sizes = shortcut_info.downloaded_icon_sizes;
proto::DownloadedShortcutsMenuIconSizes* icon_sizes_proto =
local_data->add_downloaded_shortcuts_menu_icons_sizes();
for (const SquareSizePx& icon_size :
icon_sizes.GetSizesForPurpose(IconPurpose::ANY)) {
icon_sizes_proto->add_icon_sizes(icon_size);
}
for (const SquareSizePx& icon_size :
icon_sizes.GetSizesForPurpose(IconPurpose::MASKABLE)) {
icon_sizes_proto->add_icon_sizes_maskable(icon_size);
}
for (const SquareSizePx& icon_size :
icon_sizes.GetSizesForPurpose(IconPurpose::MONOCHROME)) {
icon_sizes_proto->add_icon_sizes_monochrome(icon_size);
}
}
for (const auto& additional_search_term : web_app.additional_search_terms()) {
// Additional search terms should be sanitized before being added here.
DCHECK(!additional_search_term.empty());
local_data->add_additional_search_terms(additional_search_term);
}
for (const auto& protocol_handler : web_app.protocol_handlers()) {
proto::WebAppProtocolHandler* protocol_handler_proto =
local_data->add_protocol_handlers();
protocol_handler_proto->set_protocol(protocol_handler.protocol);
protocol_handler_proto->set_url(protocol_handler.url.spec());
}
for (const auto& allowed_launch_protocols :
web_app.allowed_launch_protocols()) {
DCHECK(!allowed_launch_protocols.empty());
local_data->add_allowed_launch_protocols(allowed_launch_protocols);
}
for (const auto& disallowed_launch_protocols :
web_app.disallowed_launch_protocols()) {
DCHECK(!disallowed_launch_protocols.empty());
local_data->add_disallowed_launch_protocols(disallowed_launch_protocols);
}
for (const auto& scope_extension : web_app.scope_extensions()) {
proto::WebAppScopeExtension* scope_extension_proto =
local_data->add_scope_extensions();
scope_extension_proto->set_origin(scope_extension.origin.Serialize());
scope_extension_proto->set_scope(scope_extension.scope.spec());
scope_extension_proto->set_has_origin_wildcard(
scope_extension.has_origin_wildcard);
}
for (const auto& valid_extension : web_app.validated_scope_extensions()) {
proto::WebAppScopeExtension* scope_extension_proto =
local_data->add_scope_extensions_validated();
scope_extension_proto->set_origin(valid_extension.origin.Serialize());
CHECK(valid_extension.scope.is_valid());
scope_extension_proto->set_scope(valid_extension.scope.spec());
scope_extension_proto->set_has_origin_wildcard(
valid_extension.has_origin_wildcard);
}
if (web_app.lock_screen_start_url().is_valid()) {
local_data->set_lock_screen_start_url(
web_app.lock_screen_start_url().spec());
}
if (web_app.note_taking_new_note_url().is_valid()) {
local_data->set_note_taking_new_note_url(
web_app.note_taking_new_note_url().spec());
}
if (web_app.capture_links() != blink::mojom::CaptureLinks::kUndefined) {
local_data->set_capture_links(CaptureLinksToProto(web_app.capture_links()));
} else {
local_data->clear_capture_links();
}
if (web_app.manifest_url().is_valid()) {
local_data->set_manifest_url(web_app.manifest_url().spec());
}
local_data->set_file_handler_approval_state(
ApiApprovalStateToProto(web_app.file_handler_approval_state()));
local_data->set_window_controls_overlay_enabled(
web_app.window_controls_overlay_enabled());
if (web_app.launch_handler()) {
local_data->mutable_launch_handler()->set_client_mode(
LaunchHandlerClientModeToProto(
web_app.launch_handler()->parsed_client_mode()));
local_data->mutable_launch_handler()->set_client_mode_valid_and_specified(
web_app.launch_handler()->client_mode_valid_and_specified());
}
if (web_app.parent_app_id_) {
local_data->set_parent_app_id(*web_app.parent_app_id_);
}
if (!web_app.permissions_policy().empty()) {
auto& policy = *local_data->mutable_permissions_policy();
const auto& feature_to_name_map =
blink::GetPermissionsPolicyFeatureToNameMap();
for (const auto& decl : web_app.permissions_policy()) {
proto::WebAppPermissionsPolicy proto_policy;
const auto feature_name = feature_to_name_map.find(decl.feature);
if (feature_name == feature_to_name_map.end()) {
continue;
}
const std::string feature_string(feature_name->second);
proto_policy.set_feature(feature_string);
for (const auto& allowed_origin : GetSerializedAllowedOrigins(decl)) {
proto_policy.add_allowed_origins(allowed_origin);
}
proto_policy.set_matches_all_origins(decl.matches_all_origins);
proto_policy.set_matches_opaque_src(decl.matches_opaque_src);
policy.Add(std::move(proto_policy));
}
}
if (!web_app.management_to_external_config_map().empty()) {
for (const auto& [source, external_config] :
web_app.management_to_external_config_map()) {
proto::ManagementToExternalConfigInfo* management_config_proto =
local_data->add_management_to_external_config_info();
management_config_proto->set_management(WebAppManagementToProto(source));
management_config_proto->set_is_placeholder(
external_config.is_placeholder);
for (const auto& url : external_config.install_urls) {
DCHECK(url.is_valid());
management_config_proto->add_install_urls(url.spec());
}
for (const auto& policy_id : external_config.additional_policy_ids) {
DCHECK(!policy_id.empty());
management_config_proto->add_additional_policy_ids(policy_id);
}
}
}
if (web_app.tab_strip()) {
TabStrip tab_strip = web_app.tab_strip().value();
auto* mutable_tab_strip = local_data->mutable_tab_strip();
if (std::holds_alternative<TabStrip::Visibility>(tab_strip.home_tab)) {
mutable_tab_strip->set_home_tab_visibility(TabStripVisibilityToProto(
std::get<TabStrip::Visibility>(tab_strip.home_tab)));
} else {
auto* mutable_home_tab_params =
mutable_tab_strip->mutable_home_tab_params();
const std::optional<std::vector<blink::Manifest::ImageResource>>& icons =
std::get<blink::Manifest::HomeTabParams>(tab_strip.home_tab).icons;
for (const auto& image_resource : *icons) {
*(mutable_home_tab_params->add_icons()) =
AppImageResourceToProto(image_resource);
}
const std::vector<blink::SafeUrlPattern>& scope_patterns =
std::get<blink::Manifest::HomeTabParams>(tab_strip.home_tab)
.scope_patterns;
for (const auto& pattern : scope_patterns) {
*(mutable_home_tab_params->add_scope_patterns()) =
ToUrlPatternProto(pattern);
}
}
auto* mutable_new_tab_button_params =
mutable_tab_strip->mutable_new_tab_button_params();
std::optional<GURL> url = tab_strip.new_tab_button.url;
if (url) {
mutable_new_tab_button_params->set_url(url.value().spec());
}
}
*local_data->mutable_current_os_integration_states() =
web_app.current_os_integration_states();
if (web_app.app_size_in_bytes().has_value()) {
local_data->set_app_size_in_bytes(web_app.app_size_in_bytes().value());
}
if (web_app.data_size_in_bytes().has_value()) {
local_data->set_data_size_in_bytes(web_app.data_size_in_bytes().value());
}
local_data->set_always_show_toolbar_in_fullscreen(
web_app.always_show_toolbar_in_fullscreen());
if (web_app.isolation_data().has_value()) {
const auto& isolation_data = *web_app.isolation_data();
auto* mutable_data = local_data->mutable_isolation_data();
IsolationDataLocationToProto(isolation_data.location(), mutable_data);
mutable_data->set_version(isolation_data.version().GetString());
for (const std::string& partition :
isolation_data.controlled_frame_partitions()) {
mutable_data->add_controlled_frame_partitions(partition);
}
if (isolation_data.pending_update_info().has_value()) {
const IsolationData::PendingUpdateInfo& pending_update_info =
*isolation_data.pending_update_info();
auto* mutable_pending_update_info =
mutable_data->mutable_pending_update_info();
// Add this check:
CHECK_EQ(isolation_data.location().dev_mode(),
pending_update_info.location.dev_mode(),
base::NotFatalUntil::M138)
<< "IsolationData dev_mode mismatch between current location and "
"pending update location during serialization.";
IsolationDataLocationToProto(pending_update_info.location,
mutable_pending_update_info);
mutable_pending_update_info->set_version(
pending_update_info.version.GetString());
if (pending_update_info.integrity_block_data) {
*mutable_pending_update_info->mutable_integrity_block_data() =
pending_update_info.integrity_block_data->ToProto();
}
}
if (isolation_data.integrity_block_data()) {
*mutable_data->mutable_integrity_block_data() =
isolation_data.integrity_block_data()->ToProto();
}
if (const auto& update_manifest_url = isolation_data.update_manifest_url();
update_manifest_url.has_value() && update_manifest_url->is_valid()) {
mutable_data->set_update_manifest_url(update_manifest_url->spec());
}
if (const auto& update_channel = isolation_data.update_channel()) {
mutable_data->set_update_channel(update_channel->ToString());
}
}
local_data->set_user_link_capturing_preference(
web_app.user_link_capturing_preference());
if (!web_app.latest_install_time().is_null()) {
local_data->set_latest_install_time(
syncer::TimeToProtoTime(web_app.latest_install_time()));
}
if (web_app.generated_icon_fix().has_value()) {
*local_data->mutable_generated_icon_fix() =
web_app.generated_icon_fix().value();
}
local_data->set_supported_links_offer_ignore_count(
web_app.supported_links_offer_ignore_count());
local_data->set_supported_links_offer_dismiss_count(
web_app.supported_links_offer_dismiss_count());
local_data->set_is_diy_app(web_app.is_diy_app());
local_data->set_was_shortcut_app(web_app.was_shortcut_app());
local_data->set_diy_app_icons_masked_on_mac(
web_app.diy_app_icons_masked_on_mac());
for (const auto& related_application : web_app.related_applications()) {
proto::RelatedApplications* related_application_proto =
local_data->add_related_applications();
if (related_application.platform) {
related_application_proto->set_platform(
base::UTF16ToUTF8(related_application.platform.value()));
}
CHECK(related_application.url.is_empty() ||
related_application.url.is_valid());
related_application_proto->set_url(related_application.url.spec());
if (related_application.id) {
related_application_proto->set_id(
base::UTF16ToUTF8(related_application.id.value()));
}
}
return local_data;
}
} // namespace web_app