summaryrefslogtreecommitdiffstats
path: root/chrome/common
diff options
context:
space:
mode:
authortbarzic@chromium.org <tbarzic@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-09-18 05:53:45 +0000
committertbarzic@chromium.org <tbarzic@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-09-18 05:53:45 +0000
commit684cc6a02fb5e536a52cbccece01ef5c319252f1 (patch)
treea79b30a8b8d997a71e8e323fa10672c2497a3436 /chrome/common
parent910b34bb21c120b78aa84648d09330a415d538dc (diff)
downloadchromium_src-684cc6a02fb5e536a52cbccece01ef5c319252f1.zip
chromium_src-684cc6a02fb5e536a52cbccece01ef5c319252f1.tar.gz
chromium_src-684cc6a02fb5e536a52cbccece01ef5c319252f1.tar.bz2
Change browser/page action default icon defined in manifest to support hidpi.
To support hidpi for browser action default icons, ability to define dictionary of icon in manifest as default icons is added. Defining images of sizes 19 and 38 is allowed (other dictionary values will be ignored). The image to be painted will be determined based on screen density. Similary, for script badges, default icon is determined using 16 and 32 px icon defined in extension manifest. I have extracted actual icon loading code to extension_action_icon_factory.h/.cc, so it doesn't have to be implemented for all platform specific solutions. BUG=138025,135271 Review URL: https://chromiumcodereview.appspot.com/10905005 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@157309 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/common')
-rw-r--r--chrome/common/extensions/docs/templates/intros/browserAction.html45
-rw-r--r--chrome/common/extensions/docs/templates/intros/pageAction.html29
-rw-r--r--chrome/common/extensions/extension.cc135
-rw-r--r--chrome/common/extensions/extension.h3
-rw-r--r--chrome/common/extensions/extension_action.cc60
-rw-r--r--chrome/common/extensions/extension_action.h46
-rw-r--r--chrome/common/extensions/extension_action_unittest.cc54
-rw-r--r--chrome/common/extensions/extension_constants.cc16
-rw-r--r--chrome/common/extensions/extension_constants.h9
-rw-r--r--chrome/common/extensions/extension_file_util.cc59
-rw-r--r--chrome/common/extensions/extension_unittest.cc6
-rw-r--r--chrome/common/extensions/manifest_tests/extension_manifests_browseraction_unittest.cc102
-rw-r--r--chrome/common/extensions/manifest_tests/extension_manifests_pageaction_unittest.cc2
-rw-r--r--chrome/common/extensions/manifest_tests/extension_manifests_scriptbadge_unittest.cc54
14 files changed, 414 insertions, 206 deletions
diff --git a/chrome/common/extensions/docs/templates/intros/browserAction.html b/chrome/common/extensions/docs/templates/intros/browserAction.html
index d4c7476..c414c96 100644
--- a/chrome/common/extensions/docs/templates/intros/browserAction.html
+++ b/chrome/common/extensions/docs/templates/intros/browserAction.html
@@ -44,13 +44,34 @@ like this:
"name": "My extension",
...
<b>"browser_action": {
- "default_icon": "images/icon19.png", <em>// optional</em>
+ "default_icon": { <em>// optional</em>
+ "19": "images/icon19.png", <em>// optional</em>
+ "38": "images/icon38.png" <em>// optional</em>
+ },
"default_title": "Google Mail", <em>// optional; shown in tooltip</em>
"default_popup": "popup.html" <em>// optional</em>
}</b>,
...
}</pre>
+<p>
+If you only provide one of the 19px or 38px icon size, the extension system will
+scale the icon you provide to the pixel density of the user's display, which
+can lose detail or make it look fuzzy. The old syntax for registering the
+default icon is still supported:
+</p>
+
+<pre>{
+ "name": "My extension",
+ ...
+ <b>"browser_action": {
+ ...
+ "default_icon": "images/icon19.png" <em>// optional</em>
+ <em>// equivalent to "default_icon": { "19": "images/icon19.png" }</em>
+ }</b>,
+ ...
+}</pre>
+
<h2 id="ui">Parts of the UI</h2>
<p>
@@ -62,9 +83,9 @@ and a <a href="#popups">popup</a>.
<h3 id="icon">Icon</h3>
-<p>Browser action icons can be up to 19 pixels wide and high.
- Larger icons are resized to fit, but for best results,
- use a 19-pixel square icon.</p>
+<p>Browser action icons can be up to 19 dips (device-independent pixels)
+ wide and high. Larger icons are resized to fit, but for best results,
+ use a 19-dip square icon.</p>
<p>You can set the icon in two ways:
using a static image or using the
@@ -80,10 +101,18 @@ and a <a href="#popups">popup</a>.
</p>
<p>To set the icon,
-use the <b>default_icon</b> field of <b>browser_action</b>
-in the <a href="#manifest">manifest</a>,
-or call the <a href="#method-setIcon">setIcon()</a> method.
+ use the <b>default_icon</b> field of <b>browser_action</b>
+ in the <a href="#manifest">manifest</a>,
+ or call the <a href="#method-setIcon">setIcon()</a> method.
+ </p>
+<p>To properly display icon when screen pixel density (ratio
+ <code>size_in_pixel / size_in_dip</code>) is different than 1,
+ the icon can be defined as set of images with different sizes.
+ The actual image to display will be selected from the set to
+ best fit the pixel size of 19 dip icon.
+ At the moment, the icon set can contain images with pixel sizes 19 and 38.
+ </p>
<h3 id="tooltip">Tooltip</h3>
@@ -169,4 +198,4 @@ You can find simple examples of using browser actions in the
directory.
For other examples and for help in viewing the source code, see
<a href="samples.html">Samples</a>.
-</p> \ No newline at end of file
+</p>
diff --git a/chrome/common/extensions/docs/templates/intros/pageAction.html b/chrome/common/extensions/docs/templates/intros/pageAction.html
index 86930f1..c490abf 100644
--- a/chrome/common/extensions/docs/templates/intros/pageAction.html
+++ b/chrome/common/extensions/docs/templates/intros/pageAction.html
@@ -43,9 +43,30 @@ like this:
"name": "My extension",
...
<b>"page_action": {
- "default_icon": "icons/foo.png", <em>// optional</em>
- "default_title": "Do action", <em>// optional; shown in tooltip</em>
- "default_popup": "popup.html" <em>// optional</em>
+ "default_icon": { <em>// optional</em>
+ "19": "images/icon19.png", <em>// optional</em>
+ "38": "images/icon38.png" <em>// optional</em>
+ },
+ "default_title": "Google Mail", <em>// optional; shown in tooltip</em>
+ "default_popup": "popup.html" <em>// optional</em>
+ }</b>,
+ ...
+}</pre>
+
+<p>
+If you only provide one of the 19px or 38px icon size, the extension system will
+scale the icon you provide to the pixel density of the user's display, which
+can lose detail or make it look fuzzy. The old syntax for registering the
+default icon is still supported:
+</p>
+
+<pre>{
+ "name": "My extension",
+ ...
+ <b>"page_action": {
+ ...
+ "default_icon": "images/icon19.png" <em>// optional</em>
+ <em>// equivalent to "default_icon": { "19": "images/icon19.png" }</em>
}</b>,
...
}</pre>
@@ -105,4 +126,4 @@ You can find simple examples of using page actions in the
directory.
For other examples and for help in viewing the source code, see
<a href="samples.html">Samples</a>.
-</p> \ No newline at end of file
+</p>
diff --git a/chrome/common/extensions/extension.cc b/chrome/common/extensions/extension.cc
index faa4933..f042c988e 100644
--- a/chrome/common/extensions/extension.cc
+++ b/chrome/common/extensions/extension.cc
@@ -130,6 +130,40 @@ static void ConvertHexadecimalToIDAlphabet(std::string* id) {
}
}
+// Loads icon paths defined in dictionary |icons_value| into ExtensionIconSet
+// |icons|. |icons_value| is a dictionary value {icon size -> icon path}. Icons
+// in |icons_value| whose size is not in |icon_sizes| will be ignored.
+// Returns success. If load fails, |error| will be set.
+bool LoadIconsFromDictionary(const DictionaryValue* icons_value,
+ const int* icon_sizes,
+ size_t num_icon_sizes,
+ ExtensionIconSet* icons,
+ string16* error) {
+ DCHECK(icons);
+ for (size_t i = 0; i < num_icon_sizes; ++i) {
+ std::string key = base::IntToString(icon_sizes[i]);
+ if (icons_value->HasKey(key)) {
+ std::string icon_path;
+ if (!icons_value->GetString(key, &icon_path)) {
+ *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidIconPath, key);
+ return false;
+ }
+
+ if (!icon_path.empty() && icon_path[0] == '/')
+ icon_path = icon_path.substr(1);
+
+ if (icon_path.empty()) {
+ *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidIconPath, key);
+ return false;
+ }
+ icons->Add(icon_sizes[i], icon_path);
+ }
+ }
+ return true;
+}
+
// A singleton object containing global data needed by the extension objects.
class ExtensionConfig {
public:
@@ -823,7 +857,9 @@ scoped_ptr<ExtensionAction> Extension::LoadExtensionActionHelper(
return scoped_ptr<ExtensionAction>();
}
- result->set_default_icon_path(path);
+ scoped_ptr<ExtensionIconSet> icon_set(new ExtensionIconSet);
+ icon_set->Add(extension_misc::EXTENSION_ICON_ACTION, path);
+ result->set_default_icon(icon_set.Pass());
break;
}
}
@@ -838,16 +874,34 @@ scoped_ptr<ExtensionAction> Extension::LoadExtensionActionHelper(
}
}
- std::string default_icon;
// Read the page action |default_icon| (optional).
+ // The |default_icon| value can be either dictionary {icon size -> icon path}
+ // or non empty string value.
if (extension_action->HasKey(keys::kPageActionDefaultIcon)) {
- if (!extension_action->GetString(keys::kPageActionDefaultIcon,
- &default_icon) ||
- default_icon.empty()) {
+ const DictionaryValue* icons_value = NULL;
+ std::string default_icon;
+ if (extension_action->GetDictionary(keys::kPageActionDefaultIcon,
+ &icons_value)) {
+ scoped_ptr<ExtensionIconSet> default_icons(new ExtensionIconSet());
+ if (!LoadIconsFromDictionary(icons_value,
+ extension_misc::kExtensionActionIconSizes,
+ extension_misc::kNumExtensionActionIconSizes,
+ default_icons.get(),
+ error)) {
+ return scoped_ptr<ExtensionAction>();
+ }
+
+ result->set_default_icon(default_icons.Pass());
+ } else if (extension_action->GetString(keys::kPageActionDefaultIcon,
+ &default_icon) &&
+ !default_icon.empty()) {
+ scoped_ptr<ExtensionIconSet> icon_set(new ExtensionIconSet);
+ icon_set->Add(extension_misc::EXTENSION_ICON_ACTION, default_icon);
+ result->set_default_icon(icon_set.Pass());
+ } else {
*error = ASCIIToUTF16(errors::kInvalidPageActionIconPath);
return scoped_ptr<ExtensionAction>();
}
- result->set_default_icon_path(default_icon);
}
// Read the page action title from |default_title| if present, |name| if not
@@ -1375,28 +1429,11 @@ bool Extension::LoadIcons(string16* error) {
return false;
}
- for (size_t i = 0; i < extension_misc::kNumExtensionIconSizes; ++i) {
- std::string key = base::IntToString(extension_misc::kExtensionIconSizes[i]);
- if (icons_value->HasKey(key)) {
- std::string icon_path;
- if (!icons_value->GetString(key, &icon_path)) {
- *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
- errors::kInvalidIconPath, key);
- return false;
- }
-
- if (!icon_path.empty() && icon_path[0] == '/')
- icon_path = icon_path.substr(1);
-
- if (icon_path.empty()) {
- *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
- errors::kInvalidIconPath, key);
- return false;
- }
- icons_.Add(extension_misc::kExtensionIconSizes[i], icon_path);
- }
- }
- return true;
+ return LoadIconsFromDictionary(icons_value,
+ extension_misc::kExtensionIconSizes,
+ extension_misc::kNumExtensionIconSizes,
+ &icons_,
+ error);
}
bool Extension::LoadCommands(string16* error) {
@@ -2369,20 +2406,26 @@ bool Extension::LoadScriptBadge(string16* error) {
}
script_badge_->SetTitle(ExtensionAction::kDefaultTabId, name());
- if (!script_badge_->default_icon_path().empty()) {
+ if (script_badge_->default_icon()) {
install_warnings_.push_back(
InstallWarning(InstallWarning::FORMAT_TEXT,
errors::kScriptBadgeIconIgnored));
}
- std::string icon16_path = icons().Get(extension_misc::EXTENSION_ICON_BITTY,
- ExtensionIconSet::MATCH_EXACTLY);
- if (!icon16_path.empty()) {
- script_badge_->set_default_icon_path(icon16_path);
+
+ scoped_ptr<ExtensionIconSet> icon_set(new ExtensionIconSet);
+
+ for (size_t i = 0; i < extension_misc::kNumScriptBadgeIconSizes; i++) {
+ std::string path = icons().Get(extension_misc::kScriptBadgeIconSizes[i],
+ ExtensionIconSet::MATCH_EXACTLY);
+ if (!path.empty()) {
+ icon_set->Add(extension_misc::kScriptBadgeIconSizes[i], path);
+ }
+ }
+
+ if (!icon_set->map().empty()) {
+ script_badge_->set_default_icon(icon_set.Pass());
} else {
- script_badge_->SetIcon(
- ExtensionAction::kDefaultTabId,
- ui::ResourceBundle::GetSharedInstance().GetImageNamed(
- IDR_EXTENSIONS_FAVICON));
+ script_badge_->set_default_icon(scoped_ptr<ExtensionIconSet>());
}
return true;
@@ -3200,16 +3243,22 @@ std::set<FilePath> Extension::GetBrowserImages() const {
}
}
- // Page action icons.
if (page_action()) {
- image_paths.insert(FilePath::FromWStringHack(UTF8ToWide(
- page_action()->default_icon_path())));
+ for (ExtensionIconSet::IconMap::const_iterator iter =
+ page_action()->default_icon()->map().begin();
+ iter != page_action()->default_icon()->map().end();
+ ++iter) {
+ image_paths.insert(FilePath::FromWStringHack(UTF8ToWide(iter->second)));
+ }
}
- // Browser action icons.
if (browser_action()) {
- image_paths.insert(FilePath::FromWStringHack(UTF8ToWide(
- browser_action()->default_icon_path())));
+ for (ExtensionIconSet::IconMap::const_iterator iter =
+ browser_action()->default_icon()->map().begin();
+ iter != browser_action()->default_icon()->map().end();
+ ++iter) {
+ image_paths.insert(FilePath::FromWStringHack(UTF8ToWide(iter->second)));
+ }
}
return image_paths;
diff --git a/chrome/common/extensions/extension.h b/chrome/common/extensions/extension.h
index 55e8d2a..d0aef2a 100644
--- a/chrome/common/extensions/extension.h
+++ b/chrome/common/extensions/extension.h
@@ -629,8 +629,7 @@ class Extension : public base::RefCountedThreadSafe<Extension> {
return !omnibox_keyword().empty() ||
browser_action() ||
(page_action() &&
- (page_action_command() ||
- !page_action()->default_icon_path().empty()));
+ (page_action_command() || page_action()->default_icon()));
}
const FileBrowserHandlerList* file_browser_handlers() const {
return file_browser_handlers_.get();
diff --git a/chrome/common/extensions/extension_action.cc b/chrome/common/extensions/extension_action.cc
index 448aa11..c857864 100644
--- a/chrome/common/extensions/extension_action.cc
+++ b/chrome/common/extensions/extension_action.cc
@@ -10,6 +10,7 @@
#include "base/logging.h"
#include "base/message_loop.h"
#include "chrome/common/badge_util.h"
+#include "chrome/common/extensions/extension_constants.h"
#include "googleurl/src/gurl.h"
#include "grit/theme_resources.h"
#include "grit/ui_resources.h"
@@ -253,11 +254,28 @@ scoped_ptr<ExtensionAction> ExtensionAction::CopyForTest() const {
copy->badge_text_color_ = badge_text_color_;
copy->appearance_ = appearance_;
copy->icon_animation_ = icon_animation_;
- copy->default_icon_path_ = default_icon_path_;
copy->id_ = id_;
+
+ if (default_icon_.get())
+ copy->default_icon_.reset(new ExtensionIconSet(*default_icon_));
+
return copy.Pass();
}
+// static
+int ExtensionAction::GetIconSizeForType(ExtensionAction::Type type) {
+ switch (type) {
+ case ExtensionAction::TYPE_BROWSER:
+ case ExtensionAction::TYPE_PAGE:
+ return extension_misc::EXTENSION_ICON_ACTION;
+ case ExtensionAction::TYPE_SCRIPT_BADGE:
+ return extension_misc::EXTENSION_ICON_BITTY;
+ default:
+ NOTREACHED();
+ return 0;
+ }
+}
+
void ExtensionAction::SetPopupUrl(int tab_id, const GURL& url) {
// We store |url| even if it is empty, rather than removing a URL from the
// map. If an extension has a default popup, and removes it for a tab via
@@ -275,27 +293,14 @@ GURL ExtensionAction::GetPopupUrl(int tab_id) const {
return GetValue(&popup_url_, tab_id);
}
-void ExtensionAction::CacheIcon(const gfx::Image& icon) {
- if (!icon.IsEmpty())
- cached_icon_.reset(new gfx::ImageSkia(*icon.ToImageSkia()));
-}
-
void ExtensionAction::SetIcon(int tab_id, const gfx::Image& image) {
SetValue(&icon_, tab_id, image.AsImageSkia());
}
-gfx::Image ExtensionAction::GetIcon(int tab_id) const {
- // Check if a specific icon is set for this tab.
- gfx::ImageSkia icon = GetExplicitlySetIcon(tab_id);
- if (icon.isNull()) {
- if (cached_icon_.get()) {
- icon = *cached_icon_;
- } else {
- icon = *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
- IDR_EXTENSIONS_FAVICON);
- }
- }
-
+gfx::Image ExtensionAction::ApplyAttentionAndAnimation(
+ const gfx::ImageSkia& original_icon,
+ int tab_id) const {
+ gfx::ImageSkia icon = original_icon;
if (GetValue(&appearance_, tab_id) == WANTS_ATTENTION)
icon = gfx::ImageSkia(new GetAttentionImageSource(icon), icon.size());
@@ -343,7 +348,7 @@ void ExtensionAction::PaintBadge(gfx::Canvas* canvas,
GetBadgeText(tab_id),
GetBadgeTextColor(tab_id),
GetBadgeBackgroundColor(tab_id),
- GetValue(&icon_, tab_id).size().width());
+ GetIconWidth(tab_id));
}
gfx::ImageSkia ExtensionAction::GetIconWithBadge(
@@ -362,6 +367,23 @@ gfx::ImageSkia ExtensionAction::GetIconWithBadge(
icon.size());
}
+// Determines which icon would be returned by |GetIcon|, and returns its width.
+int ExtensionAction::GetIconWidth(int tab_id) const {
+ // If icon has been set, return its width.
+ gfx::ImageSkia icon = GetValue(&icon_, tab_id);
+ if (!icon.isNull())
+ return icon.width();
+ // If there is a default icon, the icon width will be set depending on our
+ // action type.
+ if (default_icon_.get())
+ return GetIconSizeForType(action_type());
+
+ // If no icon has been set and there is no default icon, we need favicon
+ // width.
+ return ui::ResourceBundle::GetSharedInstance().GetImageNamed(
+ IDR_EXTENSIONS_FAVICON).ToImageSkia()->width();
+}
+
// static
void ExtensionAction::DoPaintBadge(gfx::Canvas* canvas,
const gfx::Rect& bounds,
diff --git a/chrome/common/extensions/extension_action.h b/chrome/common/extensions/extension_action.h
index b5a1339..1dee6c2 100644
--- a/chrome/common/extensions/extension_action.h
+++ b/chrome/common/extensions/extension_action.h
@@ -12,8 +12,10 @@
#include "base/basictypes.h"
#include "base/memory/linked_ptr.h"
#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
+#include "chrome/common/extensions/extension_icon_set.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/animation/linear_animation.h"
@@ -123,6 +125,11 @@ class ExtensionAction {
// It doesn't make sense to copy of an ExtensionAction except in tests.
scoped_ptr<ExtensionAction> CopyForTest() const;
+ // Given the extension action type, returns the size the extension action icon
+ // should have. The icon should be square, so only one dimension is
+ // returned.
+ static int GetIconSizeForType(Type type);
+
// extension id
const std::string& extension_id() const { return extension_id_; }
@@ -158,33 +165,28 @@ class ExtensionAction {
// Icons are a bit different because the default value can be set to either a
// bitmap or a path. However, conceptually, there is only one default icon.
// Setting the default icon using a path clears the bitmap and vice-versa.
-
- // Since ExtensionAction, living in common/, can't interact with the browser
- // to load images, the UI code needs to load the icon. Load the image there
- // using an ImageLoadingTracker and call CacheIcon(image) with the result.
- //
- // If an image is cached redundantly, the first load will be used.
- void CacheIcon(const gfx::Image& icon);
+ // To retrieve the icon for the extension action, use
+ // ExtensionActionIconFactory.
// Set this action's icon bitmap on a specific tab.
void SetIcon(int tab_id, const gfx::Image& image);
- // Get the icon for a tab, or the default if no icon was set for this tab,
- // retrieving icons that have been specified by path from the previous
- // arguments to CacheIcon(). If the default icon isn't found in the cache,
- // returns the puzzle piece icon.
- gfx::Image GetIcon(int tab_id) const;
+ // Applies the attention and animation image transformations registered for
+ // the tab on the provided icon.
+ gfx::Image ApplyAttentionAndAnimation(const gfx::ImageSkia& icon,
+ int tab_id) const;
// Gets the icon that has been set using |SetIcon| for the tab.
gfx::ImageSkia GetExplicitlySetIcon(int tab_id) const;
// Non-tab-specific icon path. This is used to support the default_icon key of
// page and browser actions.
- void set_default_icon_path(const std::string& path) {
- default_icon_path_ = path;
+ void set_default_icon(scoped_ptr<ExtensionIconSet> icon_set) {
+ default_icon_ = icon_set.Pass();
}
- const std::string& default_icon_path() const {
- return default_icon_path_;
+
+ const ExtensionIconSet* default_icon() const {
+ return default_icon_.get();
}
// Set this action's badge text on a specific tab.
@@ -252,6 +254,11 @@ class ExtensionAction {
gfx::ImageSkia ApplyIconAnimation(int tab_id,
const gfx::ImageSkia& orig) const;
+ // Returns width of the current icon for tab_id.
+ // TODO(tbarzic): The icon selection is done in ExtensionActionIconFactory.
+ // We should probably move this there too.
+ int GetIconWidth(int tab_id) const;
+
// Paints badge with specified parameters to |canvas|.
static void DoPaintBadge(gfx::Canvas* canvas,
const gfx::Rect& bounds,
@@ -306,15 +313,14 @@ class ExtensionAction {
// NULLs to prevent the map from growing without bound.
mutable std::map<int, base::WeakPtr<IconAnimation> > icon_animation_;
- std::string default_icon_path_;
+ // ExtensionIconSet containing paths to bitmaps from which default icon's
+ // image representations will be selected.
+ scoped_ptr<const ExtensionIconSet> default_icon_;
// The id for the ExtensionAction, for example: "RssPageAction". This is
// needed for compat with an older version of the page actions API.
std::string id_;
- // Saves the arguments from CacheIcon() calls.
- scoped_ptr<gfx::ImageSkia> cached_icon_;
-
// True if the ExtensionAction's settings have changed from what was
// specified in the manifest.
bool has_changed_;
diff --git a/chrome/common/extensions/extension_action_unittest.cc b/chrome/common/extensions/extension_action_unittest.cc
index f34ab88..7a52911 100644
--- a/chrome/common/extensions/extension_action_unittest.cc
+++ b/chrome/common/extensions/extension_action_unittest.cc
@@ -2,44 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "base/file_path.h"
-#include "base/file_util.h"
#include "base/message_loop.h"
-#include "base/path_service.h"
-#include "chrome/common/chrome_paths.h"
#include "chrome/common/extensions/extension_action.h"
#include "googleurl/src/gurl.h"
-#include "grit/theme_resources.h"
#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/skia/include/core/SkBitmap.h"
-#include "ui/base/resource/resource_bundle.h"
-#include "ui/gfx/image/image_skia.h"
-#include "ui/gfx/skia_util.h"
-#include "webkit/glue/image_decoder.h"
namespace {
-bool ImagesAreEqual(const gfx::Image& i1, const gfx::Image& i2) {
- return gfx::BitmapsAreEqual(*i1.ToSkBitmap(), *i2.ToSkBitmap());
-}
-
-gfx::Image LoadIcon(const std::string& filename) {
- FilePath path;
- PathService::Get(chrome::DIR_TEST_DATA, &path);
- path = path.AppendASCII("extensions").AppendASCII(filename);
-
- std::string file_contents;
- file_util::ReadFileToString(path, &file_contents);
- const unsigned char* data =
- reinterpret_cast<const unsigned char*>(file_contents.data());
-
- SkBitmap bitmap;
- webkit_glue::ImageDecoder decoder;
- bitmap = decoder.Decode(data, file_contents.length());
-
- return gfx::Image(bitmap);
-}
-
class ExtensionActionTest : public testing::Test {
public:
ExtensionActionTest()
@@ -63,29 +32,6 @@ TEST_F(ExtensionActionTest, Title) {
ASSERT_EQ("baz", action.GetTitle(100));
}
-TEST_F(ExtensionActionTest, Icon) {
- gfx::Image puzzle_piece =
- ui::ResourceBundle::GetSharedInstance().GetImageNamed(
- IDR_EXTENSIONS_FAVICON);
- gfx::Image icon1 = LoadIcon("icon1.png");
- gfx::Image icon2 = LoadIcon("icon2.png");
- ASSERT_TRUE(ImagesAreEqual(puzzle_piece, action.GetIcon(1)));
-
- action.set_default_icon_path("the_default.png");
- ASSERT_TRUE(ImagesAreEqual(puzzle_piece, action.GetIcon(1)))
- << "Still returns the puzzle piece because the image isn't loaded yet.";
- action.CacheIcon(icon2);
- ASSERT_TRUE(ImagesAreEqual(icon2, action.GetIcon(1)));
-
- action.SetIcon(ExtensionAction::kDefaultTabId, icon1);
- ASSERT_TRUE(ImagesAreEqual(icon1, action.GetIcon(100)))
- << "SetIcon(kDefaultTabId) overrides the default_icon_path.";
-
- action.SetIcon(100, icon2);
- ASSERT_TRUE(ImagesAreEqual(icon1, action.GetIcon(1)));
- ASSERT_TRUE(ImagesAreEqual(icon2, action.GetIcon(100)));
-}
-
TEST_F(ExtensionActionTest, Visibility) {
// Supports the icon animation.
MessageLoop message_loop;
diff --git a/chrome/common/extensions/extension_constants.cc b/chrome/common/extensions/extension_constants.cc
index b96ba511..bb54bf8 100644
--- a/chrome/common/extensions/extension_constants.cc
+++ b/chrome/common/extensions/extension_constants.cc
@@ -164,4 +164,20 @@ const int kExtensionIconSizes[] = {
const size_t kNumExtensionIconSizes =
arraysize(kExtensionIconSizes);
+const int kExtensionActionIconSizes[] = {
+ EXTENSION_ICON_ACTION, // 19,
+ 2 * EXTENSION_ICON_ACTION // 38
+};
+
+const size_t kNumExtensionActionIconSizes =
+ arraysize(kExtensionActionIconSizes);
+
+const int kScriptBadgeIconSizes[] = {
+ EXTENSION_ICON_BITTY, // 16
+ 2 * EXTENSION_ICON_BITTY // 32
+};
+
+const size_t kNumScriptBadgeIconSizes =
+ arraysize(kScriptBadgeIconSizes);
+
} // namespace extension_misc
diff --git a/chrome/common/extensions/extension_constants.h b/chrome/common/extensions/extension_constants.h
index 33b5434..e00658b 100644
--- a/chrome/common/extensions/extension_constants.h
+++ b/chrome/common/extensions/extension_constants.h
@@ -263,6 +263,7 @@ namespace extension_misc {
EXTENSION_ICON_MEDIUM = 48,
EXTENSION_ICON_SMALL = 32,
EXTENSION_ICON_SMALLISH = 24,
+ EXTENSION_ICON_ACTION = 19,
EXTENSION_ICON_BITTY = 16,
EXTENSION_ICON_INVALID = 0,
};
@@ -271,6 +272,14 @@ namespace extension_misc {
extern const int kExtensionIconSizes[];
extern const size_t kNumExtensionIconSizes;
+ // List of sizes for extension icons that can be defined in the manifest.
+ extern const int kExtensionActionIconSizes[];
+ extern const size_t kNumExtensionActionIconSizes;
+
+ // List of sizes for extension icons that can be defined in the manifest.
+ extern const int kScriptBadgeIconSizes[];
+ extern const size_t kNumScriptBadgeIconSizes;
+
} // extension_misc
#endif // CHROME_COMMON_EXTENSIONS_EXTENSION_CONSTANTS_H_
diff --git a/chrome/common/extensions/extension_file_util.cc b/chrome/common/extensions/extension_file_util.cc
index 2b94f67..fce46dd 100644
--- a/chrome/common/extensions/extension_file_util.cc
+++ b/chrome/common/extensions/extension_file_util.cc
@@ -35,6 +35,27 @@ using extensions::Extension;
namespace errors = extension_manifest_errors;
+namespace {
+
+bool ValidateExtensionIconSet(const ExtensionIconSet* icon_set,
+ const Extension* extension,
+ int error_message_id,
+ std::string* error) {
+ for (ExtensionIconSet::IconMap::const_iterator iter = icon_set->map().begin();
+ iter != icon_set->map().end();
+ ++iter) {
+ const FilePath path = extension->GetResource(iter->second).GetFilePath();
+ if (!extension_file_util::ValidateFilePath(path)) {
+ *error = l10n_util::GetStringFUTF8(error_message_id,
+ UTF8ToUTF16(iter->second));
+ return false;
+ }
+ }
+ return true;
+}
+
+} // namespace
+
namespace extension_file_util {
// Validates locale info. Doesn't check if messages.json files are valid.
@@ -320,36 +341,18 @@ bool ValidateExtension(const Extension* extension,
}
}
- // Validate icon location and icon file size for page actions.
- ExtensionAction* page_action = extension->page_action();
- if (page_action) {
- std::string path = page_action->default_icon_path();
- if (!path.empty()) {
- const FilePath file_path = extension->GetResource(path).GetFilePath();
- if (!ValidateFilePath(file_path)) {
- *error =
- l10n_util::GetStringFUTF8(
- IDS_EXTENSION_LOAD_ICON_FOR_PAGE_ACTION_FAILED,
- UTF8ToUTF16(path));
- return false;
- }
- }
+ const ExtensionAction* action = extension->page_action();
+ if (action && action->default_icon() &&
+ !ValidateExtensionIconSet(action->default_icon(), extension,
+ IDS_EXTENSION_LOAD_ICON_FOR_PAGE_ACTION_FAILED, error)) {
+ return false;
}
- // Validate icon location and icon file size for browser actions.
- ExtensionAction* browser_action = extension->browser_action();
- if (browser_action) {
- std::string path = browser_action->default_icon_path();
- if (!path.empty()) {
- const FilePath file_path = extension->GetResource(path).GetFilePath();
- if (!ValidateFilePath(file_path)) {
- *error =
- l10n_util::GetStringFUTF8(
- IDS_EXTENSION_LOAD_ICON_FOR_BROWSER_ACTION_FAILED,
- UTF8ToUTF16(path));
- return false;
- }
- }
+ action = extension->browser_action();
+ if (action && action->default_icon() &&
+ !ValidateExtensionIconSet(action->default_icon(), extension,
+ IDS_EXTENSION_LOAD_ICON_FOR_BROWSER_ACTION_FAILED, error)) {
+ return false;
}
// Validate that background scripts exist.
diff --git a/chrome/common/extensions/extension_unittest.cc b/chrome/common/extensions/extension_unittest.cc
index 5f5c329..7529734 100644
--- a/chrome/common/extensions/extension_unittest.cc
+++ b/chrome/common/extensions/extension_unittest.cc
@@ -229,7 +229,9 @@ TEST(ExtensionTest, LoadPageActionHelper) {
// No title, so fall back to name.
ASSERT_EQ(name, action->GetTitle(1));
- ASSERT_EQ(img1, action->default_icon_path());
+ ASSERT_EQ(img1,
+ action->default_icon()->Get(extension_misc::EXTENSION_ICON_ACTION,
+ ExtensionIconSet::MATCH_EXACTLY));
// Same test with explicitly set type.
action = LoadAction("page_action_type.json");
@@ -256,7 +258,7 @@ TEST(ExtensionTest, LoadPageActionHelper) {
action = LoadAction("page_action_new_format.json");
ASSERT_TRUE(action.get());
ASSERT_EQ(kTitle, action->GetTitle(1));
- ASSERT_FALSE(action->default_icon_path().empty());
+ ASSERT_TRUE(action->default_icon());
// Invalid title should give an error even with a valid name.
LoadActionAndExpectError("page_action_invalid_title.json",
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_browseraction_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_browseraction_unittest.cc
new file mode 100644
index 0000000..7c6a53b
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_browseraction_unittest.cc
@@ -0,0 +1,102 @@
+// Copyright (c) 2012 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/common/extensions/extension_action.h"
+#include "chrome/common/extensions/extension_builder.h"
+#include "chrome/common/extensions/extension_error_utils.h"
+#include "chrome/common/extensions/extension_icon_set.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+#include "chrome/common/extensions/value_builder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace errors = extension_manifest_errors;
+
+namespace extensions {
+namespace {
+
+TEST_F(ExtensionManifestTest, BrowserActionManifestIcons_NoDefaultIcons) {
+ scoped_refptr<const Extension> extension =
+ ExtensionBuilder()
+ .SetManifest(DictionaryBuilder()
+ .Set("name", "No default properties")
+ .Set("version", "1.0.0")
+ .Set("manifest_version", 2)
+ .Set("browser_action", DictionaryBuilder()
+ .Set("default_title", "Title")))
+ .Build();
+
+ ASSERT_TRUE(extension.get());
+ ASSERT_TRUE(extension->browser_action());
+ EXPECT_FALSE(extension->browser_action()->default_icon());
+}
+
+
+TEST_F(ExtensionManifestTest, BrowserActionManifestIcons_StringDefaultIcon) {
+ scoped_refptr<const Extension> extension =
+ ExtensionBuilder()
+ .SetManifest(DictionaryBuilder()
+ .Set("name", "String default icon")
+ .Set("version", "1.0.0")
+ .Set("manifest_version", 2)
+ .Set("browser_action", DictionaryBuilder()
+ .Set("default_icon", "icon.png")))
+ .Build();
+
+ ASSERT_TRUE(extension.get());
+ ASSERT_TRUE(extension->browser_action());
+ ASSERT_TRUE(extension->browser_action()->default_icon());
+
+ const ExtensionIconSet* icons = extension->browser_action()->default_icon();
+
+ EXPECT_EQ(1u, icons->map().size());
+ EXPECT_EQ("icon.png", icons->Get(19, ExtensionIconSet::MATCH_EXACTLY));
+}
+
+TEST_F(ExtensionManifestTest, BrowserActionManifestIcons_DictDefaultIcon) {
+ scoped_refptr<const Extension> extension =
+ ExtensionBuilder()
+ .SetManifest(DictionaryBuilder()
+ .Set("name", "Dictionary default icon")
+ .Set("version", "1.0.0")
+ .Set("manifest_version", 2)
+ .Set("browser_action", DictionaryBuilder()
+ .Set("default_icon", DictionaryBuilder()
+ .Set("19", "icon19.png")
+ .Set("24", "icon24.png") // Should be ignored.
+ .Set("38", "icon38.png"))))
+ .Build();
+
+ ASSERT_TRUE(extension.get());
+ ASSERT_TRUE(extension->browser_action());
+ ASSERT_TRUE(extension->browser_action()->default_icon());
+
+ const ExtensionIconSet* icons = extension->browser_action()->default_icon();
+
+ // 24px icon should be ignored.
+ EXPECT_EQ(2u, icons->map().size());
+ EXPECT_EQ("icon19.png", icons->Get(19, ExtensionIconSet::MATCH_EXACTLY));
+ EXPECT_EQ("icon38.png", icons->Get(38, ExtensionIconSet::MATCH_EXACTLY));
+}
+
+TEST_F(ExtensionManifestTest, BrowserActionManifestIcons_InvalidDefaultIcon) {
+ scoped_ptr<DictionaryValue> manifest_value = DictionaryBuilder()
+ .Set("name", "Invalid default icon")
+ .Set("version", "1.0.0")
+ .Set("manifest_version", 2)
+ .Set("browser_action", DictionaryBuilder()
+ .Set("default_icon", DictionaryBuilder()
+ .Set("19", "") // Invalid value.
+ .Set("24", "icon24.png")
+ .Set("38", "icon38.png")))
+ .Build();
+
+ string16 error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidIconPath, "19");
+ LoadAndExpectError(Manifest(manifest_value.get(), "Invalid default icon"),
+ errors::kInvalidIconPath);
+}
+
+} // namespace
+} // namespace extensions
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_pageaction_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_pageaction_unittest.cc
index 40d4149..354440a 100644
--- a/chrome/common/extensions/manifest_tests/extension_manifests_pageaction_unittest.cc
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_pageaction_unittest.cc
@@ -17,7 +17,7 @@ TEST_F(ExtensionManifestTest, PageActionManifestVersion2) {
ASSERT_TRUE(extension->page_action());
EXPECT_EQ("", extension->page_action()->id());
- EXPECT_TRUE(extension->page_action()->default_icon_path().empty());
+ EXPECT_FALSE(extension->page_action()->default_icon());
EXPECT_EQ("", extension->page_action()->GetTitle(
ExtensionAction::kDefaultTabId));
EXPECT_FALSE(extension->page_action()->HasPopup(
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_scriptbadge_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_scriptbadge_unittest.cc
index 6b2c39d..1ba17b4 100644
--- a/chrome/common/extensions/manifest_tests/extension_manifests_scriptbadge_unittest.cc
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_scriptbadge_unittest.cc
@@ -2,17 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
-
#include "chrome/common/extensions/extension_action.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "chrome/common/extensions/extension_icon_set.h"
#include "chrome/common/extensions/extension_manifest_constants.h"
#include "chrome/common/extensions/extension_switch_utils.h"
-#include "grit/theme_resources.h"
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
-#include "ui/base/resource/resource_bundle.h"
-#include "ui/gfx/image/image.h"
-#include "ui/gfx/skia_util.h"
namespace errors = extension_manifest_errors;
namespace switch_utils = extensions::switch_utils;
@@ -20,10 +17,6 @@ using extensions::Extension;
namespace {
-bool ImagesAreEqual(const gfx::Image& i1, const gfx::Image& i2) {
- return gfx::BitmapsAreEqual(*i1.ToSkBitmap(), *i2.ToSkBitmap());
-}
-
std::vector<Extension::InstallWarning> StripMissingFlagWarning(
const std::vector<Extension::InstallWarning>& install_warnings) {
std::vector<Extension::InstallWarning> result;
@@ -42,15 +35,24 @@ TEST_F(ExtensionManifestTest, ScriptBadgeBasic) {
EXPECT_THAT(StripMissingFlagWarning(extension->install_warnings()),
testing::ElementsAre(/*empty*/));
+ const ExtensionIconSet* default_icon =
+ extension->script_badge()->default_icon();
+ // Default icon set should not be NULL.
+ ASSERT_TRUE(default_icon);
+
+ // Verify that correct icon paths are registered in default_icon.
+ EXPECT_EQ(2u, default_icon->map().size());
+ EXPECT_EQ("icon16.png",
+ default_icon->Get(extension_misc::EXTENSION_ICON_BITTY,
+ ExtensionIconSet::MATCH_EXACTLY));
+ EXPECT_EQ("icon32.png",
+ default_icon->Get(2 * extension_misc::EXTENSION_ICON_BITTY,
+ ExtensionIconSet::MATCH_EXACTLY));
+
EXPECT_EQ("my extension", extension->script_badge()->GetTitle(
ExtensionAction::kDefaultTabId));
EXPECT_TRUE(extension->script_badge()->HasPopup(
ExtensionAction::kDefaultTabId));
- EXPECT_TRUE(ImagesAreEqual(
- ui::ResourceBundle::GetSharedInstance().GetImageNamed(
- IDR_EXTENSIONS_FAVICON),
- extension->script_badge()->GetIcon(ExtensionAction::kDefaultTabId)));
- EXPECT_EQ("icon16.png", extension->script_badge()->default_icon_path());
}
TEST_F(ExtensionManifestTest, ScriptBadgeExplicitTitleAndIconsIgnored) {
@@ -67,13 +69,18 @@ TEST_F(ExtensionManifestTest, ScriptBadgeExplicitTitleAndIconsIgnored) {
Extension::InstallWarning(
Extension::InstallWarning::FORMAT_TEXT,
errors::kScriptBadgeIconIgnored)));
+
+ const ExtensionIconSet* default_icon =
+ extension->script_badge()->default_icon();
+ ASSERT_TRUE(default_icon);
+
+ EXPECT_EQ(1u, default_icon->map().size());
+ EXPECT_EQ("icon16.png",
+ default_icon->Get(extension_misc::EXTENSION_ICON_BITTY,
+ ExtensionIconSet::MATCH_EXACTLY));
+
EXPECT_EQ("my extension", extension->script_badge()->GetTitle(
ExtensionAction::kDefaultTabId));
- EXPECT_TRUE(ImagesAreEqual(
- ui::ResourceBundle::GetSharedInstance().GetImageNamed(
- IDR_EXTENSIONS_FAVICON),
- extension->script_badge()->GetIcon(ExtensionAction::kDefaultTabId)));
- EXPECT_EQ("icon16.png", extension->script_badge()->default_icon_path());
}
TEST_F(ExtensionManifestTest, ScriptBadgeIconFallsBackToPuzzlePiece) {
@@ -84,12 +91,9 @@ TEST_F(ExtensionManifestTest, ScriptBadgeIconFallsBackToPuzzlePiece) {
EXPECT_THAT(extension->install_warnings(),
testing::ElementsAre(/*empty*/));
- EXPECT_EQ("", extension->script_badge()->default_icon_path())
+ EXPECT_FALSE(extension->script_badge()->default_icon())
<< "Should not fall back to the 64px icon.";
- EXPECT_FALSE(extension->script_badge()->GetIcon(
- ExtensionAction::kDefaultTabId).IsEmpty())
- << "Should set the puzzle piece as the default, but there's no way "
- << "to assert in a unittest what the image looks like.";
+ EXPECT_EQ(NULL, extension->script_badge()->default_icon());
}
} // namespace