extension_toolbar_model.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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/extensions/browser_event_router.h" 9#include "chrome/browser/extensions/extension_action.h" 10#include "chrome/browser/extensions/extension_action_manager.h" 11#include "chrome/browser/extensions/extension_prefs.h" 12#include "chrome/browser/extensions/extension_service.h" 13#include "chrome/browser/extensions/extension_tab_util.h" 14#include "chrome/browser/extensions/tab_helper.h" 15#include "chrome/browser/profiles/profile.h" 16#include "chrome/browser/ui/browser.h" 17#include "chrome/browser/ui/tabs/tab_strip_model.h" 18#include "chrome/common/chrome_notification_types.h" 19#include "chrome/common/extensions/extension.h" 20#include "chrome/common/extensions/feature_switch.h" 21#include "chrome/common/pref_names.h" 22#include "content/public/browser/notification_details.h" 23#include "content/public/browser/notification_source.h" 24#include "content/public/browser/web_contents.h" 25 26using extensions::Extension; 27using extensions::ExtensionIdList; 28using extensions::ExtensionList; 29 30namespace { 31 32// Returns true if an |extension| is in an |extension_list|. 33bool IsInExtensionList(const Extension* extension, 34 const extensions::ExtensionList& extension_list) { 35 for (size_t i = 0; i < extension_list.size(); i++) { 36 if (extension_list[i].get() == extension) 37 return true; 38 } 39 return false; 40} 41 42} // namespace 43 44ExtensionToolbarModel::ExtensionToolbarModel(ExtensionService* service) 45 : service_(service), 46 prefs_(service->profile()->GetPrefs()), 47 extensions_initialized_(false) { 48 DCHECK(service_); 49 50 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED, 51 content::Source<Profile>(service_->profile())); 52 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, 53 content::Source<Profile>(service_->profile())); 54 registrar_.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY, 55 content::Source<Profile>(service_->profile())); 56 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED, 57 content::Source<Profile>(service_->profile())); 58 registrar_.Add( 59 this, chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED, 60 content::Source<extensions::ExtensionPrefs>(service_->extension_prefs())); 61 62 visible_icon_count_ = prefs_->GetInteger(prefs::kExtensionToolbarSize); 63} 64 65ExtensionToolbarModel::~ExtensionToolbarModel() { 66} 67 68void ExtensionToolbarModel::AddObserver(Observer* observer) { 69 observers_.AddObserver(observer); 70} 71 72void ExtensionToolbarModel::RemoveObserver(Observer* observer) { 73 observers_.RemoveObserver(observer); 74} 75 76void ExtensionToolbarModel::MoveBrowserAction(const Extension* extension, 77 int index) { 78 ExtensionList::iterator pos = std::find(toolbar_items_.begin(), 79 toolbar_items_.end(), extension); 80 if (pos == toolbar_items_.end()) { 81 NOTREACHED(); 82 return; 83 } 84 toolbar_items_.erase(pos); 85 86 ExtensionIdList::iterator pos_id; 87 pos_id = std::find(last_known_positions_.begin(), 88 last_known_positions_.end(), extension->id()); 89 if (pos_id != last_known_positions_.end()) 90 last_known_positions_.erase(pos_id); 91 92 int i = 0; 93 bool inserted = false; 94 for (ExtensionList::iterator iter = toolbar_items_.begin(); 95 iter != toolbar_items_.end(); 96 ++iter, ++i) { 97 if (i == index) { 98 pos_id = std::find(last_known_positions_.begin(), 99 last_known_positions_.end(), (*iter)->id()); 100 last_known_positions_.insert(pos_id, extension->id()); 101 102 toolbar_items_.insert(iter, make_scoped_refptr(extension)); 103 inserted = true; 104 break; 105 } 106 } 107 108 if (!inserted) { 109 DCHECK_EQ(index, static_cast<int>(toolbar_items_.size())); 110 index = toolbar_items_.size(); 111 112 toolbar_items_.push_back(make_scoped_refptr(extension)); 113 last_known_positions_.push_back(extension->id()); 114 } 115 116 FOR_EACH_OBSERVER(Observer, observers_, BrowserActionMoved(extension, index)); 117 118 UpdatePrefs(); 119} 120 121ExtensionToolbarModel::Action ExtensionToolbarModel::ExecuteBrowserAction( 122 const Extension* extension, 123 Browser* browser, 124 GURL* popup_url_out) { 125 content::WebContents* web_contents = 126 browser->tab_strip_model()->GetActiveWebContents(); 127 if (!web_contents) 128 return ACTION_NONE; 129 130 int tab_id = ExtensionTabUtil::GetTabId(web_contents); 131 if (tab_id < 0) 132 return ACTION_NONE; 133 134 ExtensionAction* browser_action = 135 extensions::ExtensionActionManager::Get(service_->profile())-> 136 GetBrowserAction(*extension); 137 138 // For browser actions, visibility == enabledness. 139 if (!browser_action->GetIsVisible(tab_id)) 140 return ACTION_NONE; 141 142 extensions::TabHelper::FromWebContents(web_contents)-> 143 active_tab_permission_granter()->GrantIfRequested(extension); 144 145 if (browser_action->HasPopup(tab_id)) { 146 if (popup_url_out) 147 *popup_url_out = browser_action->GetPopupUrl(tab_id); 148 return ACTION_SHOW_POPUP; 149 } 150 151 service_->browser_event_router()->BrowserActionExecuted( 152 *browser_action, browser); 153 return ACTION_NONE; 154} 155 156void ExtensionToolbarModel::SetVisibleIconCount(int count) { 157 visible_icon_count_ = 158 count == static_cast<int>(toolbar_items_.size()) ? -1 : count; 159 prefs_->SetInteger(prefs::kExtensionToolbarSize, visible_icon_count_); 160} 161 162void ExtensionToolbarModel::Observe( 163 int type, 164 const content::NotificationSource& source, 165 const content::NotificationDetails& details) { 166 if (type == chrome::NOTIFICATION_EXTENSIONS_READY) { 167 InitializeExtensionList(); 168 return; 169 } 170 171 if (!service_->is_ready()) 172 return; 173 174 const Extension* extension = NULL; 175 if (type == chrome::NOTIFICATION_EXTENSION_UNLOADED) { 176 extension = content::Details<extensions::UnloadedExtensionInfo>( 177 details)->extension; 178 } else { 179 extension = content::Details<const Extension>(details).ptr(); 180 } 181 if (type == chrome::NOTIFICATION_EXTENSION_LOADED) { 182 // We don't want to add the same extension twice. It may have already been 183 // added by EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED below, if the user 184 // hides the browser action and then disables and enables the extension. 185 for (size_t i = 0; i < toolbar_items_.size(); i++) { 186 if (toolbar_items_[i].get() == extension) 187 return; // Already exists. 188 } 189 if (service_->extension_prefs()->GetBrowserActionVisibility(extension)) 190 AddExtension(extension); 191 } else if (type == chrome::NOTIFICATION_EXTENSION_UNLOADED) { 192 RemoveExtension(extension); 193 } else if (type == chrome::NOTIFICATION_EXTENSION_UNINSTALLED) { 194 UninstalledExtension(extension); 195 } else if (type == 196 chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED) { 197 if (service_->extension_prefs()->GetBrowserActionVisibility(extension)) 198 AddExtension(extension); 199 else 200 RemoveExtension(extension); 201 } else { 202 NOTREACHED() << "Received unexpected notification"; 203 } 204} 205 206size_t ExtensionToolbarModel::FindNewPositionFromLastKnownGood( 207 const Extension* extension) { 208 // See if we have last known good position for this extension. 209 size_t new_index = 0; 210 // Loop through the ID list of known positions, to count the number of visible 211 // browser action icons preceding |extension|. 212 for (ExtensionIdList::const_iterator iter_id = last_known_positions_.begin(); 213 iter_id < last_known_positions_.end(); ++iter_id) { 214 if ((*iter_id) == extension->id()) 215 return new_index; // We've found the right position. 216 // Found an id, need to see if it is visible. 217 for (ExtensionList::const_iterator iter_ext = toolbar_items_.begin(); 218 iter_ext < toolbar_items_.end(); ++iter_ext) { 219 if ((*iter_ext)->id().compare(*iter_id) == 0) { 220 // This extension is visible, update the index value. 221 ++new_index; 222 break; 223 } 224 } 225 } 226 227 return -1; 228} 229 230void ExtensionToolbarModel::AddExtension(const Extension* extension) { 231 // We only care about extensions with browser actions. 232 if (!extensions::ExtensionActionManager::Get(service_->profile())-> 233 GetBrowserAction(*extension)) { 234 return; 235 } 236 237 size_t new_index = -1; 238 239 // See if we have a last known good position for this extension. 240 ExtensionIdList::iterator last_pos = std::find(last_known_positions_.begin(), 241 last_known_positions_.end(), 242 extension->id()); 243 if (last_pos != last_known_positions_.end()) { 244 new_index = FindNewPositionFromLastKnownGood(extension); 245 if (new_index != toolbar_items_.size()) { 246 toolbar_items_.insert(toolbar_items_.begin() + new_index, 247 make_scoped_refptr(extension)); 248 } else { 249 toolbar_items_.push_back(make_scoped_refptr(extension)); 250 } 251 } else { 252 // This is a never before seen extension, that was added to the end. Make 253 // sure to reflect that. 254 toolbar_items_.push_back(make_scoped_refptr(extension)); 255 last_known_positions_.push_back(extension->id()); 256 new_index = toolbar_items_.size() - 1; 257 UpdatePrefs(); 258 } 259 260 FOR_EACH_OBSERVER(Observer, observers_, 261 BrowserActionAdded(extension, new_index)); 262} 263 264void ExtensionToolbarModel::RemoveExtension(const Extension* extension) { 265 ExtensionList::iterator pos = 266 std::find(toolbar_items_.begin(), toolbar_items_.end(), extension); 267 if (pos == toolbar_items_.end()) 268 return; 269 270 toolbar_items_.erase(pos); 271 FOR_EACH_OBSERVER(Observer, observers_, 272 BrowserActionRemoved(extension)); 273 274 UpdatePrefs(); 275} 276 277void ExtensionToolbarModel::UninstalledExtension(const Extension* extension) { 278 // Remove the extension id from the ordered list, if it exists (the extension 279 // might not be represented in the list because it might not have an icon). 280 ExtensionIdList::iterator pos = 281 std::find(last_known_positions_.begin(), 282 last_known_positions_.end(), extension->id()); 283 284 if (pos != last_known_positions_.end()) { 285 last_known_positions_.erase(pos); 286 UpdatePrefs(); 287 } 288} 289 290// Combine the currently enabled extensions that have browser actions (which 291// we get from the ExtensionService) with the ordering we get from the 292// pref service. For robustness we use a somewhat inefficient process: 293// 1. Create a vector of extensions sorted by their pref values. This vector may 294// have holes. 295// 2. Create a vector of extensions that did not have a pref value. 296// 3. Remove holes from the sorted vector and append the unsorted vector. 297void ExtensionToolbarModel::InitializeExtensionList() { 298 DCHECK(service_->is_ready()); 299 300 Populate(); 301 UpdatePrefs(); 302 303 extensions_initialized_ = true; 304 FOR_EACH_OBSERVER(Observer, observers_, ModelLoaded()); 305} 306 307void ExtensionToolbarModel::Populate() { 308 const extensions::ExtensionIdList pref_order = 309 service_->extension_prefs()->GetToolbarOrder(); 310 last_known_positions_ = pref_order; 311 312 // Items that have a pref for their position. 313 ExtensionList sorted; 314 sorted.resize(pref_order.size(), NULL); 315 // The items that don't have a pref for their position. 316 ExtensionList unsorted; 317 318 extensions::ExtensionActionManager* extension_action_manager = 319 extensions::ExtensionActionManager::Get(service_->profile()); 320 321 // Create the lists. 322 for (ExtensionSet::const_iterator it = service_->extensions()->begin(); 323 it != service_->extensions()->end(); ++it) { 324 const Extension* extension = *it; 325 if (!extension_action_manager->GetBrowserAction(*extension)) 326 continue; 327 if (!service_->extension_prefs()->GetBrowserActionVisibility(extension)) 328 continue; 329 330 extensions::ExtensionIdList::const_iterator pos = 331 std::find(pref_order.begin(), pref_order.end(), extension->id()); 332 if (pos != pref_order.end()) 333 sorted[pos - pref_order.begin()] = extension; 334 else 335 unsorted.push_back(make_scoped_refptr(extension)); 336 } 337 338 // Merge the lists. 339 toolbar_items_.clear(); 340 toolbar_items_.reserve(sorted.size() + unsorted.size()); 341 for (ExtensionList::const_iterator iter = sorted.begin(); 342 iter != sorted.end(); ++iter) { 343 // It's possible for the extension order to contain items that aren't 344 // actually loaded on this machine. For example, when extension sync is on, 345 // we sync the extension order as-is but double-check with the user before 346 // syncing NPAPI-containing extensions, so if one of those is not actually 347 // synced, we'll get a NULL in the list. This sort of case can also happen 348 // if some error prevents an extension from loading. 349 if (*iter != NULL) 350 toolbar_items_.push_back(*iter); 351 } 352 toolbar_items_.insert(toolbar_items_.end(), unsorted.begin(), 353 unsorted.end()); 354 355 // Inform observers. 356 for (size_t i = 0; i < toolbar_items_.size(); i++) { 357 FOR_EACH_OBSERVER(Observer, observers_, 358 BrowserActionAdded(toolbar_items_[i], i)); 359 } 360} 361 362void ExtensionToolbarModel::FillExtensionList( 363 const extensions::ExtensionIdList& order) { 364 toolbar_items_.clear(); 365 toolbar_items_.reserve(order.size()); 366 for (size_t i = 0; i < order.size(); ++i) { 367 const extensions::Extension* extension = 368 service_->GetExtensionById(order[i], false); 369 if (extension) 370 AddExtension(extension); 371 } 372} 373 374void ExtensionToolbarModel::UpdatePrefs() { 375 if (!service_->extension_prefs()) 376 return; 377 378 service_->extension_prefs()->SetToolbarOrder(last_known_positions_); 379} 380 381int ExtensionToolbarModel::IncognitoIndexToOriginal(int incognito_index) { 382 int original_index = 0, i = 0; 383 for (ExtensionList::iterator iter = toolbar_items_.begin(); 384 iter != toolbar_items_.end(); 385 ++iter, ++original_index) { 386 if (service_->IsIncognitoEnabled((*iter)->id())) { 387 if (incognito_index == i) 388 break; 389 ++i; 390 } 391 } 392 return original_index; 393} 394 395int ExtensionToolbarModel::OriginalIndexToIncognito(int original_index) { 396 int incognito_index = 0, i = 0; 397 for (ExtensionList::iterator iter = toolbar_items_.begin(); 398 iter != toolbar_items_.end(); 399 ++iter, ++i) { 400 if (original_index == i) 401 break; 402 if (service_->IsIncognitoEnabled((*iter)->id())) 403 ++incognito_index; 404 } 405 return incognito_index; 406} 407