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"
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"
61 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
66 // Overlays a shortcut icon over the bottom left corner of a given image.
67 class GeneratedIconImageSource
: public gfx::CanvasImageSource
{
69 explicit GeneratedIconImageSource(char letter
, SkColor color
, int output_size
)
70 : gfx::CanvasImageSource(gfx::Size(output_size
, output_size
), false),
73 output_size_(output_size
) {}
74 ~GeneratedIconImageSource() override
{}
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
;
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
,
110 gfx::Canvas::TEXT_ALIGN_CENTER
);
119 DISALLOW_COPY_AND_ASSIGN(GeneratedIconImageSource
);
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();
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
,
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(
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
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
=
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
);
199 namespace extensions
{
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
;
224 web_app_info
->icons
.push_back(info
);
230 void BookmarkAppHelper::GenerateIcon(std::map
<int, SkBitmap
>* bitmaps
,
234 // Do nothing if there is already an icon of |output_size|.
235 if (bitmaps
->count(output_size
))
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
)
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
;
258 extensions::NOTIFICATION_CRX_INSTALLER_DONE
,
259 content::Source
<CrxInstaller
>(crx_installer_
.get()));
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
;
274 contents_
->GetManifest(base::Bind(&BookmarkAppHelper::OnDidGetManifest
,
275 base::Unretained(this)));
277 OnIconsDownloaded(true, std::map
<GURL
, std::vector
<SkBitmap
> >());
281 void BookmarkAppHelper::OnDidGetManifest(const content::Manifest
& manifest
) {
282 if (contents_
->IsBeingDestroyed())
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();
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(
307 const std::map
<GURL
, std::vector
<SkBitmap
> >& bitmaps
) {
308 // The tab has navigated away during the icon download. Cancel the bookmark
311 favicon_downloader_
.reset();
312 callback_
.Run(nullptr, web_app_info_
);
316 std::vector
<SkBitmap
> downloaded_icons
;
317 for (FaviconDownloader::FaviconMap::const_iterator map_it
= bitmaps
.begin();
318 map_it
!= bitmaps
.end();
320 for (std::vector
<SkBitmap
>::const_iterator bitmap_it
=
321 map_it
->second
.begin();
322 bitmap_it
!= map_it
->second
.end();
324 if (bitmap_it
->empty() || bitmap_it
->width() != bitmap_it
->height())
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();
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();
371 // The web contents can be null in tests.
372 OnBubbleCompleted(true, web_app_info_
);
376 Browser
* browser
= chrome::FindBrowserWithWebContents(contents_
);
378 // The browser can be null in tests.
379 OnBubbleCompleted(true, web_app_info_
);
382 browser
->window()->ShowBookmarkAppBubble(
383 web_app_info_
, base::Bind(&BookmarkAppHelper::OnBubbleCompleted
,
384 base::Unretained(this)));
387 void BookmarkAppHelper::OnBubbleCompleted(
389 const WebApplicationInfo
& web_app_info
) {
391 web_app_info_
= web_app_info
;
392 crx_installer_
->InstallWebApp(web_app_info_
);
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
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
);
411 // The web contents can be null in tests.
412 callback_
.Run(extension
, web_app_info_
);
416 Browser
* browser
= chrome::FindBrowserWithWebContents(contents_
);
418 // The browser can be null in tests.
419 callback_
.Run(extension
, web_app_info_
);
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;
435 creation_locations
.on_desktop
= false;
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
);
444 ChromeLauncherController::instance()->PinAppWithID(extension
->id());
449 #if defined(OS_MACOSX)
450 if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
451 switches::kDisableHostedAppShimCreation
)) {
452 web_app::RevealAppShimInFinderForApp(current_profile
, extension
);
456 callback_
.Run(extension
, web_app_info_
);
459 void BookmarkAppHelper::Observe(int type
,
460 const content::NotificationSource
& source
,
461 const content::NotificationDetails
& details
) {
463 case extensions::NOTIFICATION_CRX_INSTALLER_DONE
: {
464 const Extension
* extension
=
465 content::Details
<const Extension
>(details
).ptr();
467 DCHECK_EQ(AppLaunchInfo::GetLaunchWebURL(extension
),
468 web_app_info_
.app_url
);
469 FinishInstallation(extension
);
472 case extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR
:
473 callback_
.Run(nullptr, web_app_info_
);
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());
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(
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