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