[sessions]: Componentize TabRestore code
[chromium-blink-merge.git] / chrome / browser / extensions / bookmark_app_helper.cc
blobbb52f29978bf063e54f4a5c1c8ebaed24bb08922
1 // Copyright 2014 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.
5 #include "chrome/browser/extensions/bookmark_app_helper.h"
7 #include <cctype>
9 #include "base/prefs/pref_service.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/browser/bitmap_fetcher/bitmap_fetcher.h"
12 #include "chrome/browser/bitmap_fetcher/bitmap_fetcher_delegate.h"
13 #include "chrome/browser/chrome_notification_types.h"
14 #include "chrome/browser/extensions/crx_installer.h"
15 #include "chrome/browser/extensions/extension_service.h"
16 #include "chrome/browser/extensions/favicon_downloader.h"
17 #include "chrome/browser/extensions/launch_util.h"
18 #include "chrome/browser/extensions/tab_helper.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/browser/ui/app_list/app_list_service.h"
21 #include "chrome/browser/ui/app_list/app_list_util.h"
22 #include "chrome/browser/ui/browser_finder.h"
23 #include "chrome/browser/ui/browser_window.h"
24 #include "chrome/browser/ui/host_desktop.h"
25 #include "chrome/browser/web_applications/web_app.h"
26 #include "chrome/common/extensions/extension_constants.h"
27 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
28 #include "chrome/common/url_constants.h"
29 #include "content/public/browser/navigation_controller.h"
30 #include "content/public/browser/notification_service.h"
31 #include "content/public/browser/notification_source.h"
32 #include "content/public/browser/web_contents.h"
33 #include "extensions/browser/extension_registry.h"
34 #include "extensions/browser/extension_system.h"
35 #include "extensions/browser/image_loader.h"
36 #include "extensions/browser/notification_types.h"
37 #include "extensions/browser/pref_names.h"
38 #include "extensions/common/constants.h"
39 #include "extensions/common/extension.h"
40 #include "extensions/common/manifest_handlers/icons_handler.h"
41 #include "extensions/common/url_pattern.h"
42 #include "grit/platform_locale_settings.h"
43 #include "net/base/load_flags.h"
44 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
45 #include "net/url_request/url_request.h"
46 #include "skia/ext/image_operations.h"
47 #include "skia/ext/platform_canvas.h"
48 #include "third_party/skia/include/core/SkBitmap.h"
49 #include "ui/base/l10n/l10n_util.h"
50 #include "ui/gfx/canvas.h"
51 #include "ui/gfx/color_analysis.h"
52 #include "ui/gfx/color_utils.h"
53 #include "ui/gfx/font.h"
54 #include "ui/gfx/font_list.h"
55 #include "ui/gfx/geometry/rect.h"
56 #include "ui/gfx/image/canvas_image_source.h"
57 #include "ui/gfx/image/image.h"
58 #include "ui/gfx/image/image_family.h"
60 #if defined(OS_MACOSX)
61 #include "base/command_line.h"
62 #include "chrome/browser/web_applications/web_app_mac.h"
63 #include "chrome/common/chrome_switches.h"
64 #endif
66 #if defined(USE_ASH)
67 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
68 #endif
70 namespace {
72 using extensions::BookmarkAppHelper;
74 // Overlays a shortcut icon over the bottom left corner of a given image.
75 class GeneratedIconImageSource : public gfx::CanvasImageSource {
76 public:
77 explicit GeneratedIconImageSource(char letter, SkColor color, int output_size)
78 : gfx::CanvasImageSource(gfx::Size(output_size, output_size), false),
79 letter_(letter),
80 color_(color),
81 output_size_(output_size) {}
82 ~GeneratedIconImageSource() override {}
84 private:
85 // gfx::CanvasImageSource overrides:
86 void Draw(gfx::Canvas* canvas) override {
87 const unsigned char kLuminanceThreshold = 190;
88 const int icon_size = output_size_ * 3 / 4;
89 const int icon_inset = output_size_ / 8;
90 const size_t border_radius = output_size_ / 16;
91 const size_t font_size = output_size_ * 7 / 16;
93 std::string font_name =
94 l10n_util::GetStringUTF8(IDS_SANS_SERIF_FONT_FAMILY);
95 #if defined(OS_CHROMEOS)
96 const std::string kChromeOSFontFamily = "Noto Sans";
97 font_name = kChromeOSFontFamily;
98 #endif
100 // Draw a rounded rect of the given |color|.
101 SkPaint background_paint;
102 background_paint.setFlags(SkPaint::kAntiAlias_Flag);
103 background_paint.setColor(color_);
105 gfx::Rect icon_rect(icon_inset, icon_inset, icon_size, icon_size);
106 canvas->DrawRoundRect(icon_rect, border_radius, background_paint);
108 // The text rect's size needs to be odd to center the text correctly.
109 gfx::Rect text_rect(icon_inset, icon_inset, icon_size + 1, icon_size + 1);
110 // Draw the letter onto the rounded rect. The letter's color depends on the
111 // luminance of |color|.
112 unsigned char luminance = color_utils::GetLuminanceForColor(color_);
113 canvas->DrawStringRectWithFlags(
114 base::string16(1, std::toupper(letter_)),
115 gfx::FontList(gfx::Font(font_name, font_size)),
116 luminance > kLuminanceThreshold ? SK_ColorBLACK : SK_ColorWHITE,
117 text_rect,
118 gfx::Canvas::TEXT_ALIGN_CENTER);
121 char letter_;
123 SkColor color_;
125 int output_size_;
127 DISALLOW_COPY_AND_ASSIGN(GeneratedIconImageSource);
130 void OnIconsLoaded(
131 WebApplicationInfo web_app_info,
132 const base::Callback<void(const WebApplicationInfo&)> callback,
133 const gfx::ImageFamily& image_family) {
134 for (gfx::ImageFamily::const_iterator it = image_family.begin();
135 it != image_family.end();
136 ++it) {
137 WebApplicationInfo::IconInfo icon_info;
138 icon_info.data = *it->ToSkBitmap();
139 icon_info.width = icon_info.data.width();
140 icon_info.height = icon_info.data.height();
141 web_app_info.icons.push_back(icon_info);
143 callback.Run(web_app_info);
146 std::set<int> SizesToGenerate() {
147 // Generate container icons from smaller icons.
148 const int kIconSizesToGenerate[] = {
149 extension_misc::EXTENSION_ICON_SMALL,
150 extension_misc::EXTENSION_ICON_MEDIUM,
151 extension_misc::EXTENSION_ICON_LARGE,
153 return std::set<int>(kIconSizesToGenerate,
154 kIconSizesToGenerate + arraysize(kIconSizesToGenerate));
157 void GenerateIcons(
158 std::set<int> generate_sizes,
159 const GURL& app_url,
160 SkColor generated_icon_color,
161 std::map<int, BookmarkAppHelper::BitmapAndSource>* bitmap_map) {
162 // The letter that will be painted on the generated icon.
163 char icon_letter = ' ';
164 std::string domain_and_registry(
165 net::registry_controlled_domains::GetDomainAndRegistry(
166 app_url,
167 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES));
168 if (!domain_and_registry.empty()) {
169 icon_letter = domain_and_registry[0];
170 } else if (!app_url.host().empty()) {
171 icon_letter = app_url.host()[0];
174 // If no color has been specified, use a dark gray so it will stand out on the
175 // black shelf.
176 if (generated_icon_color == SK_ColorTRANSPARENT)
177 generated_icon_color = SK_ColorDKGRAY;
179 for (std::set<int>::const_iterator it = generate_sizes.begin();
180 it != generate_sizes.end(); ++it) {
181 extensions::BookmarkAppHelper::GenerateIcon(
182 bitmap_map, *it, generated_icon_color, icon_letter);
183 // Also generate the 2x resource for this size.
184 extensions::BookmarkAppHelper::GenerateIcon(
185 bitmap_map, *it * 2, generated_icon_color, icon_letter);
189 void ReplaceWebAppIcons(
190 std::map<int, BookmarkAppHelper::BitmapAndSource> bitmap_map,
191 WebApplicationInfo* web_app_info) {
192 web_app_info->icons.clear();
194 // Populate the icon data into the WebApplicationInfo we are using to
195 // install the bookmark app.
196 for (const auto& pair : bitmap_map) {
197 WebApplicationInfo::IconInfo icon_info;
198 icon_info.data = pair.second.bitmap;
199 icon_info.url = pair.second.source_url;
200 icon_info.width = icon_info.data.width();
201 icon_info.height = icon_info.data.height();
202 web_app_info->icons.push_back(icon_info);
206 // Class to handle installing a bookmark app after it has synced. Handles
207 // downloading and decoding the icons.
208 class BookmarkAppInstaller : public base::RefCounted<BookmarkAppInstaller>,
209 public content::WebContentsObserver {
210 public:
211 BookmarkAppInstaller(ExtensionService* service,
212 const WebApplicationInfo& web_app_info)
213 : service_(service),
214 web_app_info_(web_app_info) {}
216 void Run() {
217 for (const auto& icon : web_app_info_.icons) {
218 if (icon.url.is_valid())
219 urls_to_download_.push_back(icon.url);
222 if (urls_to_download_.size()) {
223 // Matched in OnIconsDownloaded.
224 AddRef();
225 SetupWebContents();
227 return;
230 FinishInstallation();
233 void SetupWebContents() {
234 // Spin up a web contents process so we can use FaviconDownloader.
235 // This is necessary to make sure we pick up all of the images provided
236 // in favicon URLs. Without this, bookmark app sync can fail due to
237 // missing icons which are not correctly extracted from a favicon.
238 // (The eventual error indicates that there are missing files, which
239 // are the not-extracted favicon images).
241 // TODO(dominickn): refactor bookmark app syncing to reuse one web
242 // contents for all pending synced bookmark apps. This will avoid
243 // pathological cases where n renderers for n bookmark apps are spun up on
244 // first sign-in to a new machine.
245 web_contents_.reset(content::WebContents::Create(
246 content::WebContents::CreateParams(service_->profile())));
247 Observe(web_contents_.get());
249 // Load about:blank so that the process actually starts.
250 // Image download continues in DidFinishLoad.
251 content::NavigationController::LoadURLParams load_params(
252 GURL("about:blank"));
253 load_params.transition_type = ui::PAGE_TRANSITION_GENERATED;
254 web_contents_->GetController().LoadURLWithParams(load_params);
257 void DidFinishLoad(content::RenderFrameHost* render_frame_host,
258 const GURL& validated_url) override {
259 favicon_downloader_.reset(new FaviconDownloader(
260 web_contents_.get(), urls_to_download_,
261 base::Bind(&BookmarkAppInstaller::OnIconsDownloaded,
262 base::Unretained(this))));
264 // Skip downloading the page favicons as everything in is the URL list.
265 favicon_downloader_->SkipPageFavicons();
266 favicon_downloader_->Start();
269 private:
270 friend class base::RefCounted<BookmarkAppInstaller>;
271 ~BookmarkAppInstaller() override {}
273 void OnIconsDownloaded(bool success,
274 const std::map<GURL, std::vector<SkBitmap>>& bitmaps) {
275 // Ignore the unsuccessful case, as the necessary icons will be generated.
276 if (success) {
277 for (const auto& url_bitmaps : bitmaps) {
278 for (const auto& bitmap : url_bitmaps.second) {
279 // Only accept square icons.
280 if (bitmap.empty() || bitmap.width() != bitmap.height())
281 continue;
283 downloaded_bitmaps_.push_back(
284 BookmarkAppHelper::BitmapAndSource(url_bitmaps.first, bitmap));
288 FinishInstallation();
289 Release();
292 void FinishInstallation() {
293 // Ensure that all icons that are in web_app_info are present, by generating
294 // icons for any sizes which have failed to download. This ensures that the
295 // created manifest for the bookmark app does not contain links to icons
296 // which are not actually created and linked on disk.
298 // Ensure that all icon widths in the web app info icon array are present in
299 // the sizes to generate set. This ensures that we will have all of the
300 // icon sizes from when the app was originally added, even if icon URLs are
301 // no longer accessible.
302 std::set<int> sizes_to_generate = SizesToGenerate();
303 for (const auto& icon : web_app_info_.icons)
304 sizes_to_generate.insert(icon.width);
306 std::map<int, BookmarkAppHelper::BitmapAndSource> size_map =
307 BookmarkAppHelper::ResizeIconsAndGenerateMissing(
308 downloaded_bitmaps_, sizes_to_generate, &web_app_info_);
309 BookmarkAppHelper::UpdateWebAppIconsWithoutChangingLinks(size_map,
310 &web_app_info_);
311 scoped_refptr<extensions::CrxInstaller> installer(
312 extensions::CrxInstaller::CreateSilent(service_));
313 installer->set_error_on_unsupported_requirements(true);
314 installer->InstallWebApp(web_app_info_);
317 ExtensionService* service_;
318 WebApplicationInfo web_app_info_;
320 scoped_ptr<content::WebContents> web_contents_;
321 scoped_ptr<FaviconDownloader> favicon_downloader_;
322 std::vector<GURL> urls_to_download_;
323 std::vector<BookmarkAppHelper::BitmapAndSource> downloaded_bitmaps_;
326 } // namespace
328 namespace extensions {
330 // static
331 void BookmarkAppHelper::UpdateWebAppInfoFromManifest(
332 const content::Manifest& manifest,
333 WebApplicationInfo* web_app_info) {
334 if (!manifest.short_name.is_null())
335 web_app_info->title = manifest.short_name.string();
337 // Give the full length name priority.
338 if (!manifest.name.is_null())
339 web_app_info->title = manifest.name.string();
341 // Set the url based on the manifest value, if any.
342 if (manifest.start_url.is_valid())
343 web_app_info->app_url = manifest.start_url;
345 // If any icons are specified in the manifest, they take precedence over any
346 // we picked up from the web_app stuff.
347 if (!manifest.icons.empty()) {
348 web_app_info->icons.clear();
349 for (const auto& icon : manifest.icons) {
350 // TODO(benwells): Take the declared icon density and sizes into account.
351 WebApplicationInfo::IconInfo info;
352 info.url = icon.src;
353 web_app_info->icons.push_back(info);
358 // static
359 std::map<int, BookmarkAppHelper::BitmapAndSource>
360 BookmarkAppHelper::ConstrainBitmapsToSizes(
361 const std::vector<BookmarkAppHelper::BitmapAndSource>& bitmaps,
362 const std::set<int>& sizes) {
363 std::map<int, BitmapAndSource> output_bitmaps;
364 std::map<int, BitmapAndSource> ordered_bitmaps;
365 for (std::vector<BitmapAndSource>::const_iterator it = bitmaps.begin();
366 it != bitmaps.end(); ++it) {
367 DCHECK(it->bitmap.width() == it->bitmap.height());
368 ordered_bitmaps[it->bitmap.width()] = *it;
371 for (const auto& size : sizes) {
372 // Find the closest not-smaller bitmap.
373 auto bitmaps_it = ordered_bitmaps.lower_bound(size);
374 if (bitmaps_it != ordered_bitmaps.end()) {
375 output_bitmaps[size] = bitmaps_it->second;
376 // Resize the bitmap if it does not exactly match the desired size.
377 if (output_bitmaps[size].bitmap.width() != size) {
378 output_bitmaps[size].bitmap = skia::ImageOperations::Resize(
379 output_bitmaps[size].bitmap, skia::ImageOperations::RESIZE_LANCZOS3,
380 size, size);
385 return output_bitmaps;
388 // static
389 void BookmarkAppHelper::GenerateIcon(
390 std::map<int, BookmarkAppHelper::BitmapAndSource>* bitmaps,
391 int output_size,
392 SkColor color,
393 char letter) {
394 // Do nothing if there is already an icon of |output_size|.
395 if (bitmaps->count(output_size))
396 return;
398 gfx::ImageSkia icon_image(
399 new GeneratedIconImageSource(letter, color, output_size),
400 gfx::Size(output_size, output_size));
401 icon_image.bitmap()->deepCopyTo(&(*bitmaps)[output_size].bitmap);
404 // static
405 bool BookmarkAppHelper::BookmarkOrHostedAppInstalled(
406 content::BrowserContext* browser_context,
407 const GURL& url) {
408 ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context);
409 const ExtensionSet& extensions = registry->enabled_extensions();
411 // Iterate through the extensions and extract the LaunchWebUrl (bookmark apps)
412 // or check the web extent (hosted apps).
413 for (extensions::ExtensionSet::const_iterator iter = extensions.begin();
414 iter != extensions.end(); ++iter) {
415 const Extension* extension = iter->get();
416 if (!extension->is_hosted_app())
417 continue;
419 if (extension->web_extent().MatchesURL(url) ||
420 AppLaunchInfo::GetLaunchWebURL(extension) == url) {
421 return true;
424 return false;
427 // static
428 std::map<int, BookmarkAppHelper::BitmapAndSource>
429 BookmarkAppHelper::ResizeIconsAndGenerateMissing(
430 std::vector<BookmarkAppHelper::BitmapAndSource> icons,
431 std::set<int> sizes_to_generate,
432 WebApplicationInfo* web_app_info) {
433 // Add the downloaded icons. Extensions only allow certain icon sizes. First
434 // populate icons that match the allowed sizes exactly and then downscale
435 // remaining icons to the closest allowed size that doesn't yet have an icon.
436 std::set<int> allowed_sizes(extension_misc::kExtensionIconSizes,
437 extension_misc::kExtensionIconSizes +
438 extension_misc::kNumExtensionIconSizes);
440 // If there are icons that don't match the accepted icon sizes, find the
441 // closest bigger icon to the accepted sizes and resize the icon to it.
442 std::map<int, BitmapAndSource> resized_bitmaps(
443 ConstrainBitmapsToSizes(icons, allowed_sizes));
445 // Determine the color that will be used for the icon's background. For this
446 // the dominant color of the first icon found is used.
447 if (resized_bitmaps.size()) {
448 color_utils::GridSampler sampler;
449 web_app_info->generated_icon_color =
450 color_utils::CalculateKMeanColorOfBitmap(
451 resized_bitmaps.begin()->second.bitmap);
454 // Work out what icons we need to generate here. Icons are only generated if
455 // there is no icon in the required size.
456 std::set<int> generate_sizes;
457 for (int size : sizes_to_generate) {
458 if (resized_bitmaps.find(size) == resized_bitmaps.end())
459 generate_sizes.insert(size);
461 GenerateIcons(generate_sizes, web_app_info->app_url,
462 web_app_info->generated_icon_color, &resized_bitmaps);
464 return resized_bitmaps;
467 // static
468 void BookmarkAppHelper::UpdateWebAppIconsWithoutChangingLinks(
469 std::map<int, BookmarkAppHelper::BitmapAndSource> bitmap_map,
470 WebApplicationInfo* web_app_info) {
471 // First add in the icon data that have urls with the url / size data from the
472 // original web app info, and the data from the new icons (if any).
473 for (auto& icon : web_app_info->icons) {
474 if (!icon.url.is_empty() && icon.data.empty()) {
475 const auto& it = bitmap_map.find(icon.width);
476 if (it != bitmap_map.end() && it->second.source_url == icon.url)
477 icon.data = it->second.bitmap;
481 // Now add in any icons from the updated list that don't have URLs.
482 for (const auto& pair : bitmap_map) {
483 if (pair.second.source_url.is_empty()) {
484 WebApplicationInfo::IconInfo icon_info;
485 icon_info.data = pair.second.bitmap;
486 icon_info.width = pair.first;
487 icon_info.height = pair.first;
488 web_app_info->icons.push_back(icon_info);
493 BookmarkAppHelper::BitmapAndSource::BitmapAndSource() {
496 BookmarkAppHelper::BitmapAndSource::BitmapAndSource(const GURL& source_url_p,
497 const SkBitmap& bitmap_p)
498 : source_url(source_url_p),
499 bitmap(bitmap_p) {
502 BookmarkAppHelper::BitmapAndSource::~BitmapAndSource() {
505 BookmarkAppHelper::BookmarkAppHelper(Profile* profile,
506 WebApplicationInfo web_app_info,
507 content::WebContents* contents)
508 : profile_(profile),
509 contents_(contents),
510 web_app_info_(web_app_info),
511 crx_installer_(extensions::CrxInstaller::CreateSilent(
512 ExtensionSystem::Get(profile)->extension_service())) {
513 web_app_info_.open_as_window =
514 profile_->GetPrefs()->GetInteger(
515 extensions::pref_names::kBookmarkAppCreationLaunchType) ==
516 extensions::LAUNCH_TYPE_WINDOW;
518 // The default app title is the page title, which can be quite long. Limit the
519 // default name used to something sensible.
520 const int kMaxDefaultTitle = 40;
521 if (web_app_info_.title.length() > kMaxDefaultTitle) {
522 web_app_info_.title = web_app_info_.title.substr(0, kMaxDefaultTitle - 3) +
523 base::UTF8ToUTF16("...");
526 registrar_.Add(this,
527 extensions::NOTIFICATION_CRX_INSTALLER_DONE,
528 content::Source<CrxInstaller>(crx_installer_.get()));
530 registrar_.Add(this,
531 extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR,
532 content::Source<CrxInstaller>(crx_installer_.get()));
534 crx_installer_->set_error_on_unsupported_requirements(true);
537 BookmarkAppHelper::~BookmarkAppHelper() {}
539 void BookmarkAppHelper::Create(const CreateBookmarkAppCallback& callback) {
540 callback_ = callback;
542 // Do not fetch the manifest for extension URLs.
543 if (contents_ &&
544 !contents_->GetVisibleURL().SchemeIs(extensions::kExtensionScheme)) {
545 contents_->GetManifest(base::Bind(&BookmarkAppHelper::OnDidGetManifest,
546 base::Unretained(this)));
547 } else {
548 OnIconsDownloaded(true, std::map<GURL, std::vector<SkBitmap>>());
552 void BookmarkAppHelper::CreateFromAppBanner(
553 const CreateBookmarkAppCallback& callback,
554 const content::Manifest& manifest) {
555 DCHECK(!manifest.short_name.is_null() || !manifest.name.is_null());
556 DCHECK(manifest.start_url.is_valid());
558 callback_ = callback;
559 OnDidGetManifest(manifest);
562 void BookmarkAppHelper::OnDidGetManifest(const content::Manifest& manifest) {
563 if (contents_->IsBeingDestroyed())
564 return;
566 UpdateWebAppInfoFromManifest(manifest, &web_app_info_);
568 // Add urls from the WebApplicationInfo.
569 std::vector<GURL> web_app_info_icon_urls;
570 for (std::vector<WebApplicationInfo::IconInfo>::const_iterator it =
571 web_app_info_.icons.begin();
572 it != web_app_info_.icons.end();
573 ++it) {
574 if (it->url.is_valid())
575 web_app_info_icon_urls.push_back(it->url);
578 favicon_downloader_.reset(
579 new FaviconDownloader(contents_,
580 web_app_info_icon_urls,
581 base::Bind(&BookmarkAppHelper::OnIconsDownloaded,
582 base::Unretained(this))));
583 favicon_downloader_->Start();
586 void BookmarkAppHelper::OnIconsDownloaded(
587 bool success,
588 const std::map<GURL, std::vector<SkBitmap>>& bitmaps) {
589 // The tab has navigated away during the icon download. Cancel the bookmark
590 // app creation.
591 if (!success) {
592 favicon_downloader_.reset();
593 callback_.Run(nullptr, web_app_info_);
594 return;
597 std::vector<BitmapAndSource> downloaded_icons;
598 for (FaviconDownloader::FaviconMap::const_iterator map_it = bitmaps.begin();
599 map_it != bitmaps.end();
600 ++map_it) {
601 for (std::vector<SkBitmap>::const_iterator bitmap_it =
602 map_it->second.begin();
603 bitmap_it != map_it->second.end();
604 ++bitmap_it) {
605 if (bitmap_it->empty() || bitmap_it->width() != bitmap_it->height())
606 continue;
608 downloaded_icons.push_back(BitmapAndSource(map_it->first, *bitmap_it));
612 // Add all existing icons from WebApplicationInfo.
613 for (std::vector<WebApplicationInfo::IconInfo>::const_iterator it =
614 web_app_info_.icons.begin();
615 it != web_app_info_.icons.end();
616 ++it) {
617 const SkBitmap& icon = it->data;
618 if (!icon.drawsNothing() && icon.width() == icon.height()) {
619 downloaded_icons.push_back(BitmapAndSource(it->url, icon));
623 // Ensure that the necessary-sized icons are available by resizing larger
624 // icons down to smaller sizes, and generating icons for sizes where resizing
625 // is not possible.
626 web_app_info_.generated_icon_color = SK_ColorTRANSPARENT;
627 std::map<int, BitmapAndSource> size_to_icons = ResizeIconsAndGenerateMissing(
628 downloaded_icons, SizesToGenerate(), &web_app_info_);
629 ReplaceWebAppIcons(size_to_icons, &web_app_info_);
630 favicon_downloader_.reset();
632 if (!contents_) {
633 // The web contents can be null in tests.
634 OnBubbleCompleted(true, web_app_info_);
635 return;
638 Browser* browser = chrome::FindBrowserWithWebContents(contents_);
639 if (!browser) {
640 // The browser can be null in tests.
641 OnBubbleCompleted(true, web_app_info_);
642 return;
644 browser->window()->ShowBookmarkAppBubble(
645 web_app_info_, base::Bind(&BookmarkAppHelper::OnBubbleCompleted,
646 base::Unretained(this)));
649 void BookmarkAppHelper::OnBubbleCompleted(
650 bool user_accepted,
651 const WebApplicationInfo& web_app_info) {
652 if (user_accepted) {
653 web_app_info_ = web_app_info;
654 crx_installer_->InstallWebApp(web_app_info_);
655 } else {
656 callback_.Run(nullptr, web_app_info_);
660 void BookmarkAppHelper::FinishInstallation(const Extension* extension) {
661 // Set the default 'open as' preference for use next time the dialog is
662 // shown.
663 extensions::LaunchType launch_type = web_app_info_.open_as_window
664 ? extensions::LAUNCH_TYPE_WINDOW
665 : extensions::LAUNCH_TYPE_REGULAR;
666 profile_->GetPrefs()->SetInteger(
667 extensions::pref_names::kBookmarkAppCreationLaunchType, launch_type);
669 // Set the launcher type for the app.
670 extensions::SetLaunchType(profile_, extension->id(), launch_type);
672 if (!contents_) {
673 // The web contents can be null in tests.
674 callback_.Run(extension, web_app_info_);
675 return;
678 Browser* browser = chrome::FindBrowserWithWebContents(contents_);
679 if (!browser) {
680 // The browser can be null in tests.
681 callback_.Run(extension, web_app_info_);
682 return;
685 // Pin the app to the relevant launcher depending on the OS.
686 Profile* current_profile = profile_->GetOriginalProfile();
688 // On Mac, shortcuts are automatically created for hosted apps when they are
689 // installed, so there is no need to create them again.
690 #if !defined(OS_MACOSX)
691 chrome::HostDesktopType desktop = browser->host_desktop_type();
692 if (desktop != chrome::HOST_DESKTOP_TYPE_ASH) {
693 web_app::ShortcutLocations creation_locations;
694 #if defined(OS_LINUX)
695 creation_locations.on_desktop = true;
696 #else
697 creation_locations.on_desktop = false;
698 #endif
699 creation_locations.applications_menu_location =
700 web_app::APP_MENU_LOCATION_SUBDIR_CHROMEAPPS;
701 web_app::CreateShortcuts(web_app::SHORTCUT_CREATION_BY_USER,
702 creation_locations, current_profile, extension);
703 // Creating shortcuts in the start menu fails when the language is set
704 // to certain languages (e.g. Hindi). To work around this, the taskbar /
705 // quick launch icon is created separately to ensure it doesn't fail
706 // due to the start menu shortcut creation failing.
707 // See http://crbug.com/477297 and http://crbug.com/484577.
708 creation_locations.on_desktop = false;
709 creation_locations.applications_menu_location =
710 web_app::APP_MENU_LOCATION_NONE;
711 creation_locations.in_quick_launch_bar = true;
712 web_app::CreateShortcuts(web_app::SHORTCUT_CREATION_BY_USER,
713 creation_locations, current_profile, extension);
714 #if defined(USE_ASH)
715 } else {
716 ChromeLauncherController::instance()->PinAppWithID(extension->id());
717 #endif
719 #endif
721 #if defined(OS_MACOSX)
722 if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
723 switches::kDisableHostedAppShimCreation)) {
724 web_app::RevealAppShimInFinderForApp(current_profile, extension);
726 #endif
728 callback_.Run(extension, web_app_info_);
731 void BookmarkAppHelper::Observe(int type,
732 const content::NotificationSource& source,
733 const content::NotificationDetails& details) {
734 switch (type) {
735 case extensions::NOTIFICATION_CRX_INSTALLER_DONE: {
736 const Extension* extension =
737 content::Details<const Extension>(details).ptr();
738 DCHECK(extension);
739 DCHECK_EQ(AppLaunchInfo::GetLaunchWebURL(extension),
740 web_app_info_.app_url);
741 FinishInstallation(extension);
742 break;
744 case extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR:
745 callback_.Run(nullptr, web_app_info_);
746 break;
747 default:
748 NOTREACHED();
749 break;
753 void CreateOrUpdateBookmarkApp(ExtensionService* service,
754 WebApplicationInfo* web_app_info) {
755 scoped_refptr<BookmarkAppInstaller> installer(
756 new BookmarkAppInstaller(service, *web_app_info));
757 installer->Run();
760 void GetWebApplicationInfoFromApp(
761 content::BrowserContext* browser_context,
762 const extensions::Extension* extension,
763 const base::Callback<void(const WebApplicationInfo&)> callback) {
764 if (!extension->from_bookmark()) {
765 callback.Run(WebApplicationInfo());
766 return;
769 WebApplicationInfo web_app_info;
770 web_app_info.app_url = AppLaunchInfo::GetLaunchWebURL(extension);
771 web_app_info.title = base::UTF8ToUTF16(extension->non_localized_name());
772 web_app_info.description = base::UTF8ToUTF16(extension->description());
774 std::vector<extensions::ImageLoader::ImageRepresentation> info_list;
775 for (size_t i = 0; i < extension_misc::kNumExtensionIconSizes; ++i) {
776 int size = extension_misc::kExtensionIconSizes[i];
777 extensions::ExtensionResource resource =
778 extensions::IconsInfo::GetIconResource(
779 extension, size, ExtensionIconSet::MATCH_EXACTLY);
780 if (!resource.empty()) {
781 info_list.push_back(extensions::ImageLoader::ImageRepresentation(
782 resource,
783 extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE,
784 gfx::Size(size, size),
785 ui::SCALE_FACTOR_100P));
789 extensions::ImageLoader::Get(browser_context)->LoadImageFamilyAsync(
790 extension, info_list, base::Bind(&OnIconsLoaded, web_app_info, callback));
793 bool IsValidBookmarkAppUrl(const GURL& url) {
794 URLPattern origin_only_pattern(Extension::kValidBookmarkAppSchemes);
795 origin_only_pattern.SetMatchAllURLs(true);
796 return url.is_valid() && origin_only_pattern.MatchesURL(url);
799 } // namespace extensions