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