1// Copyright (c) 2012 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/api/extension_action/extension_action_api.h"
6
7#include <string>
8
9#include "base/base64.h"
10#include "base/lazy_instance.h"
11#include "base/strings/string_number_conversions.h"
12#include "base/strings/string_util.h"
13#include "base/values.h"
14#include "chrome/browser/chrome_notification_types.h"
15#include "chrome/browser/extensions/api/extension_action/extension_page_actions_api_constants.h"
16#include "chrome/browser/extensions/extension_action.h"
17#include "chrome/browser/extensions/extension_action_manager.h"
18#include "chrome/browser/extensions/extension_function_registry.h"
19#include "chrome/browser/extensions/extension_service.h"
20#include "chrome/browser/extensions/extension_system.h"
21#include "chrome/browser/extensions/extension_tab_util.h"
22#include "chrome/browser/extensions/location_bar_controller.h"
23#include "chrome/browser/extensions/state_store.h"
24#include "chrome/browser/extensions/tab_helper.h"
25#include "chrome/browser/profiles/profile.h"
26#include "chrome/common/extensions/api/extension_action/action_info.h"
27#include "chrome/common/render_messages.h"
28#include "content/public/browser/navigation_entry.h"
29#include "content/public/browser/notification_service.h"
30#include "extensions/common/error_utils.h"
31#include "ui/gfx/image/image.h"
32#include "ui/gfx/image/image_skia.h"
33
34namespace extensions {
35
36namespace {
37
38const char kBrowserActionStorageKey[] = "browser_action";
39const char kPopupUrlStorageKey[] = "poupup_url";
40const char kTitleStorageKey[] = "title";
41const char kIconStorageKey[] = "icon";
42const char kBadgeTextStorageKey[] = "badge_text";
43const char kBadgeBackgroundColorStorageKey[] = "badge_background_color";
44const char kBadgeTextColorStorageKey[] = "badge_text_color";
45const char kAppearanceStorageKey[] = "appearance";
46
47// Whether the browser action is visible in the toolbar.
48const char kBrowserActionVisible[] = "browser_action_visible";
49
50// Errors.
51const char kNoExtensionActionError[] =
52    "This extension has no action specified.";
53const char kNoTabError[] = "No tab with id: *.";
54const char kNoPageActionError[] =
55    "This extension has no page action specified.";
56const char kUrlNotActiveError[] = "This url is no longer active: *.";
57
58struct IconRepresentationInfo {
59  // Size as a string that will be used to retrieve representation value from
60  // SetIcon function arguments.
61  const char* size_string;
62  // Scale factor for which the represantion should be used.
63  ui::ScaleFactor scale;
64};
65
66const IconRepresentationInfo kIconSizes[] = {
67    { "19", ui::SCALE_FACTOR_100P },
68    { "38", ui::SCALE_FACTOR_200P }
69};
70
71// Conversion function for reading/writing to storage.
72SkColor RawStringToSkColor(const std::string& str) {
73  uint64 value = 0;
74  base::StringToUint64(str, &value);
75  SkColor color = static_cast<SkColor>(value);
76  DCHECK(value == color);  // ensure value fits into color's 32 bits
77  return color;
78}
79
80// Conversion function for reading/writing to storage.
81std::string SkColorToRawString(SkColor color) {
82  return base::Uint64ToString(color);
83}
84
85// Conversion function for reading/writing to storage.
86bool StringToSkBitmap(const std::string& str, SkBitmap* bitmap) {
87  // TODO(mpcomplete): Remove the base64 encode/decode step when
88  // http://crbug.com/140546 is fixed.
89  std::string raw_str;
90  if (!base::Base64Decode(str, &raw_str))
91    return false;
92  IPC::Message bitmap_pickle(raw_str.data(), raw_str.size());
93  PickleIterator iter(bitmap_pickle);
94  return IPC::ReadParam(&bitmap_pickle, &iter, bitmap);
95}
96
97// Conversion function for reading/writing to storage.
98std::string RepresentationToString(const gfx::ImageSkia& image,
99                                   ui::ScaleFactor scale) {
100  SkBitmap bitmap = image.GetRepresentation(scale).sk_bitmap();
101  IPC::Message bitmap_pickle;
102  // Clear the header values so they don't vary in serialization.
103  bitmap_pickle.SetHeaderValues(0, 0, 0);
104  IPC::WriteParam(&bitmap_pickle, bitmap);
105  std::string raw_str(static_cast<const char*>(bitmap_pickle.data()),
106                      bitmap_pickle.size());
107  std::string base64_str;
108  if (!base::Base64Encode(raw_str, &base64_str))
109    return std::string();
110  return base64_str;
111}
112
113// Set |action|'s default values to those specified in |dict|.
114void SetDefaultsFromValue(const base::DictionaryValue* dict,
115                          ExtensionAction* action) {
116  const int kTabId = ExtensionAction::kDefaultTabId;
117  std::string str_value;
118  int int_value;
119  SkBitmap bitmap;
120  gfx::ImageSkia icon;
121
122  if (dict->GetString(kPopupUrlStorageKey, &str_value))
123    action->SetPopupUrl(kTabId, GURL(str_value));
124  if (dict->GetString(kTitleStorageKey, &str_value))
125    action->SetTitle(kTabId, str_value);
126  if (dict->GetString(kBadgeTextStorageKey, &str_value))
127    action->SetBadgeText(kTabId, str_value);
128  if (dict->GetString(kBadgeBackgroundColorStorageKey, &str_value))
129    action->SetBadgeBackgroundColor(kTabId, RawStringToSkColor(str_value));
130  if (dict->GetString(kBadgeTextColorStorageKey, &str_value))
131    action->SetBadgeTextColor(kTabId, RawStringToSkColor(str_value));
132  if (dict->GetInteger(kAppearanceStorageKey, &int_value))
133    action->SetAppearance(kTabId,
134                          static_cast<ExtensionAction::Appearance>(int_value));
135
136  const base::DictionaryValue* icon_value = NULL;
137  if (dict->GetDictionary(kIconStorageKey, &icon_value)) {
138    for (size_t i = 0; i < arraysize(kIconSizes); i++) {
139      if (icon_value->GetString(kIconSizes[i].size_string, &str_value) &&
140          StringToSkBitmap(str_value, &bitmap)) {
141        CHECK(!bitmap.isNull());
142        icon.AddRepresentation(gfx::ImageSkiaRep(bitmap, kIconSizes[i].scale));
143      }
144    }
145    action->SetIcon(kTabId, gfx::Image(icon));
146  }
147}
148
149// Store |action|'s default values in a DictionaryValue for use in storing to
150// disk.
151scoped_ptr<base::DictionaryValue> DefaultsToValue(ExtensionAction* action) {
152  const int kTabId = ExtensionAction::kDefaultTabId;
153  scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
154
155  dict->SetString(kPopupUrlStorageKey, action->GetPopupUrl(kTabId).spec());
156  dict->SetString(kTitleStorageKey, action->GetTitle(kTabId));
157  dict->SetString(kBadgeTextStorageKey, action->GetBadgeText(kTabId));
158  dict->SetString(kBadgeBackgroundColorStorageKey,
159                  SkColorToRawString(action->GetBadgeBackgroundColor(kTabId)));
160  dict->SetString(kBadgeTextColorStorageKey,
161                  SkColorToRawString(action->GetBadgeTextColor(kTabId)));
162  dict->SetInteger(kAppearanceStorageKey,
163                   action->GetIsVisible(kTabId) ?
164                       ExtensionAction::ACTIVE : ExtensionAction::INVISIBLE);
165
166  gfx::ImageSkia icon = action->GetExplicitlySetIcon(kTabId);
167  if (!icon.isNull()) {
168    base::DictionaryValue* icon_value = new base::DictionaryValue();
169    for (size_t i = 0; i < arraysize(kIconSizes); i++) {
170      if (icon.HasRepresentation(kIconSizes[i].scale)) {
171        icon_value->SetString(
172            kIconSizes[i].size_string,
173            RepresentationToString(icon, kIconSizes[i].scale));
174      }
175    }
176    dict->Set(kIconStorageKey, icon_value);
177  }
178  return dict.Pass();
179}
180
181}  // namespace
182
183//
184// ExtensionActionAPI
185//
186
187static base::LazyInstance<ProfileKeyedAPIFactory<ExtensionActionAPI> >
188    g_factory = LAZY_INSTANCE_INITIALIZER;
189
190ExtensionActionAPI::ExtensionActionAPI(Profile* profile) {
191  ExtensionFunctionRegistry* registry =
192      ExtensionFunctionRegistry::GetInstance();
193
194  // Browser Actions
195  registry->RegisterFunction<BrowserActionSetIconFunction>();
196  registry->RegisterFunction<BrowserActionSetTitleFunction>();
197  registry->RegisterFunction<BrowserActionSetBadgeTextFunction>();
198  registry->RegisterFunction<BrowserActionSetBadgeBackgroundColorFunction>();
199  registry->RegisterFunction<BrowserActionSetPopupFunction>();
200  registry->RegisterFunction<BrowserActionGetTitleFunction>();
201  registry->RegisterFunction<BrowserActionGetBadgeTextFunction>();
202  registry->RegisterFunction<BrowserActionGetBadgeBackgroundColorFunction>();
203  registry->RegisterFunction<BrowserActionGetPopupFunction>();
204  registry->RegisterFunction<BrowserActionEnableFunction>();
205  registry->RegisterFunction<BrowserActionDisableFunction>();
206
207  // Page Actions
208  registry->RegisterFunction<EnablePageActionsFunction>();
209  registry->RegisterFunction<DisablePageActionsFunction>();
210  registry->RegisterFunction<PageActionShowFunction>();
211  registry->RegisterFunction<PageActionHideFunction>();
212  registry->RegisterFunction<PageActionSetIconFunction>();
213  registry->RegisterFunction<PageActionSetTitleFunction>();
214  registry->RegisterFunction<PageActionSetPopupFunction>();
215  registry->RegisterFunction<PageActionGetTitleFunction>();
216  registry->RegisterFunction<PageActionGetPopupFunction>();
217
218  // Script Badges
219  registry->RegisterFunction<ScriptBadgeGetAttentionFunction>();
220  registry->RegisterFunction<ScriptBadgeGetPopupFunction>();
221  registry->RegisterFunction<ScriptBadgeSetPopupFunction>();
222}
223
224ExtensionActionAPI::~ExtensionActionAPI() {
225}
226
227// static
228ProfileKeyedAPIFactory<ExtensionActionAPI>*
229ExtensionActionAPI::GetFactoryInstance() {
230  return &g_factory.Get();
231}
232
233// static
234bool ExtensionActionAPI::GetBrowserActionVisibility(
235    const ExtensionPrefs* prefs,
236    const std::string& extension_id) {
237  bool visible = false;
238  if (!prefs || !prefs->ReadPrefAsBoolean(extension_id,
239                                          kBrowserActionVisible,
240                                          &visible)) {
241    return true;
242  }
243  return visible;
244}
245
246// static
247void ExtensionActionAPI::SetBrowserActionVisibility(
248    ExtensionPrefs* prefs,
249    const std::string& extension_id,
250    bool visible) {
251  if (GetBrowserActionVisibility(prefs, extension_id) == visible)
252    return;
253
254  prefs->UpdateExtensionPref(extension_id,
255                             kBrowserActionVisible,
256                             Value::CreateBooleanValue(visible));
257  content::NotificationService::current()->Notify(
258      chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED,
259      content::Source<ExtensionPrefs>(prefs),
260      content::Details<const std::string>(&extension_id));
261}
262
263//
264// ExtensionActionStorageManager
265//
266
267ExtensionActionStorageManager::ExtensionActionStorageManager(Profile* profile)
268    : profile_(profile) {
269  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
270                 content::Source<Profile>(profile_));
271  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED,
272                 content::NotificationService::AllBrowserContextsAndSources());
273
274  StateStore* storage = ExtensionSystem::Get(profile_)->state_store();
275  if (storage)
276    storage->RegisterKey(kBrowserActionStorageKey);
277}
278
279ExtensionActionStorageManager::~ExtensionActionStorageManager() {
280}
281
282void ExtensionActionStorageManager::Observe(
283    int type,
284    const content::NotificationSource& source,
285    const content::NotificationDetails& details) {
286  switch (type) {
287    case chrome::NOTIFICATION_EXTENSION_LOADED: {
288      const Extension* extension =
289          content::Details<const Extension>(details).ptr();
290      if (!ExtensionActionManager::Get(profile_)->
291          GetBrowserAction(*extension)) {
292        break;
293      }
294
295      StateStore* storage = ExtensionSystem::Get(profile_)->state_store();
296      if (storage) {
297        storage->GetExtensionValue(extension->id(), kBrowserActionStorageKey,
298            base::Bind(&ExtensionActionStorageManager::ReadFromStorage,
299                       AsWeakPtr(), extension->id()));
300      }
301      break;
302    }
303    case chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED: {
304      ExtensionAction* extension_action =
305          content::Source<ExtensionAction>(source).ptr();
306      Profile* profile = content::Details<Profile>(details).ptr();
307      if (profile != profile_)
308        break;
309
310      extension_action->set_has_changed(true);
311      WriteToStorage(extension_action);
312      break;
313    }
314    default:
315      NOTREACHED();
316      break;
317  }
318}
319
320void ExtensionActionStorageManager::WriteToStorage(
321    ExtensionAction* extension_action) {
322  StateStore* storage = ExtensionSystem::Get(profile_)->state_store();
323  if (!storage)
324    return;
325
326  scoped_ptr<base::DictionaryValue> defaults =
327      DefaultsToValue(extension_action);
328  storage->SetExtensionValue(extension_action->extension_id(),
329                             kBrowserActionStorageKey,
330                             defaults.PassAs<base::Value>());
331}
332
333void ExtensionActionStorageManager::ReadFromStorage(
334    const std::string& extension_id, scoped_ptr<base::Value> value) {
335  const Extension* extension =
336      ExtensionSystem::Get(profile_)->extension_service()->
337      extensions()->GetByID(extension_id);
338  if (!extension)
339    return;
340
341  ExtensionAction* browser_action =
342      ExtensionActionManager::Get(profile_)->GetBrowserAction(*extension);
343  CHECK(browser_action);
344
345  // Don't load values from storage if the extension has updated a value
346  // already. The extension may have only updated some of the values, but
347  // this is a good first approximation. If the extension is doing stuff
348  // to the browser action, we can assume it is ready to take over.
349  if (browser_action->has_changed())
350    return;
351
352  const base::DictionaryValue* dict = NULL;
353  if (!value.get() || !value->GetAsDictionary(&dict))
354    return;
355
356  SetDefaultsFromValue(dict, browser_action);
357}
358
359//
360// ExtensionActionFunction
361//
362
363ExtensionActionFunction::ExtensionActionFunction()
364    : details_(NULL),
365      tab_id_(ExtensionAction::kDefaultTabId),
366      contents_(NULL),
367      extension_action_(NULL) {
368}
369
370ExtensionActionFunction::~ExtensionActionFunction() {
371}
372
373bool ExtensionActionFunction::RunImpl() {
374  ExtensionActionManager* manager = ExtensionActionManager::Get(profile_);
375  const Extension* extension = GetExtension();
376  if (StartsWithASCII(name(), "scriptBadge.", false)) {
377    extension_action_ = manager->GetScriptBadge(*extension);
378  } else if (StartsWithASCII(name(), "systemIndicator.", false)) {
379    extension_action_ = manager->GetSystemIndicator(*extension);
380  } else {
381    extension_action_ = manager->GetBrowserAction(*extension);
382    if (!extension_action_) {
383      extension_action_ = manager->GetPageAction(*extension);
384    }
385  }
386  if (!extension_action_) {
387    // TODO(kalman): ideally the browserAction/pageAction APIs wouldn't event
388    // exist for extensions that don't have one declared. This should come as
389    // part of the Feature system.
390    error_ = kNoExtensionActionError;
391    return false;
392  }
393
394  // Populates the tab_id_ and details_ members.
395  EXTENSION_FUNCTION_VALIDATE(ExtractDataFromArguments());
396
397  // Find the WebContents that contains this tab id if one is required.
398  if (tab_id_ != ExtensionAction::kDefaultTabId) {
399    ExtensionTabUtil::GetTabById(
400        tab_id_, profile(), include_incognito(), NULL, NULL, &contents_, NULL);
401    if (!contents_) {
402      error_ = ErrorUtils::FormatErrorMessage(
403          kNoTabError, base::IntToString(tab_id_));
404      return false;
405    }
406  } else {
407    // Only browser actions and system indicators have a default tabId.
408    ActionInfo::Type action_type = extension_action_->action_type();
409    EXTENSION_FUNCTION_VALIDATE(
410        action_type == ActionInfo::TYPE_BROWSER ||
411        action_type == ActionInfo::TYPE_SYSTEM_INDICATOR);
412  }
413  return RunExtensionAction();
414}
415
416bool ExtensionActionFunction::ExtractDataFromArguments() {
417  // There may or may not be details (depends on the function).
418  // The tabId might appear in details (if it exists), as the first
419  // argument besides the action type (depends on the function), or be omitted
420  // entirely.
421  base::Value* first_arg = NULL;
422  if (!args_->Get(0, &first_arg))
423    return true;
424
425  switch (first_arg->GetType()) {
426    case Value::TYPE_INTEGER:
427      CHECK(first_arg->GetAsInteger(&tab_id_));
428      break;
429
430    case Value::TYPE_DICTIONARY: {
431      // Found the details argument.
432      details_ = static_cast<base::DictionaryValue*>(first_arg);
433      // Still need to check for the tabId within details.
434      base::Value* tab_id_value = NULL;
435      if (details_->Get("tabId", &tab_id_value)) {
436        switch (tab_id_value->GetType()) {
437          case Value::TYPE_NULL:
438            // OK; tabId is optional, leave it default.
439            return true;
440          case Value::TYPE_INTEGER:
441            CHECK(tab_id_value->GetAsInteger(&tab_id_));
442            return true;
443          default:
444            // Boom.
445            return false;
446        }
447      }
448      // Not found; tabId is optional, leave it default.
449      break;
450    }
451
452    case Value::TYPE_NULL:
453      // The tabId might be an optional argument.
454      break;
455
456    default:
457      return false;
458  }
459
460  return true;
461}
462
463void ExtensionActionFunction::NotifyChange() {
464  switch (extension_action_->action_type()) {
465    case ActionInfo::TYPE_BROWSER:
466    case ActionInfo::TYPE_PAGE:
467      if (ExtensionActionManager::Get(profile_)
468              ->GetBrowserAction(*extension_.get())) {
469        NotifyBrowserActionChange();
470      } else if (ExtensionActionManager::Get(profile_)
471                     ->GetPageAction(*extension_.get())) {
472        NotifyLocationBarChange();
473      }
474      return;
475    case ActionInfo::TYPE_SCRIPT_BADGE:
476      NotifyLocationBarChange();
477      return;
478    case ActionInfo::TYPE_SYSTEM_INDICATOR:
479      NotifySystemIndicatorChange();
480      return;
481  }
482  NOTREACHED();
483}
484
485void ExtensionActionFunction::NotifyBrowserActionChange() {
486  content::NotificationService::current()->Notify(
487      chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED,
488      content::Source<ExtensionAction>(extension_action_),
489      content::Details<Profile>(profile()));
490}
491
492void ExtensionActionFunction::NotifyLocationBarChange() {
493  TabHelper::FromWebContents(contents_)->
494      location_bar_controller()->NotifyChange();
495}
496
497void ExtensionActionFunction::NotifySystemIndicatorChange() {
498  content::NotificationService::current()->Notify(
499      chrome::NOTIFICATION_EXTENSION_SYSTEM_INDICATOR_UPDATED,
500      content::Source<Profile>(profile()),
501      content::Details<ExtensionAction>(extension_action_));
502}
503
504// static
505bool ExtensionActionFunction::ParseCSSColorString(
506    const std::string& color_string,
507    SkColor* result) {
508  std::string formatted_color = "#";
509  // Check the string for incorrect formatting.
510  if (color_string[0] != '#')
511    return false;
512
513  // Convert the string from #FFF format to #FFFFFF format.
514  if (color_string.length() == 4) {
515    for (size_t i = 1; i < color_string.length(); i++) {
516      formatted_color += color_string[i];
517      formatted_color += color_string[i];
518    }
519  } else {
520    formatted_color = color_string;
521  }
522
523  if (formatted_color.length() != 7)
524    return false;
525
526  // Convert the string to an integer and make sure it is in the correct value
527  // range.
528  int color_ints[3] = {0};
529  for (int i = 0; i < 3; i++) {
530    if (!base::HexStringToInt(formatted_color.substr(1 + (2 * i), 2),
531                              color_ints + i))
532      return false;
533    if (color_ints[i] > 255 || color_ints[i] < 0)
534      return false;
535  }
536
537  *result = SkColorSetARGB(255, color_ints[0], color_ints[1], color_ints[2]);
538  return true;
539}
540
541bool ExtensionActionFunction::SetVisible(bool visible) {
542  if (extension_action_->GetIsVisible(tab_id_) == visible)
543    return true;
544  extension_action_->SetAppearance(
545      tab_id_, visible ? ExtensionAction::ACTIVE : ExtensionAction::INVISIBLE);
546  NotifyChange();
547  return true;
548}
549
550TabHelper& ExtensionActionFunction::tab_helper() const {
551  CHECK(contents_);
552  return *TabHelper::FromWebContents(contents_);
553}
554
555bool ExtensionActionShowFunction::RunExtensionAction() {
556  return SetVisible(true);
557}
558
559bool ExtensionActionHideFunction::RunExtensionAction() {
560  return SetVisible(false);
561}
562
563bool ExtensionActionSetIconFunction::RunExtensionAction() {
564  EXTENSION_FUNCTION_VALIDATE(details_);
565
566  // setIcon can take a variant argument: either a dictionary of canvas
567  // ImageData, or an icon index.
568  base::DictionaryValue* canvas_set = NULL;
569  int icon_index;
570  if (details_->GetDictionary("imageData", &canvas_set)) {
571    gfx::ImageSkia icon;
572    // Extract icon representations from the ImageDataSet dictionary.
573    for (size_t i = 0; i < arraysize(kIconSizes); i++) {
574      base::BinaryValue* binary;
575      if (canvas_set->GetBinary(kIconSizes[i].size_string, &binary)) {
576        IPC::Message pickle(binary->GetBuffer(), binary->GetSize());
577        PickleIterator iter(pickle);
578        SkBitmap bitmap;
579        EXTENSION_FUNCTION_VALIDATE(IPC::ReadParam(&pickle, &iter, &bitmap));
580        CHECK(!bitmap.isNull());
581        icon.AddRepresentation(gfx::ImageSkiaRep(bitmap, kIconSizes[i].scale));
582      }
583    }
584
585    extension_action_->SetIcon(tab_id_, gfx::Image(icon));
586  } else if (details_->GetInteger("iconIndex", &icon_index)) {
587    // Obsolete argument: ignore it.
588    return true;
589  } else {
590    EXTENSION_FUNCTION_VALIDATE(false);
591  }
592  NotifyChange();
593  return true;
594}
595
596bool ExtensionActionSetTitleFunction::RunExtensionAction() {
597  EXTENSION_FUNCTION_VALIDATE(details_);
598  std::string title;
599  EXTENSION_FUNCTION_VALIDATE(details_->GetString("title", &title));
600  extension_action_->SetTitle(tab_id_, title);
601  NotifyChange();
602  return true;
603}
604
605bool ExtensionActionSetPopupFunction::RunExtensionAction() {
606  EXTENSION_FUNCTION_VALIDATE(details_);
607  std::string popup_string;
608  EXTENSION_FUNCTION_VALIDATE(details_->GetString("popup", &popup_string));
609
610  GURL popup_url;
611  if (!popup_string.empty())
612    popup_url = GetExtension()->GetResourceURL(popup_string);
613
614  extension_action_->SetPopupUrl(tab_id_, popup_url);
615  NotifyChange();
616  return true;
617}
618
619bool ExtensionActionSetBadgeTextFunction::RunExtensionAction() {
620  EXTENSION_FUNCTION_VALIDATE(details_);
621  std::string badge_text;
622  EXTENSION_FUNCTION_VALIDATE(details_->GetString("text", &badge_text));
623  extension_action_->SetBadgeText(tab_id_, badge_text);
624  NotifyChange();
625  return true;
626}
627
628bool ExtensionActionSetBadgeBackgroundColorFunction::RunExtensionAction() {
629  EXTENSION_FUNCTION_VALIDATE(details_);
630  Value* color_value = NULL;
631  EXTENSION_FUNCTION_VALIDATE(details_->Get("color", &color_value));
632  SkColor color = 0;
633  if (color_value->IsType(Value::TYPE_LIST)) {
634    base::ListValue* list = NULL;
635    EXTENSION_FUNCTION_VALIDATE(details_->GetList("color", &list));
636    EXTENSION_FUNCTION_VALIDATE(list->GetSize() == 4);
637
638    int color_array[4] = {0};
639    for (size_t i = 0; i < arraysize(color_array); ++i) {
640      EXTENSION_FUNCTION_VALIDATE(list->GetInteger(i, &color_array[i]));
641    }
642
643    color = SkColorSetARGB(color_array[3], color_array[0],
644                           color_array[1], color_array[2]);
645  } else if (color_value->IsType(Value::TYPE_STRING)) {
646    std::string color_string;
647    EXTENSION_FUNCTION_VALIDATE(details_->GetString("color", &color_string));
648    if (!ParseCSSColorString(color_string, &color))
649      return false;
650  }
651
652  extension_action_->SetBadgeBackgroundColor(tab_id_, color);
653  NotifyChange();
654  return true;
655}
656
657bool ExtensionActionGetTitleFunction::RunExtensionAction() {
658  SetResult(Value::CreateStringValue(extension_action_->GetTitle(tab_id_)));
659  return true;
660}
661
662bool ExtensionActionGetPopupFunction::RunExtensionAction() {
663  SetResult(
664      Value::CreateStringValue(extension_action_->GetPopupUrl(tab_id_).spec()));
665  return true;
666}
667
668bool ExtensionActionGetBadgeTextFunction::RunExtensionAction() {
669  SetResult(Value::CreateStringValue(extension_action_->GetBadgeText(tab_id_)));
670  return true;
671}
672
673bool ExtensionActionGetBadgeBackgroundColorFunction::RunExtensionAction() {
674  base::ListValue* list = new base::ListValue();
675  SkColor color = extension_action_->GetBadgeBackgroundColor(tab_id_);
676  list->Append(Value::CreateIntegerValue(SkColorGetR(color)));
677  list->Append(Value::CreateIntegerValue(SkColorGetG(color)));
678  list->Append(Value::CreateIntegerValue(SkColorGetB(color)));
679  list->Append(Value::CreateIntegerValue(SkColorGetA(color)));
680  SetResult(list);
681  return true;
682}
683
684//
685// ScriptBadgeGetAttentionFunction
686//
687
688ScriptBadgeGetAttentionFunction::~ScriptBadgeGetAttentionFunction() {}
689
690bool ScriptBadgeGetAttentionFunction::RunExtensionAction() {
691  tab_helper().location_bar_controller()->GetAttentionFor(extension_id());
692  return true;
693}
694
695}  // namespace extensions
696
697//
698// PageActionsFunction (deprecated)
699//
700
701namespace keys = extension_page_actions_api_constants;
702
703PageActionsFunction::PageActionsFunction() {
704}
705
706PageActionsFunction::~PageActionsFunction() {
707}
708
709bool PageActionsFunction::SetPageActionEnabled(bool enable) {
710  std::string extension_action_id;
711  EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &extension_action_id));
712  DictionaryValue* action = NULL;
713  EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &action));
714
715  int tab_id;
716  EXTENSION_FUNCTION_VALIDATE(action->GetInteger(keys::kTabIdKey, &tab_id));
717  std::string url;
718  EXTENSION_FUNCTION_VALIDATE(action->GetString(keys::kUrlKey, &url));
719
720  std::string title;
721  if (enable) {
722    if (action->HasKey(keys::kTitleKey))
723      EXTENSION_FUNCTION_VALIDATE(action->GetString(keys::kTitleKey, &title));
724  }
725
726  ExtensionAction* page_action =
727      extensions::ExtensionActionManager::Get(profile())->
728      GetPageAction(*GetExtension());
729  if (!page_action) {
730    error_ = extensions::kNoPageActionError;
731    return false;
732  }
733
734  // Find the WebContents that contains this tab id.
735  content::WebContents* contents = NULL;
736  bool result = ExtensionTabUtil::GetTabById(
737      tab_id, profile(), include_incognito(), NULL, NULL, &contents, NULL);
738  if (!result || !contents) {
739    error_ = extensions::ErrorUtils::FormatErrorMessage(
740        extensions::kNoTabError, base::IntToString(tab_id));
741    return false;
742  }
743
744  // Make sure the URL hasn't changed.
745  content::NavigationEntry* entry = contents->GetController().GetActiveEntry();
746  if (!entry || url != entry->GetURL().spec()) {
747    error_ = extensions::ErrorUtils::FormatErrorMessage(
748        extensions::kUrlNotActiveError, url);
749    return false;
750  }
751
752  // Set visibility and broadcast notifications that the UI should be updated.
753  page_action->SetAppearance(
754      tab_id, enable ? ExtensionAction::ACTIVE : ExtensionAction::INVISIBLE);
755  page_action->SetTitle(tab_id, title);
756  extensions::TabHelper::FromWebContents(contents)->
757      location_bar_controller()->NotifyChange();
758
759  return true;
760}
761
762bool EnablePageActionsFunction::RunImpl() {
763  return SetPageActionEnabled(true);
764}
765
766bool DisablePageActionsFunction::RunImpl() {
767  return SetPageActionEnabled(false);
768}
769