| // 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::Uint64ToString(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::IntToString(size); | 
 |       icon_value->SetString(size_string, BitmapToString(rep.sk_bitmap())); | 
 |     } | 
 |     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) { | 
 |   if (!ExtensionActionManager::Get(browser_context_)->GetBrowserAction( | 
 |           *extension)) | 
 |     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) { | 
 |   if (browser_context_ == browser_context && | 
 |       extension_action->action_type() == ActionInfo::TYPE_BROWSER) | 
 |     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* browser_action = | 
 |       ExtensionActionManager::Get(browser_context_)->GetBrowserAction( | 
 |           *extension); | 
 |   if (!browser_action) { | 
 |     // 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, browser_action); | 
 | } | 
 |  | 
 | StateStore* ExtensionActionStorageManager::GetStateStore() { | 
 |   return ExtensionSystem::Get(browser_context_)->state_store(); | 
 | } | 
 |  | 
 | }  // namespace extensions |