Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(56)

Side by Side Diff: chrome/browser/installable/installable_manager.cc

Issue 2160513002: Extract AppBannerDataFetcher into an InstallableManager. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Addressing comments Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 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/installable/installable_manager.h"
6
7 #include "base/bind.h"
8 #include "base/strings/string_util.h"
9 #include "chrome/browser/manifest/manifest_icon_downloader.h"
10 #include "chrome/browser/manifest/manifest_icon_selector.h"
11 #include "chrome/browser/profiles/profile.h"
12 #include "content/public/browser/browser_context.h"
13 #include "content/public/browser/browser_thread.h"
14 #include "content/public/browser/navigation_handle.h"
15 #include "content/public/browser/service_worker_context.h"
16 #include "content/public/browser/storage_partition.h"
17 #include "third_party/WebKit/public/platform/WebDisplayMode.h"
18
19 namespace {
20
21 const char kPngExtension[] = ".png";
22
23 // This constant is the icon size on Android (48dp) multiplied by the scale
24 // factor of a Nexus 5 device (3x). For mobile and desktop platforms, a 144px
25 // icon is an approximate, appropriate lower bound.
26 // TODO(dominickn): consolidate with minimum_icon_size_in_dp across platforms.
27 const int kIconMinimumSizeInPx = 144;
28
29 // Returns true if |manifest| specifies a PNG icon >= 144x144px (or size "any").
30 bool DoesManifestContainRequiredIcon(const content::Manifest& manifest) {
31 for (const auto& icon : manifest.icons) {
32 // The type field is optional. If it isn't present, fall back on checking
33 // the src extension, and allow the icon if the extension ends with png.
34 if (!base::EqualsASCII(icon.type.string(), "image/png") &&
35 !(icon.type.is_null() &&
36 base::EndsWith(icon.src.ExtractFileName(), kPngExtension,
37 base::CompareCase::INSENSITIVE_ASCII)))
38 continue;
39
40 for (const auto& size : icon.sizes) {
41 if (size.IsEmpty()) // "any"
42 return true;
43 if (size.width() >= kIconMinimumSizeInPx &&
44 size.height() >= kIconMinimumSizeInPx) {
45 return true;
46 }
47 }
48 }
49
50 return false;
51 }
52
53 } // anonymous namespace
54
55 DEFINE_WEB_CONTENTS_USER_DATA_KEY(InstallableManager);
56
57 InstallableManager::ManifestProperty::~ManifestProperty() = default;
benwells 2016/08/03 01:17:28 Nit: is this needed?
dominickn 2016/08/03 06:38:04 It was at this time (complex structs must have a d
58
59 InstallableManager::IconProperty::~IconProperty() = default;
60
61 InstallableManager::IconProperty::IconProperty() :
62 error(NO_ERROR_DETECTED), url(), icon(), fetched(false) { }
63
64 InstallableManager::IconProperty::IconProperty(
65 InstallableManager::IconProperty&& other) = default;
66
67 InstallableManager::IconProperty& InstallableManager::IconProperty::operator=(
68 IconProperty&& other) = default;
69
70 InstallableManager::InstallableManager(content::WebContents* web_contents)
71 : content::WebContentsObserver(web_contents),
72 manifest_(new ManifestProperty()),
73 installable_(new InstallableProperty()),
74 is_active_(false),
75 weak_factory_(this) { }
76
77 InstallableManager::~InstallableManager() = default;
78
79 bool InstallableManager::IsManifestValidForWebApp(
80 const content::Manifest& manifest) {
81 if (manifest.IsEmpty()) {
82 installable_->error = MANIFEST_EMPTY;
83 return false;
84 }
85
86 if (!manifest.start_url.is_valid()) {
87 installable_->error = START_URL_NOT_VALID;
88 return false;
89 }
90
91 if ((manifest.name.is_null() || manifest.name.string().empty()) &&
92 (manifest.short_name.is_null() || manifest.short_name.string().empty())) {
93 installable_->error = MANIFEST_MISSING_NAME_OR_SHORT_NAME;
94 return false;
95 }
96
97 // TODO(dominickn,mlamouri): when Chrome supports "minimal-ui", it should be
98 // accepted. If we accept it today, it would fallback to "browser" and make
99 // this check moot. See https://crbug.com/604390.
100 if (manifest.display != blink::WebDisplayModeStandalone &&
101 manifest.display != blink::WebDisplayModeFullscreen) {
102 installable_->error = MANIFEST_DISPLAY_NOT_SUPPORTED;
103 return false;
104 }
105
106 if (!DoesManifestContainRequiredIcon(manifest)) {
107 installable_->error = MANIFEST_MISSING_SUITABLE_ICON;
108 return false;
109 }
110
111 return true;
112 }
113
114 void InstallableManager::GetData(const InstallableParams& params,
115 const InstallableCallback& callback) {
116 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
117
118 // Return immediately if we've already working on a task. The new task will be
119 // looked at once the current task is finished.
120 tasks_.push_back({params, callback});
121 if (is_active_)
122 return;
123
124 is_active_ = true;
125 StartNextTask();
126 }
127
128 bool InstallableManager::IsIconFetched(const InstallableParams& params) const {
129 const auto it = icons_.find(
130 {params.ideal_icon_size_in_dp, params.minimum_icon_size_in_dp});
131 return it != icons_.end() && it->second.fetched;
132 }
133
134 void InstallableManager::SetIconFetched(const InstallableParams& params) {
135 GetIcon(params).fetched = true;
136 }
137
138 InstallableManager::IconProperty& InstallableManager::GetIcon(
139 const InstallableParams& params) {
140 return icons_[{params.ideal_icon_size_in_dp, params.minimum_icon_size_in_dp}];
141 }
142
143 InstallableErrorCode InstallableManager::GetErrorCode(
144 const InstallableParams& params) {
145 if (manifest_->error != NO_ERROR_DETECTED)
146 return manifest_->error;
147
148 if (params.check_installable && installable_->error != NO_ERROR_DETECTED)
149 return installable_->error;
150
151 if (params.fetch_valid_icon) {
152 IconProperty& icon = GetIcon(params);
153 if (icon.error != NO_ERROR_DETECTED)
154 return icon.error;
155 }
156
157 return NO_ERROR_DETECTED;
158 }
159
160 content::WebContents* InstallableManager::GetWebContents() {
161 content::WebContents* contents = web_contents();
162 if (!contents || contents->IsBeingDestroyed())
163 return nullptr;
164 return contents;
165 }
166
167 bool InstallableManager::IsComplete(const InstallableParams& params) const {
168 // Returns true if for all resources:
169 // a. the params did not request it, OR
170 // b. the resource has been fetched/checked.
171 return manifest_->fetched &&
172 (!params.check_installable || installable_->fetched) &&
173 (!params.fetch_valid_icon || IsIconFetched(params));
174 }
175
176 void InstallableManager::Reset() {
177 // Prevent any outstanding callbacks to or from this object from being called.
178 weak_factory_.InvalidateWeakPtrs();
179 tasks_.clear();
180 icons_.clear();
181
182 manifest_.reset(new ManifestProperty());
183 installable_.reset(new InstallableProperty());
184
185 is_active_ = false;
186 }
187
188 void InstallableManager::SetManifestDependentTasksComplete() {
189 DCHECK(!tasks_.empty());
190 const InstallableParams& params = tasks_[0].first;
191
192 installable_->fetched = true;
193 SetIconFetched(params);
194 }
195
196 void InstallableManager::StartNextTask() {
197 // If there's nothing to do, exit. Resources remain cached so any future calls
198 // won't re-fetch anything that has already been retrieved.
199 if (tasks_.empty()) {
200 is_active_ = false;
201 return;
202 }
203
204 DCHECK(is_active_);
205 WorkOnTask();
206 }
207
208 void InstallableManager::RunCallback(const Task& task,
209 InstallableErrorCode code) {
210 const InstallableParams& params = task.first;
211 IconProperty& icon = GetIcon(params);
212 InstallableData data = {
213 code,
214 manifest_url(),
215 manifest(),
216 params.fetch_valid_icon ? icon.url : GURL::EmptyGURL(),
217 params.fetch_valid_icon ? icon.icon.get() : nullptr,
218 params.check_installable ? is_installable() : false};
219
220 task.second.Run(data);
221 }
222
223 void InstallableManager::WorkOnTask() {
224 DCHECK(!tasks_.empty());
225 const Task& task = tasks_[0];
226 const InstallableParams& params = task.first;
227
228 InstallableErrorCode code = GetErrorCode(params);
229 if (code != NO_ERROR_DETECTED || IsComplete(params)) {
230 RunCallback(task, code);
231 tasks_.erase(tasks_.begin());
232 StartNextTask();
233 return;
234 }
235
236 if (!manifest_->fetched)
237 FetchManifest();
238 else if (params.check_installable && !installable_->fetched)
239 CheckInstallable();
240 else if (params.fetch_valid_icon && !IsIconFetched(params))
241 CheckAndFetchBestIcon();
242 else
243 NOTREACHED();
244 }
245
246 void InstallableManager::FetchManifest() {
247 DCHECK(!manifest_->fetched);
248
249 content::WebContents* web_contents = GetWebContents();
250 DCHECK(web_contents);
251
252 web_contents->GetManifest(base::Bind(&InstallableManager::OnDidGetManifest,
253 weak_factory_.GetWeakPtr()));
254 }
255
256 void InstallableManager::OnDidGetManifest(const GURL& manifest_url,
257 const content::Manifest& manifest) {
258 if (!GetWebContents()) {
259 return;
260 } else if (manifest_url.is_empty()) {
261 manifest_->error = NO_MANIFEST;
262 SetManifestDependentTasksComplete();
263 } else if (manifest.IsEmpty()) {
264 manifest_->error = MANIFEST_EMPTY;
265 SetManifestDependentTasksComplete();
266 }
267
268 manifest_->url = manifest_url;
269 manifest_->manifest = manifest;
270 manifest_->fetched = true;
271 WorkOnTask();
272 }
273
274 void InstallableManager::CheckInstallable() {
275 DCHECK(!installable_->fetched);
276 DCHECK(!manifest().IsEmpty());
277
278 if (IsManifestValidForWebApp(manifest())) {
279 CheckServiceWorker();
280 } else {
281 installable_->installable = false;
282 installable_->fetched = true;
283 WorkOnTask();
284 }
285 }
286
287 void InstallableManager::CheckServiceWorker() {
288 DCHECK(!installable_->fetched);
289 DCHECK(!manifest().IsEmpty());
290 DCHECK(manifest().start_url.is_valid());
291
292 content::WebContents* web_contents = GetWebContents();
293
294 // Check to see if there is a single service worker controlling this page
295 // and the manifest's start url.
296 content::StoragePartition* storage_partition =
297 content::BrowserContext::GetStoragePartition(
298 Profile::FromBrowserContext(web_contents->GetBrowserContext()),
299 web_contents->GetSiteInstance());
300 DCHECK(storage_partition);
301
302 storage_partition->GetServiceWorkerContext()->CheckHasServiceWorker(
303 web_contents->GetLastCommittedURL(), manifest().start_url,
304 base::Bind(&InstallableManager::OnDidCheckHasServiceWorker,
305 weak_factory_.GetWeakPtr()));
306 }
307
308 void InstallableManager::OnDidCheckHasServiceWorker(bool has_service_worker) {
309 if (!GetWebContents())
310 return;
311
312 if (has_service_worker) {
313 installable_->installable = true;
314 } else {
315 installable_->installable = false;
316 installable_->error = NO_MATCHING_SERVICE_WORKER;
317 }
318
319 installable_->fetched = true;
320 WorkOnTask();
321 }
322
323 void InstallableManager::CheckAndFetchBestIcon() {
324 DCHECK(!manifest().IsEmpty());
325 DCHECK(!tasks_.empty());
326
327 const InstallableParams& params = tasks_[0].first;
328 IconProperty& icon = GetIcon(params);
329 icon.fetched = true;
330
331 GURL icon_url = ManifestIconSelector::FindBestMatchingIcon(
332 manifest().icons, params.ideal_icon_size_in_dp,
333 params.minimum_icon_size_in_dp);
334
335 if (icon_url.is_empty()) {
336 icon.error = NO_ACCEPTABLE_ICON;
337 } else {
338 bool can_download_icon = ManifestIconDownloader::Download(
339 GetWebContents(), icon_url, params.ideal_icon_size_in_dp,
340 params.minimum_icon_size_in_dp,
341 base::Bind(&InstallableManager::OnAppIconFetched,
342 weak_factory_.GetWeakPtr(), icon_url));
343 if (can_download_icon)
344 return;
345 icon.error = CANNOT_DOWNLOAD_ICON;
346 }
347
348 WorkOnTask();
349 }
350
351 void InstallableManager::OnAppIconFetched(const GURL icon_url,
352 const SkBitmap& bitmap) {
353 DCHECK(!tasks_.empty());
354 const InstallableParams& params = tasks_[0].first;
355 IconProperty& icon = GetIcon(params);
356
357 if (!GetWebContents()) {
358 return;
359 } else if (bitmap.drawsNothing()) {
360 icon.error = NO_ICON_AVAILABLE;
361 } else {
362 icon.url = icon_url;
363 icon.icon.reset(new SkBitmap(bitmap));
364 }
365
366 WorkOnTask();
367 }
368
369 void InstallableManager::DidFinishNavigation(
370 content::NavigationHandle* handle) {
371 if (handle->IsInMainFrame() && handle->HasCommitted() &&
372 !handle->IsSamePage()) {
373 Reset();
374 }
375 }
376
377 void InstallableManager::WebContentsDestroyed() {
378 Reset();
379 Observe(nullptr);
380 }
381
382 const GURL& InstallableManager::manifest_url() const {
383 return manifest_->url;
384 }
385
386 const content::Manifest& InstallableManager::manifest() const {
387 return manifest_->manifest;
388 }
389
390 bool InstallableManager::is_installable() const {
391 return installable_->installable;
392 }
393
394 // static
395 int InstallableManager::GetMinimumIconSizeInPx() {
396 return kIconMinimumSizeInPx;
397 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698