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