extension_toolbar_model.cc revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
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/extension_toolbar_model.h" 6 7#include <string> 8 9#include "base/metrics/histogram.h" 10#include "base/metrics/histogram_base.h" 11#include "base/prefs/pref_service.h" 12#include "chrome/browser/chrome_notification_types.h" 13#include "chrome/browser/extensions/api/extension_action/extension_action_api.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_factory.h" 19#include "chrome/browser/extensions/extension_util.h" 20#include "chrome/browser/extensions/tab_helper.h" 21#include "chrome/browser/profiles/profile.h" 22#include "chrome/browser/ui/browser.h" 23#include "chrome/browser/ui/tabs/tab_strip_model.h" 24#include "chrome/common/pref_names.h" 25#include "content/public/browser/notification_details.h" 26#include "content/public/browser/notification_source.h" 27#include "content/public/browser/web_contents.h" 28#include "extensions/browser/extension_prefs.h" 29#include "extensions/browser/extension_registry.h" 30#include "extensions/browser/extension_system.h" 31#include "extensions/browser/pref_names.h" 32#include "extensions/common/extension.h" 33#include "extensions/common/extension_set.h" 34#include "extensions/common/feature_switch.h" 35 36namespace extensions { 37 38bool ExtensionToolbarModel::Observer::BrowserActionShowPopup( 39 const extensions::Extension* extension) { 40 return false; 41} 42 43ExtensionToolbarModel::ExtensionToolbarModel( 44 Profile* profile, 45 extensions::ExtensionPrefs* extension_prefs) 46 : profile_(profile), 47 extension_prefs_(extension_prefs), 48 prefs_(profile_->GetPrefs()), 49 extensions_initialized_(false), 50 is_highlighting_(false), 51 extension_registry_observer_(this), 52 weak_ptr_factory_(this) { 53 extension_registry_observer_.Add(ExtensionRegistry::Get(profile_)); 54 55 registrar_.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY, 56 content::Source<Profile>(profile_)); 57 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED, 58 content::Source<Profile>(profile_)); 59 registrar_.Add( 60 this, chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED, 61 content::Source<extensions::ExtensionPrefs>(extension_prefs_)); 62 63 visible_icon_count_ = prefs_->GetInteger( 64 extensions::pref_names::kToolbarSize); 65 pref_change_registrar_.Init(prefs_); 66 pref_change_callback_ = 67 base::Bind(&ExtensionToolbarModel::OnExtensionToolbarPrefChange, 68 base::Unretained(this)); 69 pref_change_registrar_.Add(extensions::pref_names::kToolbar, 70 pref_change_callback_); 71} 72 73ExtensionToolbarModel::~ExtensionToolbarModel() { 74} 75 76// static 77ExtensionToolbarModel* ExtensionToolbarModel::Get(Profile* profile) { 78 return ExtensionToolbarModelFactory::GetForProfile(profile); 79} 80 81void ExtensionToolbarModel::AddObserver(Observer* observer) { 82 observers_.AddObserver(observer); 83} 84 85void ExtensionToolbarModel::RemoveObserver(Observer* observer) { 86 observers_.RemoveObserver(observer); 87} 88 89void ExtensionToolbarModel::MoveBrowserAction(const Extension* extension, 90 int index) { 91 ExtensionList::iterator pos = std::find(toolbar_items_.begin(), 92 toolbar_items_.end(), extension); 93 if (pos == toolbar_items_.end()) { 94 NOTREACHED(); 95 return; 96 } 97 toolbar_items_.erase(pos); 98 99 ExtensionIdList::iterator pos_id; 100 pos_id = std::find(last_known_positions_.begin(), 101 last_known_positions_.end(), extension->id()); 102 if (pos_id != last_known_positions_.end()) 103 last_known_positions_.erase(pos_id); 104 105 int i = 0; 106 bool inserted = false; 107 for (ExtensionList::iterator iter = toolbar_items_.begin(); 108 iter != toolbar_items_.end(); 109 ++iter, ++i) { 110 if (i == index) { 111 pos_id = std::find(last_known_positions_.begin(), 112 last_known_positions_.end(), (*iter)->id()); 113 last_known_positions_.insert(pos_id, extension->id()); 114 115 toolbar_items_.insert(iter, make_scoped_refptr(extension)); 116 inserted = true; 117 break; 118 } 119 } 120 121 if (!inserted) { 122 DCHECK_EQ(index, static_cast<int>(toolbar_items_.size())); 123 index = toolbar_items_.size(); 124 125 toolbar_items_.push_back(make_scoped_refptr(extension)); 126 last_known_positions_.push_back(extension->id()); 127 } 128 129 FOR_EACH_OBSERVER(Observer, observers_, BrowserActionMoved(extension, index)); 130 131 UpdatePrefs(); 132} 133 134ExtensionToolbarModel::Action ExtensionToolbarModel::ExecuteBrowserAction( 135 const Extension* extension, 136 Browser* browser, 137 GURL* popup_url_out, 138 bool should_grant) { 139 content::WebContents* web_contents = NULL; 140 int tab_id = 0; 141 if (!extensions::ExtensionTabUtil::GetDefaultTab( 142 browser, &web_contents, &tab_id)) { 143 return ACTION_NONE; 144 } 145 146 ExtensionAction* browser_action = 147 extensions::ExtensionActionManager::Get(profile_)-> 148 GetBrowserAction(*extension); 149 150 // For browser actions, visibility == enabledness. 151 if (!browser_action->GetIsVisible(tab_id)) 152 return ACTION_NONE; 153 154 if (should_grant) { 155 extensions::TabHelper::FromWebContents(web_contents)-> 156 active_tab_permission_granter()->GrantIfRequested(extension); 157 } 158 159 if (browser_action->HasPopup(tab_id)) { 160 if (popup_url_out) 161 *popup_url_out = browser_action->GetPopupUrl(tab_id); 162 return ACTION_SHOW_POPUP; 163 } 164 165 extensions::ExtensionActionAPI::BrowserActionExecuted( 166 browser->profile(), *browser_action, web_contents); 167 return ACTION_NONE; 168} 169 170void ExtensionToolbarModel::SetVisibleIconCount(int count) { 171 visible_icon_count_ = 172 count == static_cast<int>(toolbar_items_.size()) ? -1 : count; 173 // Only set the prefs if we're not in highlight mode. Highlight mode is 174 // designed to be a transitory state, and should not persist across browser 175 // restarts (though it may be re-entered). 176 if (!is_highlighting_) { 177 prefs_->SetInteger(extensions::pref_names::kToolbarSize, 178 visible_icon_count_); 179 } 180} 181 182void ExtensionToolbarModel::OnExtensionLoaded( 183 content::BrowserContext* browser_context, 184 const Extension* extension) { 185 // We don't want to add the same extension twice. It may have already been 186 // added by EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED below, if the user 187 // hides the browser action and then disables and enables the extension. 188 for (size_t i = 0; i < toolbar_items_.size(); i++) { 189 if (toolbar_items_[i].get() == extension) 190 return; 191 } 192 if (ExtensionActionAPI::GetBrowserActionVisibility(extension_prefs_, 193 extension->id())) { 194 AddExtension(extension); 195 } 196} 197 198void ExtensionToolbarModel::OnExtensionUnloaded( 199 content::BrowserContext* browser_context, 200 const Extension* extension, 201 UnloadedExtensionInfo::Reason reason) { 202 RemoveExtension(extension); 203} 204 205void ExtensionToolbarModel::Observe( 206 int type, 207 const content::NotificationSource& source, 208 const content::NotificationDetails& details) { 209 ExtensionService* extension_service = 210 ExtensionSystem::Get(profile_)->extension_service(); 211 DCHECK(extension_service); 212 if (!extension_service->is_ready()) 213 return; 214 215 switch (type) { 216 case chrome::NOTIFICATION_EXTENSIONS_READY: 217 InitializeExtensionList(extension_service); 218 break; 219 case chrome::NOTIFICATION_EXTENSION_UNINSTALLED: { 220 const Extension* extension = 221 content::Details<const Extension>(details).ptr(); 222 UninstalledExtension(extension); 223 break; 224 } 225 case chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED: { 226 const Extension* extension = extension_service->GetExtensionById( 227 *content::Details<const std::string>(details).ptr(), true); 228 if (ExtensionActionAPI::GetBrowserActionVisibility(extension_prefs_, 229 extension->id())) { 230 AddExtension(extension); 231 } else { 232 RemoveExtension(extension); 233 } 234 break; 235 } 236 default: 237 NOTREACHED() << "Received unexpected notification"; 238 } 239} 240 241size_t ExtensionToolbarModel::FindNewPositionFromLastKnownGood( 242 const Extension* extension) { 243 // See if we have last known good position for this extension. 244 size_t new_index = 0; 245 // Loop through the ID list of known positions, to count the number of visible 246 // browser action icons preceding |extension|. 247 for (ExtensionIdList::const_iterator iter_id = last_known_positions_.begin(); 248 iter_id < last_known_positions_.end(); ++iter_id) { 249 if ((*iter_id) == extension->id()) 250 return new_index; // We've found the right position. 251 // Found an id, need to see if it is visible. 252 for (ExtensionList::const_iterator iter_ext = toolbar_items_.begin(); 253 iter_ext < toolbar_items_.end(); ++iter_ext) { 254 if ((*iter_ext)->id().compare(*iter_id) == 0) { 255 // This extension is visible, update the index value. 256 ++new_index; 257 break; 258 } 259 } 260 } 261 262 return -1; 263} 264 265void ExtensionToolbarModel::AddExtension(const Extension* extension) { 266 // We only care about extensions with browser actions. 267 if (!ExtensionActionManager::Get(profile_)->GetBrowserAction(*extension)) 268 return; 269 270 size_t new_index = -1; 271 272 // See if we have a last known good position for this extension. 273 ExtensionIdList::iterator last_pos = std::find(last_known_positions_.begin(), 274 last_known_positions_.end(), 275 extension->id()); 276 if (last_pos != last_known_positions_.end()) { 277 new_index = FindNewPositionFromLastKnownGood(extension); 278 if (new_index != toolbar_items_.size()) { 279 toolbar_items_.insert(toolbar_items_.begin() + new_index, 280 make_scoped_refptr(extension)); 281 } else { 282 toolbar_items_.push_back(make_scoped_refptr(extension)); 283 } 284 } else { 285 // This is a never before seen extension, that was added to the end. Make 286 // sure to reflect that. 287 toolbar_items_.push_back(make_scoped_refptr(extension)); 288 last_known_positions_.push_back(extension->id()); 289 new_index = toolbar_items_.size() - 1; 290 UpdatePrefs(); 291 } 292 293 // If we're currently highlighting, then even though we add a browser action 294 // to the full list (|toolbar_items_|, there won't be another *visible* 295 // browser action, which was what the observers care about. 296 if (!is_highlighting_) { 297 FOR_EACH_OBSERVER(Observer, observers_, 298 BrowserActionAdded(extension, new_index)); 299 } 300} 301 302void ExtensionToolbarModel::RemoveExtension(const Extension* extension) { 303 ExtensionList::iterator pos = 304 std::find(toolbar_items_.begin(), toolbar_items_.end(), extension); 305 if (pos == toolbar_items_.end()) 306 return; 307 308 toolbar_items_.erase(pos); 309 310 // If we're in highlight mode, we also have to remove the extension from 311 // the highlighted list. 312 if (is_highlighting_) { 313 pos = std::find(highlighted_items_.begin(), 314 highlighted_items_.end(), 315 extension); 316 if (pos != highlighted_items_.end()) { 317 highlighted_items_.erase(pos); 318 FOR_EACH_OBSERVER(Observer, observers_, BrowserActionRemoved(extension)); 319 // If the highlighted list is now empty, we stop highlighting. 320 if (highlighted_items_.empty()) 321 StopHighlighting(); 322 } 323 } else { 324 FOR_EACH_OBSERVER(Observer, observers_, BrowserActionRemoved(extension)); 325 } 326 327 UpdatePrefs(); 328} 329 330void ExtensionToolbarModel::UninstalledExtension(const Extension* extension) { 331 // Remove the extension id from the ordered list, if it exists (the extension 332 // might not be represented in the list because it might not have an icon). 333 ExtensionIdList::iterator pos = 334 std::find(last_known_positions_.begin(), 335 last_known_positions_.end(), extension->id()); 336 337 if (pos != last_known_positions_.end()) { 338 last_known_positions_.erase(pos); 339 UpdatePrefs(); 340 } 341} 342 343// Combine the currently enabled extensions that have browser actions (which 344// we get from the ExtensionService) with the ordering we get from the 345// pref service. For robustness we use a somewhat inefficient process: 346// 1. Create a vector of extensions sorted by their pref values. This vector may 347// have holes. 348// 2. Create a vector of extensions that did not have a pref value. 349// 3. Remove holes from the sorted vector and append the unsorted vector. 350void ExtensionToolbarModel::InitializeExtensionList(ExtensionService* service) { 351 DCHECK(service->is_ready()); 352 353 last_known_positions_ = extension_prefs_->GetToolbarOrder(); 354 Populate(last_known_positions_, service); 355 356 extensions_initialized_ = true; 357 FOR_EACH_OBSERVER(Observer, observers_, VisibleCountChanged()); 358} 359 360void ExtensionToolbarModel::Populate( 361 const ExtensionIdList& positions, 362 ExtensionService* service) { 363 // Items that have explicit positions. 364 ExtensionList sorted; 365 sorted.resize(positions.size(), NULL); 366 // The items that don't have explicit positions. 367 ExtensionList unsorted; 368 369 ExtensionActionManager* extension_action_manager = 370 ExtensionActionManager::Get(profile_); 371 372 // Create the lists. 373 int hidden = 0; 374 for (extensions::ExtensionSet::const_iterator it = 375 service->extensions()->begin(); 376 it != service->extensions()->end(); ++it) { 377 const Extension* extension = it->get(); 378 if (!extension_action_manager->GetBrowserAction(*extension)) 379 continue; 380 if (!ExtensionActionAPI::GetBrowserActionVisibility( 381 extension_prefs_, extension->id())) { 382 ++hidden; 383 continue; 384 } 385 386 ExtensionIdList::const_iterator pos = 387 std::find(positions.begin(), positions.end(), extension->id()); 388 if (pos != positions.end()) 389 sorted[pos - positions.begin()] = extension; 390 else 391 unsorted.push_back(make_scoped_refptr(extension)); 392 } 393 394 // Erase current icons. 395 for (size_t i = 0; i < toolbar_items_.size(); i++) { 396 FOR_EACH_OBSERVER( 397 Observer, observers_, BrowserActionRemoved(toolbar_items_[i].get())); 398 } 399 toolbar_items_.clear(); 400 401 // Merge the lists. 402 toolbar_items_.reserve(sorted.size() + unsorted.size()); 403 for (ExtensionList::const_iterator iter = sorted.begin(); 404 iter != sorted.end(); ++iter) { 405 // It's possible for the extension order to contain items that aren't 406 // actually loaded on this machine. For example, when extension sync is on, 407 // we sync the extension order as-is but double-check with the user before 408 // syncing NPAPI-containing extensions, so if one of those is not actually 409 // synced, we'll get a NULL in the list. This sort of case can also happen 410 // if some error prevents an extension from loading. 411 if (iter->get() != NULL) 412 toolbar_items_.push_back(*iter); 413 } 414 toolbar_items_.insert(toolbar_items_.end(), unsorted.begin(), 415 unsorted.end()); 416 417 UMA_HISTOGRAM_COUNTS_100( 418 "ExtensionToolbarModel.BrowserActionsPermanentlyHidden", hidden); 419 UMA_HISTOGRAM_COUNTS_100("ExtensionToolbarModel.BrowserActionsCount", 420 toolbar_items_.size()); 421 422 if (!toolbar_items_.empty()) { 423 // Visible count can be -1, meaning: 'show all'. Since UMA converts negative 424 // values to 0, this would be counted as 'show none' unless we convert it to 425 // max. 426 UMA_HISTOGRAM_COUNTS_100("ExtensionToolbarModel.BrowserActionsVisible", 427 visible_icon_count_ == -1 ? 428 base::HistogramBase::kSampleType_MAX : 429 visible_icon_count_); 430 } 431 432 // Inform observers. 433 for (size_t i = 0; i < toolbar_items_.size(); i++) { 434 FOR_EACH_OBSERVER( 435 Observer, observers_, BrowserActionAdded(toolbar_items_[i].get(), i)); 436 } 437} 438 439void ExtensionToolbarModel::UpdatePrefs() { 440 if (!extension_prefs_) 441 return; 442 443 // Don't observe change caused by self. 444 pref_change_registrar_.Remove(pref_names::kToolbar); 445 extension_prefs_->SetToolbarOrder(last_known_positions_); 446 pref_change_registrar_.Add(pref_names::kToolbar, pref_change_callback_); 447} 448 449int ExtensionToolbarModel::IncognitoIndexToOriginal(int incognito_index) { 450 int original_index = 0, i = 0; 451 for (ExtensionList::iterator iter = toolbar_items_.begin(); 452 iter != toolbar_items_.end(); 453 ++iter, ++original_index) { 454 if (util::IsIncognitoEnabled((*iter)->id(), profile_)) { 455 if (incognito_index == i) 456 break; 457 ++i; 458 } 459 } 460 return original_index; 461} 462 463int ExtensionToolbarModel::OriginalIndexToIncognito(int original_index) { 464 int incognito_index = 0, i = 0; 465 for (ExtensionList::iterator iter = toolbar_items_.begin(); 466 iter != toolbar_items_.end(); 467 ++iter, ++i) { 468 if (original_index == i) 469 break; 470 if (util::IsIncognitoEnabled((*iter)->id(), profile_)) 471 ++incognito_index; 472 } 473 return incognito_index; 474} 475 476void ExtensionToolbarModel::OnExtensionToolbarPrefChange() { 477 // If extensions are not ready, defer to later Populate() call. 478 if (!extensions_initialized_) 479 return; 480 481 // Recalculate |last_known_positions_| to be |pref_positions| followed by 482 // ones that are only in |last_known_positions_|. 483 ExtensionIdList pref_positions = extension_prefs_->GetToolbarOrder(); 484 size_t pref_position_size = pref_positions.size(); 485 for (size_t i = 0; i < last_known_positions_.size(); ++i) { 486 if (std::find(pref_positions.begin(), pref_positions.end(), 487 last_known_positions_[i]) == pref_positions.end()) { 488 pref_positions.push_back(last_known_positions_[i]); 489 } 490 } 491 last_known_positions_.swap(pref_positions); 492 493 // Re-populate. 494 Populate(last_known_positions_, 495 ExtensionSystem::Get(profile_)->extension_service()); 496 497 if (last_known_positions_.size() > pref_position_size) { 498 // Need to update pref because we have extra icons. But can't call 499 // UpdatePrefs() directly within observation closure. 500 base::MessageLoop::current()->PostTask( 501 FROM_HERE, 502 base::Bind(&ExtensionToolbarModel::UpdatePrefs, 503 weak_ptr_factory_.GetWeakPtr())); 504 } 505} 506 507bool ExtensionToolbarModel::ShowBrowserActionPopup(const Extension* extension) { 508 ObserverListBase<Observer>::Iterator it(observers_); 509 Observer* obs = NULL; 510 while ((obs = it.GetNext()) != NULL) { 511 // Stop after first popup since it should only show in the active window. 512 if (obs->BrowserActionShowPopup(extension)) 513 return true; 514 } 515 return false; 516} 517 518void ExtensionToolbarModel::EnsureVisibility( 519 const ExtensionIdList& extension_ids) { 520 if (visible_icon_count_ == -1) 521 return; // Already showing all. 522 523 // Otherwise, make sure we have enough room to show all the extensions 524 // requested. 525 if (visible_icon_count_ < static_cast<int>(extension_ids.size())) { 526 SetVisibleIconCount(extension_ids.size()); 527 528 // Inform observers. 529 FOR_EACH_OBSERVER(Observer, observers_, VisibleCountChanged()); 530 } 531 532 if (visible_icon_count_ == -1) 533 return; // May have been set to max by SetVisibleIconCount. 534 535 // Guillotine's Delight: Move an orange noble to the front of the line. 536 for (ExtensionIdList::const_iterator it = extension_ids.begin(); 537 it != extension_ids.end(); ++it) { 538 for (ExtensionList::const_iterator extension = toolbar_items_.begin(); 539 extension != toolbar_items_.end(); ++extension) { 540 if ((*extension)->id() == (*it)) { 541 if (extension - toolbar_items_.begin() >= visible_icon_count_) 542 MoveBrowserAction(*extension, 0); 543 break; 544 } 545 } 546 } 547} 548 549bool ExtensionToolbarModel::HighlightExtensions( 550 const ExtensionIdList& extension_ids) { 551 highlighted_items_.clear(); 552 553 for (ExtensionIdList::const_iterator id = extension_ids.begin(); 554 id != extension_ids.end(); 555 ++id) { 556 for (ExtensionList::const_iterator extension = toolbar_items_.begin(); 557 extension != toolbar_items_.end(); 558 ++extension) { 559 if (*id == (*extension)->id()) 560 highlighted_items_.push_back(*extension); 561 } 562 } 563 564 // If we have any items in |highlighted_items_|, then we entered highlighting 565 // mode. 566 if (highlighted_items_.size()) { 567 old_visible_icon_count_ = visible_icon_count_; 568 is_highlighting_ = true; 569 if (visible_icon_count_ != -1 && 570 visible_icon_count_ < static_cast<int>(extension_ids.size())) { 571 SetVisibleIconCount(extension_ids.size()); 572 FOR_EACH_OBSERVER(Observer, observers_, VisibleCountChanged()); 573 } 574 575 FOR_EACH_OBSERVER(Observer, observers_, HighlightModeChanged(true)); 576 return true; 577 } 578 579 // Otherwise, we didn't enter highlighting mode (and, in fact, exited it if 580 // we were otherwise in it). 581 if (is_highlighting_) 582 StopHighlighting(); 583 return false; 584} 585 586void ExtensionToolbarModel::StopHighlighting() { 587 if (is_highlighting_) { 588 highlighted_items_.clear(); 589 is_highlighting_ = false; 590 if (old_visible_icon_count_ != visible_icon_count_) { 591 SetVisibleIconCount(old_visible_icon_count_); 592 FOR_EACH_OBSERVER(Observer, observers_, VisibleCountChanged()); 593 } 594 FOR_EACH_OBSERVER(Observer, observers_, HighlightModeChanged(false)); 595 } 596}; 597 598} // namespace extensions 599