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.
RawStringToSkColor(const std::string & str)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.
SkColorToRawString(SkColor color)106 std::string SkColorToRawString(SkColor color) {
107 return base::Uint64ToString(color);
108 }
109
110 // Conversion function for reading/writing to storage.
StringToSkBitmap(const std::string & str,SkBitmap * bitmap)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.
RepresentationToString(const gfx::ImageSkia & image,float scale)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|.
SetDefaultsFromValue(const base::DictionaryValue * dict,ExtensionAction * action)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.
DefaultsToValue(ExtensionAction * action)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
ExtensionActionAPI(content::BrowserContext * context)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
~ExtensionActionAPI()272 ExtensionActionAPI::~ExtensionActionAPI() {
273 }
274
275 // static
276 BrowserContextKeyedAPIFactory<ExtensionActionAPI>*
GetFactoryInstance()277 ExtensionActionAPI::GetFactoryInstance() {
278 return g_factory.Pointer();
279 }
280
281 // static
Get(content::BrowserContext * context)282 ExtensionActionAPI* ExtensionActionAPI::Get(content::BrowserContext* context) {
283 return BrowserContextKeyedAPIFactory<ExtensionActionAPI>::Get(context);
284 }
285
286 // static
GetBrowserActionVisibility(const ExtensionPrefs * prefs,const std::string & extension_id)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
SetBrowserActionVisibility(ExtensionPrefs * prefs,const std::string & extension_id,bool visible)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
BrowserActionExecuted(content::BrowserContext * context,const ExtensionAction & browser_action,WebContents * web_contents)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
PageActionExecuted(content::BrowserContext * context,const ExtensionAction & page_action,int tab_id,const std::string & url,int button)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
DispatchEventToExtension(content::BrowserContext * context,const std::string & extension_id,const std::string & event_name,scoped_ptr<base::ListValue> event_args)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
DispatchOldPageActionEvent(content::BrowserContext * context,const std::string & extension_id,const std::string & page_action_id,int tab_id,const std::string & url,int button)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
ExtensionActionExecuted(content::BrowserContext * context,const ExtensionAction & extension_action,WebContents * web_contents)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
ExtensionActionStorageManager(Profile * profile)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
~ExtensionActionStorageManager()431 ExtensionActionStorageManager::~ExtensionActionStorageManager() {
432 }
433
OnExtensionLoaded(content::BrowserContext * browser_context,const Extension * extension)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
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)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
WriteToStorage(ExtensionAction * extension_action)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
ReadFromStorage(const std::string & extension_id,scoped_ptr<base::Value> value)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
ExtensionActionFunction()507 ExtensionActionFunction::ExtensionActionFunction()
508 : details_(NULL),
509 tab_id_(ExtensionAction::kDefaultTabId),
510 contents_(NULL),
511 extension_action_(NULL) {
512 }
513
~ExtensionActionFunction()514 ExtensionActionFunction::~ExtensionActionFunction() {
515 }
516
RunSync()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
ExtractDataFromArguments()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
NotifyChange()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
NotifyBrowserActionChange()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
NotifyLocationBarChange()636 void ExtensionActionFunction::NotifyLocationBarChange() {
637 LocationBarController::NotifyChange(contents_);
638 }
639
NotifySystemIndicatorChange()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
SetVisible(bool visible)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
tab_helper() const655 TabHelper& ExtensionActionFunction::tab_helper() const {
656 CHECK(contents_);
657 return *TabHelper::FromWebContents(contents_);
658 }
659
RunExtensionAction()660 bool ExtensionActionShowFunction::RunExtensionAction() {
661 return SetVisible(true);
662 }
663
RunExtensionAction()664 bool ExtensionActionHideFunction::RunExtensionAction() {
665 return SetVisible(false);
666 }
667
RunExtensionAction()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
RunExtensionAction()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
RunExtensionAction()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
RunExtensionAction()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
RunExtensionAction()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
RunExtensionAction()763 bool ExtensionActionGetTitleFunction::RunExtensionAction() {
764 SetResult(new base::StringValue(extension_action_->GetTitle(tab_id_)));
765 return true;
766 }
767
RunExtensionAction()768 bool ExtensionActionGetPopupFunction::RunExtensionAction() {
769 SetResult(
770 new base::StringValue(extension_action_->GetPopupUrl(tab_id_).spec()));
771 return true;
772 }
773
RunExtensionAction()774 bool ExtensionActionGetBadgeTextFunction::RunExtensionAction() {
775 SetResult(new base::StringValue(extension_action_->GetBadgeText(tab_id_)));
776 return true;
777 }
778
RunExtensionAction()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
BrowserActionOpenPopupFunction()794 BrowserActionOpenPopupFunction::BrowserActionOpenPopupFunction()
795 : response_sent_(false) {
796 }
797
RunAsync()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
OpenPopupTimedOut()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
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)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
PageActionsFunction()859 PageActionsFunction::PageActionsFunction() {
860 }
861
~PageActionsFunction()862 PageActionsFunction::~PageActionsFunction() {
863 }
864
SetPageActionEnabled(bool enable)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
RunSync()918 bool EnablePageActionsFunction::RunSync() {
919 return SetPageActionEnabled(true);
920 }
921
RunSync()922 bool DisablePageActionsFunction::RunSync() {
923 return SetPageActionEnabled(false);
924 }
925