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

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 nits 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 struct InstallableManager::ManifestProperty {
58 InstallableErrorCode error = NO_ERROR_DETECTED;
59 GURL url;
60 content::Manifest manifest;
61 bool fetched = false;
62 };
63
64 struct InstallableManager::InstallableProperty {
65 InstallableErrorCode error = NO_ERROR_DETECTED;
66 bool installable = false;
67 bool fetched = false;
68 };
69
70 struct InstallableManager::IconProperty {
71 IconProperty() :
72 error(NO_ERROR_DETECTED), url(), icon(), fetched(false) { }
73 IconProperty(IconProperty&& other) = default;
74 IconProperty& operator=(IconProperty&& other) = default;
75
76 InstallableErrorCode error = NO_ERROR_DETECTED;
77 GURL url;
78 std::unique_ptr<SkBitmap> icon;
79 bool fetched;
80
81 private:
82 // This class contains a std::unique_ptr and therefore must be move-only.
83 DISALLOW_COPY_AND_ASSIGN(IconProperty);
84 };
85
86
87 InstallableManager::InstallableManager(content::WebContents* web_contents)
88 : content::WebContentsObserver(web_contents),
89 manifest_(new ManifestProperty()),
90 installable_(new InstallableProperty()),
91 is_active_(false),
92 weak_factory_(this) { }
93
94 InstallableManager::~InstallableManager() = default;
95
96 bool InstallableManager::IsManifestValidForWebApp(
97 const content::Manifest& manifest) {
98 if (manifest.IsEmpty()) {
99 installable_->error = MANIFEST_EMPTY;
100 return false;
101 }
102
103 if (!manifest.start_url.is_valid()) {
104 installable_->error = START_URL_NOT_VALID;
105 return false;
106 }
107
108 if ((manifest.name.is_null() || manifest.name.string().empty()) &&
109 (manifest.short_name.is_null() || manifest.short_name.string().empty())) {
110 installable_->error = MANIFEST_MISSING_NAME_OR_SHORT_NAME;
111 return false;
112 }
113
114 // TODO(dominickn,mlamouri): when Chrome supports "minimal-ui", it should be
115 // accepted. If we accept it today, it would fallback to "browser" and make
116 // this check moot. See https://crbug.com/604390.
117 if (manifest.display != blink::WebDisplayModeStandalone &&
118 manifest.display != blink::WebDisplayModeFullscreen) {
119 installable_->error = MANIFEST_DISPLAY_NOT_SUPPORTED;
120 return false;
121 }
122
123 if (!DoesManifestContainRequiredIcon(manifest)) {
124 installable_->error = MANIFEST_MISSING_SUITABLE_ICON;
125 return false;
126 }
127
128 return true;
129 }
130
131 void InstallableManager::GetData(const InstallableParams& params,
132 const InstallableCallback& callback) {
133 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
134
135 // Return immediately if we've already working on a task. The new task will be
136 // looked at once the current task is finished.
137 tasks_.push_back({params, callback});
138 if (is_active_)
139 return;
140
141 is_active_ = true;
142 StartNextTask();
143 }
144
145 bool InstallableManager::IsIconFetched(const InstallableParams& params) const {
146 const auto it = icons_.find(
147 {params.ideal_icon_size_in_dp, params.minimum_icon_size_in_dp});
148 return it != icons_.end() && it->second.fetched;
149 }
150
151 void InstallableManager::SetIconFetched(const InstallableParams& params) {
152 GetIcon(params).fetched = true;
153 }
154
155 InstallableManager::IconProperty& InstallableManager::GetIcon(
156 const InstallableParams& params) {
157 return icons_[{params.ideal_icon_size_in_dp, params.minimum_icon_size_in_dp}];
158 }
159
160 InstallableErrorCode InstallableManager::GetErrorCode(
161 const InstallableParams& params) {
162 if (manifest_->error != NO_ERROR_DETECTED)
163 return manifest_->error;
164
165 if (params.check_installable && installable_->error != NO_ERROR_DETECTED)
166 return installable_->error;
167
168 if (params.fetch_valid_icon) {
169 IconProperty& icon = GetIcon(params);
170 if (icon.error != NO_ERROR_DETECTED)
171 return icon.error;
172 }
173
174 return NO_ERROR_DETECTED;
175 }
176
177 InstallableErrorCode InstallableManager::manifest_error() const {
178 return manifest_->error;
179 }
180
181 InstallableErrorCode InstallableManager::installable_error() const {
182 return installable_->error;
183 }
184
185 void InstallableManager::set_installable_error(
186 InstallableErrorCode error_code) {
187 installable_->error = error_code;
188 }
189
190 InstallableErrorCode InstallableManager::icon_error(
191 const InstallableManager::IconParams& icon_params) {
192 return icons_[icon_params].error;
193 }
194
195 GURL& InstallableManager::icon_url(
196 const InstallableManager::IconParams& icon_params) {
197 return icons_[icon_params].url;
198 }
199
200 const SkBitmap* InstallableManager::icon(
201 const InstallableManager::IconParams& icon_params) {
202 return icons_[icon_params].icon.get();
203 }
204
205 content::WebContents* InstallableManager::GetWebContents() {
206 content::WebContents* contents = web_contents();
207 if (!contents || contents->IsBeingDestroyed())
208 return nullptr;
209 return contents;
210 }
211
212 bool InstallableManager::IsComplete(const InstallableParams& params) const {
213 // Returns true if for all resources:
214 // a. the params did not request it, OR
215 // b. the resource has been fetched/checked.
216 return manifest_->fetched &&
217 (!params.check_installable || installable_->fetched) &&
218 (!params.fetch_valid_icon || IsIconFetched(params));
219 }
220
221 void InstallableManager::Reset() {
222 // Prevent any outstanding callbacks to or from this object from being called.
223 weak_factory_.InvalidateWeakPtrs();
224 tasks_.clear();
225 icons_.clear();
226
227 manifest_.reset(new ManifestProperty());
228 installable_.reset(new InstallableProperty());
229
230 is_active_ = false;
231 }
232
233 void InstallableManager::SetManifestDependentTasksComplete() {
234 DCHECK(!tasks_.empty());
235 const InstallableParams& params = tasks_[0].first;
236
237 installable_->fetched = true;
238 SetIconFetched(params);
239 }
240
241 void InstallableManager::StartNextTask() {
242 // If there's nothing to do, exit. Resources remain cached so any future calls
243 // won't re-fetch anything that has already been retrieved.
244 if (tasks_.empty()) {
245 is_active_ = false;
246 return;
247 }
248
249 DCHECK(is_active_);
250 WorkOnTask();
251 }
252
253 void InstallableManager::RunCallback(const Task& task,
254 InstallableErrorCode code) {
255 const InstallableParams& params = task.first;
256 IconProperty& icon = GetIcon(params);
257 InstallableData data = {
258 code,
259 manifest_url(),
260 manifest(),
261 params.fetch_valid_icon ? icon.url : GURL::EmptyGURL(),
262 params.fetch_valid_icon ? icon.icon.get() : nullptr,
263 params.check_installable ? is_installable() : false};
264
265 task.second.Run(data);
266 }
267
268 void InstallableManager::WorkOnTask() {
269 DCHECK(!tasks_.empty());
270 const Task& task = tasks_[0];
271 const InstallableParams& params = task.first;
272
273 InstallableErrorCode code = GetErrorCode(params);
274 if (code != NO_ERROR_DETECTED || IsComplete(params)) {
275 RunCallback(task, code);
276 tasks_.erase(tasks_.begin());
277 StartNextTask();
278 return;
279 }
280
281 if (!manifest_->fetched)
282 FetchManifest();
283 else if (params.check_installable && !installable_->fetched)
284 CheckInstallable();
285 else if (params.fetch_valid_icon && !IsIconFetched(params))
286 CheckAndFetchBestIcon();
287 else
288 NOTREACHED();
289 }
290
291 void InstallableManager::FetchManifest() {
292 DCHECK(!manifest_->fetched);
293
294 content::WebContents* web_contents = GetWebContents();
295 DCHECK(web_contents);
296
297 web_contents->GetManifest(base::Bind(&InstallableManager::OnDidGetManifest,
298 weak_factory_.GetWeakPtr()));
299 }
300
301 void InstallableManager::OnDidGetManifest(const GURL& manifest_url,
302 const content::Manifest& manifest) {
303 if (!GetWebContents()) {
304 return;
305 } else if (manifest_url.is_empty()) {
Lei Zhang 2016/08/03 06:49:51 no need for else after a return.
dominickn 2016/08/03 07:01:43 Done.
dominickn 2016/08/03 07:01:43 Done.
306 manifest_->error = NO_MANIFEST;
307 SetManifestDependentTasksComplete();
308 } else if (manifest.IsEmpty()) {
309 manifest_->error = MANIFEST_EMPTY;
310 SetManifestDependentTasksComplete();
311 }
312
313 manifest_->url = manifest_url;
314 manifest_->manifest = manifest;
315 manifest_->fetched = true;
316 WorkOnTask();
317 }
318
319 void InstallableManager::CheckInstallable() {
320 DCHECK(!installable_->fetched);
321 DCHECK(!manifest().IsEmpty());
322
323 if (IsManifestValidForWebApp(manifest())) {
324 CheckServiceWorker();
325 } else {
326 installable_->installable = false;
327 installable_->fetched = true;
328 WorkOnTask();
329 }
330 }
331
332 void InstallableManager::CheckServiceWorker() {
333 DCHECK(!installable_->fetched);
334 DCHECK(!manifest().IsEmpty());
335 DCHECK(manifest().start_url.is_valid());
336
337 content::WebContents* web_contents = GetWebContents();
338
339 // Check to see if there is a single service worker controlling this page
340 // and the manifest's start url.
341 content::StoragePartition* storage_partition =
342 content::BrowserContext::GetStoragePartition(
343 Profile::FromBrowserContext(web_contents->GetBrowserContext()),
344 web_contents->GetSiteInstance());
345 DCHECK(storage_partition);
346
347 storage_partition->GetServiceWorkerContext()->CheckHasServiceWorker(
348 web_contents->GetLastCommittedURL(), manifest().start_url,
349 base::Bind(&InstallableManager::OnDidCheckHasServiceWorker,
350 weak_factory_.GetWeakPtr()));
351 }
352
353 void InstallableManager::OnDidCheckHasServiceWorker(bool has_service_worker) {
354 if (!GetWebContents())
355 return;
356
357 if (has_service_worker) {
358 installable_->installable = true;
359 } else {
360 installable_->installable = false;
361 installable_->error = NO_MATCHING_SERVICE_WORKER;
362 }
363
364 installable_->fetched = true;
365 WorkOnTask();
366 }
367
368 void InstallableManager::CheckAndFetchBestIcon() {
369 DCHECK(!manifest().IsEmpty());
370 DCHECK(!tasks_.empty());
371
372 const InstallableParams& params = tasks_[0].first;
373 IconProperty& icon = GetIcon(params);
374 icon.fetched = true;
375
376 GURL icon_url = ManifestIconSelector::FindBestMatchingIcon(
377 manifest().icons, params.ideal_icon_size_in_dp,
378 params.minimum_icon_size_in_dp);
379
380 if (icon_url.is_empty()) {
381 icon.error = NO_ACCEPTABLE_ICON;
382 } else {
383 bool can_download_icon = ManifestIconDownloader::Download(
384 GetWebContents(), icon_url, params.ideal_icon_size_in_dp,
385 params.minimum_icon_size_in_dp,
386 base::Bind(&InstallableManager::OnAppIconFetched,
387 weak_factory_.GetWeakPtr(), icon_url));
388 if (can_download_icon)
389 return;
390 icon.error = CANNOT_DOWNLOAD_ICON;
391 }
392
393 WorkOnTask();
394 }
395
396 void InstallableManager::OnAppIconFetched(const GURL icon_url,
397 const SkBitmap& bitmap) {
398 DCHECK(!tasks_.empty());
399 const InstallableParams& params = tasks_[0].first;
400 IconProperty& icon = GetIcon(params);
401
402 if (!GetWebContents()) {
403 return;
404 } else if (bitmap.drawsNothing()) {
Lei Zhang 2016/08/03 06:49:51 Ditto
dominickn 2016/08/03 07:01:43 Done.
405 icon.error = NO_ICON_AVAILABLE;
406 } else {
407 icon.url = icon_url;
408 icon.icon.reset(new SkBitmap(bitmap));
409 }
410
411 WorkOnTask();
412 }
413
414 void InstallableManager::DidFinishNavigation(
415 content::NavigationHandle* handle) {
416 if (handle->IsInMainFrame() && handle->HasCommitted() &&
417 !handle->IsSamePage()) {
418 Reset();
419 }
420 }
421
422 void InstallableManager::WebContentsDestroyed() {
423 Reset();
424 Observe(nullptr);
425 }
426
427 const GURL& InstallableManager::manifest_url() const {
428 return manifest_->url;
429 }
430
431 const content::Manifest& InstallableManager::manifest() const {
432 return manifest_->manifest;
433 }
434
435 bool InstallableManager::is_installable() const {
436 return installable_->installable;
437 }
438
439 // static
440 int InstallableManager::GetMinimumIconSizeInPx() {
441 return kIconMinimumSizeInPx;
442 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698