Home | History | Annotate | Download | only in extensions
      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 <algorithm>
      8 #include <string>
      9 
     10 #include "base/message_loop/message_loop.h"
     11 #include "base/metrics/histogram.h"
     12 #include "base/metrics/histogram_base.h"
     13 #include "base/prefs/pref_service.h"
     14 #include "chrome/browser/chrome_notification_types.h"
     15 #include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
     16 #include "chrome/browser/extensions/extension_action_manager.h"
     17 #include "chrome/browser/extensions/extension_tab_util.h"
     18 #include "chrome/browser/extensions/extension_toolbar_model_factory.h"
     19 #include "chrome/browser/extensions/extension_util.h"
     20 #include "chrome/browser/extensions/tab_helper.h"
     21 #include "chrome/browser/profiles/profile.h"
     22 #include "chrome/browser/ui/browser.h"
     23 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     24 #include "chrome/common/pref_names.h"
     25 #include "content/public/browser/notification_details.h"
     26 #include "content/public/browser/notification_source.h"
     27 #include "content/public/browser/web_contents.h"
     28 #include "extensions/browser/extension_prefs.h"
     29 #include "extensions/browser/extension_registry.h"
     30 #include "extensions/browser/extension_system.h"
     31 #include "extensions/browser/pref_names.h"
     32 #include "extensions/common/extension.h"
     33 #include "extensions/common/extension_set.h"
     34 #include "extensions/common/feature_switch.h"
     35 #include "extensions/common/one_shot_event.h"
     36 
     37 namespace extensions {
     38 
     39 ExtensionToolbarModel::ExtensionToolbarModel(Profile* profile,
     40                                              ExtensionPrefs* extension_prefs)
     41     : profile_(profile),
     42       extension_prefs_(extension_prefs),
     43       prefs_(profile_->GetPrefs()),
     44       extensions_initialized_(false),
     45       include_all_extensions_(
     46           FeatureSwitch::extension_action_redesign()->IsEnabled()),
     47       is_highlighting_(false),
     48       extension_action_observer_(this),
     49       extension_registry_observer_(this),
     50       weak_ptr_factory_(this) {
     51   ExtensionSystem::Get(profile_)->ready().Post(
     52       FROM_HERE,
     53       base::Bind(&ExtensionToolbarModel::OnReady,
     54                  weak_ptr_factory_.GetWeakPtr()));
     55   visible_icon_count_ = prefs_->GetInteger(pref_names::kToolbarSize);
     56   pref_change_registrar_.Init(prefs_);
     57   pref_change_callback_ =
     58       base::Bind(&ExtensionToolbarModel::OnExtensionToolbarPrefChange,
     59                  base::Unretained(this));
     60   pref_change_registrar_.Add(pref_names::kToolbar, pref_change_callback_);
     61 }
     62 
     63 ExtensionToolbarModel::~ExtensionToolbarModel() {
     64 }
     65 
     66 // static
     67 ExtensionToolbarModel* ExtensionToolbarModel::Get(Profile* profile) {
     68   return ExtensionToolbarModelFactory::GetForProfile(profile);
     69 }
     70 
     71 void ExtensionToolbarModel::AddObserver(Observer* observer) {
     72   observers_.AddObserver(observer);
     73 }
     74 
     75 void ExtensionToolbarModel::RemoveObserver(Observer* observer) {
     76   observers_.RemoveObserver(observer);
     77 }
     78 
     79 void ExtensionToolbarModel::MoveExtensionIcon(const Extension* extension,
     80                                               int index) {
     81   ExtensionList::iterator pos = std::find(toolbar_items_.begin(),
     82       toolbar_items_.end(), extension);
     83   if (pos == toolbar_items_.end()) {
     84     NOTREACHED();
     85     return;
     86   }
     87   toolbar_items_.erase(pos);
     88 
     89   ExtensionIdList::iterator pos_id;
     90   pos_id = std::find(last_known_positions_.begin(),
     91                      last_known_positions_.end(), extension->id());
     92   if (pos_id != last_known_positions_.end())
     93     last_known_positions_.erase(pos_id);
     94 
     95   int i = 0;
     96   bool inserted = false;
     97   for (ExtensionList::iterator iter = toolbar_items_.begin();
     98        iter != toolbar_items_.end();
     99        ++iter, ++i) {
    100     if (i == index) {
    101       pos_id = std::find(last_known_positions_.begin(),
    102                          last_known_positions_.end(), (*iter)->id());
    103       last_known_positions_.insert(pos_id, extension->id());
    104 
    105       toolbar_items_.insert(iter, make_scoped_refptr(extension));
    106       inserted = true;
    107       break;
    108     }
    109   }
    110 
    111   if (!inserted) {
    112     DCHECK_EQ(index, static_cast<int>(toolbar_items_.size()));
    113     index = toolbar_items_.size();
    114 
    115     toolbar_items_.push_back(make_scoped_refptr(extension));
    116     last_known_positions_.push_back(extension->id());
    117   }
    118 
    119   FOR_EACH_OBSERVER(
    120       Observer, observers_, ToolbarExtensionMoved(extension, index));
    121   MaybeUpdateVisibilityPref(extension, index);
    122   UpdatePrefs();
    123 }
    124 
    125 void ExtensionToolbarModel::SetVisibleIconCount(int count) {
    126   visible_icon_count_ =
    127       count == static_cast<int>(toolbar_items_.size()) ? -1 : count;
    128 
    129   // Only set the prefs if we're not in highlight mode. Highlight mode is
    130   // designed to be a transitory state, and should not persist across browser
    131   // restarts (though it may be re-entered).
    132   if (!is_highlighting_) {
    133     // Additionally, if we are using the new toolbar, any icons which are in the
    134     // overflow menu are considered "hidden". But it so happens that the times
    135     // we are likely to call SetVisibleIconCount() are also those when we are
    136     // in flux. So wait for things to cool down before setting the prefs.
    137     base::MessageLoop::current()->PostTask(
    138         FROM_HERE,
    139         base::Bind(&ExtensionToolbarModel::MaybeUpdateVisibilityPrefs,
    140                    weak_ptr_factory_.GetWeakPtr()));
    141     prefs_->SetInteger(pref_names::kToolbarSize, visible_icon_count_);
    142   }
    143 }
    144 
    145 void ExtensionToolbarModel::OnExtensionActionUpdated(
    146     ExtensionAction* extension_action,
    147     content::WebContents* web_contents,
    148     content::BrowserContext* browser_context) {
    149   const Extension* extension =
    150       ExtensionRegistry::Get(profile_)->enabled_extensions().GetByID(
    151           extension_action->extension_id());
    152   // Notify observers if the extension exists and is in the model.
    153   if (extension &&
    154       std::find(toolbar_items_.begin(),
    155                 toolbar_items_.end(),
    156                 extension) != toolbar_items_.end()) {
    157     FOR_EACH_OBSERVER(Observer, observers_, ToolbarExtensionUpdated(extension));
    158   }
    159 }
    160 
    161 void ExtensionToolbarModel::OnExtensionLoaded(
    162     content::BrowserContext* browser_context,
    163     const Extension* extension) {
    164   // We don't want to add the same extension twice. It may have already been
    165   // added by EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED below, if the user
    166   // hides the browser action and then disables and enables the extension.
    167   for (size_t i = 0; i < toolbar_items_.size(); i++) {
    168     if (toolbar_items_[i].get() == extension)
    169       return;
    170   }
    171 
    172   AddExtension(extension);
    173 }
    174 
    175 void ExtensionToolbarModel::OnExtensionUnloaded(
    176     content::BrowserContext* browser_context,
    177     const Extension* extension,
    178     UnloadedExtensionInfo::Reason reason) {
    179   RemoveExtension(extension);
    180 }
    181 
    182 void ExtensionToolbarModel::OnExtensionUninstalled(
    183     content::BrowserContext* browser_context,
    184     const Extension* extension,
    185     extensions::UninstallReason reason) {
    186   // Remove the extension id from the ordered list, if it exists (the extension
    187   // might not be represented in the list because it might not have an icon).
    188   ExtensionIdList::iterator pos =
    189       std::find(last_known_positions_.begin(),
    190                 last_known_positions_.end(), extension->id());
    191 
    192   if (pos != last_known_positions_.end()) {
    193     last_known_positions_.erase(pos);
    194     UpdatePrefs();
    195   }
    196 }
    197 
    198 void ExtensionToolbarModel::Observe(
    199     int type,
    200     const content::NotificationSource& source,
    201     const content::NotificationDetails& details) {
    202   DCHECK_EQ(NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED, type);
    203   const Extension* extension =
    204       ExtensionRegistry::Get(profile_)->GetExtensionById(
    205           *content::Details<const std::string>(details).ptr(),
    206           ExtensionRegistry::EVERYTHING);
    207 
    208   bool visible = ExtensionActionAPI::GetBrowserActionVisibility(
    209                      extension_prefs_, extension->id());
    210   // Hiding works differently with the new and old toolbars.
    211   if (include_all_extensions_) {
    212     int new_size = 0;
    213     int new_index = 0;
    214     if (visible) {
    215       // If this action used to be hidden, we can't possible be showing all.
    216       DCHECK_NE(-1, visible_icon_count_);
    217       // Grow the bar by one and move the extension to the end of the visibles.
    218       new_size = visible_icon_count_ + 1;
    219       new_index = new_size - 1;
    220     } else {
    221       // If we're hiding one, we must be showing at least one.
    222       DCHECK_NE(visible_icon_count_, 0);
    223       // Shrink the bar by one and move the extension to the beginning of the
    224       // overflow menu.
    225       new_size = visible_icon_count_ == -1 ?
    226                      toolbar_items_.size() - 1 : visible_icon_count_ - 1;
    227       new_index = new_size;
    228     }
    229     SetVisibleIconCount(new_size);
    230     MoveExtensionIcon(extension, new_index);
    231     FOR_EACH_OBSERVER(Observer, observers_, ToolbarVisibleCountChanged());
    232   } else {  // Don't include all extensions.
    233     if (visible)
    234       AddExtension(extension);
    235     else
    236       RemoveExtension(extension);
    237   }
    238 }
    239 
    240 void ExtensionToolbarModel::OnReady() {
    241   ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
    242   InitializeExtensionList(registry->enabled_extensions());
    243   // Wait until the extension system is ready before observing any further
    244   // changes so that the toolbar buttons can be shown in their stable ordering
    245   // taken from prefs.
    246   extension_registry_observer_.Add(registry);
    247   extension_action_observer_.Add(ExtensionActionAPI::Get(profile_));
    248   registrar_.Add(
    249       this,
    250       extensions::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED,
    251       content::Source<ExtensionPrefs>(extension_prefs_));
    252 }
    253 
    254 size_t ExtensionToolbarModel::FindNewPositionFromLastKnownGood(
    255     const Extension* extension) {
    256   // See if we have last known good position for this extension.
    257   size_t new_index = 0;
    258   // Loop through the ID list of known positions, to count the number of visible
    259   // extension icons preceding |extension|.
    260   for (ExtensionIdList::const_iterator iter_id = last_known_positions_.begin();
    261        iter_id < last_known_positions_.end(); ++iter_id) {
    262     if ((*iter_id) == extension->id())
    263       return new_index;  // We've found the right position.
    264     // Found an id, need to see if it is visible.
    265     for (ExtensionList::const_iterator iter_ext = toolbar_items_.begin();
    266          iter_ext < toolbar_items_.end(); ++iter_ext) {
    267       if ((*iter_ext)->id().compare(*iter_id) == 0) {
    268         // This extension is visible, update the index value.
    269         ++new_index;
    270         break;
    271       }
    272     }
    273   }
    274 
    275   // Position not found.
    276   return toolbar_items_.size();
    277 }
    278 
    279 bool ExtensionToolbarModel::ShouldAddExtension(const Extension* extension) {
    280   ExtensionActionManager* action_manager =
    281       ExtensionActionManager::Get(profile_);
    282   if (include_all_extensions_) {
    283     // In this case, we don't care about the browser action visibility, because
    284     // we want to show each extension regardless.
    285     // TODO(devlin): Extension actions which are not visible should be moved to
    286     // the overflow menu by default.
    287     return action_manager->GetBrowserAction(*extension) ||
    288            action_manager->GetPageAction(*extension);
    289   }
    290 
    291   return action_manager->GetBrowserAction(*extension) &&
    292          ExtensionActionAPI::GetBrowserActionVisibility(
    293              extension_prefs_, extension->id());
    294 }
    295 
    296 void ExtensionToolbarModel::AddExtension(const Extension* extension) {
    297   if (!ShouldAddExtension(extension))
    298     return;
    299 
    300   size_t new_index = toolbar_items_.size();
    301 
    302   // See if we have a last known good position for this extension.
    303   ExtensionIdList::iterator last_pos = std::find(last_known_positions_.begin(),
    304                                                  last_known_positions_.end(),
    305                                                  extension->id());
    306   if (last_pos != last_known_positions_.end()) {
    307     new_index = FindNewPositionFromLastKnownGood(extension);
    308     if (new_index != toolbar_items_.size()) {
    309       toolbar_items_.insert(toolbar_items_.begin() + new_index,
    310                             make_scoped_refptr(extension));
    311     } else {
    312       toolbar_items_.push_back(make_scoped_refptr(extension));
    313     }
    314   } else {
    315     // This is a never before seen extension, that was added to the end. Make
    316     // sure to reflect that. (|new_index| was set above.)
    317     toolbar_items_.push_back(make_scoped_refptr(extension));
    318     last_known_positions_.push_back(extension->id());
    319     UpdatePrefs();
    320   }
    321 
    322   MaybeUpdateVisibilityPref(extension, new_index);
    323 
    324   // If we're currently highlighting, then even though we add a browser action
    325   // to the full list (|toolbar_items_|, there won't be another *visible*
    326   // browser action, which was what the observers care about.
    327   if (!is_highlighting_) {
    328     FOR_EACH_OBSERVER(
    329         Observer, observers_, ToolbarExtensionAdded(extension, new_index));
    330   }
    331 }
    332 
    333 void ExtensionToolbarModel::RemoveExtension(const Extension* extension) {
    334   ExtensionList::iterator pos =
    335       std::find(toolbar_items_.begin(), toolbar_items_.end(), extension);
    336   if (pos == toolbar_items_.end())
    337     return;
    338 
    339   toolbar_items_.erase(pos);
    340 
    341   // If we're in highlight mode, we also have to remove the extension from
    342   // the highlighted list.
    343   if (is_highlighting_) {
    344     pos = std::find(highlighted_items_.begin(),
    345                     highlighted_items_.end(),
    346                     extension);
    347     if (pos != highlighted_items_.end()) {
    348       highlighted_items_.erase(pos);
    349       FOR_EACH_OBSERVER(
    350           Observer, observers_, ToolbarExtensionRemoved(extension));
    351       // If the highlighted list is now empty, we stop highlighting.
    352       if (highlighted_items_.empty())
    353         StopHighlighting();
    354     }
    355   } else {
    356     FOR_EACH_OBSERVER(Observer, observers_, ToolbarExtensionRemoved(extension));
    357   }
    358 
    359   UpdatePrefs();
    360 }
    361 
    362 // Combine the currently enabled extensions that have browser actions (which
    363 // we get from the ExtensionRegistry) with the ordering we get from the
    364 // pref service. For robustness we use a somewhat inefficient process:
    365 // 1. Create a vector of extensions sorted by their pref values. This vector may
    366 // have holes.
    367 // 2. Create a vector of extensions that did not have a pref value.
    368 // 3. Remove holes from the sorted vector and append the unsorted vector.
    369 void ExtensionToolbarModel::InitializeExtensionList(
    370     const ExtensionSet& extensions) {
    371   last_known_positions_ = extension_prefs_->GetToolbarOrder();
    372   Populate(last_known_positions_, extensions);
    373 
    374   extensions_initialized_ = true;
    375   MaybeUpdateVisibilityPrefs();
    376   FOR_EACH_OBSERVER(Observer, observers_, ToolbarVisibleCountChanged());
    377 }
    378 
    379 void ExtensionToolbarModel::Populate(const ExtensionIdList& positions,
    380                                      const ExtensionSet& extensions) {
    381   // Items that have explicit positions.
    382   ExtensionList sorted;
    383   sorted.resize(positions.size(), NULL);
    384   // The items that don't have explicit positions.
    385   ExtensionList unsorted;
    386 
    387   ExtensionActionManager* extension_action_manager =
    388       ExtensionActionManager::Get(profile_);
    389 
    390   // Create the lists.
    391   int hidden = 0;
    392   for (ExtensionSet::const_iterator it = extensions.begin();
    393        it != extensions.end();
    394        ++it) {
    395     const Extension* extension = it->get();
    396     if (!ShouldAddExtension(extension)) {
    397       if (extension_action_manager->GetBrowserAction(*extension))
    398         ++hidden;
    399       continue;
    400     }
    401 
    402     ExtensionIdList::const_iterator pos =
    403         std::find(positions.begin(), positions.end(), extension->id());
    404     if (pos != positions.end())
    405       sorted[pos - positions.begin()] = extension;
    406     else
    407       unsorted.push_back(make_scoped_refptr(extension));
    408   }
    409 
    410   size_t items_count = toolbar_items_.size();
    411   for (size_t i = 0; i < items_count; i++) {
    412     const Extension* extension = toolbar_items_.back().get();
    413     // By popping the extension here (before calling BrowserActionRemoved),
    414     // we will not shrink visible count by one after BrowserActionRemoved
    415     // calls SetVisibleCount.
    416     toolbar_items_.pop_back();
    417     FOR_EACH_OBSERVER(
    418         Observer, observers_, ToolbarExtensionRemoved(extension));
    419   }
    420   DCHECK(toolbar_items_.empty());
    421 
    422   // Merge the lists.
    423   toolbar_items_.reserve(sorted.size() + unsorted.size());
    424 
    425   for (ExtensionList::const_iterator iter = sorted.begin();
    426        iter != sorted.end(); ++iter) {
    427     // It's possible for the extension order to contain items that aren't
    428     // actually loaded on this machine.  For example, when extension sync is on,
    429     // we sync the extension order as-is but double-check with the user before
    430     // syncing NPAPI-containing extensions, so if one of those is not actually
    431     // synced, we'll get a NULL in the list.  This sort of case can also happen
    432     // if some error prevents an extension from loading.
    433     if (iter->get() != NULL) {
    434       toolbar_items_.push_back(*iter);
    435       FOR_EACH_OBSERVER(
    436           Observer,
    437           observers_,
    438           ToolbarExtensionAdded(iter->get(), toolbar_items_.size() - 1));
    439     }
    440   }
    441   for (ExtensionList::const_iterator iter = unsorted.begin();
    442        iter != unsorted.end(); ++iter) {
    443     if (iter->get() != NULL) {
    444       toolbar_items_.push_back(*iter);
    445       FOR_EACH_OBSERVER(
    446           Observer,
    447           observers_,
    448           ToolbarExtensionAdded(iter->get(), toolbar_items_.size() - 1));
    449     }
    450   }
    451 
    452   UMA_HISTOGRAM_COUNTS_100(
    453       "ExtensionToolbarModel.BrowserActionsPermanentlyHidden", hidden);
    454   UMA_HISTOGRAM_COUNTS_100("ExtensionToolbarModel.BrowserActionsCount",
    455                            toolbar_items_.size());
    456 
    457   if (!toolbar_items_.empty()) {
    458     // Visible count can be -1, meaning: 'show all'. Since UMA converts negative
    459     // values to 0, this would be counted as 'show none' unless we convert it to
    460     // max.
    461     UMA_HISTOGRAM_COUNTS_100("ExtensionToolbarModel.BrowserActionsVisible",
    462                              visible_icon_count_ == -1 ?
    463                                  base::HistogramBase::kSampleType_MAX :
    464                                  visible_icon_count_);
    465   }
    466 }
    467 
    468 void ExtensionToolbarModel::UpdatePrefs() {
    469   if (!extension_prefs_)
    470     return;
    471 
    472   // Don't observe change caused by self.
    473   pref_change_registrar_.Remove(pref_names::kToolbar);
    474   extension_prefs_->SetToolbarOrder(last_known_positions_);
    475   pref_change_registrar_.Add(pref_names::kToolbar, pref_change_callback_);
    476 }
    477 
    478 void ExtensionToolbarModel::MaybeUpdateVisibilityPref(
    479     const Extension* extension, int index) {
    480   // We only update the visibility pref for hidden/not hidden based on the
    481   // overflow menu with the new toolbar design.
    482   if (include_all_extensions_) {
    483     bool visible = index < visible_icon_count_ || visible_icon_count_ == -1;
    484     if (visible != ExtensionActionAPI::GetBrowserActionVisibility(
    485                        extension_prefs_, extension->id())) {
    486       // Don't observe changes caused by ourselves.
    487       bool was_registered = false;
    488       if (registrar_.IsRegistered(
    489               this,
    490               NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED,
    491               content::Source<ExtensionPrefs>(extension_prefs_))) {
    492         was_registered = true;
    493         registrar_.Remove(
    494             this,
    495             NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED,
    496             content::Source<ExtensionPrefs>(extension_prefs_));
    497       }
    498       ExtensionActionAPI::SetBrowserActionVisibility(
    499           extension_prefs_, extension->id(), visible);
    500       if (was_registered) {
    501         registrar_.Add(this,
    502                        NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED,
    503                        content::Source<ExtensionPrefs>(extension_prefs_));
    504       }
    505     }
    506   }
    507 }
    508 
    509 void ExtensionToolbarModel::MaybeUpdateVisibilityPrefs() {
    510   for (size_t i = 0u; i < toolbar_items_.size(); ++i)
    511     MaybeUpdateVisibilityPref(toolbar_items_[i].get(), i);
    512 }
    513 
    514 int ExtensionToolbarModel::IncognitoIndexToOriginal(int incognito_index) {
    515   int original_index = 0, i = 0;
    516   for (ExtensionList::iterator iter = toolbar_items_.begin();
    517        iter != toolbar_items_.end();
    518        ++iter, ++original_index) {
    519     if (util::IsIncognitoEnabled((*iter)->id(), profile_)) {
    520       if (incognito_index == i)
    521         break;
    522       ++i;
    523     }
    524   }
    525   return original_index;
    526 }
    527 
    528 int ExtensionToolbarModel::OriginalIndexToIncognito(int original_index) {
    529   int incognito_index = 0, i = 0;
    530   for (ExtensionList::iterator iter = toolbar_items_.begin();
    531        iter != toolbar_items_.end();
    532        ++iter, ++i) {
    533     if (original_index == i)
    534       break;
    535     if (util::IsIncognitoEnabled((*iter)->id(), profile_))
    536       ++incognito_index;
    537   }
    538   return incognito_index;
    539 }
    540 
    541 void ExtensionToolbarModel::OnExtensionToolbarPrefChange() {
    542   // If extensions are not ready, defer to later Populate() call.
    543   if (!extensions_initialized_)
    544     return;
    545 
    546   // Recalculate |last_known_positions_| to be |pref_positions| followed by
    547   // ones that are only in |last_known_positions_|.
    548   ExtensionIdList pref_positions = extension_prefs_->GetToolbarOrder();
    549   size_t pref_position_size = pref_positions.size();
    550   for (size_t i = 0; i < last_known_positions_.size(); ++i) {
    551     if (std::find(pref_positions.begin(), pref_positions.end(),
    552                   last_known_positions_[i]) == pref_positions.end()) {
    553       pref_positions.push_back(last_known_positions_[i]);
    554     }
    555   }
    556   last_known_positions_.swap(pref_positions);
    557 
    558   // Re-populate.
    559   Populate(last_known_positions_,
    560            ExtensionRegistry::Get(profile_)->enabled_extensions());
    561 
    562   if (last_known_positions_.size() > pref_position_size) {
    563     // Need to update pref because we have extra icons. But can't call
    564     // UpdatePrefs() directly within observation closure.
    565     base::MessageLoop::current()->PostTask(
    566         FROM_HERE,
    567         base::Bind(&ExtensionToolbarModel::UpdatePrefs,
    568                    weak_ptr_factory_.GetWeakPtr()));
    569   }
    570 }
    571 
    572 bool ExtensionToolbarModel::ShowExtensionActionPopup(
    573     const Extension* extension,
    574     Browser* browser,
    575     bool grant_active_tab) {
    576   ObserverListBase<Observer>::Iterator it(observers_);
    577   Observer* obs = NULL;
    578   // Look for the Observer associated with the browser.
    579   // This would be cleaner if we had an abstract class for the Toolbar UI
    580   // (like we do for LocationBar), but sadly, we don't.
    581   while ((obs = it.GetNext()) != NULL) {
    582     if (obs->GetBrowser() == browser)
    583       return obs->ShowExtensionActionPopup(extension, grant_active_tab);
    584   }
    585   return false;
    586 }
    587 
    588 void ExtensionToolbarModel::EnsureVisibility(
    589     const ExtensionIdList& extension_ids) {
    590   if (visible_icon_count_ == -1)
    591     return;  // Already showing all.
    592 
    593   // Otherwise, make sure we have enough room to show all the extensions
    594   // requested.
    595   if (visible_icon_count_ < static_cast<int>(extension_ids.size())) {
    596     SetVisibleIconCount(extension_ids.size());
    597 
    598     // Inform observers.
    599     FOR_EACH_OBSERVER(Observer, observers_, ToolbarVisibleCountChanged());
    600   }
    601 
    602   if (visible_icon_count_ == -1)
    603     return;  // May have been set to max by SetVisibleIconCount.
    604 
    605   // Guillotine's Delight: Move an orange noble to the front of the line.
    606   for (ExtensionIdList::const_iterator it = extension_ids.begin();
    607        it != extension_ids.end(); ++it) {
    608     for (ExtensionList::const_iterator extension = toolbar_items_.begin();
    609          extension != toolbar_items_.end(); ++extension) {
    610       if ((*extension)->id() == (*it)) {
    611         if (extension - toolbar_items_.begin() >= visible_icon_count_)
    612           MoveExtensionIcon(extension->get(), 0);
    613         break;
    614       }
    615     }
    616   }
    617 }
    618 
    619 bool ExtensionToolbarModel::HighlightExtensions(
    620     const ExtensionIdList& extension_ids) {
    621   highlighted_items_.clear();
    622 
    623   for (ExtensionIdList::const_iterator id = extension_ids.begin();
    624        id != extension_ids.end();
    625        ++id) {
    626     for (ExtensionList::const_iterator extension = toolbar_items_.begin();
    627          extension != toolbar_items_.end();
    628          ++extension) {
    629       if (*id == (*extension)->id())
    630         highlighted_items_.push_back(*extension);
    631     }
    632   }
    633 
    634   // If we have any items in |highlighted_items_|, then we entered highlighting
    635   // mode.
    636   if (highlighted_items_.size()) {
    637     old_visible_icon_count_ = visible_icon_count_;
    638     is_highlighting_ = true;
    639     if (visible_icon_count_ != -1 &&
    640         visible_icon_count_ < static_cast<int>(extension_ids.size())) {
    641       SetVisibleIconCount(extension_ids.size());
    642       FOR_EACH_OBSERVER(Observer, observers_, ToolbarVisibleCountChanged());
    643     }
    644 
    645     FOR_EACH_OBSERVER(Observer, observers_, ToolbarHighlightModeChanged(true));
    646     return true;
    647   }
    648 
    649   // Otherwise, we didn't enter highlighting mode (and, in fact, exited it if
    650   // we were otherwise in it).
    651   if (is_highlighting_)
    652     StopHighlighting();
    653   return false;
    654 }
    655 
    656 void ExtensionToolbarModel::StopHighlighting() {
    657   if (is_highlighting_) {
    658     highlighted_items_.clear();
    659     is_highlighting_ = false;
    660     if (old_visible_icon_count_ != visible_icon_count_) {
    661       SetVisibleIconCount(old_visible_icon_count_);
    662       FOR_EACH_OBSERVER(Observer, observers_, ToolbarVisibleCountChanged());
    663     }
    664     FOR_EACH_OBSERVER(Observer, observers_, ToolbarHighlightModeChanged(false));
    665   }
    666 }
    667 
    668 void ExtensionToolbarModel::SetVisibleIconCountForTest(size_t visible_icons) {
    669   SetVisibleIconCount(visible_icons);
    670   FOR_EACH_OBSERVER(Observer, observers_, ToolbarVisibleCountChanged());
    671 }
    672 
    673 }  // namespace extensions
    674