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