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