Add ENABLE_MEDIA_ROUTER define to builds other than Android and iOS.
[chromium-blink-merge.git] / chrome / browser / extensions / bookmark_app_helper.cc
blobb7e8db5e2a11c94dd8c5878f72365b2e2980b922
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/chrome_notification_types.h"
12 #include "chrome/browser/extensions/crx_installer.h"
13 #include "chrome/browser/extensions/extension_service.h"
14 #include "chrome/browser/extensions/favicon_downloader.h"
15 #include "chrome/browser/extensions/launch_util.h"
16 #include "chrome/browser/extensions/tab_helper.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/ui/app_list/app_list_service.h"
19 #include "chrome/browser/ui/app_list/app_list_util.h"
20 #include "chrome/browser/ui/browser_finder.h"
21 #include "chrome/browser/ui/browser_window.h"
22 #include "chrome/browser/ui/host_desktop.h"
23 #include "chrome/browser/web_applications/web_app.h"
24 #include "chrome/common/extensions/extension_constants.h"
25 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
26 #include "chrome/common/url_constants.h"
27 #include "content/public/browser/notification_service.h"
28 #include "content/public/browser/notification_source.h"
29 #include "content/public/browser/web_contents.h"
30 #include "extensions/browser/extension_system.h"
31 #include "extensions/browser/image_loader.h"
32 #include "extensions/browser/notification_types.h"
33 #include "extensions/browser/pref_names.h"
34 #include "extensions/common/constants.h"
35 #include "extensions/common/extension.h"
36 #include "extensions/common/manifest_handlers/icons_handler.h"
37 #include "extensions/common/url_pattern.h"
38 #include "grit/platform_locale_settings.h"
39 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
40 #include "skia/ext/image_operations.h"
41 #include "skia/ext/platform_canvas.h"
42 #include "third_party/skia/include/core/SkBitmap.h"
43 #include "ui/base/l10n/l10n_util.h"
44 #include "ui/gfx/canvas.h"
45 #include "ui/gfx/color_analysis.h"
46 #include "ui/gfx/color_utils.h"
47 #include "ui/gfx/font.h"
48 #include "ui/gfx/font_list.h"
49 #include "ui/gfx/geometry/rect.h"
50 #include "ui/gfx/image/canvas_image_source.h"
51 #include "ui/gfx/image/image.h"
52 #include "ui/gfx/image/image_family.h"
54 #if defined(OS_MACOSX)
55 #include "base/command_line.h"
56 #include "chrome/browser/web_applications/web_app_mac.h"
57 #include "chrome/common/chrome_switches.h"
58 #endif
60 #if defined(USE_ASH)
61 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
62 #endif
64 namespace {
66 // Overlays a shortcut icon over the bottom left corner of a given image.
67 class GeneratedIconImageSource : public gfx::CanvasImageSource {
68 public:
69 explicit GeneratedIconImageSource(char letter, SkColor color, int output_size)
70 : gfx::CanvasImageSource(gfx::Size(output_size, output_size), false),
71 letter_(letter),
72 color_(color),
73 output_size_(output_size) {}
74 ~GeneratedIconImageSource() override {}
76 private:
77 // gfx::CanvasImageSource overrides:
78 void Draw(gfx::Canvas* canvas) override {
79 const unsigned char kLuminanceThreshold = 190;
80 const int icon_size = output_size_ * 3 / 4;
81 const int icon_inset = output_size_ / 8;
82 const size_t border_radius = output_size_ / 16;
83 const size_t font_size = output_size_ * 7 / 16;
85 std::string font_name =
86 l10n_util::GetStringUTF8(IDS_SANS_SERIF_FONT_FAMILY);
87 #if defined(OS_CHROMEOS)
88 const std::string kChromeOSFontFamily = "Noto Sans";
89 font_name = kChromeOSFontFamily;
90 #endif
92 // Draw a rounded rect of the given |color|.
93 SkPaint background_paint;
94 background_paint.setFlags(SkPaint::kAntiAlias_Flag);
95 background_paint.setColor(color_);
97 gfx::Rect icon_rect(icon_inset, icon_inset, icon_size, icon_size);
98 canvas->DrawRoundRect(icon_rect, border_radius, background_paint);
100 // The text rect's size needs to be odd to center the text correctly.
101 gfx::Rect text_rect(icon_inset, icon_inset, icon_size + 1, icon_size + 1);
102 // Draw the letter onto the rounded rect. The letter's color depends on the
103 // luminance of |color|.
104 unsigned char luminance = color_utils::GetLuminanceForColor(color_);
105 canvas->DrawStringRectWithFlags(
106 base::string16(1, std::toupper(letter_)),
107 gfx::FontList(gfx::Font(font_name, font_size)),
108 luminance > kLuminanceThreshold ? SK_ColorBLACK : SK_ColorWHITE,
109 text_rect,
110 gfx::Canvas::TEXT_ALIGN_CENTER);
113 char letter_;
115 SkColor color_;
117 int output_size_;
119 DISALLOW_COPY_AND_ASSIGN(GeneratedIconImageSource);
122 void OnIconsLoaded(
123 WebApplicationInfo web_app_info,
124 const base::Callback<void(const WebApplicationInfo&)> callback,
125 const gfx::ImageFamily& image_family) {
126 for (gfx::ImageFamily::const_iterator it = image_family.begin();
127 it != image_family.end();
128 ++it) {
129 WebApplicationInfo::IconInfo icon_info;
130 icon_info.data = *it->ToSkBitmap();
131 icon_info.width = icon_info.data.width();
132 icon_info.height = icon_info.data.height();
133 web_app_info.icons.push_back(icon_info);
135 callback.Run(web_app_info);
138 std::set<int> SizesToGenerate() {
139 // Generate container icons from smaller icons.
140 const int kIconSizesToGenerate[] = {
141 extension_misc::EXTENSION_ICON_SMALL,
142 extension_misc::EXTENSION_ICON_MEDIUM,
143 extension_misc::EXTENSION_ICON_LARGE,
145 return std::set<int>(kIconSizesToGenerate,
146 kIconSizesToGenerate + arraysize(kIconSizesToGenerate));
149 void GenerateIcons(std::set<int> generate_sizes,
150 const GURL& app_url,
151 SkColor generated_icon_color,
152 std::map<int, SkBitmap>* bitmap_map) {
153 // The letter that will be painted on the generated icon.
154 char icon_letter = ' ';
155 std::string domain_and_registry(
156 net::registry_controlled_domains::GetDomainAndRegistry(
157 app_url,
158 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES));
159 if (!domain_and_registry.empty()) {
160 icon_letter = domain_and_registry[0];
161 } else if (!app_url.host().empty()) {
162 icon_letter = app_url.host()[0];
165 // If no color has been specified, use a dark gray so it will stand out on the
166 // black shelf.
167 if (generated_icon_color == SK_ColorTRANSPARENT)
168 generated_icon_color = SK_ColorDKGRAY;
170 for (std::set<int>::const_iterator it = generate_sizes.begin();
171 it != generate_sizes.end(); ++it) {
172 extensions::BookmarkAppHelper::GenerateIcon(
173 bitmap_map, *it, generated_icon_color, icon_letter);
174 // Also generate the 2x resource for this size.
175 extensions::BookmarkAppHelper::GenerateIcon(
176 bitmap_map, *it * 2, generated_icon_color, icon_letter);
180 void ReplaceWebAppIcons(std::map<int, SkBitmap> bitmap_map,
181 WebApplicationInfo* web_app_info) {
182 web_app_info->icons.clear();
184 // Populate the icon data into the WebApplicationInfo we are using to
185 // install the bookmark app.
186 for (std::map<int, SkBitmap>::const_iterator bitmap_map_it =
187 bitmap_map.begin();
188 bitmap_map_it != bitmap_map.end(); ++bitmap_map_it) {
189 WebApplicationInfo::IconInfo icon_info;
190 icon_info.data = bitmap_map_it->second;
191 icon_info.width = icon_info.data.width();
192 icon_info.height = icon_info.data.height();
193 web_app_info->icons.push_back(icon_info);
197 } // namespace
199 namespace extensions {
201 // static
202 void BookmarkAppHelper::UpdateWebAppInfoFromManifest(
203 const content::Manifest& manifest,
204 WebApplicationInfo* web_app_info) {
205 if (!manifest.short_name.is_null())
206 web_app_info->title = manifest.short_name.string();
208 // Give the full length name priority.
209 if (!manifest.name.is_null())
210 web_app_info->title = manifest.name.string();
212 // Set the url based on the manifest value, if any.
213 if (manifest.start_url.is_valid())
214 web_app_info->app_url = manifest.start_url;
216 // If any icons are specified in the manifest, they take precedence over any
217 // we picked up from the web_app stuff.
218 if (!manifest.icons.empty()) {
219 web_app_info->icons.clear();
220 for (const auto& icon : manifest.icons) {
221 // TODO(benwells): Take the declared icon density and sizes into account.
222 WebApplicationInfo::IconInfo info;
223 info.url = icon.src;
224 web_app_info->icons.push_back(info);
229 // static
230 void BookmarkAppHelper::GenerateIcon(std::map<int, SkBitmap>* bitmaps,
231 int output_size,
232 SkColor color,
233 char letter) {
234 // Do nothing if there is already an icon of |output_size|.
235 if (bitmaps->count(output_size))
236 return;
238 gfx::ImageSkia icon_image(
239 new GeneratedIconImageSource(letter, color, output_size),
240 gfx::Size(output_size, output_size));
241 icon_image.bitmap()->deepCopyTo(&(*bitmaps)[output_size]);
244 BookmarkAppHelper::BookmarkAppHelper(Profile* profile,
245 WebApplicationInfo web_app_info,
246 content::WebContents* contents)
247 : profile_(profile),
248 contents_(contents),
249 web_app_info_(web_app_info),
250 crx_installer_(extensions::CrxInstaller::CreateSilent(
251 ExtensionSystem::Get(profile)->extension_service())) {
252 web_app_info_.open_as_window =
253 profile_->GetPrefs()->GetInteger(
254 extensions::pref_names::kBookmarkAppCreationLaunchType) ==
255 extensions::LAUNCH_TYPE_WINDOW;
257 registrar_.Add(this,
258 extensions::NOTIFICATION_CRX_INSTALLER_DONE,
259 content::Source<CrxInstaller>(crx_installer_.get()));
261 registrar_.Add(this,
262 extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR,
263 content::Source<CrxInstaller>(crx_installer_.get()));
265 crx_installer_->set_error_on_unsupported_requirements(true);
268 BookmarkAppHelper::~BookmarkAppHelper() {}
270 void BookmarkAppHelper::Create(const CreateBookmarkAppCallback& callback) {
271 callback_ = callback;
273 if (contents_) {
274 contents_->GetManifest(base::Bind(&BookmarkAppHelper::OnDidGetManifest,
275 base::Unretained(this)));
276 } else {
277 OnIconsDownloaded(true, std::map<GURL, std::vector<SkBitmap> >());
281 void BookmarkAppHelper::OnDidGetManifest(const content::Manifest& manifest) {
282 if (contents_->IsBeingDestroyed())
283 return;
285 UpdateWebAppInfoFromManifest(manifest, &web_app_info_);
287 // Add urls from the WebApplicationInfo.
288 std::vector<GURL> web_app_info_icon_urls;
289 for (std::vector<WebApplicationInfo::IconInfo>::const_iterator it =
290 web_app_info_.icons.begin();
291 it != web_app_info_.icons.end();
292 ++it) {
293 if (it->url.is_valid())
294 web_app_info_icon_urls.push_back(it->url);
297 favicon_downloader_.reset(
298 new FaviconDownloader(contents_,
299 web_app_info_icon_urls,
300 base::Bind(&BookmarkAppHelper::OnIconsDownloaded,
301 base::Unretained(this))));
302 favicon_downloader_->Start();
305 void BookmarkAppHelper::OnIconsDownloaded(
306 bool success,
307 const std::map<GURL, std::vector<SkBitmap> >& bitmaps) {
308 // The tab has navigated away during the icon download. Cancel the bookmark
309 // app creation.
310 if (!success) {
311 favicon_downloader_.reset();
312 callback_.Run(nullptr, web_app_info_);
313 return;
316 std::vector<SkBitmap> downloaded_icons;
317 for (FaviconDownloader::FaviconMap::const_iterator map_it = bitmaps.begin();
318 map_it != bitmaps.end();
319 ++map_it) {
320 for (std::vector<SkBitmap>::const_iterator bitmap_it =
321 map_it->second.begin();
322 bitmap_it != map_it->second.end();
323 ++bitmap_it) {
324 if (bitmap_it->empty() || bitmap_it->width() != bitmap_it->height())
325 continue;
327 downloaded_icons.push_back(*bitmap_it);
331 // Add all existing icons from WebApplicationInfo.
332 for (std::vector<WebApplicationInfo::IconInfo>::const_iterator it =
333 web_app_info_.icons.begin();
334 it != web_app_info_.icons.end();
335 ++it) {
336 const SkBitmap& icon = it->data;
337 if (!icon.drawsNothing() && icon.width() == icon.height())
338 downloaded_icons.push_back(icon);
341 // Add the downloaded icons. Extensions only allow certain icon sizes. First
342 // populate icons that match the allowed sizes exactly and then downscale
343 // remaining icons to the closest allowed size that doesn't yet have an icon.
344 std::set<int> allowed_sizes(extension_misc::kExtensionIconSizes,
345 extension_misc::kExtensionIconSizes +
346 extension_misc::kNumExtensionIconSizes);
348 web_app_info_.generated_icon_color = SK_ColorTRANSPARENT;
349 // Determine the color that will be used for the icon's background. For this
350 // the dominant color of the first icon found is used.
351 if (downloaded_icons.size()) {
352 color_utils::GridSampler sampler;
353 web_app_info_.generated_icon_color =
354 color_utils::CalculateKMeanColorOfBitmap(downloaded_icons[0]);
357 std::set<int> generate_sizes = SizesToGenerate();
359 std::map<int, SkBitmap> generated_icons;
360 // Icons are always generated, replacing the icons that were downloaded. This
361 // is done so that the icons are consistent across machines.
362 // TODO(benwells): Use blob sync once it is available to sync the downloaded
363 // icons, and then only generate when there are required sizes missing.
364 GenerateIcons(generate_sizes, web_app_info_.app_url,
365 web_app_info_.generated_icon_color, &generated_icons);
367 ReplaceWebAppIcons(generated_icons, &web_app_info_);
368 favicon_downloader_.reset();
370 if (!contents_) {
371 // The web contents can be null in tests.
372 OnBubbleCompleted(true, web_app_info_);
373 return;
376 Browser* browser = chrome::FindBrowserWithWebContents(contents_);
377 if (!browser) {
378 // The browser can be null in tests.
379 OnBubbleCompleted(true, web_app_info_);
380 return;
382 browser->window()->ShowBookmarkAppBubble(
383 web_app_info_, base::Bind(&BookmarkAppHelper::OnBubbleCompleted,
384 base::Unretained(this)));
387 void BookmarkAppHelper::OnBubbleCompleted(
388 bool user_accepted,
389 const WebApplicationInfo& web_app_info) {
390 if (user_accepted) {
391 web_app_info_ = web_app_info;
392 crx_installer_->InstallWebApp(web_app_info_);
393 } else {
394 callback_.Run(nullptr, web_app_info_);
398 void BookmarkAppHelper::FinishInstallation(const Extension* extension) {
399 // Set the default 'open as' preference for use next time the dialog is
400 // shown.
401 extensions::LaunchType launch_type = web_app_info_.open_as_window
402 ? extensions::LAUNCH_TYPE_WINDOW
403 : extensions::LAUNCH_TYPE_REGULAR;
404 profile_->GetPrefs()->SetInteger(
405 extensions::pref_names::kBookmarkAppCreationLaunchType, launch_type);
407 // Set the launcher type for the app.
408 extensions::SetLaunchType(profile_, extension->id(), launch_type);
410 if (!contents_) {
411 // The web contents can be null in tests.
412 callback_.Run(extension, web_app_info_);
413 return;
416 Browser* browser = chrome::FindBrowserWithWebContents(contents_);
417 if (!browser) {
418 // The browser can be null in tests.
419 callback_.Run(extension, web_app_info_);
420 return;
423 // Pin the app to the relevant launcher depending on the OS.
424 Profile* current_profile = profile_->GetOriginalProfile();
426 // On Mac, shortcuts are automatically created for hosted apps when they are
427 // installed, so there is no need to create them again.
428 #if !defined(OS_MACOSX)
429 chrome::HostDesktopType desktop = browser->host_desktop_type();
430 if (desktop != chrome::HOST_DESKTOP_TYPE_ASH) {
431 web_app::ShortcutLocations creation_locations;
432 #if defined(OS_LINUX)
433 creation_locations.on_desktop = true;
434 #else
435 creation_locations.on_desktop = false;
436 #endif
437 creation_locations.applications_menu_location =
438 web_app::APP_MENU_LOCATION_SUBDIR_CHROMEAPPS;
439 creation_locations.in_quick_launch_bar = true;
440 web_app::CreateShortcuts(web_app::SHORTCUT_CREATION_BY_USER,
441 creation_locations, current_profile, extension);
442 #if defined(USE_ASH)
443 } else {
444 ChromeLauncherController::instance()->PinAppWithID(extension->id());
445 #endif
447 #endif
449 #if defined(OS_MACOSX)
450 if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
451 switches::kDisableHostedAppShimCreation)) {
452 web_app::RevealAppShimInFinderForApp(current_profile, extension);
454 #endif
456 callback_.Run(extension, web_app_info_);
459 void BookmarkAppHelper::Observe(int type,
460 const content::NotificationSource& source,
461 const content::NotificationDetails& details) {
462 switch (type) {
463 case extensions::NOTIFICATION_CRX_INSTALLER_DONE: {
464 const Extension* extension =
465 content::Details<const Extension>(details).ptr();
466 DCHECK(extension);
467 DCHECK_EQ(AppLaunchInfo::GetLaunchWebURL(extension),
468 web_app_info_.app_url);
469 FinishInstallation(extension);
470 break;
472 case extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR:
473 callback_.Run(nullptr, web_app_info_);
474 break;
475 default:
476 NOTREACHED();
477 break;
481 void CreateOrUpdateBookmarkApp(ExtensionService* service,
482 WebApplicationInfo* web_app_info) {
483 scoped_refptr<extensions::CrxInstaller> installer(
484 extensions::CrxInstaller::CreateSilent(service));
485 installer->set_error_on_unsupported_requirements(true);
486 if (web_app_info->icons.empty()) {
487 std::map<int, SkBitmap> bitmap_map;
488 GenerateIcons(SizesToGenerate(), web_app_info->app_url,
489 web_app_info->generated_icon_color, &bitmap_map);
490 ReplaceWebAppIcons(bitmap_map, web_app_info);
493 installer->InstallWebApp(*web_app_info);
496 void GetWebApplicationInfoFromApp(
497 content::BrowserContext* browser_context,
498 const extensions::Extension* extension,
499 const base::Callback<void(const WebApplicationInfo&)> callback) {
500 if (!extension->from_bookmark()) {
501 callback.Run(WebApplicationInfo());
502 return;
505 WebApplicationInfo web_app_info;
506 web_app_info.app_url = AppLaunchInfo::GetLaunchWebURL(extension);
507 web_app_info.title = base::UTF8ToUTF16(extension->non_localized_name());
508 web_app_info.description = base::UTF8ToUTF16(extension->description());
510 std::vector<extensions::ImageLoader::ImageRepresentation> info_list;
511 for (size_t i = 0; i < extension_misc::kNumExtensionIconSizes; ++i) {
512 int size = extension_misc::kExtensionIconSizes[i];
513 extensions::ExtensionResource resource =
514 extensions::IconsInfo::GetIconResource(
515 extension, size, ExtensionIconSet::MATCH_EXACTLY);
516 if (!resource.empty()) {
517 info_list.push_back(extensions::ImageLoader::ImageRepresentation(
518 resource,
519 extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE,
520 gfx::Size(size, size),
521 ui::SCALE_FACTOR_100P));
525 extensions::ImageLoader::Get(browser_context)->LoadImageFamilyAsync(
526 extension, info_list, base::Bind(&OnIconsLoaded, web_app_info, callback));
529 bool IsValidBookmarkAppUrl(const GURL& url) {
530 URLPattern origin_only_pattern(Extension::kValidWebExtentSchemes);
531 origin_only_pattern.SetMatchAllURLs(true);
532 return url.is_valid() && origin_only_pattern.MatchesURL(url);
535 } // namespace extensions