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