1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/web_applications/components/web_app_data_retriever.h"
6 
7 #include <memory>
8 #include <set>
9 #include <string>
10 #include <utility>
11 #include <vector>
12 
13 #include "base/bind.h"
14 #include "base/callback.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "chrome/browser/installable/installable_data.h"
17 #include "chrome/browser/installable/installable_manager.h"
18 #include "chrome/browser/web_applications/components/web_app_icon_generator.h"
19 #include "chrome/browser/web_applications/components/web_application_info.h"
20 #include "chrome/common/chrome_render_frame.mojom.h"
21 #include "content/public/browser/navigation_entry.h"
22 #include "content/public/browser/render_frame_host.h"
23 #include "content/public/browser/web_contents.h"
24 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
25 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
26 #include "third_party/skia/include/core/SkColor.h"
27 
28 namespace web_app {
29 
30 WebAppDataRetriever::WebAppDataRetriever() = default;
31 
32 WebAppDataRetriever::~WebAppDataRetriever() = default;
33 
GetWebApplicationInfo(content::WebContents * web_contents,GetWebApplicationInfoCallback callback)34 void WebAppDataRetriever::GetWebApplicationInfo(
35     content::WebContents* web_contents,
36     GetWebApplicationInfoCallback callback) {
37   Observe(web_contents);
38 
39   // Concurrent calls are not allowed.
40   DCHECK(!get_web_app_info_callback_);
41   get_web_app_info_callback_ = std::move(callback);
42 
43   content::NavigationEntry* entry =
44       web_contents->GetController().GetLastCommittedEntry();
45   if (!entry) {
46     base::ThreadTaskRunnerHandle::Get()->PostTask(
47         FROM_HERE, base::BindOnce(&WebAppDataRetriever::CallCallbackOnError,
48                                   weak_ptr_factory_.GetWeakPtr()));
49     return;
50   }
51 
52   // Makes a copy of WebContents fields right after Commit but before a mojo
53   // request to the renderer process.
54   default_web_application_info_ = std::make_unique<WebApplicationInfo>();
55   default_web_application_info_->start_url =
56       web_contents->GetLastCommittedURL();
57   default_web_application_info_->title = web_contents->GetTitle();
58   if (default_web_application_info_->title.empty()) {
59     default_web_application_info_->title =
60         base::UTF8ToUTF16(default_web_application_info_->start_url.spec());
61   }
62 
63   mojo::AssociatedRemote<chrome::mojom::ChromeRenderFrame> chrome_render_frame;
64   web_contents->GetMainFrame()->GetRemoteAssociatedInterfaces()->GetInterface(
65       &chrome_render_frame);
66 
67   // Set the error handler so that we can run |get_web_app_info_callback_| if
68   // the WebContents or the RenderFrameHost are destroyed and the connection
69   // to ChromeRenderFrame is lost.
70   chrome_render_frame.set_disconnect_handler(
71       base::BindOnce(&WebAppDataRetriever::CallCallbackOnError,
72                      weak_ptr_factory_.GetWeakPtr()));
73   // Bind the InterfacePtr into the callback so that it's kept alive
74   // until there's either a connection error or a response.
75   auto* web_page_metadata_proxy = chrome_render_frame.get();
76   web_page_metadata_proxy->GetWebPageMetadata(
77       base::BindOnce(&WebAppDataRetriever::OnGetWebPageMetadata,
78                      weak_ptr_factory_.GetWeakPtr(),
79                      std::move(chrome_render_frame), entry->GetUniqueID()));
80 }
81 
CheckInstallabilityAndRetrieveManifest(content::WebContents * web_contents,bool bypass_service_worker_check,CheckInstallabilityCallback callback)82 void WebAppDataRetriever::CheckInstallabilityAndRetrieveManifest(
83     content::WebContents* web_contents,
84     bool bypass_service_worker_check,
85     CheckInstallabilityCallback callback) {
86   InstallableManager* installable_manager =
87       InstallableManager::FromWebContents(web_contents);
88   DCHECK(installable_manager);
89 
90   Observe(web_contents);
91 
92   // Concurrent calls are not allowed.
93   DCHECK(!check_installability_callback_);
94   check_installability_callback_ = std::move(callback);
95 
96   // TODO(crbug.com/829232) Unify with other calls to GetData.
97   InstallableParams params;
98   params.check_eligibility = true;
99   params.valid_primary_icon = true;
100   params.valid_manifest = true;
101   params.check_webapp_manifest_display = false;
102   // Do not wait for a service worker if it doesn't exist.
103   params.has_worker = !bypass_service_worker_check;
104   // Do not wait_for_worker. OnDidPerformInstallableCheck is always invoked.
105   installable_manager->GetData(
106       params, base::BindOnce(&WebAppDataRetriever::OnDidPerformInstallableCheck,
107                              weak_ptr_factory_.GetWeakPtr()));
108 }
109 
GetIcons(content::WebContents * web_contents,const std::vector<GURL> & icon_urls,bool skip_page_favicons,WebAppIconDownloader::Histogram histogram,GetIconsCallback callback)110 void WebAppDataRetriever::GetIcons(content::WebContents* web_contents,
111                                    const std::vector<GURL>& icon_urls,
112                                    bool skip_page_favicons,
113                                    WebAppIconDownloader::Histogram histogram,
114                                    GetIconsCallback callback) {
115   Observe(web_contents);
116 
117   // Concurrent calls are not allowed.
118   CHECK(!get_icons_callback_);
119   get_icons_callback_ = std::move(callback);
120 
121   // TODO(loyso): Refactor WebAppIconDownloader: crbug.com/907296.
122   icon_downloader_ = std::make_unique<WebAppIconDownloader>(
123       web_contents, icon_urls, histogram,
124       base::BindOnce(&WebAppDataRetriever::OnIconsDownloaded,
125                      weak_ptr_factory_.GetWeakPtr()));
126 
127   if (skip_page_favicons)
128     icon_downloader_->SkipPageFavicons();
129 
130   icon_downloader_->Start();
131 }
132 
WebContentsDestroyed()133 void WebAppDataRetriever::WebContentsDestroyed() {
134   CallCallbackOnError();
135 }
136 
RenderProcessGone(base::TerminationStatus status)137 void WebAppDataRetriever::RenderProcessGone(base::TerminationStatus status) {
138   CallCallbackOnError();
139 }
140 
OnGetWebPageMetadata(mojo::AssociatedRemote<chrome::mojom::ChromeRenderFrame> chrome_render_frame,int last_committed_nav_entry_unique_id,chrome::mojom::WebPageMetadataPtr web_page_metadata)141 void WebAppDataRetriever::OnGetWebPageMetadata(
142     mojo::AssociatedRemote<chrome::mojom::ChromeRenderFrame>
143         chrome_render_frame,
144     int last_committed_nav_entry_unique_id,
145     chrome::mojom::WebPageMetadataPtr web_page_metadata) {
146   if (ShouldStopRetrieval())
147     return;
148 
149   DCHECK(default_web_application_info_);
150 
151   content::WebContents* contents = web_contents();
152   Observe(nullptr);
153 
154   std::unique_ptr<WebApplicationInfo> info;
155 
156   content::NavigationEntry* entry =
157       contents->GetController().GetLastCommittedEntry();
158 
159   if (entry) {
160     if (entry->GetUniqueID() == last_committed_nav_entry_unique_id) {
161       info = std::make_unique<WebApplicationInfo>(*web_page_metadata);
162       if (info->start_url.is_empty())
163         info->start_url = std::move(default_web_application_info_->start_url);
164       if (info->title.empty())
165         info->title = std::move(default_web_application_info_->title);
166     } else {
167       // WebContents navigation state changed during the call. Ignore the mojo
168       // request result. Use default initial info instead.
169       info = std::move(default_web_application_info_);
170     }
171   }
172 
173   default_web_application_info_.reset();
174 
175   std::move(get_web_app_info_callback_).Run(std::move(info));
176 }
177 
OnDidPerformInstallableCheck(const InstallableData & data)178 void WebAppDataRetriever::OnDidPerformInstallableCheck(
179     const InstallableData& data) {
180   if (ShouldStopRetrieval())
181     return;
182 
183   Observe(nullptr);
184 
185   DCHECK(data.manifest);
186   DCHECK(data.manifest_url.is_valid() || data.manifest->IsEmpty());
187 
188   const bool is_installable = data.errors.empty();
189   DCHECK(!is_installable || data.valid_manifest);
190   base::Optional<blink::Manifest> opt_manifest;
191   if (!data.manifest->IsEmpty())
192     opt_manifest = *data.manifest;
193 
194   std::move(check_installability_callback_)
195       .Run(std::move(opt_manifest), data.valid_manifest, is_installable);
196 }
197 
OnIconsDownloaded(bool success,IconsMap icons_map)198 void WebAppDataRetriever::OnIconsDownloaded(bool success, IconsMap icons_map) {
199   if (ShouldStopRetrieval())
200     return;
201 
202   Observe(nullptr);
203   icon_downloader_.reset();
204   std::move(get_icons_callback_).Run(std::move(icons_map));
205 }
206 
CallCallbackOnError()207 void WebAppDataRetriever::CallCallbackOnError() {
208   Observe(nullptr);
209   DCHECK(ShouldStopRetrieval());
210 
211   default_web_application_info_.reset();
212 
213   // Call a callback as a tail call. The callback may destroy |this|.
214   if (get_web_app_info_callback_) {
215     std::move(get_web_app_info_callback_).Run(nullptr);
216   } else if (check_installability_callback_) {
217     std::move(check_installability_callback_)
218         .Run(base::nullopt, /*valid_manifest_for_web_app=*/false,
219              /*is_installable=*/false);
220   } else if (get_icons_callback_) {
221     std::move(get_icons_callback_).Run(IconsMap{});
222   }
223 }
224 
ShouldStopRetrieval() const225 bool WebAppDataRetriever::ShouldStopRetrieval() const {
226   return !web_contents();
227 }
228 
229 }  // namespace web_app
230