Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / api / extension_action / extension_action_api.cc
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/lazy_instance.h"
8 #include "base/strings/string_number_conversions.h"
9 #include "base/values.h"
10 #include "chrome/browser/extensions/active_script_controller.h"
11 #include "chrome/browser/extensions/extension_action_manager.h"
12 #include "chrome/browser/extensions/extension_tab_util.h"
13 #include "chrome/browser/extensions/extension_toolbar_model.h"
14 #include "chrome/browser/extensions/tab_helper.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/sessions/session_tab_helper.h"
17 #include "chrome/browser/ui/browser.h"
18 #include "chrome/browser/ui/browser_finder.h"
19 #include "chrome/browser/ui/browser_window.h"
20 #include "chrome/browser/ui/location_bar/location_bar.h"
21 #include "chrome/browser/ui/tabs/tab_strip_model.h"
22 #include "chrome/common/extensions/api/extension_action/action_info.h"
23 #include "chrome/common/render_messages.h"
24 #include "content/public/browser/notification_service.h"
25 #include "extensions/browser/event_router.h"
26 #include "extensions/browser/extension_function_registry.h"
27 #include "extensions/browser/extension_host.h"
28 #include "extensions/browser/extension_registry.h"
29 #include "extensions/browser/image_util.h"
30 #include "extensions/browser/notification_types.h"
31 #include "extensions/common/error_utils.h"
32 #include "extensions/common/feature_switch.h"
33 #include "ui/gfx/image/image.h"
34 #include "ui/gfx/image/image_skia.h"
35
36 using content::WebContents;
37
38 namespace extensions {
39
40 namespace {
41
42 // Whether the browser action is visible in the toolbar.
43 const char kBrowserActionVisible[] = "browser_action_visible";
44
45 // Errors.
46 const char kNoExtensionActionError[] =
47     "This extension has no action specified.";
48 const char kNoTabError[] = "No tab with id: *.";
49 const char kOpenPopupError[] =
50     "Failed to show popup either because there is an existing popup or another "
51     "error occurred.";
52
53 }  // namespace
54
55 //
56 // ExtensionActionAPI::Observer
57 //
58
59 void ExtensionActionAPI::Observer::OnExtensionActionUpdated(
60     ExtensionAction* extension_action,
61     content::WebContents* web_contents,
62     content::BrowserContext* browser_context) {
63 }
64
65 void ExtensionActionAPI::Observer::OnPageActionsUpdated(
66     content::WebContents* web_contents) {
67 }
68
69 void ExtensionActionAPI::Observer::OnExtensionActionAPIShuttingDown() {
70 }
71
72 ExtensionActionAPI::Observer::~Observer() {
73 }
74
75 //
76 // ExtensionActionAPI
77 //
78
79 static base::LazyInstance<BrowserContextKeyedAPIFactory<ExtensionActionAPI> >
80     g_factory = LAZY_INSTANCE_INITIALIZER;
81
82 ExtensionActionAPI::ExtensionActionAPI(content::BrowserContext* context)
83     : browser_context_(context) {
84   ExtensionFunctionRegistry* registry =
85       ExtensionFunctionRegistry::GetInstance();
86
87   // Browser Actions
88   registry->RegisterFunction<BrowserActionSetIconFunction>();
89   registry->RegisterFunction<BrowserActionSetTitleFunction>();
90   registry->RegisterFunction<BrowserActionSetBadgeTextFunction>();
91   registry->RegisterFunction<BrowserActionSetBadgeBackgroundColorFunction>();
92   registry->RegisterFunction<BrowserActionSetPopupFunction>();
93   registry->RegisterFunction<BrowserActionGetTitleFunction>();
94   registry->RegisterFunction<BrowserActionGetBadgeTextFunction>();
95   registry->RegisterFunction<BrowserActionGetBadgeBackgroundColorFunction>();
96   registry->RegisterFunction<BrowserActionGetPopupFunction>();
97   registry->RegisterFunction<BrowserActionEnableFunction>();
98   registry->RegisterFunction<BrowserActionDisableFunction>();
99   registry->RegisterFunction<BrowserActionOpenPopupFunction>();
100
101   // Page Actions
102   registry->RegisterFunction<PageActionShowFunction>();
103   registry->RegisterFunction<PageActionHideFunction>();
104   registry->RegisterFunction<PageActionSetIconFunction>();
105   registry->RegisterFunction<PageActionSetTitleFunction>();
106   registry->RegisterFunction<PageActionSetPopupFunction>();
107   registry->RegisterFunction<PageActionGetTitleFunction>();
108   registry->RegisterFunction<PageActionGetPopupFunction>();
109 }
110
111 ExtensionActionAPI::~ExtensionActionAPI() {
112 }
113
114 // static
115 BrowserContextKeyedAPIFactory<ExtensionActionAPI>*
116 ExtensionActionAPI::GetFactoryInstance() {
117   return g_factory.Pointer();
118 }
119
120 // static
121 ExtensionActionAPI* ExtensionActionAPI::Get(content::BrowserContext* context) {
122   return BrowserContextKeyedAPIFactory<ExtensionActionAPI>::Get(context);
123 }
124
125 // static
126 bool ExtensionActionAPI::GetBrowserActionVisibility(
127     const ExtensionPrefs* prefs,
128     const std::string& extension_id) {
129   bool visible = false;
130   if (!prefs || !prefs->ReadPrefAsBoolean(extension_id,
131                                           kBrowserActionVisible,
132                                           &visible)) {
133     return true;
134   }
135   return visible;
136 }
137
138 // static
139 void ExtensionActionAPI::SetBrowserActionVisibility(
140     ExtensionPrefs* prefs,
141     const std::string& extension_id,
142     bool visible) {
143   if (GetBrowserActionVisibility(prefs, extension_id) == visible)
144     return;
145
146   prefs->UpdateExtensionPref(extension_id,
147                              kBrowserActionVisible,
148                              new base::FundamentalValue(visible));
149   content::NotificationService::current()->Notify(
150       NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED,
151       content::Source<ExtensionPrefs>(prefs),
152       content::Details<const std::string>(&extension_id));
153 }
154
155 void ExtensionActionAPI::AddObserver(Observer* observer) {
156   observers_.AddObserver(observer);
157 }
158
159 void ExtensionActionAPI::RemoveObserver(Observer* observer) {
160   observers_.RemoveObserver(observer);
161 }
162
163 ExtensionAction::ShowAction ExtensionActionAPI::ExecuteExtensionAction(
164     const Extension* extension,
165     Browser* browser,
166     bool grant_active_tab_permissions) {
167   content::WebContents* web_contents =
168       browser->tab_strip_model()->GetActiveWebContents();
169   if (!web_contents)
170     return ExtensionAction::ACTION_NONE;
171
172   int tab_id = SessionTabHelper::IdForTab(web_contents);
173
174   ActiveScriptController* active_script_controller =
175       ActiveScriptController::GetForWebContents(web_contents);
176   bool has_pending_scripts = false;
177   if (active_script_controller &&
178       active_script_controller->WantsToRun(extension)) {
179     has_pending_scripts = true;
180   }
181
182   // Grant active tab if appropriate.
183   if (grant_active_tab_permissions) {
184     TabHelper::FromWebContents(web_contents)->active_tab_permission_granter()->
185         GrantIfRequested(extension);
186   }
187
188   // If this was a request to run a script, it will have been run once active
189   // tab was granted. Return without executing the action, since we should only
190   // run pending scripts OR the extension action, not both.
191   if (has_pending_scripts)
192     return ExtensionAction::ACTION_NONE;
193
194   ExtensionAction* extension_action =
195       ExtensionActionManager::Get(browser_context_)->GetExtensionAction(
196           *extension);
197
198   // Anything that gets here should have a page or browser action.
199   DCHECK(extension_action);
200   if (!extension_action->GetIsVisible(tab_id))
201     return ExtensionAction::ACTION_NONE;
202
203   if (extension_action->HasPopup(tab_id))
204     return ExtensionAction::ACTION_SHOW_POPUP;
205
206   ExtensionActionExecuted(*extension_action, web_contents);
207   return ExtensionAction::ACTION_NONE;
208 }
209
210 bool ExtensionActionAPI::ShowExtensionActionPopup(
211     const Extension* extension,
212     Browser* browser,
213     bool grant_active_tab_permissions) {
214   ExtensionAction* extension_action =
215       ExtensionActionManager::Get(browser_context_)->GetExtensionAction(
216           *extension);
217   if (!extension_action)
218     return false;
219
220   if (extension_action->action_type() == ActionInfo::TYPE_PAGE &&
221       !FeatureSwitch::extension_action_redesign()->IsEnabled()) {
222     // We show page actions in the location bar unless the new toolbar is
223     // enabled.
224     return browser->window()->GetLocationBar()->ShowPageActionPopup(
225         extension, grant_active_tab_permissions);
226   } else {
227     return ExtensionToolbarModel::Get(browser->profile())->
228         ShowExtensionActionPopup(
229             extension, browser, grant_active_tab_permissions);
230   }
231 }
232
233 void ExtensionActionAPI::NotifyChange(ExtensionAction* extension_action,
234                                       content::WebContents* web_contents,
235                                       content::BrowserContext* context) {
236   FOR_EACH_OBSERVER(
237       Observer,
238       observers_,
239       OnExtensionActionUpdated(extension_action, web_contents, context));
240
241   if (extension_action->action_type() == ActionInfo::TYPE_PAGE)
242     NotifyPageActionsChanged(web_contents);
243 }
244
245 void ExtensionActionAPI::ClearAllValuesForTab(
246     content::WebContents* web_contents) {
247   DCHECK(web_contents);
248   int tab_id = SessionTabHelper::IdForTab(web_contents);
249   content::BrowserContext* browser_context = web_contents->GetBrowserContext();
250   const ExtensionSet& enabled_extensions =
251       ExtensionRegistry::Get(browser_context_)->enabled_extensions();
252   ExtensionActionManager* action_manager =
253       ExtensionActionManager::Get(browser_context_);
254
255   for (ExtensionSet::const_iterator iter = enabled_extensions.begin();
256        iter != enabled_extensions.end(); ++iter) {
257     ExtensionAction* extension_action =
258         action_manager->GetBrowserAction(*iter->get());
259     if (!extension_action)
260       extension_action = action_manager->GetPageAction(*iter->get());
261     if (extension_action) {
262       extension_action->ClearAllValuesForTab(tab_id);
263       NotifyChange(extension_action, web_contents, browser_context);
264     }
265   }
266 }
267
268 void ExtensionActionAPI::DispatchEventToExtension(
269     content::BrowserContext* context,
270     const std::string& extension_id,
271     const std::string& event_name,
272     scoped_ptr<base::ListValue> event_args) {
273   if (!EventRouter::Get(context))
274     return;
275
276   scoped_ptr<Event> event(new Event(event_name, event_args.Pass()));
277   event->restrict_to_browser_context = context;
278   event->user_gesture = EventRouter::USER_GESTURE_ENABLED;
279   EventRouter::Get(context)
280       ->DispatchEventToExtension(extension_id, event.Pass());
281 }
282
283 void ExtensionActionAPI::ExtensionActionExecuted(
284     const ExtensionAction& extension_action,
285     WebContents* web_contents) {
286   const char* event_name = NULL;
287   switch (extension_action.action_type()) {
288     case ActionInfo::TYPE_BROWSER:
289       event_name = "browserAction.onClicked";
290       break;
291     case ActionInfo::TYPE_PAGE:
292       event_name = "pageAction.onClicked";
293       break;
294     case ActionInfo::TYPE_SYSTEM_INDICATOR:
295       // The System Indicator handles its own clicks.
296       break;
297   }
298
299   if (event_name) {
300     scoped_ptr<base::ListValue> args(new base::ListValue());
301     base::DictionaryValue* tab_value =
302         ExtensionTabUtil::CreateTabValue(web_contents);
303     args->Append(tab_value);
304
305     DispatchEventToExtension(
306         web_contents->GetBrowserContext(),
307         extension_action.extension_id(),
308         event_name,
309         args.Pass());
310   }
311 }
312
313 void ExtensionActionAPI::NotifyPageActionsChanged(
314     content::WebContents* web_contents) {
315   Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
316   if (!browser)
317     return;
318   LocationBar* location_bar =
319       browser->window() ? browser->window()->GetLocationBar() : NULL;
320   if (!location_bar)
321     return;
322   location_bar->UpdatePageActions();
323
324   FOR_EACH_OBSERVER(Observer, observers_, OnPageActionsUpdated(web_contents));
325 }
326
327 void ExtensionActionAPI::Shutdown() {
328   FOR_EACH_OBSERVER(Observer, observers_, OnExtensionActionAPIShuttingDown());
329 }
330
331 //
332 // ExtensionActionFunction
333 //
334
335 ExtensionActionFunction::ExtensionActionFunction()
336     : details_(NULL),
337       tab_id_(ExtensionAction::kDefaultTabId),
338       contents_(NULL),
339       extension_action_(NULL) {
340 }
341
342 ExtensionActionFunction::~ExtensionActionFunction() {
343 }
344
345 bool ExtensionActionFunction::RunSync() {
346   ExtensionActionManager* manager = ExtensionActionManager::Get(GetProfile());
347   if (StartsWithASCII(name(), "systemIndicator.", false)) {
348     extension_action_ = manager->GetSystemIndicator(*extension());
349   } else {
350     extension_action_ = manager->GetBrowserAction(*extension());
351     if (!extension_action_) {
352       extension_action_ = manager->GetPageAction(*extension());
353     }
354   }
355   if (!extension_action_) {
356     // TODO(kalman): ideally the browserAction/pageAction APIs wouldn't event
357     // exist for extensions that don't have one declared. This should come as
358     // part of the Feature system.
359     error_ = kNoExtensionActionError;
360     return false;
361   }
362
363   // Populates the tab_id_ and details_ members.
364   EXTENSION_FUNCTION_VALIDATE(ExtractDataFromArguments());
365
366   // Find the WebContents that contains this tab id if one is required.
367   if (tab_id_ != ExtensionAction::kDefaultTabId) {
368     ExtensionTabUtil::GetTabById(tab_id_,
369                                  GetProfile(),
370                                  include_incognito(),
371                                  NULL,
372                                  NULL,
373                                  &contents_,
374                                  NULL);
375     if (!contents_) {
376       error_ = ErrorUtils::FormatErrorMessage(
377           kNoTabError, base::IntToString(tab_id_));
378       return false;
379     }
380   } else {
381     // Only browser actions and system indicators have a default tabId.
382     ActionInfo::Type action_type = extension_action_->action_type();
383     EXTENSION_FUNCTION_VALIDATE(
384         action_type == ActionInfo::TYPE_BROWSER ||
385         action_type == ActionInfo::TYPE_SYSTEM_INDICATOR);
386   }
387   return RunExtensionAction();
388 }
389
390 bool ExtensionActionFunction::ExtractDataFromArguments() {
391   // There may or may not be details (depends on the function).
392   // The tabId might appear in details (if it exists), as the first
393   // argument besides the action type (depends on the function), or be omitted
394   // entirely.
395   base::Value* first_arg = NULL;
396   if (!args_->Get(0, &first_arg))
397     return true;
398
399   switch (first_arg->GetType()) {
400     case base::Value::TYPE_INTEGER:
401       CHECK(first_arg->GetAsInteger(&tab_id_));
402       break;
403
404     case base::Value::TYPE_DICTIONARY: {
405       // Found the details argument.
406       details_ = static_cast<base::DictionaryValue*>(first_arg);
407       // Still need to check for the tabId within details.
408       base::Value* tab_id_value = NULL;
409       if (details_->Get("tabId", &tab_id_value)) {
410         switch (tab_id_value->GetType()) {
411           case base::Value::TYPE_NULL:
412             // OK; tabId is optional, leave it default.
413             return true;
414           case base::Value::TYPE_INTEGER:
415             CHECK(tab_id_value->GetAsInteger(&tab_id_));
416             return true;
417           default:
418             // Boom.
419             return false;
420         }
421       }
422       // Not found; tabId is optional, leave it default.
423       break;
424     }
425
426     case base::Value::TYPE_NULL:
427       // The tabId might be an optional argument.
428       break;
429
430     default:
431       return false;
432   }
433
434   return true;
435 }
436
437 void ExtensionActionFunction::NotifyChange() {
438   ExtensionActionAPI::Get(GetProfile())->NotifyChange(
439       extension_action_, contents_, GetProfile());
440 }
441
442 bool ExtensionActionFunction::SetVisible(bool visible) {
443   if (extension_action_->GetIsVisible(tab_id_) == visible)
444     return true;
445   extension_action_->SetIsVisible(tab_id_, visible);
446   NotifyChange();
447   return true;
448 }
449
450 bool ExtensionActionShowFunction::RunExtensionAction() {
451   return SetVisible(true);
452 }
453
454 bool ExtensionActionHideFunction::RunExtensionAction() {
455   return SetVisible(false);
456 }
457
458 bool ExtensionActionSetIconFunction::RunExtensionAction() {
459   EXTENSION_FUNCTION_VALIDATE(details_);
460
461   // setIcon can take a variant argument: either a dictionary of canvas
462   // ImageData, or an icon index.
463   base::DictionaryValue* canvas_set = NULL;
464   int icon_index;
465   if (details_->GetDictionary("imageData", &canvas_set)) {
466     gfx::ImageSkia icon;
467
468     EXTENSION_FUNCTION_VALIDATE(
469         ExtensionAction::ParseIconFromCanvasDictionary(*canvas_set, &icon));
470
471     extension_action_->SetIcon(tab_id_, gfx::Image(icon));
472   } else if (details_->GetInteger("iconIndex", &icon_index)) {
473     // Obsolete argument: ignore it.
474     return true;
475   } else {
476     EXTENSION_FUNCTION_VALIDATE(false);
477   }
478   NotifyChange();
479   return true;
480 }
481
482 bool ExtensionActionSetTitleFunction::RunExtensionAction() {
483   EXTENSION_FUNCTION_VALIDATE(details_);
484   std::string title;
485   EXTENSION_FUNCTION_VALIDATE(details_->GetString("title", &title));
486   extension_action_->SetTitle(tab_id_, title);
487   NotifyChange();
488   return true;
489 }
490
491 bool ExtensionActionSetPopupFunction::RunExtensionAction() {
492   EXTENSION_FUNCTION_VALIDATE(details_);
493   std::string popup_string;
494   EXTENSION_FUNCTION_VALIDATE(details_->GetString("popup", &popup_string));
495
496   GURL popup_url;
497   if (!popup_string.empty())
498     popup_url = extension()->GetResourceURL(popup_string);
499
500   extension_action_->SetPopupUrl(tab_id_, popup_url);
501   NotifyChange();
502   return true;
503 }
504
505 bool ExtensionActionSetBadgeTextFunction::RunExtensionAction() {
506   EXTENSION_FUNCTION_VALIDATE(details_);
507   std::string badge_text;
508   EXTENSION_FUNCTION_VALIDATE(details_->GetString("text", &badge_text));
509   extension_action_->SetBadgeText(tab_id_, badge_text);
510   NotifyChange();
511   return true;
512 }
513
514 bool ExtensionActionSetBadgeBackgroundColorFunction::RunExtensionAction() {
515   EXTENSION_FUNCTION_VALIDATE(details_);
516   base::Value* color_value = NULL;
517   EXTENSION_FUNCTION_VALIDATE(details_->Get("color", &color_value));
518   SkColor color = 0;
519   if (color_value->IsType(base::Value::TYPE_LIST)) {
520     base::ListValue* list = NULL;
521     EXTENSION_FUNCTION_VALIDATE(details_->GetList("color", &list));
522     EXTENSION_FUNCTION_VALIDATE(list->GetSize() == 4);
523
524     int color_array[4] = {0};
525     for (size_t i = 0; i < arraysize(color_array); ++i) {
526       EXTENSION_FUNCTION_VALIDATE(list->GetInteger(i, &color_array[i]));
527     }
528
529     color = SkColorSetARGB(color_array[3], color_array[0],
530                            color_array[1], color_array[2]);
531   } else if (color_value->IsType(base::Value::TYPE_STRING)) {
532     std::string color_string;
533     EXTENSION_FUNCTION_VALIDATE(details_->GetString("color", &color_string));
534     if (!image_util::ParseCSSColorString(color_string, &color))
535       return false;
536   }
537
538   extension_action_->SetBadgeBackgroundColor(tab_id_, color);
539   NotifyChange();
540   return true;
541 }
542
543 bool ExtensionActionGetTitleFunction::RunExtensionAction() {
544   SetResult(new base::StringValue(extension_action_->GetTitle(tab_id_)));
545   return true;
546 }
547
548 bool ExtensionActionGetPopupFunction::RunExtensionAction() {
549   SetResult(
550       new base::StringValue(extension_action_->GetPopupUrl(tab_id_).spec()));
551   return true;
552 }
553
554 bool ExtensionActionGetBadgeTextFunction::RunExtensionAction() {
555   SetResult(new base::StringValue(extension_action_->GetBadgeText(tab_id_)));
556   return true;
557 }
558
559 bool ExtensionActionGetBadgeBackgroundColorFunction::RunExtensionAction() {
560   base::ListValue* list = new base::ListValue();
561   SkColor color = extension_action_->GetBadgeBackgroundColor(tab_id_);
562   list->Append(
563       new base::FundamentalValue(static_cast<int>(SkColorGetR(color))));
564   list->Append(
565       new base::FundamentalValue(static_cast<int>(SkColorGetG(color))));
566   list->Append(
567       new base::FundamentalValue(static_cast<int>(SkColorGetB(color))));
568   list->Append(
569       new base::FundamentalValue(static_cast<int>(SkColorGetA(color))));
570   SetResult(list);
571   return true;
572 }
573
574 BrowserActionOpenPopupFunction::BrowserActionOpenPopupFunction()
575     : response_sent_(false) {
576 }
577
578 bool BrowserActionOpenPopupFunction::RunAsync() {
579   // We only allow the popup in the active window.
580   Browser* browser = chrome::FindLastActiveWithProfile(
581                          GetProfile(), chrome::GetActiveDesktop());
582
583   // If there's no active browser, or the Toolbar isn't visible, abort.
584   // Otherwise, try to open a popup in the active browser.
585   // TODO(justinlin): Remove toolbar check when http://crbug.com/308645 is
586   // fixed.
587   if (!browser ||
588       !browser->window()->IsActive() ||
589       !browser->window()->IsToolbarVisible() ||
590       !ExtensionActionAPI::Get(GetProfile())->ShowExtensionActionPopup(
591           extension_.get(), browser, false)) {
592     error_ = kOpenPopupError;
593     return false;
594   }
595
596   registrar_.Add(this,
597                  NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING,
598                  content::Source<Profile>(GetProfile()));
599
600   // Set a timeout for waiting for the notification that the popup is loaded.
601   // Waiting is required so that the popup view can be retrieved by the custom
602   // bindings for the response callback. It's also needed to keep this function
603   // instance around until a notification is observed.
604   base::MessageLoopForUI::current()->PostDelayedTask(
605       FROM_HERE,
606       base::Bind(&BrowserActionOpenPopupFunction::OpenPopupTimedOut, this),
607       base::TimeDelta::FromSeconds(10));
608   return true;
609 }
610
611 void BrowserActionOpenPopupFunction::OpenPopupTimedOut() {
612   if (response_sent_)
613     return;
614
615   DVLOG(1) << "chrome.browserAction.openPopup did not show a popup.";
616   error_ = kOpenPopupError;
617   SendResponse(false);
618   response_sent_ = true;
619 }
620
621 void BrowserActionOpenPopupFunction::Observe(
622     int type,
623     const content::NotificationSource& source,
624     const content::NotificationDetails& details) {
625   DCHECK_EQ(NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING, type);
626   if (response_sent_)
627     return;
628
629   ExtensionHost* host = content::Details<ExtensionHost>(details).ptr();
630   if (host->extension_host_type() != VIEW_TYPE_EXTENSION_POPUP ||
631       host->extension()->id() != extension_->id())
632     return;
633
634   SendResponse(true);
635   response_sent_ = true;
636   registrar_.RemoveAll();
637 }
638
639 }  // namespace extensions