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