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
37namespace extensions {
38
39ExtensionToolbarModel::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
63ExtensionToolbarModel::~ExtensionToolbarModel() {
64}
65
66// static
67ExtensionToolbarModel* ExtensionToolbarModel::Get(Profile* profile) {
68  return ExtensionToolbarModelFactory::GetForProfile(profile);
69}
70
71void ExtensionToolbarModel::AddObserver(Observer* observer) {
72  observers_.AddObserver(observer);
73}
74
75void ExtensionToolbarModel::RemoveObserver(Observer* observer) {
76  observers_.RemoveObserver(observer);
77}
78
79void 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
125void 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
145void 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
161void 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
175void ExtensionToolbarModel::OnExtensionUnloaded(
176    content::BrowserContext* browser_context,
177    const Extension* extension,
178    UnloadedExtensionInfo::Reason reason) {
179  RemoveExtension(extension);
180}
181
182void 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
198void 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
240void 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
254size_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
279bool 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
296void 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
333void 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.
369void 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
379void 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
468void 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
478void 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
509void ExtensionToolbarModel::MaybeUpdateVisibilityPrefs() {
510  for (size_t i = 0u; i < toolbar_items_.size(); ++i)
511    MaybeUpdateVisibilityPref(toolbar_items_[i].get(), i);
512}
513
514int 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
528int 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
541void 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
572bool 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
588void 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
619bool 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
656void 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
668void ExtensionToolbarModel::SetVisibleIconCountForTest(size_t visible_icons) {
669  SetVisibleIconCount(visible_icons);
670  FOR_EACH_OBSERVER(Observer, observers_, ToolbarVisibleCountChanged());
671}
672
673}  // namespace extensions
674