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
38using content::WebContents;
39
40namespace page_actions_keys = extension_page_actions_api_constants;
41
42namespace extensions {
43
44namespace {
45
46const char kBrowserActionStorageKey[] = "browser_action";
47const char kPopupUrlStorageKey[] = "poupup_url";
48const char kTitleStorageKey[] = "title";
49const char kIconStorageKey[] = "icon";
50const char kBadgeTextStorageKey[] = "badge_text";
51const char kBadgeBackgroundColorStorageKey[] = "badge_background_color";
52const char kBadgeTextColorStorageKey[] = "badge_text_color";
53const 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.
58enum 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.
69const char kBrowserActionVisible[] = "browser_action_visible";
70
71// Errors.
72const char kNoExtensionActionError[] =
73    "This extension has no action specified.";
74const char kNoTabError[] = "No tab with id: *.";
75const char kNoPageActionError[] =
76    "This extension has no page action specified.";
77const char kUrlNotActiveError[] = "This url is no longer active: *.";
78const char kOpenPopupError[] =
79    "Failed to show popup either because there is an existing popup or another "
80    "error occurred.";
81const char kInternalError[] = "Internal error.";
82
83struct 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
91const IconRepresentationInfo kIconSizes[] = {
92    { "19", ui::SCALE_FACTOR_100P },
93    { "38", ui::SCALE_FACTOR_200P }
94};
95
96// Conversion function for reading/writing to storage.
97SkColor 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.
106std::string SkColorToRawString(SkColor color) {
107  return base::Uint64ToString(color);
108}
109
110// Conversion function for reading/writing to storage.
111bool 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.
125std::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|.
141void 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.
201scoped_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
239static base::LazyInstance<BrowserContextKeyedAPIFactory<ExtensionActionAPI> >
240    g_factory = LAZY_INSTANCE_INITIALIZER;
241
242ExtensionActionAPI::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
272ExtensionActionAPI::~ExtensionActionAPI() {
273}
274
275// static
276BrowserContextKeyedAPIFactory<ExtensionActionAPI>*
277ExtensionActionAPI::GetFactoryInstance() {
278  return g_factory.Pointer();
279}
280
281// static
282ExtensionActionAPI* ExtensionActionAPI::Get(content::BrowserContext* context) {
283  return BrowserContextKeyedAPIFactory<ExtensionActionAPI>::Get(context);
284}
285
286// static
287bool 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
300void 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
317void 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
325void 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
351void 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
367void 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
388void 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
420ExtensionActionStorageManager::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
431ExtensionActionStorageManager::~ExtensionActionStorageManager() {
432}
433
434void 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
452void 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
466void 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
479void 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
507ExtensionActionFunction::ExtensionActionFunction()
508    : details_(NULL),
509      tab_id_(ExtensionAction::kDefaultTabId),
510      contents_(NULL),
511      extension_action_(NULL) {
512}
513
514ExtensionActionFunction::~ExtensionActionFunction() {
515}
516
517bool 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
563bool 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
610void 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
629void 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
636void ExtensionActionFunction::NotifyLocationBarChange() {
637  LocationBarController::NotifyChange(contents_);
638}
639
640void 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
647bool 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
655TabHelper& ExtensionActionFunction::tab_helper() const {
656  CHECK(contents_);
657  return *TabHelper::FromWebContents(contents_);
658}
659
660bool ExtensionActionShowFunction::RunExtensionAction() {
661  return SetVisible(true);
662}
663
664bool ExtensionActionHideFunction::RunExtensionAction() {
665  return SetVisible(false);
666}
667
668bool 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
702bool 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
711bool 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
725bool 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
734bool 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
763bool ExtensionActionGetTitleFunction::RunExtensionAction() {
764  SetResult(new base::StringValue(extension_action_->GetTitle(tab_id_)));
765  return true;
766}
767
768bool ExtensionActionGetPopupFunction::RunExtensionAction() {
769  SetResult(
770      new base::StringValue(extension_action_->GetPopupUrl(tab_id_).spec()));
771  return true;
772}
773
774bool ExtensionActionGetBadgeTextFunction::RunExtensionAction() {
775  SetResult(new base::StringValue(extension_action_->GetBadgeText(tab_id_)));
776  return true;
777}
778
779bool 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
794BrowserActionOpenPopupFunction::BrowserActionOpenPopupFunction()
795    : response_sent_(false) {
796}
797
798bool 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
825void 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
835void 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
859PageActionsFunction::PageActionsFunction() {
860}
861
862PageActionsFunction::~PageActionsFunction() {
863}
864
865bool 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
918bool EnablePageActionsFunction::RunSync() {
919  return SetPageActionEnabled(true);
920}
921
922bool DisablePageActionsFunction::RunSync() {
923  return SetPageActionEnabled(false);
924}
925