diff options
author | calamity@chromium.org <calamity@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-25 06:28:15 +0000 |
---|---|---|
committer | calamity@chromium.org <calamity@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-25 06:28:15 +0000 |
commit | 33c878970a834a4f64bd2205c1183216019bb156 (patch) | |
tree | 2cd1182db3352fb9fdaf8c11583a1487aa31f25e | |
parent | 008e3b3ce883855afaaeb0ea150594c09c8877f1 (diff) | |
download | chromium_src-33c878970a834a4f64bd2205c1183216019bb156.zip chromium_src-33c878970a834a4f64bd2205c1183216019bb156.tar.gz chromium_src-33c878970a834a4f64bd2205c1183216019bb156.tar.bz2 |
Pull bookmark app creation from TabHelper into its own class.
This CL adds a BookmarkAppHelper which encapsulates the creation
of a bookmark app from a WebContents, allowing tests to be written.
The implementation is pulled from TabHelper where this functionality was previously implemented.
BUG=318607
Review URL: https://codereview.chromium.org/197683004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@259165 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/extensions/bookmark_app_helper.cc | 285 | ||||
-rw-r--r-- | chrome/browser/extensions/bookmark_app_helper.h | 93 | ||||
-rw-r--r-- | chrome/browser/extensions/bookmark_app_helper_unittest.cc | 195 | ||||
-rw-r--r-- | chrome/browser/extensions/tab_helper.cc | 302 | ||||
-rw-r--r-- | chrome/browser/extensions/tab_helper.h | 26 | ||||
-rw-r--r-- | chrome/browser/extensions/tab_helper_unittest.cc | 103 | ||||
-rw-r--r-- | chrome/chrome_browser_extensions.gypi | 2 | ||||
-rw-r--r-- | chrome/chrome_tests_unit.gypi | 2 |
8 files changed, 616 insertions, 392 deletions
diff --git a/chrome/browser/extensions/bookmark_app_helper.cc b/chrome/browser/extensions/bookmark_app_helper.cc new file mode 100644 index 0000000..24ce2f8 --- /dev/null +++ b/chrome/browser/extensions/bookmark_app_helper.cc @@ -0,0 +1,285 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/extensions/bookmark_app_helper.h" + +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/chrome_notification_types.h" +#include "chrome/browser/extensions/crx_installer.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/favicon_downloader.h" +#include "chrome/browser/extensions/tab_helper.h" +#include "chrome/common/extensions/extension_constants.h" +#include "chrome/common/extensions/manifest_handlers/app_launch_info.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_source.h" +#include "content/public/browser/web_contents.h" +#include "extensions/common/extension.h" +#include "skia/ext/image_operations.h" +#include "skia/ext/platform_canvas.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/color_analysis.h" +#include "ui/gfx/image/image.h" + +namespace extensions { + +// static +std::map<int, SkBitmap> BookmarkAppHelper::ConstrainBitmapsToSizes( + const std::vector<SkBitmap>& bitmaps, + const std::set<int>& sizes) { + std::map<int, SkBitmap> output_bitmaps; + std::map<int, SkBitmap> ordered_bitmaps; + for (std::vector<SkBitmap>::const_iterator it = bitmaps.begin(); + it != bitmaps.end(); + ++it) { + DCHECK(it->width() == it->height()); + ordered_bitmaps[it->width()] = *it; + } + + std::set<int>::const_iterator sizes_it = sizes.begin(); + std::map<int, SkBitmap>::const_iterator bitmaps_it = ordered_bitmaps.begin(); + while (sizes_it != sizes.end() && bitmaps_it != ordered_bitmaps.end()) { + int size = *sizes_it; + // Find the closest not-smaller bitmap. + bitmaps_it = ordered_bitmaps.lower_bound(size); + ++sizes_it; + // Ensure the bitmap is valid and smaller than the next allowed size. + if (bitmaps_it != ordered_bitmaps.end() && + (sizes_it == sizes.end() || bitmaps_it->second.width() < *sizes_it)) { + // Resize the bitmap if it does not exactly match the desired size. + output_bitmaps[size] = bitmaps_it->second.width() == size + ? bitmaps_it->second + : skia::ImageOperations::Resize( + bitmaps_it->second, + skia::ImageOperations::RESIZE_LANCZOS3, + size, + size); + } + } + return output_bitmaps; +} + +// static +void BookmarkAppHelper::GenerateContainerIcon(std::map<int, SkBitmap>* bitmaps, + int output_size) { + std::map<int, SkBitmap>::const_iterator it = + bitmaps->lower_bound(output_size); + // Do nothing if there is no icon smaller than the desired size or there is + // already an icon of |output_size|. + if (it == bitmaps->begin() || bitmaps->count(output_size)) + return; + + --it; + // This is the biggest icon smaller than |output_size|. + const SkBitmap& base_icon = it->second; + + const size_t kBorderRadius = 5; + const size_t kColorStripHeight = 3; + const SkColor kBorderColor = 0xFFD5D5D5; + const SkColor kBackgroundColor = 0xFFFFFFFF; + + // Create a separate canvas for the color strip. + scoped_ptr<SkCanvas> color_strip_canvas( + skia::CreateBitmapCanvas(output_size, output_size, false)); + DCHECK(color_strip_canvas); + + // Draw a rounded rect of the |base_icon|'s dominant color. + SkPaint color_strip_paint; + color_utils::GridSampler sampler; + color_strip_paint.setFlags(SkPaint::kAntiAlias_Flag); + color_strip_paint.setColor(color_utils::CalculateKMeanColorOfPNG( + gfx::Image::CreateFrom1xBitmap(base_icon).As1xPNGBytes(), + 100, + 665, + &sampler)); + color_strip_canvas->drawRoundRect(SkRect::MakeWH(output_size, output_size), + kBorderRadius, + kBorderRadius, + color_strip_paint); + + // Erase the top of the rounded rect to leave a color strip. + SkPaint clear_paint; + clear_paint.setColor(SK_ColorTRANSPARENT); + clear_paint.setXfermodeMode(SkXfermode::kSrc_Mode); + color_strip_canvas->drawRect( + SkRect::MakeWH(output_size, output_size - kColorStripHeight), + clear_paint); + + // Draw each element to an output canvas. + scoped_ptr<SkCanvas> canvas( + skia::CreateBitmapCanvas(output_size, output_size, false)); + DCHECK(canvas); + + // Draw the background. + SkPaint background_paint; + background_paint.setColor(kBackgroundColor); + background_paint.setFlags(SkPaint::kAntiAlias_Flag); + canvas->drawRoundRect(SkRect::MakeWH(output_size, output_size), + kBorderRadius, + kBorderRadius, + background_paint); + + // Draw the color strip. + canvas->drawBitmap( + color_strip_canvas->getDevice()->accessBitmap(false), 0, 0); + + // Draw the border. + SkPaint border_paint; + border_paint.setColor(kBorderColor); + border_paint.setStyle(SkPaint::kStroke_Style); + border_paint.setFlags(SkPaint::kAntiAlias_Flag); + canvas->drawRoundRect(SkRect::MakeWH(output_size, output_size), + kBorderRadius, + kBorderRadius, + border_paint); + + // Draw the centered base icon to the output canvas. + canvas->drawBitmap(base_icon, + (output_size - base_icon.width()) / 2, + (output_size - base_icon.height()) / 2); + + const SkBitmap& generated_icon = canvas->getDevice()->accessBitmap(false); + generated_icon.deepCopyTo(&(*bitmaps)[output_size]); +} + +BookmarkAppHelper::BookmarkAppHelper(ExtensionService* service, + WebApplicationInfo web_app_info, + content::WebContents* contents) + : web_app_info_(web_app_info), + crx_installer_(extensions::CrxInstaller::CreateSilent(service)) { + registrar_.Add(this, + chrome::NOTIFICATION_CRX_INSTALLER_DONE, + content::Source<CrxInstaller>(crx_installer_.get())); + + registrar_.Add(this, + chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR, + content::Source<CrxInstaller>(crx_installer_.get())); + + crx_installer_->set_error_on_unsupported_requirements(true); + + // Add urls from the WebApplicationInfo. + std::vector<GURL> web_app_info_icon_urls; + for (std::vector<WebApplicationInfo::IconInfo>::const_iterator it = + web_app_info_.icons.begin(); + it != web_app_info_.icons.end(); + ++it) { + if (it->url.is_valid()) + web_app_info_icon_urls.push_back(it->url); + } + + favicon_downloader_.reset( + new FaviconDownloader(contents, + web_app_info_icon_urls, + base::Bind(&BookmarkAppHelper::OnIconsDownloaded, + base::Unretained(this)))); +} + +BookmarkAppHelper::~BookmarkAppHelper() {} + +void BookmarkAppHelper::Create(const CreateBookmarkAppCallback& callback) { + callback_ = callback; + favicon_downloader_->Start(); +} + +void BookmarkAppHelper::OnIconsDownloaded( + bool success, + const std::map<GURL, std::vector<SkBitmap> >& bitmaps) { + // The tab has navigated away during the icon download. Cancel the bookmark + // app creation. + if (!success) { + favicon_downloader_.reset(); + callback_.Run(NULL, web_app_info_); + return; + } + + // Add the downloaded icons. Extensions only allow certain icon sizes. First + // populate icons that match the allowed sizes exactly and then downscale + // remaining icons to the closest allowed size that doesn't yet have an icon. + std::set<int> allowed_sizes(extension_misc::kExtensionIconSizes, + extension_misc::kExtensionIconSizes + + extension_misc::kNumExtensionIconSizes); + std::vector<SkBitmap> downloaded_icons; + for (FaviconDownloader::FaviconMap::const_iterator map_it = bitmaps.begin(); + map_it != bitmaps.end(); + ++map_it) { + for (std::vector<SkBitmap>::const_iterator bitmap_it = + map_it->second.begin(); + bitmap_it != map_it->second.end(); + ++bitmap_it) { + if (bitmap_it->empty() || bitmap_it->width() != bitmap_it->height()) + continue; + + downloaded_icons.push_back(*bitmap_it); + } + } + + // If there are icons that don't match the accepted icon sizes, find the + // closest bigger icon to the accepted sizes and resize the icon to it. An + // icon will be resized and used for at most one size. + std::map<int, SkBitmap> resized_bitmaps( + ConstrainBitmapsToSizes(downloaded_icons, allowed_sizes)); + + // Generate container icons from smaller icons. + const int kIconSizesToGenerate[] = {extension_misc::EXTENSION_ICON_SMALL, + extension_misc::EXTENSION_ICON_MEDIUM, }; + const std::set<int> generate_sizes( + kIconSizesToGenerate, + kIconSizesToGenerate + arraysize(kIconSizesToGenerate)); + + // Only generate icons if larger icons don't exist. This means the app + // launcher and the taskbar will do their best downsizing large icons and + // these container icons are only generated as a last resort against upscaling + // a smaller icon. + if (resized_bitmaps.lower_bound(*generate_sizes.rbegin()) == + resized_bitmaps.end()) { + // Generate these from biggest to smallest so we don't end up with + // concentric container icons. + for (std::set<int>::const_reverse_iterator it = generate_sizes.rbegin(); + it != generate_sizes.rend(); + ++it) { + GenerateContainerIcon(&resized_bitmaps, *it); + } + } + + // Populate the icon data into the WebApplicationInfo we are using to + // install the bookmark app. + for (std::map<int, SkBitmap>::const_iterator resized_bitmaps_it = + resized_bitmaps.begin(); + resized_bitmaps_it != resized_bitmaps.end(); + ++resized_bitmaps_it) { + WebApplicationInfo::IconInfo icon_info; + icon_info.data = resized_bitmaps_it->second; + icon_info.width = icon_info.data.width(); + icon_info.height = icon_info.data.height(); + web_app_info_.icons.push_back(icon_info); + } + + // Install the app. + crx_installer_->InstallWebApp(web_app_info_); + favicon_downloader_.reset(); +} + +void BookmarkAppHelper::Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + switch (type) { + case chrome::NOTIFICATION_CRX_INSTALLER_DONE: { + const Extension* extension = + content::Details<const Extension>(details).ptr(); + DCHECK(extension); + DCHECK_EQ(AppLaunchInfo::GetLaunchWebURL(extension), + web_app_info_.app_url); + callback_.Run(extension, web_app_info_); + break; + } + case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR: + callback_.Run(NULL, web_app_info_); + break; + default: + NOTREACHED(); + break; + } +} + +} // namespace extensions diff --git a/chrome/browser/extensions/bookmark_app_helper.h b/chrome/browser/extensions/bookmark_app_helper.h new file mode 100644 index 0000000..6218dc3 --- /dev/null +++ b/chrome/browser/extensions/bookmark_app_helper.h @@ -0,0 +1,93 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_EXTENSIONS_BOOKMARK_APP_HELPER_H_ +#define CHROME_BROWSER_EXTENSIONS_BOOKMARK_APP_HELPER_H_ + +#include <map> +#include <set> +#include <vector> + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "chrome/common/web_application_info.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" + +class ExtensionService; +class FaviconDownloader; +class SkBitmap; + +namespace content { +class WebContents; +} + +namespace extensions { +class CrxInstaller; +class Extension; + +// A helper class for creating bookmark apps from a WebContents. +class BookmarkAppHelper : public content::NotificationObserver { + public: + typedef base::Callback<void(const Extension*, const WebApplicationInfo&)> + CreateBookmarkAppCallback; + + // This helper class will create a bookmark app out of |web_app_info| and + // install it to |service|. Icons will be downloaded from the URLs in + // |web_app_info.icons| using |contents|. + BookmarkAppHelper(ExtensionService* service, + WebApplicationInfo web_app_info, + content::WebContents* contents); + virtual ~BookmarkAppHelper(); + + // This finds the closest not-smaller bitmap in |bitmaps| for each size in + // |sizes| and resizes it to that size. This returns a map of sizes to bitmaps + // which contains only bitmaps of a size in |sizes| and at most one bitmap of + // each size. + static std::map<int, SkBitmap> ConstrainBitmapsToSizes( + const std::vector<SkBitmap>& bitmaps, + const std::set<int>& sizes); + + // Adds a square container icon of |output_size| pixels to |bitmaps| by + // centering the biggest smaller icon in |bitmaps| and drawing a rounded + // rectangle with strip of the that icon's dominant color at the bottom. + // Does nothing if an icon of |output_size| already exists in |bitmaps|. + static void GenerateContainerIcon(std::map<int, SkBitmap>* bitmaps, + int output_size); + + // Begins the asynchronous bookmark app creation. + void Create(const CreateBookmarkAppCallback& callback); + + private: + friend class TestBookmarkAppHelper; + + // Performs post icon download tasks including installing the bookmark app. + void OnIconsDownloaded(bool success, + const std::map<GURL, std::vector<SkBitmap> >& bitmaps); + + // Overridden from content::NotificationObserver: + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + // The WebApplicationInfo that the bookmark app is being created for. + WebApplicationInfo web_app_info_; + + // Called on app creation or failure. + CreateBookmarkAppCallback callback_; + + // Downloads icons from the given WebApplicationInfo using the given + // WebContents. + scoped_ptr<FaviconDownloader> favicon_downloader_; + + // Used to install the created bookmark app. + scoped_refptr<extensions::CrxInstaller> crx_installer_; + + content::NotificationRegistrar registrar_; +}; + +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_BOOKMARK_APP_HELPER_H_ diff --git a/chrome/browser/extensions/bookmark_app_helper_unittest.cc b/chrome/browser/extensions/bookmark_app_helper_unittest.cc new file mode 100644 index 0000000..bac705e --- /dev/null +++ b/chrome/browser/extensions/bookmark_app_helper_unittest.cc @@ -0,0 +1,195 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/extensions/bookmark_app_helper.h" + +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/extensions/extension_service_unittest.h" +#include "chrome/common/extensions/extension_icon_set.h" +#include "chrome/common/extensions/manifest_handlers/app_launch_info.h" +#include "chrome/common/extensions/manifest_handlers/icons_handler.h" +#include "chrome/test/base/testing_profile.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkBitmap.h" + +namespace { + +#if !defined(OS_ANDROID) +const char kAppUrl[] = "http://www.chromium.org"; +const char kAppTitle[] = "Test title"; +const char kAppDescription[] = "Test description"; + +const int kIconSizeSmall = extension_misc::EXTENSION_ICON_SMALL; +#endif + +class BookmarkAppHelperTest : public testing::Test { + public: + BookmarkAppHelperTest() {} + virtual ~BookmarkAppHelperTest() {} + + private: + DISALLOW_COPY_AND_ASSIGN(BookmarkAppHelperTest); +}; + +class BookmarkAppHelperExtensionServiceTest : public ExtensionServiceTestBase { + public: + BookmarkAppHelperExtensionServiceTest() {} + virtual ~BookmarkAppHelperExtensionServiceTest() {} + + virtual void SetUp() OVERRIDE { + ExtensionServiceTestBase::SetUp(); + InitializeEmptyExtensionService(); + service_->Init(); + EXPECT_EQ(0u, service_->extensions()->size()); + } + + private: + DISALLOW_COPY_AND_ASSIGN(BookmarkAppHelperExtensionServiceTest); +}; + +SkBitmap CreateSquareBitmapWithColor(int size, SkColor color) { + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, size, size); + bitmap.allocPixels(); + bitmap.eraseColor(color); + return bitmap; +} + +void ValidateBitmapSizeAndColor(SkBitmap bitmap, int size, SkColor color) { + // Obtain pixel lock to access pixels. + SkAutoLockPixels lock(bitmap); + EXPECT_EQ(color, bitmap.getColor(0, 0)); + EXPECT_EQ(size, bitmap.width()); + EXPECT_EQ(size, bitmap.height()); +} + +} // namespace + +namespace extensions { + +class TestBookmarkAppHelper : public BookmarkAppHelper { + public: + TestBookmarkAppHelper(ExtensionService* service, + WebApplicationInfo web_app_info, + content::WebContents* contents) + : BookmarkAppHelper(service, web_app_info, contents) {} + + virtual ~TestBookmarkAppHelper() {} + + void CreationComplete(const extensions::Extension* extension, + const WebApplicationInfo& web_app_info) { + extension_ = extension; + } + + void CompleteIconDownload( + bool success, + const std::map<GURL, std::vector<SkBitmap> >& bitmaps) { + BookmarkAppHelper::OnIconsDownloaded(success, bitmaps); + } + + const Extension* extension() { return extension_; } + + private: + const Extension* extension_; + + DISALLOW_COPY_AND_ASSIGN(TestBookmarkAppHelper); +}; + +// Android doesn't support extensions. +#if !defined(OS_ANDROID) +TEST_F(BookmarkAppHelperExtensionServiceTest, CreateBookmarkApp) { + WebApplicationInfo web_app_info; + web_app_info.app_url = GURL(kAppUrl); + web_app_info.title = base::UTF8ToUTF16(kAppTitle); + web_app_info.description = base::UTF8ToUTF16(kAppDescription); + + TestBookmarkAppHelper helper(service_, web_app_info, NULL); + helper.Create(base::Bind(&TestBookmarkAppHelper::CreationComplete, + base::Unretained(&helper))); + + std::map<GURL, std::vector<SkBitmap> > icon_map; + icon_map[GURL(kAppUrl)].push_back( + CreateSquareBitmapWithColor(kIconSizeSmall, SK_ColorRED)); + helper.CompleteIconDownload(true, icon_map); + + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(helper.extension()); + const Extension* extension = + service_->GetInstalledExtension(helper.extension()->id()); + EXPECT_TRUE(extension); + EXPECT_EQ(1u, service_->extensions()->size()); + EXPECT_TRUE(extension->from_bookmark()); + EXPECT_EQ(kAppTitle, extension->name()); + EXPECT_EQ(kAppDescription, extension->description()); + EXPECT_EQ(GURL(kAppUrl), AppLaunchInfo::GetLaunchWebURL(extension)); + EXPECT_FALSE( + IconsInfo::GetIconResource( + extension, kIconSizeSmall, ExtensionIconSet::MATCH_EXACTLY).empty()); +} +#endif + +TEST_F(BookmarkAppHelperTest, ConstrainBitmapsToSizes) { + std::set<int> desired_sizes; + desired_sizes.insert(16); + desired_sizes.insert(32); + desired_sizes.insert(128); + desired_sizes.insert(256); + + { + std::vector<SkBitmap> bitmaps; + bitmaps.push_back(CreateSquareBitmapWithColor(16, SK_ColorRED)); + bitmaps.push_back(CreateSquareBitmapWithColor(32, SK_ColorGREEN)); + bitmaps.push_back(CreateSquareBitmapWithColor(48, SK_ColorBLUE)); + bitmaps.push_back(CreateSquareBitmapWithColor(144, SK_ColorYELLOW)); + + std::map<int, SkBitmap> results( + BookmarkAppHelper::ConstrainBitmapsToSizes(bitmaps, desired_sizes)); + + EXPECT_EQ(3u, results.size()); + ValidateBitmapSizeAndColor(results[16], 16, SK_ColorRED); + ValidateBitmapSizeAndColor(results[32], 32, SK_ColorGREEN); + ValidateBitmapSizeAndColor(results[128], 128, SK_ColorYELLOW); + } + { + std::vector<SkBitmap> bitmaps; + bitmaps.push_back(CreateSquareBitmapWithColor(512, SK_ColorRED)); + bitmaps.push_back(CreateSquareBitmapWithColor(18, SK_ColorGREEN)); + bitmaps.push_back(CreateSquareBitmapWithColor(33, SK_ColorBLUE)); + bitmaps.push_back(CreateSquareBitmapWithColor(17, SK_ColorYELLOW)); + + std::map<int, SkBitmap> results( + BookmarkAppHelper::ConstrainBitmapsToSizes(bitmaps, desired_sizes)); + + EXPECT_EQ(3u, results.size()); + ValidateBitmapSizeAndColor(results[16], 16, SK_ColorYELLOW); + ValidateBitmapSizeAndColor(results[32], 32, SK_ColorBLUE); + ValidateBitmapSizeAndColor(results[256], 256, SK_ColorRED); + } +} + +TEST_F(BookmarkAppHelperTest, GenerateIcons) { + { + // The 32x32 icon should be generated from the 16x16 icon. + std::map<int, SkBitmap> bitmaps; + bitmaps[16] = CreateSquareBitmapWithColor(16, SK_ColorRED); + BookmarkAppHelper::GenerateContainerIcon(&bitmaps, 32); + EXPECT_EQ(1u, bitmaps.count(32)); + EXPECT_EQ(32, bitmaps[32].width()); + } + { + // The 32x32 icon should not be generated because no smaller icon exists. + std::map<int, SkBitmap> bitmaps; + bitmaps[48] = CreateSquareBitmapWithColor(48, SK_ColorRED); + BookmarkAppHelper::GenerateContainerIcon(&bitmaps, 32); + EXPECT_EQ(0u, bitmaps.count(32)); + } + { + // The 32x32 icon should not be generated with no base icons. + std::map<int, SkBitmap> bitmaps; + BookmarkAppHelper::GenerateContainerIcon(&bitmaps, 32); + EXPECT_EQ(0u, bitmaps.count(32)); + } +} + +} // namespace extensions diff --git a/chrome/browser/extensions/tab_helper.cc b/chrome/browser/extensions/tab_helper.cc index 5bc94af..9eacd19 100644 --- a/chrome/browser/extensions/tab_helper.cc +++ b/chrome/browser/extensions/tab_helper.cc @@ -12,13 +12,12 @@ #include "chrome/browser/extensions/activity_log/activity_log.h" #include "chrome/browser/extensions/api/declarative/rules_registry_service.h" #include "chrome/browser/extensions/api/declarative_content/content_rules_registry.h" -#include "chrome/browser/extensions/crx_installer.h" +#include "chrome/browser/extensions/bookmark_app_helper.h" #include "chrome/browser/extensions/error_console/error_console.h" #include "chrome/browser/extensions/extension_action.h" #include "chrome/browser/extensions/extension_action_manager.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_tab_util.h" -#include "chrome/browser/extensions/favicon_downloader.h" #include "chrome/browser/extensions/image_loader.h" #include "chrome/browser/extensions/page_action_controller.h" #include "chrome/browser/extensions/script_executor.h" @@ -64,10 +63,6 @@ #include "extensions/common/extension_resource.h" #include "extensions/common/extension_urls.h" #include "extensions/common/feature_switch.h" -#include "skia/ext/image_operations.h" -#include "skia/ext/platform_canvas.h" -#include "ui/gfx/color_analysis.h" -#include "ui/gfx/image/image.h" #if defined(OS_CHROMEOS) #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h" @@ -97,118 +92,6 @@ TabHelper::ScriptExecutionObserver::~ScriptExecutionObserver() { tab_helper_->RemoveScriptExecutionObserver(this); } -// static -std::map<int, SkBitmap> TabHelper::ConstrainBitmapsToSizes( - const std::vector<SkBitmap>& bitmaps, - const std::set<int>& sizes) { - std::map<int, SkBitmap> output_bitmaps; - std::map<int, SkBitmap> ordered_bitmaps; - for (std::vector<SkBitmap>::const_iterator it = bitmaps.begin(); - it != bitmaps.end(); ++it) { - DCHECK(it->width() == it->height()); - ordered_bitmaps[it->width()] = *it; - } - - std::set<int>::const_iterator sizes_it = sizes.begin(); - std::map<int, SkBitmap>::const_iterator bitmaps_it = ordered_bitmaps.begin(); - while (sizes_it != sizes.end() && bitmaps_it != ordered_bitmaps.end()) { - int size = *sizes_it; - // Find the closest not-smaller bitmap. - bitmaps_it = ordered_bitmaps.lower_bound(size); - ++sizes_it; - // Ensure the bitmap is valid and smaller than the next allowed size. - if (bitmaps_it != ordered_bitmaps.end() && - (sizes_it == sizes.end() || bitmaps_it->second.width() < *sizes_it)) { - // Resize the bitmap if it does not exactly match the desired size. - output_bitmaps[size] = bitmaps_it->second.width() == size - ? bitmaps_it->second - : skia::ImageOperations::Resize( - bitmaps_it->second, skia::ImageOperations::RESIZE_LANCZOS3, - size, size); - } - } - return output_bitmaps; -} - -// static -void TabHelper::GenerateContainerIcon(std::map<int, SkBitmap>* bitmaps, - int output_size) { - std::map<int, SkBitmap>::const_iterator it = - bitmaps->lower_bound(output_size); - // Do nothing if there is no icon smaller than the desired size or there is - // already an icon of |output_size|. - if (it == bitmaps->begin() || bitmaps->count(output_size)) - return; - - --it; - // This is the biggest icon smaller than |output_size|. - const SkBitmap& base_icon = it->second; - - const size_t kBorderRadius = 5; - const size_t kColorStripHeight = 3; - const SkColor kBorderColor = 0xFFD5D5D5; - const SkColor kBackgroundColor = 0xFFFFFFFF; - - // Create a separate canvas for the color strip. - SkBitmap color_strip_bitmap; - color_strip_bitmap.allocN32Pixels(output_size, output_size); - SkCanvas color_strip_canvas(color_strip_bitmap); - color_strip_canvas.clear(SK_ColorTRANSPARENT); - - // Draw a rounded rect of the |base_icon|'s dominant color. - SkPaint color_strip_paint; - color_strip_paint.setFlags(SkPaint::kAntiAlias_Flag); - color_strip_paint.setColor( - color_utils::CalculateKMeanColorOfBitmap(base_icon)); - color_strip_canvas.drawRoundRect(SkRect::MakeWH(output_size, output_size), - kBorderRadius, - kBorderRadius, - color_strip_paint); - - // Erase the top of the rounded rect to leave a color strip. - SkPaint clear_paint; - clear_paint.setColor(SK_ColorTRANSPARENT); - clear_paint.setXfermodeMode(SkXfermode::kSrc_Mode); - color_strip_canvas.drawRect( - SkRect::MakeWH(output_size, output_size - kColorStripHeight), - clear_paint); - - // Draw each element to an output canvas. - SkBitmap generated_icon; - generated_icon.allocN32Pixels(output_size, output_size); - SkCanvas generated_icon_canvas(generated_icon); - generated_icon_canvas.clear(SK_ColorTRANSPARENT); - - // Draw the background. - SkPaint background_paint; - background_paint.setColor(kBackgroundColor); - background_paint.setFlags(SkPaint::kAntiAlias_Flag); - generated_icon_canvas.drawRoundRect(SkRect::MakeWH(output_size, output_size), - kBorderRadius, - kBorderRadius, - background_paint); - - // Draw the color strip. - generated_icon_canvas.drawBitmap(color_strip_bitmap, 0, 0); - - // Draw the border. - SkPaint border_paint; - border_paint.setColor(kBorderColor); - border_paint.setStyle(SkPaint::kStroke_Style); - border_paint.setFlags(SkPaint::kAntiAlias_Flag); - generated_icon_canvas.drawRoundRect(SkRect::MakeWH(output_size, output_size), - kBorderRadius, - kBorderRadius, - border_paint); - - // Draw the centered base icon to the output canvas. - generated_icon_canvas.drawBitmap(base_icon, - (output_size - base_icon.width()) / 2, - (output_size - base_icon.height()) / 2); - - generated_icon.deepCopyTo(&(*bitmaps)[output_size]); -} - TabHelper::TabHelper(content::WebContents* web_contents) : content::WebContentsObserver(web_contents), extension_app_(NULL), @@ -242,14 +125,6 @@ TabHelper::TabHelper(content::WebContents* web_contents) content::NOTIFICATION_LOAD_STOP, content::Source<NavigationController>( &web_contents->GetController())); - - registrar_.Add(this, - chrome::NOTIFICATION_CRX_INSTALLER_DONE, - content::Source<CrxInstaller>(NULL)); - - registrar_.Add(this, - chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR, - content::NotificationService::AllSources()); } TabHelper::~TabHelper() { @@ -329,6 +204,28 @@ SkBitmap* TabHelper::GetExtensionAppIcon() { return &extension_app_icon_; } +void TabHelper::FinishCreateBookmarkApp( + const extensions::Extension* extension, + const WebApplicationInfo& web_app_info) { + pending_web_app_action_ = NONE; + + // There was an error with downloading the icons or installing the app. + if (!extension) + return; + +#if defined(OS_CHROMEOS) + ChromeLauncherController::instance()->PinAppWithID(extension->id()); +#endif + +// Android does not implement browser_finder.cc. +#if !defined(OS_ANDROID) + Browser* browser = chrome::FindBrowserWithWebContents(web_contents()); + if (browser) { + browser->window()->ShowBookmarkAppBubble(web_app_info, extension->id()); + } +#endif +} + void TabHelper::RenderViewCreated(RenderViewHost* render_view_host) { SetTabId(render_view_host); } @@ -405,115 +302,6 @@ bool TabHelper::OnMessageReceived(const IPC::Message& message) { return handled; } -void TabHelper::CreateHostedApp() { - // Add urls from the WebApplicationInfo. - std::vector<GURL> web_app_info_icon_urls; - for (std::vector<WebApplicationInfo::IconInfo>::const_iterator it = - web_app_info_.icons.begin(); - it != web_app_info_.icons.end(); ++it) { - if (it->url.is_valid()) - web_app_info_icon_urls.push_back(it->url); - } - - favicon_downloader_.reset( - new FaviconDownloader(web_contents(), - web_app_info_icon_urls, - base::Bind(&TabHelper::FinishCreateHostedApp, - base::Unretained(this)))); - favicon_downloader_->Start(); -} - -// TODO(calamity): Move hosted app generation into its own file. -void TabHelper::FinishCreateHostedApp( - bool success, - const std::map<GURL, std::vector<SkBitmap> >& bitmaps) { - // The tab has navigated away during the icon download. Cancel the hosted app - // creation. - if (!success) { - favicon_downloader_.reset(); - return; - } - - if (web_app_info_.app_url.is_empty()) - web_app_info_.app_url = web_contents()->GetURL(); - - if (web_app_info_.title.empty()) - web_app_info_.title = web_contents()->GetTitle(); - if (web_app_info_.title.empty()) - web_app_info_.title = base::UTF8ToUTF16(web_app_info_.app_url.spec()); - - // Add the downloaded icons. Extensions only allow certain icon sizes. First - // populate icons that match the allowed sizes exactly and then downscale - // remaining icons to the closest allowed size that doesn't yet have an icon. - std::set<int> allowed_sizes( - extension_misc::kExtensionIconSizes, - extension_misc::kExtensionIconSizes + - extension_misc::kNumExtensionIconSizes); - std::vector<SkBitmap> downloaded_icons; - for (FaviconDownloader::FaviconMap::const_iterator map_it = bitmaps.begin(); - map_it != bitmaps.end(); ++map_it) { - for (std::vector<SkBitmap>::const_iterator bitmap_it = - map_it->second.begin(); - bitmap_it != map_it->second.end(); ++bitmap_it) { - if (bitmap_it->empty() || bitmap_it->width() != bitmap_it->height()) - continue; - - downloaded_icons.push_back(*bitmap_it); - } - } - - // If there are icons that don't match the accepted icon sizes, find the - // closest bigger icon to the accepted sizes and resize the icon to it. An - // icon will be resized and used for at most one size. - std::map<int, SkBitmap> resized_bitmaps( - TabHelper::ConstrainBitmapsToSizes(downloaded_icons, - allowed_sizes)); - - // Generate container icons from smaller icons. - const int kIconSizesToGenerate[] = { - extension_misc::EXTENSION_ICON_SMALL, - extension_misc::EXTENSION_ICON_MEDIUM, - }; - const std::set<int> generate_sizes( - kIconSizesToGenerate, - kIconSizesToGenerate + arraysize(kIconSizesToGenerate)); - - // Only generate icons if larger icons don't exist. This means the app - // launcher and the taskbar will do their best downsizing large icons and - // these container icons are only generated as a last resort against upscaling - // a smaller icon. - if (resized_bitmaps.lower_bound(*generate_sizes.rbegin()) == - resized_bitmaps.end()) { - // Generate these from biggest to smallest so we don't end up with - // concentric container icons. - for (std::set<int>::const_reverse_iterator it = generate_sizes.rbegin(); - it != generate_sizes.rend(); ++it) { - TabHelper::GenerateContainerIcon(&resized_bitmaps, *it); - } - } - - // Populate a the icon data into the WebApplicationInfo we are using to - // install the bookmark app. - for (std::map<int, SkBitmap>::const_iterator resized_bitmaps_it = - resized_bitmaps.begin(); - resized_bitmaps_it != resized_bitmaps.end(); ++resized_bitmaps_it) { - WebApplicationInfo::IconInfo icon_info; - icon_info.data = resized_bitmaps_it->second; - icon_info.width = icon_info.data.width(); - icon_info.height = icon_info.data.height(); - web_app_info_.icons.push_back(icon_info); - } - - // Install the app. - Profile* profile = - Profile::FromBrowserContext(web_contents()->GetBrowserContext()); - scoped_refptr<extensions::CrxInstaller> installer( - extensions::CrxInstaller::CreateSilent(profile->GetExtensionService())); - installer->set_error_on_unsupported_requirements(true); - installer->InstallWebApp(web_app_info_); - favicon_downloader_.reset(); -} - void TabHelper::DidCloneToNewWebContents(WebContents* old_web_contents, WebContents* new_web_contents) { // When the WebContents that this is attached to is cloned, give the new clone @@ -544,7 +332,18 @@ void TabHelper::OnDidGetApplicationInfo(int32 page_id, break; } case CREATE_HOSTED_APP: { - CreateHostedApp(); + if (web_app_info_.app_url.is_empty()) + web_app_info_.app_url = web_contents()->GetURL(); + + if (web_app_info_.title.empty()) + web_app_info_.title = web_contents()->GetTitle(); + if (web_app_info_.title.empty()) + web_app_info_.title = base::UTF8ToUTF16(web_app_info_.app_url.spec()); + + bookmark_app_helper_.reset(new BookmarkAppHelper( + profile_->GetExtensionService(), web_app_info_, web_contents())); + bookmark_app_helper_->Create(base::Bind( + &TabHelper::FinishCreateBookmarkApp, base::Unretained(this))); break; } case UPDATE_SHORTCUT: { @@ -745,37 +544,6 @@ void TabHelper::Observe(int type, } break; } - case chrome::NOTIFICATION_CRX_INSTALLER_DONE: { - if (pending_web_app_action_ != CREATE_HOSTED_APP) - return; - - pending_web_app_action_ = NONE; - - const Extension* extension = - content::Details<const Extension>(details).ptr(); - if (!extension || - AppLaunchInfo::GetLaunchWebURL(extension) != web_app_info_.app_url) - return; - -#if defined(OS_CHROMEOS) - ChromeLauncherController::instance()->PinAppWithID(extension->id()); -#endif - - // Android does not implement browser_finder.cc. -#if !defined(OS_ANDROID) - Browser* browser = - chrome::FindBrowserWithWebContents(web_contents()); - if (browser) { - browser->window()->ShowBookmarkAppBubble(web_app_info_, - extension->id()); - } -#endif - } - case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR: { - if (pending_web_app_action_ == CREATE_HOSTED_APP) - pending_web_app_action_ = NONE; - break; - } } } diff --git a/chrome/browser/extensions/tab_helper.h b/chrome/browser/extensions/tab_helper.h index bb30c76..42603de 100644 --- a/chrome/browser/extensions/tab_helper.h +++ b/chrome/browser/extensions/tab_helper.h @@ -35,6 +35,7 @@ class Image; } namespace extensions { +class BookmarkAppHelper; class Extension; class LocationBarController; class ScriptExecutor; @@ -86,21 +87,6 @@ class TabHelper : public content::WebContentsObserver, TabHelper* tab_helper_; }; - // This finds the closest not-smaller bitmap in |bitmaps| for each size in - // |sizes| and resizes it to that size. This returns a map of sizes to bitmaps - // which contains only bitmaps of a size in |sizes| and at most one bitmap of - // each size. - static std::map<int, SkBitmap> ConstrainBitmapsToSizes( - const std::vector<SkBitmap>& bitmaps, - const std::set<int>& sizes); - - // Adds a square container icon of |output_size| pixels to |bitmaps| by - // centering the biggest smaller icon in |bitmaps| and drawing a rounded - // rectangle with strip of the that icon's dominant color at the bottom. - // Does nothing if an icon of |output_size| already exists in |bitmaps|. - static void GenerateContainerIcon(std::map<int, SkBitmap>* bitmaps, - int output_size); - virtual ~TabHelper(); void AddScriptExecutionObserver(ScriptExecutionObserver* observer) { @@ -180,11 +166,9 @@ class TabHelper : public content::WebContentsObserver, explicit TabHelper(content::WebContents* web_contents); friend class content::WebContentsUserData<TabHelper>; - // Creates a hosted app for the current tab. Requires the |web_app_info_| to - // be populated. - void CreateHostedApp(); - void FinishCreateHostedApp( - bool success, const std::map<GURL, std::vector<SkBitmap> >& bitmaps); + // Displays UI for completion of creating a bookmark hosted app. + void FinishCreateBookmarkApp(const extensions::Extension* extension, + const WebApplicationInfo& web_app_info); // content::WebContentsObserver overrides. virtual void RenderViewCreated( @@ -283,7 +267,7 @@ class TabHelper : public content::WebContentsObserver, scoped_ptr<ActiveTabPermissionGranter> active_tab_permission_granter_; - scoped_ptr<FaviconDownloader> favicon_downloader_; + scoped_ptr<BookmarkAppHelper> bookmark_app_helper_; Profile* profile_; diff --git a/chrome/browser/extensions/tab_helper_unittest.cc b/chrome/browser/extensions/tab_helper_unittest.cc deleted file mode 100644 index 7169063..0000000 --- a/chrome/browser/extensions/tab_helper_unittest.cc +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2014 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chrome/browser/extensions/tab_helper.h" - -#include "testing/gtest/include/gtest/gtest.h" -#include "third_party/skia/include/core/SkBitmap.h" - -namespace { - -SkBitmap CreateSquareBitmapWithColor(int size, SkColor color) { - SkBitmap bitmap; - bitmap.setConfig(SkBitmap::kARGB_8888_Config, size, size); - bitmap.allocPixels(); - bitmap.eraseColor(color); - return bitmap; -} - -void ValidateBitmapSizeAndColor(SkBitmap bitmap, int size, SkColor color) { - // Obtain pixel lock to access pixels. - SkAutoLockPixels lock(bitmap); - EXPECT_EQ(color, bitmap.getColor(0, 0)); - EXPECT_EQ(size, bitmap.width()); - EXPECT_EQ(size, bitmap.height()); -} - -class TabHelperTest : public testing::Test { - protected: - TabHelperTest() { - } - - virtual ~TabHelperTest() { - } - - private: - DISALLOW_COPY_AND_ASSIGN(TabHelperTest); -}; - -} // namespace - -TEST_F(TabHelperTest, ConstrainBitmapsToSizes) { - std::set<int> desired_sizes; - desired_sizes.insert(16); - desired_sizes.insert(32); - desired_sizes.insert(128); - desired_sizes.insert(256); - - { - std::vector<SkBitmap> bitmaps; - bitmaps.push_back(CreateSquareBitmapWithColor(16, SK_ColorRED)); - bitmaps.push_back(CreateSquareBitmapWithColor(32, SK_ColorGREEN)); - bitmaps.push_back(CreateSquareBitmapWithColor(48, SK_ColorBLUE)); - bitmaps.push_back(CreateSquareBitmapWithColor(144, SK_ColorYELLOW)); - - std::map<int, SkBitmap> results( - extensions::TabHelper::ConstrainBitmapsToSizes(bitmaps, desired_sizes)); - - EXPECT_EQ(3u, results.size()); - ValidateBitmapSizeAndColor(results[16], 16, SK_ColorRED); - ValidateBitmapSizeAndColor(results[32], 32, SK_ColorGREEN); - ValidateBitmapSizeAndColor(results[128], 128, SK_ColorYELLOW); - } - { - std::vector<SkBitmap> bitmaps; - bitmaps.push_back(CreateSquareBitmapWithColor(512, SK_ColorRED)); - bitmaps.push_back(CreateSquareBitmapWithColor(18, SK_ColorGREEN)); - bitmaps.push_back(CreateSquareBitmapWithColor(33, SK_ColorBLUE)); - bitmaps.push_back(CreateSquareBitmapWithColor(17, SK_ColorYELLOW)); - - std::map<int, SkBitmap> results( - extensions::TabHelper::ConstrainBitmapsToSizes(bitmaps, desired_sizes)); - - EXPECT_EQ(3u, results.size()); - ValidateBitmapSizeAndColor(results[16], 16, SK_ColorYELLOW); - ValidateBitmapSizeAndColor(results[32], 32, SK_ColorBLUE); - ValidateBitmapSizeAndColor(results[256], 256, SK_ColorRED); - } -} - -TEST_F(TabHelperTest, GenerateIcons) { - { - // The 32x32 icon should be generated from the 16x16 icon. - std::map<int, SkBitmap> bitmaps; - bitmaps[16] = CreateSquareBitmapWithColor(16, SK_ColorRED); - extensions::TabHelper::GenerateContainerIcon(&bitmaps, 32); - EXPECT_EQ(1u, bitmaps.count(32)); - EXPECT_EQ(32, bitmaps[32].width()); - } - { - // The 32x32 icon should not be generated because no smaller icon exists. - std::map<int, SkBitmap> bitmaps; - bitmaps[48] = CreateSquareBitmapWithColor(48, SK_ColorRED); - extensions::TabHelper::GenerateContainerIcon(&bitmaps, 32); - EXPECT_EQ(0u, bitmaps.count(32)); - } - { - // The 32x32 icon should not be generated with no base icons. - std::map<int, SkBitmap> bitmaps; - extensions::TabHelper::GenerateContainerIcon(&bitmaps, 32); - EXPECT_EQ(0u, bitmaps.count(32)); - } -} diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi index a0b5081..c0119b0 100644 --- a/chrome/chrome_browser_extensions.gypi +++ b/chrome/chrome_browser_extensions.gypi @@ -609,6 +609,8 @@ 'browser/extensions/blacklist_state_fetcher.h', 'browser/extensions/blob_reader.cc', 'browser/extensions/blob_reader.h', + 'browser/extensions/bookmark_app_helper.cc', + 'browser/extensions/bookmark_app_helper.h', 'browser/extensions/browser_action_test_util.h', 'browser/extensions/browser_context_keyed_service_factories.cc', 'browser/extensions/browser_context_keyed_service_factories.h', diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index f5da7d0..a9d737b 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -917,6 +917,7 @@ 'browser/extensions/app_sync_data_unittest.cc', 'browser/extensions/blacklist_unittest.cc', 'browser/extensions/blacklist_state_fetcher_unittest.cc', + 'browser/extensions/bookmark_app_helper_unittest.cc', 'browser/extensions/browser_permissions_policy_delegate_unittest.cc', 'browser/extensions/chrome_app_sorting_unittest.cc', 'browser/extensions/component_loader_unittest.cc', @@ -960,7 +961,6 @@ 'browser/extensions/policy_handlers_unittest.cc', 'browser/extensions/sandboxed_unpacker_unittest.cc', 'browser/extensions/standard_management_policy_provider_unittest.cc', - 'browser/extensions/tab_helper_unittest.cc', 'browser/extensions/token_cache/token_cache_service_unittest.cc', 'browser/extensions/updater/extension_cache_fake.h', 'browser/extensions/updater/extension_cache_fake.cc', |