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