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.
5 #include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
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"
36 using content::WebContents;
38 namespace extensions {
42 // Whether the browser action is visible in the toolbar.
43 const char kBrowserActionVisible[] = "browser_action_visible";
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 "
56 // ExtensionActionAPI::Observer
59 void ExtensionActionAPI::Observer::OnExtensionActionUpdated(
60 ExtensionAction* extension_action,
61 content::WebContents* web_contents,
62 content::BrowserContext* browser_context) {
65 void ExtensionActionAPI::Observer::OnPageActionsUpdated(
66 content::WebContents* web_contents) {
69 void ExtensionActionAPI::Observer::OnExtensionActionAPIShuttingDown() {
72 ExtensionActionAPI::Observer::~Observer() {
79 static base::LazyInstance<BrowserContextKeyedAPIFactory<ExtensionActionAPI> >
80 g_factory = LAZY_INSTANCE_INITIALIZER;
82 ExtensionActionAPI::ExtensionActionAPI(content::BrowserContext* context)
83 : browser_context_(context) {
84 ExtensionFunctionRegistry* registry =
85 ExtensionFunctionRegistry::GetInstance();
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>();
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>();
111 ExtensionActionAPI::~ExtensionActionAPI() {
115 BrowserContextKeyedAPIFactory<ExtensionActionAPI>*
116 ExtensionActionAPI::GetFactoryInstance() {
117 return g_factory.Pointer();
121 ExtensionActionAPI* ExtensionActionAPI::Get(content::BrowserContext* context) {
122 return BrowserContextKeyedAPIFactory<ExtensionActionAPI>::Get(context);
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,
139 void ExtensionActionAPI::SetBrowserActionVisibility(
140 ExtensionPrefs* prefs,
141 const std::string& extension_id,
143 if (GetBrowserActionVisibility(prefs, extension_id) == visible)
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));
155 void ExtensionActionAPI::AddObserver(Observer* observer) {
156 observers_.AddObserver(observer);
159 void ExtensionActionAPI::RemoveObserver(Observer* observer) {
160 observers_.RemoveObserver(observer);
163 ExtensionAction::ShowAction ExtensionActionAPI::ExecuteExtensionAction(
164 const Extension* extension,
166 bool grant_active_tab_permissions) {
167 content::WebContents* web_contents =
168 browser->tab_strip_model()->GetActiveWebContents();
170 return ExtensionAction::ACTION_NONE;
172 int tab_id = SessionTabHelper::IdForTab(web_contents);
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;
182 // Grant active tab if appropriate.
183 if (grant_active_tab_permissions) {
184 TabHelper::FromWebContents(web_contents)->active_tab_permission_granter()->
185 GrantIfRequested(extension);
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;
194 ExtensionAction* extension_action =
195 ExtensionActionManager::Get(browser_context_)->GetExtensionAction(
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;
203 if (extension_action->HasPopup(tab_id))
204 return ExtensionAction::ACTION_SHOW_POPUP;
206 ExtensionActionExecuted(*extension_action, web_contents);
207 return ExtensionAction::ACTION_NONE;
210 bool ExtensionActionAPI::ShowExtensionActionPopup(
211 const Extension* extension,
213 bool grant_active_tab_permissions) {
214 ExtensionAction* extension_action =
215 ExtensionActionManager::Get(browser_context_)->GetExtensionAction(
217 if (!extension_action)
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
224 return browser->window()->GetLocationBar()->ShowPageActionPopup(
225 extension, grant_active_tab_permissions);
227 return ExtensionToolbarModel::Get(browser->profile())->
228 ShowExtensionActionPopup(
229 extension, browser, grant_active_tab_permissions);
233 void ExtensionActionAPI::NotifyChange(ExtensionAction* extension_action,
234 content::WebContents* web_contents,
235 content::BrowserContext* context) {
239 OnExtensionActionUpdated(extension_action, web_contents, context));
241 if (extension_action->action_type() == ActionInfo::TYPE_PAGE)
242 NotifyPageActionsChanged(web_contents);
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_);
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);
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))
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());
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";
291 case ActionInfo::TYPE_PAGE:
292 event_name = "pageAction.onClicked";
294 case ActionInfo::TYPE_SYSTEM_INDICATOR:
295 // The System Indicator handles its own clicks.
300 scoped_ptr<base::ListValue> args(new base::ListValue());
301 base::DictionaryValue* tab_value =
302 ExtensionTabUtil::CreateTabValue(web_contents);
303 args->Append(tab_value);
305 DispatchEventToExtension(
306 web_contents->GetBrowserContext(),
307 extension_action.extension_id(),
313 void ExtensionActionAPI::NotifyPageActionsChanged(
314 content::WebContents* web_contents) {
315 Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
318 LocationBar* location_bar =
319 browser->window() ? browser->window()->GetLocationBar() : NULL;
322 location_bar->UpdatePageActions();
324 FOR_EACH_OBSERVER(Observer, observers_, OnPageActionsUpdated(web_contents));
327 void ExtensionActionAPI::Shutdown() {
328 FOR_EACH_OBSERVER(Observer, observers_, OnExtensionActionAPIShuttingDown());
332 // ExtensionActionFunction
335 ExtensionActionFunction::ExtensionActionFunction()
337 tab_id_(ExtensionAction::kDefaultTabId),
339 extension_action_(NULL) {
342 ExtensionActionFunction::~ExtensionActionFunction() {
345 bool ExtensionActionFunction::RunSync() {
346 ExtensionActionManager* manager = ExtensionActionManager::Get(GetProfile());
347 if (StartsWithASCII(name(), "systemIndicator.", false)) {
348 extension_action_ = manager->GetSystemIndicator(*extension());
350 extension_action_ = manager->GetBrowserAction(*extension());
351 if (!extension_action_) {
352 extension_action_ = manager->GetPageAction(*extension());
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;
363 // Populates the tab_id_ and details_ members.
364 EXTENSION_FUNCTION_VALIDATE(ExtractDataFromArguments());
366 // Find the WebContents that contains this tab id if one is required.
367 if (tab_id_ != ExtensionAction::kDefaultTabId) {
368 ExtensionTabUtil::GetTabById(tab_id_,
376 error_ = ErrorUtils::FormatErrorMessage(
377 kNoTabError, base::IntToString(tab_id_));
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);
387 return RunExtensionAction();
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
395 base::Value* first_arg = NULL;
396 if (!args_->Get(0, &first_arg))
399 switch (first_arg->GetType()) {
400 case base::Value::TYPE_INTEGER:
401 CHECK(first_arg->GetAsInteger(&tab_id_));
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.
414 case base::Value::TYPE_INTEGER:
415 CHECK(tab_id_value->GetAsInteger(&tab_id_));
422 // Not found; tabId is optional, leave it default.
426 case base::Value::TYPE_NULL:
427 // The tabId might be an optional argument.
437 void ExtensionActionFunction::NotifyChange() {
438 ExtensionActionAPI::Get(GetProfile())->NotifyChange(
439 extension_action_, contents_, GetProfile());
442 bool ExtensionActionFunction::SetVisible(bool visible) {
443 if (extension_action_->GetIsVisible(tab_id_) == visible)
445 extension_action_->SetIsVisible(tab_id_, visible);
450 bool ExtensionActionShowFunction::RunExtensionAction() {
451 return SetVisible(true);
454 bool ExtensionActionHideFunction::RunExtensionAction() {
455 return SetVisible(false);
458 bool ExtensionActionSetIconFunction::RunExtensionAction() {
459 EXTENSION_FUNCTION_VALIDATE(details_);
461 // setIcon can take a variant argument: either a dictionary of canvas
462 // ImageData, or an icon index.
463 base::DictionaryValue* canvas_set = NULL;
465 if (details_->GetDictionary("imageData", &canvas_set)) {
468 EXTENSION_FUNCTION_VALIDATE(
469 ExtensionAction::ParseIconFromCanvasDictionary(*canvas_set, &icon));
471 extension_action_->SetIcon(tab_id_, gfx::Image(icon));
472 } else if (details_->GetInteger("iconIndex", &icon_index)) {
473 // Obsolete argument: ignore it.
476 EXTENSION_FUNCTION_VALIDATE(false);
482 bool ExtensionActionSetTitleFunction::RunExtensionAction() {
483 EXTENSION_FUNCTION_VALIDATE(details_);
485 EXTENSION_FUNCTION_VALIDATE(details_->GetString("title", &title));
486 extension_action_->SetTitle(tab_id_, title);
491 bool ExtensionActionSetPopupFunction::RunExtensionAction() {
492 EXTENSION_FUNCTION_VALIDATE(details_);
493 std::string popup_string;
494 EXTENSION_FUNCTION_VALIDATE(details_->GetString("popup", &popup_string));
497 if (!popup_string.empty())
498 popup_url = extension()->GetResourceURL(popup_string);
500 extension_action_->SetPopupUrl(tab_id_, popup_url);
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);
514 bool ExtensionActionSetBadgeBackgroundColorFunction::RunExtensionAction() {
515 EXTENSION_FUNCTION_VALIDATE(details_);
516 base::Value* color_value = NULL;
517 EXTENSION_FUNCTION_VALIDATE(details_->Get("color", &color_value));
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);
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]));
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))
538 extension_action_->SetBadgeBackgroundColor(tab_id_, color);
543 bool ExtensionActionGetTitleFunction::RunExtensionAction() {
544 SetResult(new base::StringValue(extension_action_->GetTitle(tab_id_)));
548 bool ExtensionActionGetPopupFunction::RunExtensionAction() {
550 new base::StringValue(extension_action_->GetPopupUrl(tab_id_).spec()));
554 bool ExtensionActionGetBadgeTextFunction::RunExtensionAction() {
555 SetResult(new base::StringValue(extension_action_->GetBadgeText(tab_id_)));
559 bool ExtensionActionGetBadgeBackgroundColorFunction::RunExtensionAction() {
560 base::ListValue* list = new base::ListValue();
561 SkColor color = extension_action_->GetBadgeBackgroundColor(tab_id_);
563 new base::FundamentalValue(static_cast<int>(SkColorGetR(color))));
565 new base::FundamentalValue(static_cast<int>(SkColorGetG(color))));
567 new base::FundamentalValue(static_cast<int>(SkColorGetB(color))));
569 new base::FundamentalValue(static_cast<int>(SkColorGetA(color))));
574 BrowserActionOpenPopupFunction::BrowserActionOpenPopupFunction()
575 : response_sent_(false) {
578 bool BrowserActionOpenPopupFunction::RunAsync() {
579 // We only allow the popup in the active window.
580 Browser* browser = chrome::FindLastActiveWithProfile(
581 GetProfile(), chrome::GetActiveDesktop());
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
588 !browser->window()->IsActive() ||
589 !browser->window()->IsToolbarVisible() ||
590 !ExtensionActionAPI::Get(GetProfile())->ShowExtensionActionPopup(
591 extension_.get(), browser, false)) {
592 error_ = kOpenPopupError;
597 NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING,
598 content::Source<Profile>(GetProfile()));
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(
606 base::Bind(&BrowserActionOpenPopupFunction::OpenPopupTimedOut, this),
607 base::TimeDelta::FromSeconds(10));
611 void BrowserActionOpenPopupFunction::OpenPopupTimedOut() {
615 DVLOG(1) << "chrome.browserAction.openPopup did not show a popup.";
616 error_ = kOpenPopupError;
618 response_sent_ = true;
621 void BrowserActionOpenPopupFunction::Observe(
623 const content::NotificationSource& source,
624 const content::NotificationDetails& details) {
625 DCHECK_EQ(NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING, type);
629 ExtensionHost* host = content::Details<ExtensionHost>(details).ptr();
630 if (host->extension_host_type() != VIEW_TYPE_EXTENSION_POPUP ||
631 host->extension()->id() != extension_->id())
635 response_sent_ = true;
636 registrar_.RemoveAll();
639 } // namespace extensions