1// Copyright (c) 2011 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 "chrome/browser/extensions/extension_prefs.h"
8#include "chrome/browser/extensions/extension_service.h"
9#include "chrome/browser/prefs/pref_service.h"
10#include "chrome/browser/profiles/profile.h"
11#include "chrome/common/extensions/extension.h"
12#include "chrome/common/pref_names.h"
13#include "content/common/notification_service.h"
14
15ExtensionToolbarModel::ExtensionToolbarModel(ExtensionService* service)
16    : service_(service),
17      prefs_(service->profile()->GetPrefs()),
18      extensions_initialized_(false) {
19  DCHECK(service_);
20
21  registrar_.Add(this, NotificationType::EXTENSION_LOADED,
22                 Source<Profile>(service_->profile()));
23  registrar_.Add(this, NotificationType::EXTENSION_UNLOADED,
24                 Source<Profile>(service_->profile()));
25  registrar_.Add(this, NotificationType::EXTENSIONS_READY,
26                 Source<Profile>(service_->profile()));
27  registrar_.Add(this,
28                 NotificationType::EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED,
29                 NotificationService::AllSources());
30
31  visible_icon_count_ = prefs_->GetInteger(prefs::kExtensionToolbarSize);
32}
33
34ExtensionToolbarModel::~ExtensionToolbarModel() {
35}
36
37void ExtensionToolbarModel::DestroyingProfile() {
38  registrar_.RemoveAll();
39}
40
41void ExtensionToolbarModel::AddObserver(Observer* observer) {
42  observers_.AddObserver(observer);
43}
44
45void ExtensionToolbarModel::RemoveObserver(Observer* observer) {
46  observers_.RemoveObserver(observer);
47}
48
49void ExtensionToolbarModel::MoveBrowserAction(const Extension* extension,
50                                              int index) {
51  ExtensionList::iterator pos = std::find(begin(), end(), extension);
52  if (pos == end()) {
53    NOTREACHED();
54    return;
55  }
56  toolitems_.erase(pos);
57
58  int i = 0;
59  bool inserted = false;
60  for (ExtensionList::iterator iter = begin(); iter != end(); ++iter, ++i) {
61    if (i == index) {
62      toolitems_.insert(iter, make_scoped_refptr(extension));
63      inserted = true;
64      break;
65    }
66  }
67
68  if (!inserted) {
69    DCHECK_EQ(index, static_cast<int>(toolitems_.size()));
70    index = toolitems_.size();
71
72    toolitems_.push_back(make_scoped_refptr(extension));
73  }
74
75  FOR_EACH_OBSERVER(Observer, observers_, BrowserActionMoved(extension, index));
76
77  UpdatePrefs();
78}
79
80void ExtensionToolbarModel::SetVisibleIconCount(int count) {
81  visible_icon_count_ = count == static_cast<int>(size()) ? -1 : count;
82  prefs_->SetInteger(prefs::kExtensionToolbarSize, visible_icon_count_);
83  prefs_->ScheduleSavePersistentPrefs();
84}
85
86void ExtensionToolbarModel::Observe(NotificationType type,
87                                    const NotificationSource& source,
88                                    const NotificationDetails& details) {
89  if (type == NotificationType::EXTENSIONS_READY) {
90    InitializeExtensionList();
91    return;
92  }
93
94  if (!service_->is_ready())
95    return;
96
97  const Extension* extension = NULL;
98  if (type == NotificationType::EXTENSION_UNLOADED) {
99    extension = Details<UnloadedExtensionInfo>(details)->extension;
100  } else {
101    extension = Details<const Extension>(details).ptr();
102  }
103  if (type == NotificationType::EXTENSION_LOADED) {
104    // We don't want to add the same extension twice. It may have already been
105    // added by EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED below, if the user
106    // hides the browser action and then disables and enables the extension.
107    for (size_t i = 0; i < toolitems_.size(); i++) {
108      if (toolitems_[i].get() == extension)
109        return;  // Already exists.
110    }
111    if (service_->GetBrowserActionVisibility(extension))
112      AddExtension(extension);
113  } else if (type == NotificationType::EXTENSION_UNLOADED) {
114    RemoveExtension(extension);
115  } else if (type ==
116             NotificationType::EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED) {
117    if (service_->GetBrowserActionVisibility(extension))
118      AddExtension(extension);
119    else
120      RemoveExtension(extension);
121  } else {
122    NOTREACHED() << "Received unexpected notification";
123  }
124}
125
126void ExtensionToolbarModel::AddExtension(const Extension* extension) {
127  // We only care about extensions with browser actions.
128  if (!extension->browser_action())
129    return;
130
131  if (extension->id() == last_extension_removed_ &&
132      last_extension_removed_index_ < toolitems_.size()) {
133    toolitems_.insert(begin() + last_extension_removed_index_,
134                      make_scoped_refptr(extension));
135    FOR_EACH_OBSERVER(Observer, observers_,
136        BrowserActionAdded(extension, last_extension_removed_index_));
137  } else {
138    toolitems_.push_back(make_scoped_refptr(extension));
139    FOR_EACH_OBSERVER(Observer, observers_,
140                      BrowserActionAdded(extension, toolitems_.size() - 1));
141  }
142
143  last_extension_removed_ = "";
144  last_extension_removed_index_ = -1;
145
146  UpdatePrefs();
147}
148
149void ExtensionToolbarModel::RemoveExtension(const Extension* extension) {
150  ExtensionList::iterator pos = std::find(begin(), end(), extension);
151  if (pos == end()) {
152    return;
153  }
154
155  last_extension_removed_ = extension->id();
156  last_extension_removed_index_ = pos - begin();
157
158  toolitems_.erase(pos);
159  FOR_EACH_OBSERVER(Observer, observers_,
160                    BrowserActionRemoved(extension));
161
162  UpdatePrefs();
163}
164
165// Combine the currently enabled extensions that have browser actions (which
166// we get from the ExtensionService) with the ordering we get from the
167// pref service. For robustness we use a somewhat inefficient process:
168// 1. Create a vector of extensions sorted by their pref values. This vector may
169// have holes.
170// 2. Create a vector of extensions that did not have a pref value.
171// 3. Remove holes from the sorted vector and append the unsorted vector.
172void ExtensionToolbarModel::InitializeExtensionList() {
173  DCHECK(service_->is_ready());
174
175  std::vector<std::string> pref_order = service_->extension_prefs()->
176      GetToolbarOrder();
177  // Items that have a pref for their position.
178  ExtensionList sorted;
179  sorted.resize(pref_order.size(), NULL);
180  // The items that don't have a pref for their position.
181  ExtensionList unsorted;
182
183  // Create the lists.
184  for (size_t i = 0; i < service_->extensions()->size(); ++i) {
185    const Extension* extension = service_->extensions()->at(i);
186    if (!extension->browser_action())
187      continue;
188    if (!service_->GetBrowserActionVisibility(extension))
189      continue;
190
191    std::vector<std::string>::iterator pos =
192        std::find(pref_order.begin(), pref_order.end(), extension->id());
193    if (pos != pref_order.end()) {
194      int index = std::distance(pref_order.begin(), pos);
195      sorted[index] = extension;
196    } else {
197      unsorted.push_back(make_scoped_refptr(extension));
198    }
199  }
200
201  // Merge the lists.
202  toolitems_.reserve(sorted.size() + unsorted.size());
203  for (ExtensionList::iterator iter = sorted.begin();
204       iter != sorted.end(); ++iter) {
205    if (*iter != NULL)
206      toolitems_.push_back(*iter);
207  }
208  toolitems_.insert(toolitems_.end(), unsorted.begin(), unsorted.end());
209
210  // Inform observers.
211  for (size_t i = 0; i < toolitems_.size(); i++) {
212    FOR_EACH_OBSERVER(Observer, observers_,
213                      BrowserActionAdded(toolitems_[i], i));
214  }
215
216  UpdatePrefs();
217
218  extensions_initialized_ = true;
219  FOR_EACH_OBSERVER(Observer, observers_, ModelLoaded());
220}
221
222void ExtensionToolbarModel::UpdatePrefs() {
223  if (!service_->extension_prefs())
224    return;
225
226  std::vector<std::string> ids;
227  ids.reserve(toolitems_.size());
228  for (ExtensionList::iterator iter = begin(); iter != end(); ++iter)
229    ids.push_back((*iter)->id());
230  service_->extension_prefs()->SetToolbarOrder(ids);
231}
232
233const Extension* ExtensionToolbarModel::GetExtensionByIndex(int index) const {
234  return toolitems_[index];
235}
236
237int ExtensionToolbarModel::IncognitoIndexToOriginal(int incognito_index) {
238  int original_index = 0, i = 0;
239  for (ExtensionList::iterator iter = begin(); iter != end();
240       ++iter, ++original_index) {
241    if (service_->IsIncognitoEnabled((*iter)->id())) {
242      if (incognito_index == i)
243        break;
244      ++i;
245    }
246  }
247  return original_index;
248}
249
250int ExtensionToolbarModel::OriginalIndexToIncognito(int original_index) {
251  int incognito_index = 0, i = 0;
252  for (ExtensionList::iterator iter = begin(); iter != end();
253       ++iter, ++i) {
254    if (original_index == i)
255      break;
256    if (service_->IsIncognitoEnabled((*iter)->id()))
257      ++incognito_index;
258  }
259  return incognito_index;
260}
261