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 bool ExtensionActionAPI::ExtensionWantsToRun(
234 const Extension* extension, content::WebContents* web_contents) {
235 // An extension wants to act if it has a visible page action on the given
237 ExtensionAction* page_action =
238 ExtensionActionManager::Get(browser_context_)->GetPageAction(*extension);
240 page_action->GetIsVisible(SessionTabHelper::IdForTab(web_contents)))
243 // ... Or if it has pending scripts that need approval for execution.
244 ActiveScriptController* active_script_controller =
245 ActiveScriptController::GetForWebContents(web_contents);
246 if (active_script_controller &&
247 active_script_controller->WantsToRun(extension))
253 void ExtensionActionAPI::NotifyChange(ExtensionAction* extension_action,
254 content::WebContents* web_contents,
255 content::BrowserContext* context) {
259 OnExtensionActionUpdated(extension_action, web_contents, context));
261 if (extension_action->action_type() == ActionInfo::TYPE_PAGE)
262 NotifyPageActionsChanged(web_contents);
265 void ExtensionActionAPI::ClearAllValuesForTab(
266 content::WebContents* web_contents) {
267 DCHECK(web_contents);
268 int tab_id = SessionTabHelper::IdForTab(web_contents);
269 content::BrowserContext* browser_context = web_contents->GetBrowserContext();
270 const ExtensionSet& enabled_extensions =
271 ExtensionRegistry::Get(browser_context_)->enabled_extensions();
272 ExtensionActionManager* action_manager =
273 ExtensionActionManager::Get(browser_context_);
275 for (ExtensionSet::const_iterator iter = enabled_extensions.begin();
276 iter != enabled_extensions.end(); ++iter) {
277 ExtensionAction* extension_action =
278 action_manager->GetExtensionAction(**iter);
279 if (extension_action) {
280 extension_action->ClearAllValuesForTab(tab_id);
281 NotifyChange(extension_action, web_contents, browser_context);
286 void ExtensionActionAPI::DispatchEventToExtension(
287 content::BrowserContext* context,
288 const std::string& extension_id,
289 const std::string& event_name,
290 scoped_ptr<base::ListValue> event_args) {
291 if (!EventRouter::Get(context))
294 scoped_ptr<Event> event(new Event(event_name, event_args.Pass()));
295 event->restrict_to_browser_context = context;
296 event->user_gesture = EventRouter::USER_GESTURE_ENABLED;
297 EventRouter::Get(context)
298 ->DispatchEventToExtension(extension_id, event.Pass());
301 void ExtensionActionAPI::ExtensionActionExecuted(
302 const ExtensionAction& extension_action,
303 WebContents* web_contents) {
304 const char* event_name = NULL;
305 switch (extension_action.action_type()) {
306 case ActionInfo::TYPE_BROWSER:
307 event_name = "browserAction.onClicked";
309 case ActionInfo::TYPE_PAGE:
310 event_name = "pageAction.onClicked";
312 case ActionInfo::TYPE_SYSTEM_INDICATOR:
313 // The System Indicator handles its own clicks.
318 scoped_ptr<base::ListValue> args(new base::ListValue());
319 base::DictionaryValue* tab_value =
320 ExtensionTabUtil::CreateTabValue(web_contents);
321 args->Append(tab_value);
323 DispatchEventToExtension(
324 web_contents->GetBrowserContext(),
325 extension_action.extension_id(),
331 void ExtensionActionAPI::NotifyPageActionsChanged(
332 content::WebContents* web_contents) {
333 Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
336 LocationBar* location_bar =
337 browser->window() ? browser->window()->GetLocationBar() : NULL;
340 location_bar->UpdatePageActions();
342 FOR_EACH_OBSERVER(Observer, observers_, OnPageActionsUpdated(web_contents));
345 void ExtensionActionAPI::Shutdown() {
346 FOR_EACH_OBSERVER(Observer, observers_, OnExtensionActionAPIShuttingDown());
350 // ExtensionActionFunction
353 ExtensionActionFunction::ExtensionActionFunction()
355 tab_id_(ExtensionAction::kDefaultTabId),
357 extension_action_(NULL) {
360 ExtensionActionFunction::~ExtensionActionFunction() {
363 bool ExtensionActionFunction::RunSync() {
364 ExtensionActionManager* manager = ExtensionActionManager::Get(GetProfile());
365 if (StartsWithASCII(name(), "systemIndicator.", false)) {
366 extension_action_ = manager->GetSystemIndicator(*extension());
368 extension_action_ = manager->GetBrowserAction(*extension());
369 if (!extension_action_) {
370 extension_action_ = manager->GetPageAction(*extension());
373 if (!extension_action_) {
374 // TODO(kalman): ideally the browserAction/pageAction APIs wouldn't event
375 // exist for extensions that don't have one declared. This should come as
376 // part of the Feature system.
377 error_ = kNoExtensionActionError;
381 // Populates the tab_id_ and details_ members.
382 EXTENSION_FUNCTION_VALIDATE(ExtractDataFromArguments());
384 // Find the WebContents that contains this tab id if one is required.
385 if (tab_id_ != ExtensionAction::kDefaultTabId) {
386 ExtensionTabUtil::GetTabById(tab_id_,
394 error_ = ErrorUtils::FormatErrorMessage(
395 kNoTabError, base::IntToString(tab_id_));
399 // Only browser actions and system indicators have a default tabId.
400 ActionInfo::Type action_type = extension_action_->action_type();
401 EXTENSION_FUNCTION_VALIDATE(
402 action_type == ActionInfo::TYPE_BROWSER ||
403 action_type == ActionInfo::TYPE_SYSTEM_INDICATOR);
405 return RunExtensionAction();
408 bool ExtensionActionFunction::ExtractDataFromArguments() {
409 // There may or may not be details (depends on the function).
410 // The tabId might appear in details (if it exists), as the first
411 // argument besides the action type (depends on the function), or be omitted
413 base::Value* first_arg = NULL;
414 if (!args_->Get(0, &first_arg))
417 switch (first_arg->GetType()) {
418 case base::Value::TYPE_INTEGER:
419 CHECK(first_arg->GetAsInteger(&tab_id_));
422 case base::Value::TYPE_DICTIONARY: {
423 // Found the details argument.
424 details_ = static_cast<base::DictionaryValue*>(first_arg);
425 // Still need to check for the tabId within details.
426 base::Value* tab_id_value = NULL;
427 if (details_->Get("tabId", &tab_id_value)) {
428 switch (tab_id_value->GetType()) {
429 case base::Value::TYPE_NULL:
430 // OK; tabId is optional, leave it default.
432 case base::Value::TYPE_INTEGER:
433 CHECK(tab_id_value->GetAsInteger(&tab_id_));
440 // Not found; tabId is optional, leave it default.
444 case base::Value::TYPE_NULL:
445 // The tabId might be an optional argument.
455 void ExtensionActionFunction::NotifyChange() {
456 ExtensionActionAPI::Get(GetProfile())->NotifyChange(
457 extension_action_, contents_, GetProfile());
460 bool ExtensionActionFunction::SetVisible(bool visible) {
461 if (extension_action_->GetIsVisible(tab_id_) == visible)
463 extension_action_->SetIsVisible(tab_id_, visible);
468 bool ExtensionActionShowFunction::RunExtensionAction() {
469 return SetVisible(true);
472 bool ExtensionActionHideFunction::RunExtensionAction() {
473 return SetVisible(false);
476 bool ExtensionActionSetIconFunction::RunExtensionAction() {
477 EXTENSION_FUNCTION_VALIDATE(details_);
479 // setIcon can take a variant argument: either a dictionary of canvas
480 // ImageData, or an icon index.
481 base::DictionaryValue* canvas_set = NULL;
483 if (details_->GetDictionary("imageData", &canvas_set)) {
486 EXTENSION_FUNCTION_VALIDATE(
487 ExtensionAction::ParseIconFromCanvasDictionary(*canvas_set, &icon));
489 extension_action_->SetIcon(tab_id_, gfx::Image(icon));
490 } else if (details_->GetInteger("iconIndex", &icon_index)) {
491 // Obsolete argument: ignore it.
494 EXTENSION_FUNCTION_VALIDATE(false);
500 bool ExtensionActionSetTitleFunction::RunExtensionAction() {
501 EXTENSION_FUNCTION_VALIDATE(details_);
503 EXTENSION_FUNCTION_VALIDATE(details_->GetString("title", &title));
504 extension_action_->SetTitle(tab_id_, title);
509 bool ExtensionActionSetPopupFunction::RunExtensionAction() {
510 EXTENSION_FUNCTION_VALIDATE(details_);
511 std::string popup_string;
512 EXTENSION_FUNCTION_VALIDATE(details_->GetString("popup", &popup_string));
515 if (!popup_string.empty())
516 popup_url = extension()->GetResourceURL(popup_string);
518 extension_action_->SetPopupUrl(tab_id_, popup_url);
523 bool ExtensionActionSetBadgeTextFunction::RunExtensionAction() {
524 EXTENSION_FUNCTION_VALIDATE(details_);
525 std::string badge_text;
526 EXTENSION_FUNCTION_VALIDATE(details_->GetString("text", &badge_text));
527 extension_action_->SetBadgeText(tab_id_, badge_text);
532 bool ExtensionActionSetBadgeBackgroundColorFunction::RunExtensionAction() {
533 EXTENSION_FUNCTION_VALIDATE(details_);
534 base::Value* color_value = NULL;
535 EXTENSION_FUNCTION_VALIDATE(details_->Get("color", &color_value));
537 if (color_value->IsType(base::Value::TYPE_LIST)) {
538 base::ListValue* list = NULL;
539 EXTENSION_FUNCTION_VALIDATE(details_->GetList("color", &list));
540 EXTENSION_FUNCTION_VALIDATE(list->GetSize() == 4);
542 int color_array[4] = {0};
543 for (size_t i = 0; i < arraysize(color_array); ++i) {
544 EXTENSION_FUNCTION_VALIDATE(list->GetInteger(i, &color_array[i]));
547 color = SkColorSetARGB(color_array[3], color_array[0],
548 color_array[1], color_array[2]);
549 } else if (color_value->IsType(base::Value::TYPE_STRING)) {
550 std::string color_string;
551 EXTENSION_FUNCTION_VALIDATE(details_->GetString("color", &color_string));
552 if (!image_util::ParseCSSColorString(color_string, &color))
556 extension_action_->SetBadgeBackgroundColor(tab_id_, color);
561 bool ExtensionActionGetTitleFunction::RunExtensionAction() {
562 SetResult(new base::StringValue(extension_action_->GetTitle(tab_id_)));
566 bool ExtensionActionGetPopupFunction::RunExtensionAction() {
568 new base::StringValue(extension_action_->GetPopupUrl(tab_id_).spec()));
572 bool ExtensionActionGetBadgeTextFunction::RunExtensionAction() {
573 SetResult(new base::StringValue(extension_action_->GetBadgeText(tab_id_)));
577 bool ExtensionActionGetBadgeBackgroundColorFunction::RunExtensionAction() {
578 base::ListValue* list = new base::ListValue();
579 SkColor color = extension_action_->GetBadgeBackgroundColor(tab_id_);
581 new base::FundamentalValue(static_cast<int>(SkColorGetR(color))));
583 new base::FundamentalValue(static_cast<int>(SkColorGetG(color))));
585 new base::FundamentalValue(static_cast<int>(SkColorGetB(color))));
587 new base::FundamentalValue(static_cast<int>(SkColorGetA(color))));
592 BrowserActionOpenPopupFunction::BrowserActionOpenPopupFunction()
593 : response_sent_(false) {
596 bool BrowserActionOpenPopupFunction::RunAsync() {
597 // We only allow the popup in the active window.
598 Browser* browser = chrome::FindLastActiveWithProfile(
599 GetProfile(), chrome::GetActiveDesktop());
601 // If there's no active browser, or the Toolbar isn't visible, abort.
602 // Otherwise, try to open a popup in the active browser.
603 // TODO(justinlin): Remove toolbar check when http://crbug.com/308645 is
606 !browser->window()->IsActive() ||
607 !browser->window()->IsToolbarVisible() ||
608 !ExtensionActionAPI::Get(GetProfile())->ShowExtensionActionPopup(
609 extension_.get(), browser, false)) {
610 error_ = kOpenPopupError;
615 NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING,
616 content::Source<Profile>(GetProfile()));
618 // Set a timeout for waiting for the notification that the popup is loaded.
619 // Waiting is required so that the popup view can be retrieved by the custom
620 // bindings for the response callback. It's also needed to keep this function
621 // instance around until a notification is observed.
622 base::MessageLoopForUI::current()->PostDelayedTask(
624 base::Bind(&BrowserActionOpenPopupFunction::OpenPopupTimedOut, this),
625 base::TimeDelta::FromSeconds(10));
629 void BrowserActionOpenPopupFunction::OpenPopupTimedOut() {
633 DVLOG(1) << "chrome.browserAction.openPopup did not show a popup.";
634 error_ = kOpenPopupError;
636 response_sent_ = true;
639 void BrowserActionOpenPopupFunction::Observe(
641 const content::NotificationSource& source,
642 const content::NotificationDetails& details) {
643 DCHECK_EQ(NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING, type);
647 ExtensionHost* host = content::Details<ExtensionHost>(details).ptr();
648 if (host->extension_host_type() != VIEW_TYPE_EXTENSION_POPUP ||
649 host->extension()->id() != extension_->id())
653 response_sent_ = true;
654 registrar_.RemoveAll();
657 } // namespace extensions