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 640