| // Copyright 2018 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 "chrome/browser/web_applications/web_app_data_retriever.h" |
| |
| #include <memory> |
| #include <set> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "chrome/browser/web_applications/web_app_constants.h" |
| #include "chrome/browser/web_applications/web_app_icon_generator.h" |
| #include "chrome/browser/web_applications/web_app_install_info.h" |
| #include "components/webapps/browser/installable/installable_data.h" |
| #include "components/webapps/browser/installable/installable_manager.h" |
| #include "components/webapps/common/web_page_metadata.mojom.h" |
| #include "components/webapps/common/web_page_metadata_agent.mojom.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "net/base/registry_controlled_domains/registry_controlled_domain.h" |
| #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h" |
| #include "third_party/blink/public/common/manifest/manifest_util.h" |
| #include "third_party/blink/public/mojom/manifest/manifest.mojom.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| |
| namespace web_app { |
| |
| WebAppDataRetriever::WebAppDataRetriever() = default; |
| |
| WebAppDataRetriever::~WebAppDataRetriever() = default; |
| |
| void WebAppDataRetriever::GetWebAppInstallInfo( |
| content::WebContents* web_contents, |
| GetWebAppInstallInfoCallback callback) { |
| DCHECK(!web_contents->IsBeingDestroyed()); |
| Observe(web_contents); |
| |
| // Concurrent calls are not allowed. |
| DCHECK(!get_web_app_info_callback_); |
| get_web_app_info_callback_ = std::move(callback); |
| |
| content::NavigationEntry* entry = |
| web_contents->GetController().GetLastCommittedEntry(); |
| if (!entry || entry->IsInitialEntry()) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&WebAppDataRetriever::CallCallbackOnError, |
| weak_ptr_factory_.GetWeakPtr())); |
| return; |
| } |
| |
| // Makes a copy of WebContents fields right after Commit but before a mojo |
| // request to the renderer process. |
| fallback_install_info_ = std::make_unique<WebAppInstallInfo>(); |
| fallback_install_info_->start_url = web_contents->GetLastCommittedURL(); |
| fallback_install_info_->title = web_contents->GetTitle(); |
| if (fallback_install_info_->title.empty()) { |
| fallback_install_info_->title = |
| base::UTF8ToUTF16(fallback_install_info_->start_url.spec()); |
| } |
| |
| mojo::AssociatedRemote<webapps::mojom::WebPageMetadataAgent> metadata_agent; |
| web_contents->GetPrimaryMainFrame() |
| ->GetRemoteAssociatedInterfaces() |
| ->GetInterface(&metadata_agent); |
| |
| // Set the error handler so that we can run |get_web_app_info_callback_| if |
| // the WebContents or the RenderFrameHost are destroyed and the connection |
| // to ChromeRenderFrame is lost. |
| metadata_agent.set_disconnect_handler( |
| base::BindOnce(&WebAppDataRetriever::CallCallbackOnError, |
| weak_ptr_factory_.GetWeakPtr())); |
| // Bind the InterfacePtr into the callback so that it's kept alive |
| // until there's either a connection error or a response. |
| auto* web_page_metadata_proxy = metadata_agent.get(); |
| web_page_metadata_proxy->GetWebPageMetadata( |
| base::BindOnce(&WebAppDataRetriever::OnGetWebPageMetadata, |
| weak_ptr_factory_.GetWeakPtr(), std::move(metadata_agent), |
| entry->GetUniqueID())); |
| } |
| |
| void WebAppDataRetriever::CheckInstallabilityAndRetrieveManifest( |
| content::WebContents* web_contents, |
| bool bypass_service_worker_check, |
| CheckInstallabilityCallback callback) { |
| DCHECK(!web_contents->IsBeingDestroyed()); |
| webapps::InstallableManager* installable_manager = |
| webapps::InstallableManager::FromWebContents(web_contents); |
| DCHECK(installable_manager); |
| |
| Observe(web_contents); |
| |
| // Concurrent calls are not allowed. |
| DCHECK(!check_installability_callback_); |
| check_installability_callback_ = std::move(callback); |
| |
| // TODO(crbug.com/829232) Unify with other calls to GetData. |
| webapps::InstallableParams params; |
| params.check_eligibility = true; |
| params.valid_primary_icon = true; |
| params.valid_manifest = true; |
| params.check_webapp_manifest_display = false; |
| // Do not wait for a service worker if it doesn't exist. |
| params.has_worker = !bypass_service_worker_check; |
| // Do not wait_for_worker. OnDidPerformInstallableCheck is always invoked. |
| installable_manager->GetData( |
| params, base::BindOnce(&WebAppDataRetriever::OnDidPerformInstallableCheck, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void WebAppDataRetriever::GetIcons(content::WebContents* web_contents, |
| base::flat_set<GURL> icon_urls, |
| bool skip_page_favicons, |
| GetIconsCallback callback) { |
| DCHECK(!web_contents->IsBeingDestroyed()); |
| Observe(web_contents); |
| |
| // Concurrent calls are not allowed. |
| CHECK(!get_icons_callback_); |
| get_icons_callback_ = std::move(callback); |
| |
| // TODO(loyso): Refactor WebAppIconDownloader: crbug.com/907296. |
| icon_downloader_ = std::make_unique<WebAppIconDownloader>( |
| web_contents, std::move(icon_urls), |
| base::BindOnce(&WebAppDataRetriever::OnIconsDownloaded, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| if (skip_page_favicons) |
| icon_downloader_->SkipPageFavicons(); |
| |
| icon_downloader_->Start(); |
| } |
| |
| void WebAppDataRetriever::WebContentsDestroyed() { |
| CallCallbackOnError(); |
| } |
| |
| void WebAppDataRetriever::PrimaryMainFrameRenderProcessGone( |
| base::TerminationStatus status) { |
| CallCallbackOnError(); |
| } |
| |
| void WebAppDataRetriever::OnGetWebPageMetadata( |
| mojo::AssociatedRemote<webapps::mojom::WebPageMetadataAgent> metadata_agent, |
| int last_committed_nav_entry_unique_id, |
| webapps::mojom::WebPageMetadataPtr web_page_metadata) { |
| if (ShouldStopRetrieval()) |
| return; |
| |
| DCHECK(fallback_install_info_); |
| |
| content::WebContents* contents = web_contents(); |
| Observe(nullptr); |
| |
| std::unique_ptr<WebAppInstallInfo> info; |
| |
| content::NavigationEntry* entry = |
| contents->GetController().GetLastCommittedEntry(); |
| |
| if (entry && !entry->IsInitialEntry()) { |
| if (entry->GetUniqueID() == last_committed_nav_entry_unique_id) { |
| info = std::make_unique<WebAppInstallInfo>(*web_page_metadata); |
| if (info->start_url.is_empty()) |
| info->start_url = std::move(fallback_install_info_->start_url); |
| if (info->title.empty()) |
| info->title = std::move(fallback_install_info_->title); |
| } else { |
| // WebContents navigation state changed during the call. Ignore the mojo |
| // request result. Use default initial info instead. |
| info = std::move(fallback_install_info_); |
| } |
| } |
| |
| fallback_install_info_.reset(); |
| |
| DCHECK(!get_web_app_info_callback_.is_null()); |
| std::move(get_web_app_info_callback_).Run(std::move(info)); |
| } |
| |
| void WebAppDataRetriever::OnDidPerformInstallableCheck( |
| const webapps::InstallableData& data) { |
| if (ShouldStopRetrieval()) |
| return; |
| |
| Observe(nullptr); |
| |
| const bool is_installable = data.NoBlockingErrors(); |
| DCHECK(!is_installable || data.valid_manifest); |
| blink::mojom::ManifestPtr opt_manifest; |
| if (!blink::IsEmptyManifest(data.manifest)) |
| opt_manifest = data.manifest.Clone(); |
| |
| DCHECK(!check_installability_callback_.is_null()); |
| std::move(check_installability_callback_) |
| .Run(std::move(opt_manifest), data.manifest_url, data.valid_manifest, |
| is_installable); |
| } |
| |
| void WebAppDataRetriever::OnIconsDownloaded( |
| IconsDownloadedResult result, |
| IconsMap icons_map, |
| DownloadedIconsHttpResults icons_http_results) { |
| if (ShouldStopRetrieval()) |
| return; |
| |
| Observe(nullptr); |
| icon_downloader_.reset(); |
| |
| DCHECK(!get_icons_callback_.is_null()); |
| std::move(get_icons_callback_) |
| .Run(result, std::move(icons_map), std::move(icons_http_results)); |
| } |
| |
| void WebAppDataRetriever::CallCallbackOnError() { |
| Observe(nullptr); |
| DCHECK(ShouldStopRetrieval()); |
| icon_downloader_.reset(); |
| fallback_install_info_.reset(); |
| weak_ptr_factory_.InvalidateWeakPtrs(); |
| |
| // Call a callback as a tail call. The callback may destroy |this|. |
| if (get_web_app_info_callback_) { |
| std::move(get_web_app_info_callback_).Run(nullptr); |
| } else if (check_installability_callback_) { |
| std::move(check_installability_callback_) |
| .Run(/*manifest=*/nullptr, /*manifest_url=*/GURL(), |
| /*valid_manifest_for_web_app=*/false, |
| /*is_installable=*/false); |
| } else if (get_icons_callback_) { |
| std::move(get_icons_callback_) |
| .Run(IconsDownloadedResult::kPrimaryPageChanged, IconsMap{}, |
| DownloadedIconsHttpResults{}); |
| } |
| } |
| |
| bool WebAppDataRetriever::ShouldStopRetrieval() const { |
| return !web_contents() || web_contents()->IsBeingDestroyed(); |
| } |
| |
| } // namespace web_app |