// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/extensions/extension_toolbar_model.h" #include "chrome/browser/extensions/browser_event_router.h" #include "chrome/browser/extensions/extension_action.h" #include "chrome/browser/extensions/extension_action_manager.h" #include "chrome/browser/extensions/extension_prefs.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_tab_util.h" #include "chrome/browser/extensions/tab_helper.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_tabstrip.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/common/chrome_notification_types.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/feature_switch.h" #include "chrome/common/pref_names.h" #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_source.h" #include "content/public/browser/web_contents.h" using extensions::Extension; using extensions::ExtensionList; namespace { // Returns true if an |extension| is in an |extension_list|. bool IsInExtensionList(const Extension* extension, const extensions::ExtensionList& extension_list) { for (size_t i = 0; i < extension_list.size(); i++) { if (extension_list[i].get() == extension) return true; } return false; } } // namespace ExtensionToolbarModel::ExtensionToolbarModel(ExtensionService* service) : service_(service), prefs_(service->profile()->GetPrefs()), extensions_initialized_(false) { DCHECK(service_); registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED, content::Source(service_->profile())); registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, content::Source(service_->profile())); registrar_.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY, content::Source(service_->profile())); registrar_.Add( this, chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED, content::Source(service_->extension_prefs())); visible_icon_count_ = prefs_->GetInteger(prefs::kExtensionToolbarSize); } ExtensionToolbarModel::~ExtensionToolbarModel() { } void ExtensionToolbarModel::AddObserver(Observer* observer) { observers_.AddObserver(observer); } void ExtensionToolbarModel::RemoveObserver(Observer* observer) { observers_.RemoveObserver(observer); } void ExtensionToolbarModel::MoveBrowserAction(const Extension* extension, int index) { ExtensionList::iterator pos = std::find(toolbar_items_.begin(), toolbar_items_.end(), extension); if (pos == toolbar_items_.end()) { NOTREACHED(); return; } toolbar_items_.erase(pos); int i = 0; bool inserted = false; for (ExtensionList::iterator iter = toolbar_items_.begin(); iter != toolbar_items_.end(); ++iter, ++i) { if (i == index) { toolbar_items_.insert(iter, make_scoped_refptr(extension)); inserted = true; break; } } if (!inserted) { DCHECK_EQ(index, static_cast(toolbar_items_.size())); index = toolbar_items_.size(); toolbar_items_.push_back(make_scoped_refptr(extension)); } FOR_EACH_OBSERVER(Observer, observers_, BrowserActionMoved(extension, index)); UpdatePrefs(); } ExtensionToolbarModel::Action ExtensionToolbarModel::ExecuteBrowserAction( const Extension* extension, Browser* browser, GURL* popup_url_out) { content::WebContents* web_contents = browser->tab_strip_model()->GetActiveWebContents(); if (!web_contents) return ACTION_NONE; int tab_id = ExtensionTabUtil::GetTabId(web_contents); if (tab_id < 0) return ACTION_NONE; ExtensionAction* browser_action = extensions::ExtensionActionManager::Get(service_->profile())-> GetBrowserAction(*extension); // For browser actions, visibility == enabledness. if (!browser_action->GetIsVisible(tab_id)) return ACTION_NONE; extensions::TabHelper::FromWebContents(web_contents)-> active_tab_permission_granter()->GrantIfRequested(extension); if (browser_action->HasPopup(tab_id)) { if (popup_url_out) *popup_url_out = browser_action->GetPopupUrl(tab_id); return ACTION_SHOW_POPUP; } service_->browser_event_router()->BrowserActionExecuted( *browser_action, browser); return ACTION_NONE; } void ExtensionToolbarModel::SetVisibleIconCount(int count) { visible_icon_count_ = count == static_cast(toolbar_items_.size()) ? -1 : count; prefs_->SetInteger(prefs::kExtensionToolbarSize, visible_icon_count_); } void ExtensionToolbarModel::Observe( int type, const content::NotificationSource& source, const content::NotificationDetails& details) { if (type == chrome::NOTIFICATION_EXTENSIONS_READY) { InitializeExtensionLists(); return; } if (!service_->is_ready()) return; const Extension* extension = NULL; if (type == chrome::NOTIFICATION_EXTENSION_UNLOADED) { extension = content::Details( details)->extension; } else { extension = content::Details(details).ptr(); } ExtensionList* list_with_extension = FindListWithExtension(extension); if (type == chrome::NOTIFICATION_EXTENSION_LOADED) { // We don't want to add the same extension twice. It may have already been // added by EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED below, if the user // hides the browser action and then disables and enables the extension. if (list_with_extension) return; if (extensions::FeatureSwitch::extensions_in_action_box()->IsEnabled()) AddExtension(extension, &action_box_menu_items_); else if (service_->extension_prefs()->GetBrowserActionVisibility(extension)) AddExtension(extension, &toolbar_items_); } else if (type == chrome::NOTIFICATION_EXTENSION_UNLOADED) { if (list_with_extension) RemoveExtension(extension, list_with_extension); } else if (type == chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED) { if (extensions::FeatureSwitch::extensions_in_action_box()->IsEnabled()) { // TODO(yefim): Implement this when implementing drag & drop // for action box menu. } else if ( service_->extension_prefs()->GetBrowserActionVisibility(extension)) { AddExtension(extension, &toolbar_items_); } else { RemoveExtension(extension, &toolbar_items_); } } else { NOTREACHED() << "Received unexpected notification"; } } void ExtensionToolbarModel::AddExtension(const Extension* extension, ExtensionList* list) { // We only care about extensions with browser actions. if (!extensions::ExtensionActionManager::Get(service_->profile())-> GetBrowserAction(*extension)) { return; } if (extension->id() == last_extension_removed_ && last_extension_removed_index_ < list->size()) { list->insert(list->begin() + last_extension_removed_index_, make_scoped_refptr(extension)); // TODO: figure out the right long term solution. if (list == &toolbar_items_) { FOR_EACH_OBSERVER(Observer, observers_, BrowserActionAdded(extension, last_extension_removed_index_)); } } else { list->push_back(make_scoped_refptr(extension)); // TODO: figure out the right long term solution. if (list == &toolbar_items_) { FOR_EACH_OBSERVER(Observer, observers_, BrowserActionAdded(extension, list->size() - 1)); } } last_extension_removed_ = ""; last_extension_removed_index_ = -1; UpdatePrefs(); } void ExtensionToolbarModel::RemoveExtension(const Extension* extension, ExtensionList* list) { ExtensionList::iterator pos = std::find(list->begin(), list->end(), extension); if (pos == list->end()) return; last_extension_removed_ = extension->id(); last_extension_removed_index_ = pos - list->begin(); list->erase(pos); // TODO: figure out the right long term solution. if (list == &toolbar_items_) { FOR_EACH_OBSERVER(Observer, observers_, BrowserActionRemoved(extension)); } UpdatePrefs(); } extensions::ExtensionList* ExtensionToolbarModel::FindListWithExtension( const Extension* extension) { if (IsInExtensionList(extension, toolbar_items_)) return &toolbar_items_; return IsInExtensionList(extension, action_box_menu_items_) ? &action_box_menu_items_ : NULL; } // Combine the currently enabled extensions that have browser actions (which // we get from the ExtensionService) with the ordering we get from the // pref service. For robustness we use a somewhat inefficient process: // 1. Create a vector of extensions sorted by their pref values. This vector may // have holes. // 2. Create a vector of extensions that did not have a pref value. // 3. Remove holes from the sorted vector and append the unsorted vector. void ExtensionToolbarModel::InitializeExtensionLists() { DCHECK(service_->is_ready()); if (extensions::FeatureSwitch::extensions_in_action_box()->IsEnabled()) PopulateForActionBoxMode(); else PopulateForNonActionBoxMode(); UpdatePrefs(); extensions_initialized_ = true; FOR_EACH_OBSERVER(Observer, observers_, ModelLoaded()); } void ExtensionToolbarModel::PopulateForActionBoxMode() { const extensions::ExtensionIdList toolbar_order = service_->extension_prefs()->GetToolbarOrder(); extensions::ExtensionIdList action_box_order = service_->extension_prefs()->GetActionBoxOrder(); extensions::ExtensionActionManager* extension_action_manager = extensions::ExtensionActionManager::Get(service_->profile()); // Add all browser actions not already in the toolbar or action box // to the action box (the prefs list may omit some extensions). for (ExtensionSet::const_iterator iter = service_->extensions()->begin(); iter != service_->extensions()->end(); ++iter) { const Extension* extension = *iter; if (!extension_action_manager->GetBrowserAction(*extension)) continue; if (std::find(toolbar_order.begin(), toolbar_order.end(), extension->id()) != toolbar_order.end()) continue; if (std::find(action_box_order.begin(), action_box_order.end(), extension->id()) != action_box_order.end()) continue; action_box_order.push_back(extension->id()); } FillExtensionList(action_box_order, &action_box_menu_items_); FillExtensionList(toolbar_order, &toolbar_items_); } void ExtensionToolbarModel::PopulateForNonActionBoxMode() { const extensions::ExtensionIdList pref_order = service_->extension_prefs()->GetToolbarOrder(); // Items that have a pref for their position. ExtensionList sorted; sorted.resize(pref_order.size(), NULL); // The items that don't have a pref for their position. ExtensionList unsorted; extensions::ExtensionActionManager* extension_action_manager = extensions::ExtensionActionManager::Get(service_->profile()); // Create the lists. for (ExtensionSet::const_iterator it = service_->extensions()->begin(); it != service_->extensions()->end(); ++it) { const Extension* extension = *it; if (!extension_action_manager->GetBrowserAction(*extension)) continue; if (!service_->extension_prefs()->GetBrowserActionVisibility(extension)) continue; extensions::ExtensionIdList::const_iterator pos = std::find(pref_order.begin(), pref_order.end(), extension->id()); if (pos != pref_order.end()) sorted[pos - pref_order.begin()] = extension; else unsorted.push_back(make_scoped_refptr(extension)); } // Merge the lists. toolbar_items_.clear(); toolbar_items_.reserve(sorted.size() + unsorted.size()); for (ExtensionList::const_iterator iter = sorted.begin(); iter != sorted.end(); ++iter) { // It's possible for the extension order to contain items that aren't // actually loaded on this machine. For example, when extension sync is on, // we sync the extension order as-is but double-check with the user before // syncing NPAPI-containing extensions, so if one of those is not actually // synced, we'll get a NULL in the list. This sort of case can also happen // if some error prevents an extension from loading. if (*iter != NULL) toolbar_items_.push_back(*iter); } toolbar_items_.insert(toolbar_items_.end(), unsorted.begin(), unsorted.end()); // Inform observers. for (size_t i = 0; i < toolbar_items_.size(); i++) { FOR_EACH_OBSERVER(Observer, observers_, BrowserActionAdded(toolbar_items_[i], i)); } } void ExtensionToolbarModel::FillExtensionList( const extensions::ExtensionIdList& order, ExtensionList* list) { list->clear(); list->reserve(order.size()); for (size_t i = 0; i < order.size(); ++i) { const extensions::Extension* extension = service_->GetExtensionById(order[i], false); if (extension) AddExtension(extension, list); } } void ExtensionToolbarModel::UpdatePrefs() { if (!service_->extension_prefs()) return; extensions::ExtensionIdList toolbar_ids; toolbar_ids.reserve(toolbar_items_.size()); for (size_t i = 0; i < toolbar_items_.size(); ++i) toolbar_ids.push_back(toolbar_items_[i]->id()); service_->extension_prefs()->SetToolbarOrder(toolbar_ids); extensions::ExtensionIdList action_box_ids; action_box_ids.reserve(action_box_menu_items_.size()); for (size_t i = 0; i < action_box_menu_items_.size(); ++i) action_box_ids.push_back(action_box_menu_items_[i]->id()); service_->extension_prefs()->SetActionBoxOrder(action_box_ids); } int ExtensionToolbarModel::IncognitoIndexToOriginal(int incognito_index) { int original_index = 0, i = 0; for (ExtensionList::iterator iter = toolbar_items_.begin(); iter != toolbar_items_.end(); ++iter, ++original_index) { if (service_->IsIncognitoEnabled((*iter)->id())) { if (incognito_index == i) break; ++i; } } return original_index; } int ExtensionToolbarModel::OriginalIndexToIncognito(int original_index) { int incognito_index = 0, i = 0; for (ExtensionList::iterator iter = toolbar_items_.begin(); iter != toolbar_items_.end(); ++iter, ++i) { if (original_index == i) break; if (service_->IsIncognitoEnabled((*iter)->id())) ++incognito_index; } return incognito_index; }