blob: 0a0889f4019c4456fc248ce33b13b57d0e321fb3 [file] [log] [blame]
// 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/extension_action_storage_manager.h"
#include <stdint.h>
#include <memory>
#include <utility>
#include "base/base64.h"
#include "base/bind.h"
#include "base/strings/string_number_conversions.h"
#include "base/values.h"
#include "chrome/browser/extensions/extension_action.h"
#include "chrome/browser/extensions/extension_action_manager.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/state_store.h"
#include "extensions/common/constants.h"
#include "ui/base/layout.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia.h"
namespace extensions {
namespace {
const char kBrowserActionStorageKey[] = "browser_action";
const char kPopupUrlStorageKey[] = "poupup_url";
const char kTitleStorageKey[] = "title";
const char kIconStorageKey[] = "icon";
const char kBadgeTextStorageKey[] = "badge_text";
const char kBadgeBackgroundColorStorageKey[] = "badge_background_color";
const char kBadgeTextColorStorageKey[] = "badge_text_color";
const char kAppearanceStorageKey[] = "appearance";
// Only add values to the end of this enum, since it's stored in the user's
// Extension State, under the kAppearanceStorageKey. It represents the
// ExtensionAction's default visibility.
enum StoredAppearance {
// The action icon is hidden.
INVISIBLE = 0,
// The action is trying to get the user's attention but isn't yet
// running on the page. Was only used for script badges.
OBSOLETE_WANTS_ATTENTION = 1,
// The action icon is visible with its normal appearance.
ACTIVE = 2,
};
// Conversion function for reading/writing to storage.
SkColor RawStringToSkColor(const std::string& str) {
uint64_t value = 0;
base::StringToUint64(str, &value);
SkColor color = static_cast<SkColor>(value);
DCHECK(value == color); // ensure value fits into color's 32 bits
return color;
}
// Conversion function for reading/writing to storage.
std::string SkColorToRawString(SkColor color) {
return base::NumberToString(color);
}
// Conversion function for reading/writing to storage.
bool StringToSkBitmap(const std::string& str, SkBitmap* bitmap) {
// TODO(mpcomplete): Remove the base64 encode/decode step when
// http://crbug.com/140546 is fixed.
std::string raw_str;
if (!base::Base64Decode(str, &raw_str))
return false;
bool success = gfx::PNGCodec::Decode(
reinterpret_cast<unsigned const char*>(raw_str.data()), raw_str.size(),
bitmap);
return success;
}
// Conversion function for reading/writing to storage.
std::string BitmapToString(const SkBitmap& bitmap) {
std::vector<unsigned char> data;
bool success = gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &data);
if (!success)
return std::string();
base::StringPiece raw_str(
reinterpret_cast<const char*>(&data[0]), data.size());
std::string base64_str;
base::Base64Encode(raw_str, &base64_str);
return base64_str;
}
// Set |action|'s default values to those specified in |dict|.
void SetDefaultsFromValue(const base::DictionaryValue* dict,
ExtensionAction* action) {
const int kDefaultTabId = ExtensionAction::kDefaultTabId;
std::string str_value;
// For each value, don't set it if it has been modified already.
if (dict->GetString(kPopupUrlStorageKey, &str_value) &&
!action->HasPopupUrl(kDefaultTabId)) {
action->SetPopupUrl(kDefaultTabId, GURL(str_value));
}
if (dict->GetString(kTitleStorageKey, &str_value) &&
!action->HasTitle(kDefaultTabId)) {
action->SetTitle(kDefaultTabId, str_value);
}
if (dict->GetString(kBadgeTextStorageKey, &str_value) &&
!action->HasBadgeText(kDefaultTabId)) {
action->SetBadgeText(kDefaultTabId, str_value);
}
if (dict->GetString(kBadgeBackgroundColorStorageKey, &str_value) &&
!action->HasBadgeBackgroundColor(kDefaultTabId)) {
action->SetBadgeBackgroundColor(kDefaultTabId,
RawStringToSkColor(str_value));
}
if (dict->GetString(kBadgeTextColorStorageKey, &str_value) &&
!action->HasBadgeTextColor(kDefaultTabId)) {
action->SetBadgeTextColor(kDefaultTabId, RawStringToSkColor(str_value));
}
int appearance_storage = 0;
if (dict->GetInteger(kAppearanceStorageKey, &appearance_storage) &&
!action->HasIsVisible(kDefaultTabId)) {
switch (appearance_storage) {
case INVISIBLE:
case OBSOLETE_WANTS_ATTENTION:
action->SetIsVisible(kDefaultTabId, false);
break;
case ACTIVE:
action->SetIsVisible(kDefaultTabId, true);
break;
}
}
const base::DictionaryValue* icon_value = NULL;
if (dict->GetDictionary(kIconStorageKey, &icon_value) &&
!action->HasIcon(kDefaultTabId)) {
gfx::ImageSkia icon;
SkBitmap bitmap;
for (base::DictionaryValue::Iterator iter(*icon_value); !iter.IsAtEnd();
iter.Advance()) {
int icon_size = 0;
std::string icon_string;
if (base::StringToInt(iter.key(), &icon_size) &&
iter.value().GetAsString(&icon_string) &&
StringToSkBitmap(icon_string, &bitmap)) {
CHECK(!bitmap.isNull());
float scale =
static_cast<float>(icon_size) / ExtensionAction::ActionIconSize();
icon.AddRepresentation(gfx::ImageSkiaRep(bitmap, scale));
}
}
action->SetIcon(kDefaultTabId, gfx::Image(icon));
}
}
// Store |action|'s default values in a DictionaryValue for use in storing to
// disk.
std::unique_ptr<base::DictionaryValue> DefaultsToValue(
ExtensionAction* action) {
const int kDefaultTabId = ExtensionAction::kDefaultTabId;
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
dict->SetString(kPopupUrlStorageKey,
action->GetPopupUrl(kDefaultTabId).spec());
dict->SetString(kTitleStorageKey, action->GetTitle(kDefaultTabId));
dict->SetString(kBadgeTextStorageKey, action->GetBadgeText(kDefaultTabId));
dict->SetString(
kBadgeBackgroundColorStorageKey,
SkColorToRawString(action->GetBadgeBackgroundColor(kDefaultTabId)));
dict->SetString(kBadgeTextColorStorageKey,
SkColorToRawString(action->GetBadgeTextColor(kDefaultTabId)));
dict->SetInteger(kAppearanceStorageKey,
action->GetIsVisible(kDefaultTabId) ? ACTIVE : INVISIBLE);
gfx::ImageSkia icon =
action->GetExplicitlySetIcon(kDefaultTabId).AsImageSkia();
if (!icon.isNull()) {
std::unique_ptr<base::DictionaryValue> icon_value(
new base::DictionaryValue());
std::vector<gfx::ImageSkiaRep> image_reps = icon.image_reps();
for (const gfx::ImageSkiaRep& rep : image_reps) {
int size = static_cast<int>(rep.scale() * icon.width());
std::string size_string = base::NumberToString(size);
icon_value->SetString(size_string, BitmapToString(rep.GetBitmap()));
}
dict->Set(kIconStorageKey, std::move(icon_value));
}
return dict;
}
} // namespace
ExtensionActionStorageManager::ExtensionActionStorageManager(
content::BrowserContext* context)
: browser_context_(context),
extension_action_observer_(this),
extension_registry_observer_(this),
weak_factory_(this) {
extension_action_observer_.Add(ExtensionActionAPI::Get(browser_context_));
extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
StateStore* store = GetStateStore();
if (store)
store->RegisterKey(kBrowserActionStorageKey);
}
ExtensionActionStorageManager::~ExtensionActionStorageManager() {
}
void ExtensionActionStorageManager::OnExtensionLoaded(
content::BrowserContext* browser_context,
const Extension* extension) {
ExtensionAction* action = ExtensionActionManager::Get(browser_context_)
->GetExtensionAction(*extension);
if (!action || action->action_type() != ActionInfo::TYPE_BROWSER)
return;
StateStore* store = GetStateStore();
if (store) {
store->GetExtensionValue(
extension->id(),
kBrowserActionStorageKey,
base::Bind(&ExtensionActionStorageManager::ReadFromStorage,
weak_factory_.GetWeakPtr(),
extension->id()));
}
}
void ExtensionActionStorageManager::OnExtensionActionUpdated(
ExtensionAction* extension_action,
content::WebContents* web_contents,
content::BrowserContext* browser_context) {
// This is an update to the default settings of the action iff |web_contents|
// is null. We only persist the default settings to disk, since per-tab
// settings can't be persisted across browser sessions.
bool for_default_tab = !web_contents;
// TODO(devlin): We should probably persist for TYPE_ACTION as well.
if (browser_context_ == browser_context &&
extension_action->action_type() == ActionInfo::TYPE_BROWSER &&
for_default_tab) {
WriteToStorage(extension_action);
}
}
void ExtensionActionStorageManager::OnExtensionActionAPIShuttingDown() {
extension_action_observer_.RemoveAll();
}
void ExtensionActionStorageManager::WriteToStorage(
ExtensionAction* extension_action) {
StateStore* store = GetStateStore();
if (store) {
std::unique_ptr<base::DictionaryValue> defaults =
DefaultsToValue(extension_action);
store->SetExtensionValue(extension_action->extension_id(),
kBrowserActionStorageKey, std::move(defaults));
}
}
void ExtensionActionStorageManager::ReadFromStorage(
const std::string& extension_id,
std::unique_ptr<base::Value> value) {
const Extension* extension = ExtensionRegistry::Get(browser_context_)->
enabled_extensions().GetByID(extension_id);
if (!extension)
return;
ExtensionAction* action = ExtensionActionManager::Get(browser_context_)
->GetExtensionAction(*extension);
if (!action || action->action_type() != ActionInfo::TYPE_BROWSER) {
// This can happen if the extension is updated between startup and when the
// storage read comes back, and the update removes the browser action.
// http://crbug.com/349371
return;
}
const base::DictionaryValue* dict = NULL;
if (!value.get() || !value->GetAsDictionary(&dict))
return;
SetDefaultsFromValue(dict, action);
}
StateStore* ExtensionActionStorageManager::GetStateStore() {
return ExtensionSystem::Get(browser_context_)->state_store();
}
} // namespace extensions