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