| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <string> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/feature_list.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ssl/security_state_tab_helper.h" |
| #include "chrome/browser/web_applications/install_bounce_metric.h" |
| #include "chrome/browser/web_applications/web_app_data_retriever.h" |
| #include "chrome/browser/web_applications/web_app_helpers.h" |
| #include "chrome/browser/web_applications/web_app_icon_generator.h" |
| #include "chrome/browser/web_applications/web_app_install_info.h" |
| #include "chrome/browser/web_applications/web_app_install_manager.h" |
| #include "chrome/browser/web_applications/web_app_install_task.h" |
| #include "chrome/browser/web_applications/web_app_install_utils.h" |
| #include "chrome/browser/web_applications/web_app_installation_utils.h" |
| #include "chrome/browser/web_applications/web_app_registrar.h" |
| #include "chrome/browser/web_applications/web_app_system_web_app_data.h" |
| #include "chrome/browser/web_applications/web_app_url_loader.h" |
| #include "chrome/browser/web_applications/web_app_utils.h" |
| #include "chrome/common/chrome_features.h" |
| #include "components/webapps/browser/installable/installable_metrics.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/web_contents.h" |
| #include "net/http/http_status_code.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "third_party/blink/public/mojom/manifest/manifest.mojom.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| #include "url/gurl.h" |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| #include "ash/components/arc/mojom/app.mojom.h" |
| #include "ash/components/arc/mojom/intent_helper.mojom.h" |
| #include "ash/components/arc/session/arc_bridge_service.h" |
| #include "ash/components/arc/session/arc_service_manager.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "net/base/url_util.h" |
| #endif |
| |
| namespace web_app { |
| |
| namespace { |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| const char kChromeOsPlayPlatform[] = "chromeos_play"; |
| const char kPlayIntentPrefix[] = |
| "https://play.google.com/store/apps/details?id="; |
| const char kPlayStorePackage[] = "com.android.vending"; |
| |
| constexpr bool kAddAppsToQuickLaunchBarByDefault = false; |
| |
| #else |
| constexpr bool kAddAppsToQuickLaunchBarByDefault = true; |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| bool IsEmptyIconBitmapsForIconUrl(const IconsMap& icons_map, |
| const GURL& icon_url) { |
| IconsMap::const_iterator iter = icons_map.find(icon_url); |
| if (iter == icons_map.end()) |
| return true; |
| |
| const std::vector<SkBitmap>& icon_bitmaps = iter->second; |
| if (icon_bitmaps.empty()) |
| return true; |
| |
| for (const SkBitmap& icon_bitmap : icon_bitmaps) { |
| if (!icon_bitmap.isNull() && !icon_bitmap.drawsNothing()) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| struct PlayStoreIntent { |
| std::string app_id; |
| std::string intent; |
| }; |
| |
| // Find the first Chrome OS app in related_applications of |manifest| and return |
| // the details necessary to redirect the user to the app's listing in the Play |
| // Store. |
| absl::optional<PlayStoreIntent> GetPlayStoreIntentFromManifest( |
| const blink::mojom::Manifest& manifest) { |
| for (const auto& app : manifest.related_applications) { |
| std::string id = base::UTF16ToUTF8(app.id.value_or(std::u16string())); |
| if (!base::EqualsASCII(app.platform.value_or(std::u16string()), |
| kChromeOsPlayPlatform)) { |
| continue; |
| } |
| |
| if (id.empty()) { |
| // Fallback to ID in the URL. |
| if (!net::GetValueForKeyInQuery(app.url, "id", &id) || id.empty()) { |
| continue; |
| } |
| } |
| |
| std::string referrer; |
| if (net::GetValueForKeyInQuery(app.url, "referrer", &referrer) && |
| !referrer.empty()) { |
| referrer = "&referrer=" + referrer; |
| } |
| |
| std::string intent = kPlayIntentPrefix + id + referrer; |
| return PlayStoreIntent{id, intent}; |
| } |
| return absl::nullopt; |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| } // namespace |
| |
| WebAppInstallTask::WebAppInstallTask( |
| Profile* profile, |
| WebAppInstallManager* install_manager, |
| WebAppInstallFinalizer* install_finalizer, |
| std::unique_ptr<WebAppDataRetriever> data_retriever, |
| WebAppRegistrar* registrar) |
| : data_retriever_(std::move(data_retriever)), |
| install_manager_(install_manager), |
| install_finalizer_(install_finalizer), |
| profile_(profile), |
| registrar_(registrar) { |
| if (base::FeatureList::IsEnabled(features::kRecordWebAppDebugInfo)) |
| error_dict_ = std::make_unique<base::Value>(base::Value::Type::DICTIONARY); |
| } |
| |
| WebAppInstallTask::~WebAppInstallTask() { |
| // If this task is still observing a WebContents, then the callbacks haven't |
| // yet been run. Run them before the task is destroyed. |
| if (web_contents()) |
| CallInstallCallback(AppId(), |
| webapps::InstallResultCode::kInstallTaskDestroyed); |
| } |
| |
| void WebAppInstallTask::ExpectAppId(const AppId& expected_app_id) { |
| expected_app_id_ = expected_app_id; |
| } |
| |
| void WebAppInstallTask::SetInstallParams( |
| const WebAppInstallParams& install_params) { |
| if (!install_params.locally_installed) { |
| DCHECK(!install_params.add_to_applications_menu); |
| DCHECK(!install_params.add_to_desktop); |
| DCHECK(!install_params.add_to_quick_launch_bar); |
| } |
| install_params_ = install_params; |
| } |
| |
| void WebAppInstallTask::LoadWebAppAndCheckManifest( |
| const GURL& url, |
| webapps::WebappInstallSource install_source, |
| WebAppUrlLoader* url_loader, |
| LoadWebAppAndCheckManifestCallback callback) { |
| DCHECK(url_loader); |
| CheckInstallPreconditions(); |
| // Create a WebContents instead of reusing a shared one because we will pass |
| // it back to be used for opening the web app. |
| // TODO(loyso): Implement stealing of shared web_contents in upcoming |
| // WebContentsManager. |
| std::unique_ptr<content::WebContents> web_contents = |
| CreateWebContents(profile_); |
| |
| // Grab WebContents pointer now, before the call to BindOnce might null out |
| // |web_contents|. |
| content::WebContents* web_contents_ptr = web_contents.get(); |
| |
| Observe(web_contents.get()); |
| background_installation_ = false; |
| install_callback_ = |
| base::BindOnce(std::move(callback), std::move(web_contents)); |
| install_source_ = install_source; |
| |
| url_loader->LoadUrl( |
| url, web_contents_ptr, |
| WebAppUrlLoader::UrlComparison::kIgnoreQueryParamsAndRef, |
| base::BindOnce( |
| &WebAppInstallTask::OnWebAppUrlLoadedCheckAndRetrieveManifest, |
| GetWeakPtr(), url, web_contents_ptr)); |
| } |
| |
| void WebAppInstallTask::InstallWebAppFromManifest( |
| content::WebContents* contents, |
| bool bypass_service_worker_check, |
| webapps::WebappInstallSource install_source, |
| WebAppInstallDialogCallback dialog_callback, |
| OnceInstallCallback install_callback) { |
| DCHECK(AreWebAppsUserInstallable(profile_)); |
| CheckInstallPreconditions(); |
| |
| Observe(contents); |
| installing_web_contents_ = contents; |
| dialog_callback_ = std::move(dialog_callback); |
| install_callback_ = std::move(install_callback); |
| install_source_ = install_source; |
| |
| auto web_app_info = std::make_unique<WebAppInstallInfo>(); |
| |
| if (install_params_) |
| ApplyParamsToWebAppInstallInfo(*install_params_, *web_app_info); |
| |
| data_retriever_->CheckInstallabilityAndRetrieveManifest( |
| web_contents(), bypass_service_worker_check, |
| base::BindOnce(&WebAppInstallTask::OnDidPerformInstallableCheck, |
| GetWeakPtr(), std::move(web_app_info), |
| /*force_shortcut_app=*/false)); |
| } |
| |
| void WebAppInstallTask::InstallWebAppFromManifestWithFallback( |
| content::WebContents* contents, |
| bool force_shortcut_app, |
| webapps::WebappInstallSource install_source, |
| WebAppInstallDialogCallback dialog_callback, |
| OnceInstallCallback install_callback) { |
| DCHECK(AreWebAppsUserInstallable(profile_)); |
| CheckInstallPreconditions(); |
| |
| Observe(contents); |
| installing_web_contents_ = contents; |
| dialog_callback_ = std::move(dialog_callback); |
| install_callback_ = std::move(install_callback); |
| install_source_ = install_source; |
| |
| data_retriever_->GetWebAppInstallInfo( |
| web_contents(), base::BindOnce(&WebAppInstallTask::OnGetWebAppInstallInfo, |
| GetWeakPtr(), force_shortcut_app)); |
| } |
| |
| void WebAppInstallTask::LoadAndInstallWebAppFromManifestWithFallback( |
| const GURL& launch_url, |
| content::WebContents* contents, |
| WebAppUrlLoader* url_loader, |
| webapps::WebappInstallSource install_source, |
| OnceInstallCallback install_callback) { |
| DCHECK(AreWebAppsUserInstallable(profile_)); |
| CheckInstallPreconditions(); |
| |
| Observe(contents); |
| installing_web_contents_ = contents; |
| if (ShouldStopInstall()) |
| return; |
| |
| background_installation_ = true; |
| install_callback_ = std::move(install_callback); |
| install_source_ = install_source; |
| |
| url_loader->LoadUrl( |
| launch_url, contents, |
| WebAppUrlLoader::UrlComparison::kIgnoreQueryParamsAndRef, |
| base::BindOnce(&WebAppInstallTask::OnWebAppUrlLoadedGetWebAppInstallInfo, |
| GetWeakPtr(), launch_url)); |
| } |
| |
| void WebAppInstallTask::LoadAndInstallSubAppFromURL( |
| const GURL& install_url, |
| content::WebContents* contents, |
| WebAppUrlLoader* url_loader, |
| OnceInstallCallback install_callback) { |
| DCHECK(AreWebAppsUserInstallable(profile_)); |
| CheckInstallPreconditions(); |
| |
| Observe(contents); |
| installing_web_contents_ = contents; |
| if (ShouldStopInstall()) |
| return; |
| |
| background_installation_ = true; |
| install_callback_ = std::move(install_callback); |
| install_source_ = webapps::WebappInstallSource::SUB_APP; |
| |
| url_loader->LoadUrl( |
| install_url, contents, |
| WebAppUrlLoader::UrlComparison::kIgnoreQueryParamsAndRef, |
| base::BindOnce(&WebAppInstallTask::OnWebAppUrlLoadedGetWebAppInstallInfo, |
| GetWeakPtr(), install_url)); |
| } |
| |
| void UpdateFinalizerClientData( |
| const absl::optional<WebAppInstallParams>& params, |
| WebAppInstallFinalizer::FinalizeOptions* options) { |
| if (params) { |
| if (IsChromeOsDataMandatory()) { |
| options->chromeos_data.emplace(); |
| options->chromeos_data->show_in_launcher = |
| params->add_to_applications_menu; |
| options->chromeos_data->show_in_search = params->add_to_search; |
| options->chromeos_data->show_in_management = params->add_to_management; |
| options->chromeos_data->is_disabled = params->is_disabled; |
| options->chromeos_data->oem_installed = params->oem_installed; |
| options->chromeos_data->handles_file_open_intents = |
| params->handles_file_open_intents; |
| } |
| options->bypass_os_hooks = params->bypass_os_hooks; |
| options->add_to_applications_menu = params->add_to_applications_menu; |
| options->add_to_desktop = params->add_to_desktop; |
| options->add_to_quick_launch_bar = params->add_to_quick_launch_bar; |
| if (params->system_app_type.has_value()) { |
| options->system_web_app_data.emplace(); |
| options->system_web_app_data->system_app_type = |
| params->system_app_type.value(); |
| } |
| } |
| } |
| |
| void WebAppInstallTask::InstallWebAppFromInfo( |
| std::unique_ptr<WebAppInstallInfo> web_application_info, |
| bool overwrite_existing_manifest_fields, |
| ForInstallableSite for_installable_site, |
| webapps::WebappInstallSource install_source, |
| OnceInstallCallback callback) { |
| CheckInstallPreconditions(); |
| |
| PopulateProductIcons(web_application_info.get(), |
| /*icons_map*/ nullptr); |
| // No IconsMap to populate shortcut item icons from. |
| |
| if (install_params_) |
| ApplyParamsToWebAppInstallInfo(*install_params_, *web_application_info); |
| |
| install_source_ = install_source; |
| background_installation_ = true; |
| install_callback_ = std::move(callback); |
| |
| RecordInstallEvent(); |
| |
| WebAppInstallFinalizer::FinalizeOptions options; |
| options.install_source = install_source; |
| options.locally_installed = true; |
| options.overwrite_existing_manifest_fields = |
| overwrite_existing_manifest_fields; |
| |
| UpdateFinalizerClientData(install_params_, &options); |
| if (!install_params_) { |
| options.bypass_os_hooks = true; |
| } |
| |
| install_finalizer_->FinalizeInstall( |
| *web_application_info, options, |
| base::BindOnce(&WebAppInstallTask::OnInstallFinalized, GetWeakPtr())); |
| } |
| |
| void WebAppInstallTask::InstallWebAppWithParams( |
| content::WebContents* contents, |
| const WebAppInstallParams& install_params, |
| webapps::WebappInstallSource install_source, |
| OnceInstallCallback install_callback) { |
| CheckInstallPreconditions(); |
| |
| Observe(contents); |
| installing_web_contents_ = contents; |
| SetInstallParams(install_params); |
| install_callback_ = std::move(install_callback); |
| install_source_ = install_source; |
| background_installation_ = true; |
| |
| data_retriever_->GetWebAppInstallInfo( |
| web_contents(), |
| base::BindOnce(&WebAppInstallTask::OnGetWebAppInstallInfo, GetWeakPtr(), |
| /*force_shortcut_app=*/false)); |
| } |
| |
| void WebAppInstallTask::LoadAndRetrieveWebAppInstallInfoWithIcons( |
| const GURL& start_url, |
| WebAppUrlLoader* url_loader, |
| RetrieveWebAppInstallInfoWithIconsCallback callback) { |
| CheckInstallPreconditions(); |
| |
| retrieve_info_callback_ = std::move(callback); |
| background_installation_ = true; |
| only_retrieve_web_application_info_ = true; |
| |
| web_contents_ = CreateWebContents(profile_); |
| Observe(web_contents_.get()); |
| |
| DCHECK(url_loader); |
| url_loader->LoadUrl( |
| start_url, web_contents(), |
| WebAppUrlLoader::UrlComparison::kIgnoreQueryParamsAndRef, |
| base::BindOnce(&WebAppInstallTask::OnWebAppUrlLoadedGetWebAppInstallInfo, |
| GetWeakPtr(), start_url)); |
| } |
| |
| // static |
| std::unique_ptr<content::WebContents> WebAppInstallTask::CreateWebContents( |
| Profile* profile) { |
| std::unique_ptr<content::WebContents> web_contents = |
| content::WebContents::Create(content::WebContents::CreateParams(profile)); |
| |
| CreateWebAppInstallTabHelpers(web_contents.get()); |
| |
| return web_contents; |
| } |
| |
| content::WebContents* WebAppInstallTask::GetInstallingWebContents() { |
| return installing_web_contents_; |
| } |
| |
| base::WeakPtr<WebAppInstallTask> WebAppInstallTask::GetWeakPtr() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| void WebAppInstallTask::WebContentsDestroyed() { |
| CallInstallCallback(AppId(), |
| webapps::InstallResultCode::kWebContentsDestroyed); |
| } |
| |
| base::Value WebAppInstallTask::TakeErrorDict() { |
| DCHECK(error_dict_); |
| base::Value error_dict = std::move(*error_dict_); |
| error_dict_->DictClear(); |
| return error_dict; |
| } |
| |
| void WebAppInstallTask::SetInstallFinalizerForTesting( |
| WebAppInstallFinalizer* install_finalizer) { |
| install_finalizer_ = install_finalizer; |
| } |
| |
| void WebAppInstallTask::CheckInstallPreconditions() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| // Concurrent calls are not allowed. |
| DCHECK(!web_contents()); |
| CHECK(!install_callback_); |
| CHECK(!retrieve_info_callback_); |
| |
| DCHECK(!initiated_); |
| initiated_ = true; |
| } |
| |
| void WebAppInstallTask::RecordInstallEvent() { |
| DCHECK(install_source_ != kNoInstallSource); |
| |
| if (webapps::InstallableMetrics::IsReportableInstallSource(install_source_)) { |
| webapps::InstallableMetrics::TrackInstallEvent(install_source_); |
| } |
| } |
| |
| void WebAppInstallTask::CallInstallCallback(const AppId& app_id, |
| webapps::InstallResultCode code) { |
| Observe(nullptr); |
| dialog_callback_.Reset(); |
| |
| install_source_ = kNoInstallSource; |
| |
| if (only_retrieve_web_application_info_) { |
| DCHECK(retrieve_info_callback_); |
| std::move(retrieve_info_callback_).Run(std::move(web_application_info_)); |
| return; |
| } |
| |
| DCHECK(install_callback_); |
| std::move(install_callback_).Run(app_id, code); |
| } |
| |
| bool WebAppInstallTask::ShouldStopInstall() const { |
| // Install should stop early if WebContents is being destroyed. |
| // WebAppInstallTask::WebContentsDestroyed will get called eventually and |
| // the callback will be invoked at that point. |
| return !web_contents() || web_contents()->IsBeingDestroyed(); |
| } |
| |
| void WebAppInstallTask::OnWebAppUrlLoadedGetWebAppInstallInfo( |
| const GURL& url_to_load, |
| WebAppUrlLoader::Result result) { |
| if (ShouldStopInstall()) |
| return; |
| |
| if (result != WebAppUrlLoader::Result::kUrlLoaded) |
| LogUrlLoaderError("OnWebAppUrlLoaded", url_to_load.spec(), result); |
| |
| if (result == WebAppUrlLoader::Result::kRedirectedUrlLoaded) { |
| CallInstallCallback(AppId(), |
| webapps::InstallResultCode::kInstallURLRedirected); |
| return; |
| } |
| |
| if (result == WebAppUrlLoader::Result::kFailedPageTookTooLong) { |
| CallInstallCallback(AppId(), |
| webapps::InstallResultCode::kInstallURLLoadTimeOut); |
| return; |
| } |
| |
| if (result != WebAppUrlLoader::Result::kUrlLoaded) { |
| CallInstallCallback(AppId(), |
| webapps::InstallResultCode::kInstallURLLoadFailed); |
| return; |
| } |
| |
| data_retriever_->GetWebAppInstallInfo( |
| web_contents(), |
| base::BindOnce(&WebAppInstallTask::OnGetWebAppInstallInfo, GetWeakPtr(), |
| /*force_shortcut_app*/ false)); |
| } |
| |
| void WebAppInstallTask::OnWebAppUrlLoadedCheckAndRetrieveManifest( |
| const GURL& url_to_load, |
| content::WebContents* web_contents, |
| WebAppUrlLoader::Result result) { |
| if (ShouldStopInstall()) |
| return; |
| |
| if (result != WebAppUrlLoader::Result::kUrlLoaded) |
| LogUrlLoaderError("OnWebAppUrlLoaded", url_to_load.spec(), result); |
| |
| if (result == WebAppUrlLoader::Result::kRedirectedUrlLoaded) { |
| CallInstallCallback(AppId(), |
| webapps::InstallResultCode::kInstallURLRedirected); |
| return; |
| } |
| |
| if (result == WebAppUrlLoader::Result::kFailedPageTookTooLong) { |
| CallInstallCallback(AppId(), |
| webapps::InstallResultCode::kInstallURLLoadTimeOut); |
| return; |
| } |
| |
| if (result != WebAppUrlLoader::Result::kUrlLoaded) { |
| CallInstallCallback(AppId(), |
| webapps::InstallResultCode::kInstallURLLoadFailed); |
| return; |
| } |
| |
| data_retriever_->CheckInstallabilityAndRetrieveManifest( |
| web_contents, |
| /*bypass_service_worker_check=*/true, |
| base::BindOnce(&WebAppInstallTask::OnWebAppInstallabilityChecked, |
| GetWeakPtr())); |
| } |
| |
| void WebAppInstallTask::OnWebAppInstallabilityChecked( |
| blink::mojom::ManifestPtr opt_manifest, |
| const GURL& manifest_url, |
| bool valid_manifest_for_web_app, |
| bool is_installable) { |
| if (ShouldStopInstall()) |
| return; |
| |
| if (is_installable) { |
| DCHECK(opt_manifest); |
| CallInstallCallback(GenerateAppIdFromManifest(*opt_manifest), |
| webapps::InstallResultCode::kSuccessNewInstall); |
| } else { |
| CallInstallCallback(AppId(), webapps::InstallResultCode::kNotInstallable); |
| } |
| } |
| |
| void WebAppInstallTask::OnGetWebAppInstallInfo( |
| bool force_shortcut_app, |
| std::unique_ptr<WebAppInstallInfo> web_app_info) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (ShouldStopInstall()) |
| return; |
| |
| if (!web_app_info) { |
| CallInstallCallback( |
| AppId(), webapps::InstallResultCode::kGetWebAppInstallInfoFailed); |
| return; |
| } |
| |
| bool bypass_service_worker_check = false; |
| if (install_params_) { |
| bypass_service_worker_check = install_params_->bypass_service_worker_check; |
| |
| // Set start_url to fallback_start_url as web_contents may have been |
| // redirected. Will be overridden by manifest values if present. |
| DCHECK(install_params_->fallback_start_url.is_valid()); |
| web_app_info->start_url = install_params_->fallback_start_url; |
| |
| if (install_params_->fallback_app_name.has_value()) |
| web_app_info->title = install_params_->fallback_app_name.value(); |
| |
| ApplyParamsToWebAppInstallInfo(*install_params_, *web_app_info); |
| } |
| |
| data_retriever_->CheckInstallabilityAndRetrieveManifest( |
| web_contents(), bypass_service_worker_check, |
| base::BindOnce(&WebAppInstallTask::OnDidPerformInstallableCheck, |
| GetWeakPtr(), std::move(web_app_info), |
| force_shortcut_app)); |
| } |
| |
| void WebAppInstallTask::ApplyParamsToWebAppInstallInfo( |
| const WebAppInstallParams& install_params, |
| WebAppInstallInfo& web_app_info) { |
| if (install_params.user_display_mode != DisplayMode::kUndefined) |
| web_app_info.user_display_mode = install_params.user_display_mode; |
| |
| if (!install_params.override_manifest_id.has_value()) |
| web_app_info.manifest_id = install_params.override_manifest_id; |
| |
| // If `additional_search_terms` was a manifest property, it would be |
| // sanitized while parsing the manifest. Since it's not, we sanitize it |
| // here. |
| for (const std::string& search_term : |
| install_params.additional_search_terms) { |
| if (!search_term.empty()) |
| web_app_info.additional_search_terms.push_back(search_term); |
| } |
| |
| if (install_params.launch_query_params) |
| web_app_info.launch_query_params = install_params.launch_query_params; |
| } |
| |
| void WebAppInstallTask::OnDidPerformInstallableCheck( |
| std::unique_ptr<WebAppInstallInfo> web_app_info, |
| bool force_shortcut_app, |
| blink::mojom::ManifestPtr opt_manifest, |
| const GURL& manifest_url, |
| bool valid_manifest_for_web_app, |
| bool is_installable) { |
| if (ShouldStopInstall()) |
| return; |
| |
| DCHECK(web_app_info); |
| |
| if (install_params_ && install_params_->require_manifest && |
| !valid_manifest_for_web_app) { |
| LOG(WARNING) << "Did not install " << web_app_info->start_url.spec() |
| << " because it didn't have a manifest for web app"; |
| CallInstallCallback(AppId(), |
| webapps::InstallResultCode::kNotValidManifestForWebApp); |
| return; |
| } |
| |
| const auto for_installable_site = is_installable && !force_shortcut_app |
| ? ForInstallableSite::kYes |
| : ForInstallableSite::kNo; |
| |
| if (opt_manifest) |
| UpdateWebAppInfoFromManifest(*opt_manifest, manifest_url, |
| web_app_info.get()); |
| |
| AppId app_id = |
| GenerateAppId(web_app_info->manifest_id, web_app_info->start_url); |
| |
| // Does the app_id expectation check if requested. |
| if (expected_app_id_.has_value() && *expected_app_id_ != app_id) { |
| LogExpectedAppIdError("OnDidPerformInstallableCheck", |
| web_app_info->start_url.spec(), app_id); |
| CallInstallCallback(std::move(app_id), |
| webapps::InstallResultCode::kExpectedAppIdCheckFailed); |
| return; |
| } |
| |
| // Duplicate installation check for SUB_APP installs (done here since the |
| // AppId isn't available beforehand). It's possible that the app was already |
| // installed, but from a different source (eg. by the user manually). In that |
| // case we proceed with the installation which adds the SUB_APP install source |
| // as well. |
| if (install_source_ == webapps::WebappInstallSource::SUB_APP) { |
| DCHECK(install_params_ && install_params_->parent_app_id.has_value()); |
| if (registrar_->WasInstalledBySubApp(app_id)) { |
| CallInstallCallback(std::move(app_id), |
| webapps::InstallResultCode::kSuccessAlreadyInstalled); |
| return; |
| } |
| } |
| |
| std::vector<GURL> icon_urls = GetValidIconUrlsToDownload(*web_app_info); |
| |
| // A system app should always have a manifest icon. |
| if (install_source_ == webapps::WebappInstallSource::SYSTEM_DEFAULT) { |
| DCHECK(opt_manifest); |
| DCHECK(!opt_manifest->icons.empty()); |
| } |
| |
| // If the manifest specified icons, don't use the page icons. |
| const bool skip_page_favicons = opt_manifest && !opt_manifest->icons.empty(); |
| |
| CheckForPlayStoreIntentOrGetIcons( |
| std::move(opt_manifest), std::move(web_app_info), std::move(icon_urls), |
| for_installable_site, skip_page_favicons); |
| } |
| |
| void WebAppInstallTask::CheckForPlayStoreIntentOrGetIcons( |
| blink::mojom::ManifestPtr opt_manifest, |
| std::unique_ptr<WebAppInstallInfo> web_app_info, |
| std::vector<GURL> icon_urls, |
| ForInstallableSite for_installable_site, |
| bool skip_page_favicons) { |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| // Background installations are not a user-triggered installs, and thus |
| // cannot be sent to the store. |
| if (for_installable_site == ForInstallableSite::kYes && |
| !background_installation_ && opt_manifest) { |
| absl::optional<PlayStoreIntent> intent = |
| GetPlayStoreIntentFromManifest(*opt_manifest); |
| if (intent) { |
| auto* arc_service_manager = arc::ArcServiceManager::Get(); |
| if (arc_service_manager) { |
| auto* instance = ARC_GET_INSTANCE_FOR_METHOD( |
| arc_service_manager->arc_bridge_service()->app(), IsInstallable); |
| if (instance) { |
| instance->IsInstallable( |
| intent->app_id, |
| base::BindOnce(&WebAppInstallTask::OnDidCheckForIntentToPlayStore, |
| GetWeakPtr(), std::move(web_app_info), |
| std::move(icon_urls), for_installable_site, |
| skip_page_favicons, intent->intent)); |
| return; |
| } |
| } |
| } |
| } |
| |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| OnDidCheckForIntentToPlayStore(std::move(web_app_info), std::move(icon_urls), |
| for_installable_site, skip_page_favicons, |
| /*intent=*/"", |
| /*should_intent_to_store=*/false); |
| } |
| |
| void WebAppInstallTask::OnDidCheckForIntentToPlayStore( |
| std::unique_ptr<WebAppInstallInfo> web_app_info, |
| std::vector<GURL> icon_urls, |
| ForInstallableSite for_installable_site, |
| bool skip_page_favicons, |
| const std::string& intent, |
| bool should_intent_to_store) { |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| if (should_intent_to_store && !intent.empty()) { |
| auto* arc_service_manager = arc::ArcServiceManager::Get(); |
| if (arc_service_manager) { |
| auto* instance = ARC_GET_INSTANCE_FOR_METHOD( |
| arc_service_manager->arc_bridge_service()->intent_helper(), |
| HandleUrl); |
| if (instance) { |
| instance->HandleUrl(intent, kPlayStorePackage); |
| CallInstallCallback(AppId(), |
| webapps::InstallResultCode::kIntentToPlayStore); |
| return; |
| } |
| } |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| data_retriever_->GetIcons( |
| web_contents(), icon_urls, skip_page_favicons, |
| base::BindOnce(&WebAppInstallTask::OnIconsRetrievedShowDialog, |
| GetWeakPtr(), std::move(web_app_info), |
| for_installable_site)); |
| } |
| |
| void WebAppInstallTask::InstallWebAppFromInfoRetrieveIcons( |
| content::WebContents* web_contents, |
| std::unique_ptr<WebAppInstallInfo> web_application_info, |
| WebAppInstallFinalizer::FinalizeOptions finalize_options, |
| OnceInstallCallback callback) { |
| CheckInstallPreconditions(); |
| |
| Observe(web_contents); |
| installing_web_contents_ = web_contents; |
| if (ShouldStopInstall()) |
| return; |
| |
| install_callback_ = std::move(callback); |
| install_source_ = finalize_options.install_source; |
| background_installation_ = true; |
| |
| std::vector<GURL> icon_urls = |
| GetValidIconUrlsToDownload(*web_application_info); |
| |
| // Skip downloading the page favicons as everything in is the URL list. |
| data_retriever_->GetIcons( |
| web_contents, icon_urls, /*skip_page_favicons=*/true, |
| base::BindOnce(&WebAppInstallTask::OnIconsRetrieved, GetWeakPtr(), |
| std::move(web_application_info), |
| std::move(finalize_options))); |
| } |
| |
| void WebAppInstallTask::OnIconsRetrieved( |
| std::unique_ptr<WebAppInstallInfo> web_app_info, |
| WebAppInstallFinalizer::FinalizeOptions finalize_options, |
| IconsDownloadedResult result, |
| IconsMap icons_map, |
| DownloadedIconsHttpResults icons_http_results) { |
| DCHECK(background_installation_); |
| |
| if (ShouldStopInstall()) |
| return; |
| |
| DCHECK(web_app_info); |
| |
| PopulateProductIcons(web_app_info.get(), &icons_map); |
| PopulateOtherIcons(web_app_info.get(), icons_map); |
| |
| RecordDownloadedIconsResultAndHttpStatusCodes(result, icons_http_results); |
| LogDownloadedIconsErrors(*web_app_info, result, icons_map, |
| icons_http_results); |
| |
| install_finalizer_->FinalizeInstall( |
| *web_app_info, std::move(finalize_options), |
| base::BindOnce(&WebAppInstallTask::OnInstallFinalized, GetWeakPtr())); |
| } |
| |
| void WebAppInstallTask::OnIconsRetrievedShowDialog( |
| std::unique_ptr<WebAppInstallInfo> web_app_info, |
| ForInstallableSite for_installable_site, |
| IconsDownloadedResult result, |
| IconsMap icons_map, |
| DownloadedIconsHttpResults icons_http_results) { |
| if (ShouldStopInstall()) |
| return; |
| |
| DCHECK(web_app_info); |
| |
| PopulateProductIcons(web_app_info.get(), &icons_map); |
| PopulateOtherIcons(web_app_info.get(), icons_map); |
| |
| RecordDownloadedIconsResultAndHttpStatusCodes(result, icons_http_results); |
| LogDownloadedIconsErrors(*web_app_info, result, icons_map, |
| icons_http_results); |
| |
| if (background_installation_) { |
| DCHECK(!dialog_callback_); |
| OnDialogCompleted(for_installable_site, /*user_accepted=*/true, |
| std::move(web_app_info)); |
| } else { |
| DCHECK(dialog_callback_); |
| std::move(dialog_callback_) |
| .Run(web_contents(), std::move(web_app_info), for_installable_site, |
| base::BindOnce(&WebAppInstallTask::OnDialogCompleted, GetWeakPtr(), |
| for_installable_site)); |
| } |
| } |
| |
| void WebAppInstallTask::OnDialogCompleted( |
| ForInstallableSite for_installable_site, |
| bool user_accepted, |
| std::unique_ptr<WebAppInstallInfo> web_app_info) { |
| if (ShouldStopInstall()) |
| return; |
| |
| if (!user_accepted) { |
| CallInstallCallback(AppId(), |
| webapps::InstallResultCode::kUserInstallDeclined); |
| return; |
| } |
| |
| if (only_retrieve_web_application_info_) { |
| web_application_info_ = std::move(web_app_info); |
| CallInstallCallback(AppId(), |
| webapps::InstallResultCode::kSuccessNewInstall); |
| return; |
| } |
| |
| WebAppInstallInfo web_app_info_copy = *web_app_info; |
| |
| // This metric is recorded regardless of the installation result. |
| RecordInstallEvent(); |
| |
| WebAppInstallFinalizer::FinalizeOptions finalize_options; |
| finalize_options.install_source = install_source_; |
| |
| if (install_params_) { |
| finalize_options.locally_installed = install_params_->locally_installed; |
| finalize_options.overwrite_existing_manifest_fields = |
| install_params_->force_reinstall; |
| finalize_options.parent_app_id = install_params_->parent_app_id; |
| |
| UpdateFinalizerClientData(install_params_, &finalize_options); |
| |
| if (install_params_->user_display_mode != DisplayMode::kUndefined) |
| web_app_info_copy.user_display_mode = install_params_->user_display_mode; |
| |
| finalize_options.add_to_applications_menu = |
| install_params_->add_to_applications_menu; |
| finalize_options.add_to_desktop = install_params_->add_to_desktop; |
| finalize_options.add_to_quick_launch_bar = |
| install_params_->add_to_quick_launch_bar; |
| } else { |
| finalize_options.locally_installed = true; |
| finalize_options.overwrite_existing_manifest_fields = true; |
| finalize_options.add_to_applications_menu = true; |
| finalize_options.add_to_desktop = true; |
| finalize_options.add_to_quick_launch_bar = |
| install_source_ == webapps::WebappInstallSource::SYNC |
| ? false |
| : kAddAppsToQuickLaunchBarByDefault; |
| } |
| |
| install_finalizer_->FinalizeInstall( |
| web_app_info_copy, finalize_options, |
| base::BindOnce(&WebAppInstallTask::OnInstallFinalizedMaybeReparentTab, |
| GetWeakPtr(), std::move(web_app_info))); |
| |
| // Check that the finalizer hasn't called OnInstallFinalizedMaybeReparentTab |
| // synchronously: |
| DCHECK(install_callback_); |
| } |
| |
| void WebAppInstallTask::OnInstallFinalized(const AppId& app_id, |
| webapps::InstallResultCode code, |
| OsHooksErrors os_hooks_errors) { |
| CallInstallCallback(app_id, code); |
| } |
| |
| void WebAppInstallTask::OnInstallFinalizedMaybeReparentTab( |
| std::unique_ptr<WebAppInstallInfo> web_app_info, |
| const AppId& app_id, |
| webapps::InstallResultCode code, |
| OsHooksErrors os_hooks_errors) { |
| if (ShouldStopInstall()) |
| return; |
| |
| if (code != webapps::InstallResultCode::kSuccessNewInstall) { |
| CallInstallCallback(app_id, code); |
| return; |
| } |
| |
| RecordWebAppInstallationTimestamp(profile_->GetPrefs(), app_id, |
| install_source_); |
| |
| if (install_params_ && !install_params_->locally_installed) { |
| DCHECK(background_installation_); |
| } |
| |
| if (!install_params_ || install_params_->locally_installed) { |
| RecordAppBanner(web_contents(), web_app_info->start_url); |
| } else { |
| DCHECK(background_installation_); |
| } |
| |
| if (!background_installation_) { |
| bool error = os_hooks_errors[OsHookType::kShortcuts]; |
| const bool can_reparent_tab = |
| install_finalizer_->CanReparentTab(app_id, !error); |
| |
| if (can_reparent_tab && |
| (web_app_info->user_display_mode != DisplayMode::kBrowser)) { |
| install_finalizer_->ReparentTab(app_id, !error, web_contents()); |
| } |
| } |
| CallInstallCallback(app_id, webapps::InstallResultCode::kSuccessNewInstall); |
| } |
| |
| void WebAppInstallTask::RecordDownloadedIconsResultAndHttpStatusCodes( |
| IconsDownloadedResult result, |
| const DownloadedIconsHttpResults& icons_http_results) { |
| if (install_source_ == webapps::WebappInstallSource::SYNC) { |
| RecordDownloadedIconsHttpResultsCodeClass( |
| "WebApp.Icon.HttpStatusCodeClassOnSync", result, icons_http_results); |
| |
| UMA_HISTOGRAM_ENUMERATION("WebApp.Icon.DownloadedResultOnSync", result); |
| RecordDownloadedIconHttpStatusCodes( |
| "WebApp.Icon.DownloadedHttpStatusCodeOnSync", icons_http_results); |
| } else { |
| RecordDownloadedIconsHttpResultsCodeClass( |
| "WebApp.Icon.HttpStatusCodeClassOnCreate", result, icons_http_results); |
| |
| UMA_HISTOGRAM_ENUMERATION("WebApp.Icon.DownloadedResultOnCreate", result); |
| RecordDownloadedIconHttpStatusCodes( |
| "WebApp.Icon.DownloadedHttpStatusCodeOnCreate", icons_http_results); |
| } |
| } |
| |
| void WebAppInstallTask::LogHeaderIfLogEmpty(const std::string& url) { |
| if (!error_dict_ || !error_dict_->DictEmpty()) |
| return; |
| |
| error_dict_->SetStringKey("!url", url); |
| error_dict_->SetIntKey("install_source", static_cast<int>(install_source_)); |
| error_dict_->SetBoolKey("background_installation", background_installation_); |
| error_dict_->SetKey("stages", base::Value(base::Value::Type::LIST)); |
| |
| DCHECK(!error_dict_->DictEmpty()); |
| } |
| |
| void WebAppInstallTask::LogErrorObject(const char* stage, |
| const std::string& url, |
| base::Value object) { |
| if (!error_dict_) |
| return; |
| |
| LogHeaderIfLogEmpty(url); |
| |
| object.SetStringKey("!stage", stage); |
| error_dict_->FindKey("stages")->Append(std::move(object)); |
| } |
| |
| void WebAppInstallTask::LogUrlLoaderError(const char* stage, |
| const std::string& url, |
| WebAppUrlLoader::Result result) { |
| if (!error_dict_) |
| return; |
| |
| base::Value url_loader_error(base::Value::Type::DICTIONARY); |
| |
| url_loader_error.SetStringKey("WebAppUrlLoader::Result", |
| ConvertUrlLoaderResultToString(result)); |
| |
| LogErrorObject(stage, url, std::move(url_loader_error)); |
| } |
| |
| void WebAppInstallTask::LogExpectedAppIdError(const char* stage, |
| const std::string& url, |
| const AppId& app_id) { |
| if (!error_dict_ || !expected_app_id_.has_value()) |
| return; |
| |
| base::Value expected_app_id_error(base::Value::Type::DICTIONARY); |
| |
| expected_app_id_error.SetStringKey("expected_app_id", |
| expected_app_id_.value()); |
| expected_app_id_error.SetStringKey("app_id", app_id); |
| |
| LogErrorObject(stage, url, std::move(expected_app_id_error)); |
| } |
| |
| void WebAppInstallTask::LogDownloadedIconsErrors( |
| const WebAppInstallInfo& web_app_info, |
| IconsDownloadedResult icons_downloaded_result, |
| const IconsMap& icons_map, |
| const DownloadedIconsHttpResults& icons_http_results) { |
| if (!error_dict_) |
| return; |
| |
| base::Value icon_errors(base::Value::Type::DICTIONARY); |
| { |
| // Reports errors only, omits successful entries. |
| base::Value icons_http_errors(base::Value::Type::LIST); |
| |
| for (const auto& url_and_http_code : icons_http_results) { |
| const GURL& icon_url = url_and_http_code.first; |
| int http_status_code = url_and_http_code.second; |
| const char* http_code_desc = net::GetHttpReasonPhrase( |
| static_cast<net::HttpStatusCode>(http_status_code)); |
| |
| // If the SkBitmap for`icon_url` is missing in `icons_map` then we report |
| // this miss as an error, even for net::HttpStatusCode::HTTP_OK. |
| if (IsEmptyIconBitmapsForIconUrl(icons_map, icon_url)) { |
| base::Value icon_http_error(base::Value::Type::DICTIONARY); |
| |
| icon_http_error.SetStringKey("icon_url", icon_url.spec()); |
| icon_http_error.SetIntKey("http_status_code", http_status_code); |
| icon_http_error.SetStringKey("http_code_desc", http_code_desc); |
| |
| icons_http_errors.Append(std::move(icon_http_error)); |
| } |
| } |
| |
| if (icons_downloaded_result != IconsDownloadedResult::kCompleted || |
| !icons_http_errors.GetListDeprecated().empty()) { |
| icon_errors.SetStringKey( |
| "icons_downloaded_result", |
| IconsDownloadedResultToString(icons_downloaded_result)); |
| } |
| |
| if (!icons_http_errors.GetListDeprecated().empty()) |
| icon_errors.SetKey("icons_http_results", std::move(icons_http_errors)); |
| } |
| |
| if (web_app_info.is_generated_icon) |
| icon_errors.SetBoolKey("is_generated_icon", true); |
| |
| if (!icon_errors.DictEmpty()) { |
| LogErrorObject("OnIconsRetrieved", web_app_info.start_url.spec(), |
| std::move(icon_errors)); |
| } |
| } |
| |
| } // namespace web_app |