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. 4 5#include "chrome/browser/extensions/extension_action_storage_manager.h" 6 7#include "base/base64.h" 8#include "base/bind.h" 9#include "base/memory/scoped_ptr.h" 10#include "base/strings/string_number_conversions.h" 11#include "base/values.h" 12#include "chrome/browser/extensions/extension_action.h" 13#include "chrome/browser/extensions/extension_action_manager.h" 14#include "extensions/browser/extension_registry.h" 15#include "extensions/browser/extension_system.h" 16#include "extensions/browser/state_store.h" 17#include "extensions/common/constants.h" 18#include "ui/base/layout.h" 19#include "ui/gfx/codec/png_codec.h" 20#include "ui/gfx/image/image.h" 21#include "ui/gfx/image/image_skia.h" 22 23namespace extensions { 24 25namespace { 26 27const char kBrowserActionStorageKey[] = "browser_action"; 28const char kPopupUrlStorageKey[] = "poupup_url"; 29const char kTitleStorageKey[] = "title"; 30const char kIconStorageKey[] = "icon"; 31const char kBadgeTextStorageKey[] = "badge_text"; 32const char kBadgeBackgroundColorStorageKey[] = "badge_background_color"; 33const char kBadgeTextColorStorageKey[] = "badge_text_color"; 34const char kAppearanceStorageKey[] = "appearance"; 35 36// Only add values to the end of this enum, since it's stored in the user's 37// Extension State, under the kAppearanceStorageKey. It represents the 38// ExtensionAction's default visibility. 39enum StoredAppearance { 40 // The action icon is hidden. 41 INVISIBLE = 0, 42 // The action is trying to get the user's attention but isn't yet 43 // running on the page. Was only used for script badges. 44 OBSOLETE_WANTS_ATTENTION = 1, 45 // The action icon is visible with its normal appearance. 46 ACTIVE = 2, 47}; 48 49// Conversion function for reading/writing to storage. 50SkColor RawStringToSkColor(const std::string& str) { 51 uint64 value = 0; 52 base::StringToUint64(str, &value); 53 SkColor color = static_cast<SkColor>(value); 54 DCHECK(value == color); // ensure value fits into color's 32 bits 55 return color; 56} 57 58// Conversion function for reading/writing to storage. 59std::string SkColorToRawString(SkColor color) { 60 return base::Uint64ToString(color); 61} 62 63// Conversion function for reading/writing to storage. 64bool StringToSkBitmap(const std::string& str, SkBitmap* bitmap) { 65 // TODO(mpcomplete): Remove the base64 encode/decode step when 66 // http://crbug.com/140546 is fixed. 67 std::string raw_str; 68 if (!base::Base64Decode(str, &raw_str)) 69 return false; 70 71 bool success = gfx::PNGCodec::Decode( 72 reinterpret_cast<unsigned const char*>(raw_str.data()), raw_str.size(), 73 bitmap); 74 return success; 75} 76 77// Conversion function for reading/writing to storage. 78std::string RepresentationToString(const gfx::ImageSkia& image, float scale) { 79 SkBitmap bitmap = image.GetRepresentation(scale).sk_bitmap(); 80 SkAutoLockPixels lock_image(bitmap); 81 std::vector<unsigned char> data; 82 bool success = gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &data); 83 if (!success) 84 return std::string(); 85 86 base::StringPiece raw_str( 87 reinterpret_cast<const char*>(&data[0]), data.size()); 88 std::string base64_str; 89 base::Base64Encode(raw_str, &base64_str); 90 return base64_str; 91} 92 93// Set |action|'s default values to those specified in |dict|. 94void SetDefaultsFromValue(const base::DictionaryValue* dict, 95 ExtensionAction* action) { 96 const int kDefaultTabId = ExtensionAction::kDefaultTabId; 97 std::string str_value; 98 int int_value; 99 SkBitmap bitmap; 100 gfx::ImageSkia icon; 101 102 // For each value, don't set it if it has been modified already. 103 if (dict->GetString(kPopupUrlStorageKey, &str_value) && 104 !action->HasPopupUrl(kDefaultTabId)) { 105 action->SetPopupUrl(kDefaultTabId, GURL(str_value)); 106 } 107 if (dict->GetString(kTitleStorageKey, &str_value) && 108 !action->HasTitle(kDefaultTabId)) { 109 action->SetTitle(kDefaultTabId, str_value); 110 } 111 if (dict->GetString(kBadgeTextStorageKey, &str_value) && 112 !action->HasBadgeText(kDefaultTabId)) { 113 action->SetBadgeText(kDefaultTabId, str_value); 114 } 115 if (dict->GetString(kBadgeBackgroundColorStorageKey, &str_value) && 116 !action->HasBadgeBackgroundColor(kDefaultTabId)) { 117 action->SetBadgeBackgroundColor(kDefaultTabId, 118 RawStringToSkColor(str_value)); 119 } 120 if (dict->GetString(kBadgeTextColorStorageKey, &str_value) && 121 !action->HasBadgeTextColor(kDefaultTabId)) { 122 action->SetBadgeTextColor(kDefaultTabId, RawStringToSkColor(str_value)); 123 } 124 if (dict->GetInteger(kAppearanceStorageKey, &int_value) && 125 !action->HasIsVisible(kDefaultTabId)) { 126 switch (int_value) { 127 case INVISIBLE: 128 case OBSOLETE_WANTS_ATTENTION: 129 action->SetIsVisible(kDefaultTabId, false); 130 break; 131 case ACTIVE: 132 action->SetIsVisible(kDefaultTabId, true); 133 break; 134 } 135 } 136 137 const base::DictionaryValue* icon_value = NULL; 138 if (dict->GetDictionary(kIconStorageKey, &icon_value) && 139 !action->HasIcon(kDefaultTabId)) { 140 for (size_t i = 0; i < extension_misc::kNumExtensionActionIconSizes; i++) { 141 const extension_misc::IconRepresentationInfo& icon_info = 142 extension_misc::kExtensionActionIconSizes[i]; 143 if (icon_value->GetString(icon_info.size_string, &str_value) && 144 StringToSkBitmap(str_value, &bitmap)) { 145 CHECK(!bitmap.isNull()); 146 float scale = ui::GetScaleForScaleFactor(icon_info.scale); 147 icon.AddRepresentation(gfx::ImageSkiaRep(bitmap, scale)); 148 } 149 } 150 action->SetIcon(kDefaultTabId, gfx::Image(icon)); 151 } 152} 153 154// Store |action|'s default values in a DictionaryValue for use in storing to 155// disk. 156scoped_ptr<base::DictionaryValue> DefaultsToValue(ExtensionAction* action) { 157 const int kDefaultTabId = ExtensionAction::kDefaultTabId; 158 scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); 159 160 dict->SetString(kPopupUrlStorageKey, 161 action->GetPopupUrl(kDefaultTabId).spec()); 162 dict->SetString(kTitleStorageKey, action->GetTitle(kDefaultTabId)); 163 dict->SetString(kBadgeTextStorageKey, action->GetBadgeText(kDefaultTabId)); 164 dict->SetString( 165 kBadgeBackgroundColorStorageKey, 166 SkColorToRawString(action->GetBadgeBackgroundColor(kDefaultTabId))); 167 dict->SetString(kBadgeTextColorStorageKey, 168 SkColorToRawString(action->GetBadgeTextColor(kDefaultTabId))); 169 dict->SetInteger(kAppearanceStorageKey, 170 action->GetIsVisible(kDefaultTabId) ? ACTIVE : INVISIBLE); 171 172 gfx::ImageSkia icon = action->GetExplicitlySetIcon(kDefaultTabId); 173 if (!icon.isNull()) { 174 scoped_ptr<base::DictionaryValue> icon_value(new base::DictionaryValue()); 175 for (size_t i = 0; i < extension_misc::kNumExtensionActionIconSizes; i++) { 176 const extension_misc::IconRepresentationInfo& icon_info = 177 extension_misc::kExtensionActionIconSizes[i]; 178 float scale = ui::GetScaleForScaleFactor(icon_info.scale); 179 if (icon.HasRepresentation(scale)) { 180 icon_value->SetString(icon_info.size_string, 181 RepresentationToString(icon, scale)); 182 } 183 } 184 dict->Set(kIconStorageKey, icon_value.release()); 185 } 186 return dict.Pass(); 187} 188 189} // namespace 190 191ExtensionActionStorageManager::ExtensionActionStorageManager( 192 content::BrowserContext* context) 193 : browser_context_(context), 194 extension_action_observer_(this), 195 extension_registry_observer_(this), 196 weak_factory_(this) { 197 extension_action_observer_.Add(ExtensionActionAPI::Get(browser_context_)); 198 extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_)); 199 200 StateStore* store = GetStateStore(); 201 if (store) 202 store->RegisterKey(kBrowserActionStorageKey); 203} 204 205ExtensionActionStorageManager::~ExtensionActionStorageManager() { 206} 207 208void ExtensionActionStorageManager::OnExtensionLoaded( 209 content::BrowserContext* browser_context, 210 const Extension* extension) { 211 if (!ExtensionActionManager::Get(browser_context_)->GetBrowserAction( 212 *extension)) 213 return; 214 215 StateStore* store = GetStateStore(); 216 if (store) { 217 store->GetExtensionValue( 218 extension->id(), 219 kBrowserActionStorageKey, 220 base::Bind(&ExtensionActionStorageManager::ReadFromStorage, 221 weak_factory_.GetWeakPtr(), 222 extension->id())); 223 } 224} 225 226void ExtensionActionStorageManager::OnExtensionActionUpdated( 227 ExtensionAction* extension_action, 228 content::WebContents* web_contents, 229 content::BrowserContext* browser_context) { 230 if (browser_context_ == browser_context && 231 extension_action->action_type() == ActionInfo::TYPE_BROWSER) 232 WriteToStorage(extension_action); 233} 234 235void ExtensionActionStorageManager::OnExtensionActionAPIShuttingDown() { 236 extension_action_observer_.RemoveAll(); 237} 238 239void ExtensionActionStorageManager::WriteToStorage( 240 ExtensionAction* extension_action) { 241 StateStore* store = GetStateStore(); 242 if (store) { 243 scoped_ptr<base::DictionaryValue> defaults = 244 DefaultsToValue(extension_action); 245 store->SetExtensionValue(extension_action->extension_id(), 246 kBrowserActionStorageKey, 247 defaults.PassAs<base::Value>()); 248 } 249} 250 251void ExtensionActionStorageManager::ReadFromStorage( 252 const std::string& extension_id, scoped_ptr<base::Value> value) { 253 const Extension* extension = ExtensionRegistry::Get(browser_context_)-> 254 enabled_extensions().GetByID(extension_id); 255 if (!extension) 256 return; 257 258 ExtensionAction* browser_action = 259 ExtensionActionManager::Get(browser_context_)->GetBrowserAction( 260 *extension); 261 if (!browser_action) { 262 // This can happen if the extension is updated between startup and when the 263 // storage read comes back, and the update removes the browser action. 264 // http://crbug.com/349371 265 return; 266 } 267 268 const base::DictionaryValue* dict = NULL; 269 if (!value.get() || !value->GetAsDictionary(&dict)) 270 return; 271 272 SetDefaultsFromValue(dict, browser_action); 273} 274 275StateStore* ExtensionActionStorageManager::GetStateStore() { 276 return ExtensionSystem::Get(browser_context_)->state_store(); 277} 278 279} // namespace extensions 280