extension_toolbar_model.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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 "base/prefs/pref_service.h"
8#include "chrome/browser/extensions/browser_event_router.h"
9#include "chrome/browser/extensions/extension_action.h"
10#include "chrome/browser/extensions/extension_action_manager.h"
11#include "chrome/browser/extensions/extension_prefs.h"
12#include "chrome/browser/extensions/extension_service.h"
13#include "chrome/browser/extensions/extension_tab_util.h"
14#include "chrome/browser/extensions/tab_helper.h"
15#include "chrome/browser/profiles/profile.h"
16#include "chrome/browser/ui/browser.h"
17#include "chrome/browser/ui/tabs/tab_strip_model.h"
18#include "chrome/common/chrome_notification_types.h"
19#include "chrome/common/extensions/extension.h"
20#include "chrome/common/extensions/feature_switch.h"
21#include "chrome/common/pref_names.h"
22#include "content/public/browser/notification_details.h"
23#include "content/public/browser/notification_source.h"
24#include "content/public/browser/web_contents.h"
25
26using extensions::Extension;
27using extensions::ExtensionIdList;
28using extensions::ExtensionList;
29
30namespace {
31
32// Returns true if an |extension| is in an |extension_list|.
33bool IsInExtensionList(const Extension* extension,
34                       const extensions::ExtensionList& extension_list) {
35  for (size_t i = 0; i < extension_list.size(); i++) {
36    if (extension_list[i].get() == extension)
37      return true;
38  }
39  return false;
40}
41
42}  // namespace
43
44ExtensionToolbarModel::ExtensionToolbarModel(ExtensionService* service)
45    : service_(service),
46      prefs_(service->profile()->GetPrefs()),
47      extensions_initialized_(false) {
48  DCHECK(service_);
49
50  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
51                 content::Source<Profile>(service_->profile()));
52  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
53                 content::Source<Profile>(service_->profile()));
54  registrar_.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY,
55                 content::Source<Profile>(service_->profile()));
56  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED,
57                 content::Source<Profile>(service_->profile()));
58  registrar_.Add(
59      this, chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED,
60      content::Source<extensions::ExtensionPrefs>(service_->extension_prefs()));
61
62  visible_icon_count_ = prefs_->GetInteger(prefs::kExtensionToolbarSize);
63}
64
65ExtensionToolbarModel::~ExtensionToolbarModel() {
66}
67
68void ExtensionToolbarModel::AddObserver(Observer* observer) {
69  observers_.AddObserver(observer);
70}
71
72void ExtensionToolbarModel::RemoveObserver(Observer* observer) {
73  observers_.RemoveObserver(observer);
74}
75
76void ExtensionToolbarModel::MoveBrowserAction(const Extension* extension,
77                                              int index) {
78  ExtensionList::iterator pos = std::find(toolbar_items_.begin(),
79      toolbar_items_.end(), extension);
80  if (pos == toolbar_items_.end()) {
81    NOTREACHED();
82    return;
83  }
84  toolbar_items_.erase(pos);
85
86  ExtensionIdList::iterator pos_id;
87  pos_id = std::find(last_known_positions_.begin(),
88                     last_known_positions_.end(), extension->id());
89  if (pos_id != last_known_positions_.end())
90    last_known_positions_.erase(pos_id);
91
92  int i = 0;
93  bool inserted = false;
94  for (ExtensionList::iterator iter = toolbar_items_.begin();
95       iter != toolbar_items_.end();
96       ++iter, ++i) {
97    if (i == index) {
98      pos_id = std::find(last_known_positions_.begin(),
99                         last_known_positions_.end(), (*iter)->id());
100      last_known_positions_.insert(pos_id, extension->id());
101
102      toolbar_items_.insert(iter, make_scoped_refptr(extension));
103      inserted = true;
104      break;
105    }
106  }
107
108  if (!inserted) {
109    DCHECK_EQ(index, static_cast<int>(toolbar_items_.size()));
110    index = toolbar_items_.size();
111
112    toolbar_items_.push_back(make_scoped_refptr(extension));
113    last_known_positions_.push_back(extension->id());
114  }
115
116  FOR_EACH_OBSERVER(Observer, observers_, BrowserActionMoved(extension, index));
117
118  UpdatePrefs();
119}
120
121ExtensionToolbarModel::Action ExtensionToolbarModel::ExecuteBrowserAction(
122    const Extension* extension,
123    Browser* browser,
124    GURL* popup_url_out) {
125  content::WebContents* web_contents =
126      browser->tab_strip_model()->GetActiveWebContents();
127  if (!web_contents)
128    return ACTION_NONE;
129
130  int tab_id = ExtensionTabUtil::GetTabId(web_contents);
131  if (tab_id < 0)
132    return ACTION_NONE;
133
134  ExtensionAction* browser_action =
135      extensions::ExtensionActionManager::Get(service_->profile())->
136      GetBrowserAction(*extension);
137
138  // For browser actions, visibility == enabledness.
139  if (!browser_action->GetIsVisible(tab_id))
140    return ACTION_NONE;
141
142  extensions::TabHelper::FromWebContents(web_contents)->
143      active_tab_permission_granter()->GrantIfRequested(extension);
144
145  if (browser_action->HasPopup(tab_id)) {
146    if (popup_url_out)
147      *popup_url_out = browser_action->GetPopupUrl(tab_id);
148    return ACTION_SHOW_POPUP;
149  }
150
151  service_->browser_event_router()->BrowserActionExecuted(
152      *browser_action, browser);
153  return ACTION_NONE;
154}
155
156void ExtensionToolbarModel::SetVisibleIconCount(int count) {
157  visible_icon_count_ =
158      count == static_cast<int>(toolbar_items_.size()) ? -1 : count;
159  prefs_->SetInteger(prefs::kExtensionToolbarSize, visible_icon_count_);
160}
161
162void ExtensionToolbarModel::Observe(
163    int type,
164    const content::NotificationSource& source,
165    const content::NotificationDetails& details) {
166  if (type == chrome::NOTIFICATION_EXTENSIONS_READY) {
167    InitializeExtensionList();
168    return;
169  }
170
171  if (!service_->is_ready())
172    return;
173
174  const Extension* extension = NULL;
175  if (type == chrome::NOTIFICATION_EXTENSION_UNLOADED) {
176    extension = content::Details<extensions::UnloadedExtensionInfo>(
177        details)->extension;
178  } else {
179    extension = content::Details<const Extension>(details).ptr();
180  }
181  if (type == chrome::NOTIFICATION_EXTENSION_LOADED) {
182    // We don't want to add the same extension twice. It may have already been
183    // added by EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED below, if the user
184    // hides the browser action and then disables and enables the extension.
185    for (size_t i = 0; i < toolbar_items_.size(); i++) {
186      if (toolbar_items_[i].get() == extension)
187        return;  // Already exists.
188    }
189    if (service_->extension_prefs()->GetBrowserActionVisibility(extension))
190      AddExtension(extension);
191  } else if (type == chrome::NOTIFICATION_EXTENSION_UNLOADED) {
192    RemoveExtension(extension);
193  } else if (type == chrome::NOTIFICATION_EXTENSION_UNINSTALLED) {
194    UninstalledExtension(extension);
195  } else if (type ==
196      chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED) {
197    if (service_->extension_prefs()->GetBrowserActionVisibility(extension))
198      AddExtension(extension);
199    else
200      RemoveExtension(extension);
201  } else {
202    NOTREACHED() << "Received unexpected notification";
203  }
204}
205
206size_t ExtensionToolbarModel::FindNewPositionFromLastKnownGood(
207    const Extension* extension) {
208  // See if we have last known good position for this extension.
209  size_t new_index = 0;
210  // Loop through the ID list of known positions, to count the number of visible
211  // browser action icons preceding |extension|.
212  for (ExtensionIdList::const_iterator iter_id = last_known_positions_.begin();
213       iter_id < last_known_positions_.end(); ++iter_id) {
214    if ((*iter_id) == extension->id())
215      return new_index;  // We've found the right position.
216    // Found an id, need to see if it is visible.
217    for (ExtensionList::const_iterator iter_ext = toolbar_items_.begin();
218         iter_ext < toolbar_items_.end(); ++iter_ext) {
219      if ((*iter_ext)->id().compare(*iter_id) == 0) {
220        // This extension is visible, update the index value.
221        ++new_index;
222        break;
223      }
224    }
225  }
226
227  return -1;
228}
229
230void ExtensionToolbarModel::AddExtension(const Extension* extension) {
231  // We only care about extensions with browser actions.
232  if (!extensions::ExtensionActionManager::Get(service_->profile())->
233      GetBrowserAction(*extension)) {
234    return;
235  }
236
237  size_t new_index = -1;
238
239  // See if we have a last known good position for this extension.
240  ExtensionIdList::iterator last_pos = std::find(last_known_positions_.begin(),
241                                                 last_known_positions_.end(),
242                                                 extension->id());
243  if (last_pos != last_known_positions_.end()) {
244    new_index = FindNewPositionFromLastKnownGood(extension);
245    if (new_index != toolbar_items_.size()) {
246      toolbar_items_.insert(toolbar_items_.begin() + new_index,
247                            make_scoped_refptr(extension));
248    } else {
249      toolbar_items_.push_back(make_scoped_refptr(extension));
250    }
251  } else {
252    // This is a never before seen extension, that was added to the end. Make
253    // sure to reflect that.
254    toolbar_items_.push_back(make_scoped_refptr(extension));
255    last_known_positions_.push_back(extension->id());
256    new_index = toolbar_items_.size() - 1;
257    UpdatePrefs();
258  }
259
260  FOR_EACH_OBSERVER(Observer, observers_,
261                    BrowserActionAdded(extension, new_index));
262}
263
264void ExtensionToolbarModel::RemoveExtension(const Extension* extension) {
265  ExtensionList::iterator pos =
266      std::find(toolbar_items_.begin(), toolbar_items_.end(), extension);
267  if (pos == toolbar_items_.end())
268    return;
269
270  toolbar_items_.erase(pos);
271  FOR_EACH_OBSERVER(Observer, observers_,
272                    BrowserActionRemoved(extension));
273
274  UpdatePrefs();
275}
276
277void ExtensionToolbarModel::UninstalledExtension(const Extension* extension) {
278  // Remove the extension id from the ordered list, if it exists (the extension
279  // might not be represented in the list because it might not have an icon).
280  ExtensionIdList::iterator pos =
281      std::find(last_known_positions_.begin(),
282                last_known_positions_.end(), extension->id());
283
284  if (pos != last_known_positions_.end()) {
285    last_known_positions_.erase(pos);
286    UpdatePrefs();
287  }
288}
289
290// Combine the currently enabled extensions that have browser actions (which
291// we get from the ExtensionService) with the ordering we get from the
292// pref service. For robustness we use a somewhat inefficient process:
293// 1. Create a vector of extensions sorted by their pref values. This vector may
294// have holes.
295// 2. Create a vector of extensions that did not have a pref value.
296// 3. Remove holes from the sorted vector and append the unsorted vector.
297void ExtensionToolbarModel::InitializeExtensionList() {
298  DCHECK(service_->is_ready());
299
300  Populate();
301  UpdatePrefs();
302
303  extensions_initialized_ = true;
304  FOR_EACH_OBSERVER(Observer, observers_, ModelLoaded());
305}
306
307void ExtensionToolbarModel::Populate() {
308  const extensions::ExtensionIdList pref_order =
309      service_->extension_prefs()->GetToolbarOrder();
310  last_known_positions_ = pref_order;
311
312  // Items that have a pref for their position.
313  ExtensionList sorted;
314  sorted.resize(pref_order.size(), NULL);
315  // The items that don't have a pref for their position.
316  ExtensionList unsorted;
317
318  extensions::ExtensionActionManager* extension_action_manager =
319      extensions::ExtensionActionManager::Get(service_->profile());
320
321  // Create the lists.
322  for (ExtensionSet::const_iterator it = service_->extensions()->begin();
323       it != service_->extensions()->end(); ++it) {
324    const Extension* extension = *it;
325    if (!extension_action_manager->GetBrowserAction(*extension))
326      continue;
327    if (!service_->extension_prefs()->GetBrowserActionVisibility(extension))
328      continue;
329
330    extensions::ExtensionIdList::const_iterator pos =
331        std::find(pref_order.begin(), pref_order.end(), extension->id());
332    if (pos != pref_order.end())
333      sorted[pos - pref_order.begin()] = extension;
334    else
335      unsorted.push_back(make_scoped_refptr(extension));
336  }
337
338  // Merge the lists.
339  toolbar_items_.clear();
340  toolbar_items_.reserve(sorted.size() + unsorted.size());
341  for (ExtensionList::const_iterator iter = sorted.begin();
342       iter != sorted.end(); ++iter) {
343    // It's possible for the extension order to contain items that aren't
344    // actually loaded on this machine.  For example, when extension sync is on,
345    // we sync the extension order as-is but double-check with the user before
346    // syncing NPAPI-containing extensions, so if one of those is not actually
347    // synced, we'll get a NULL in the list.  This sort of case can also happen
348    // if some error prevents an extension from loading.
349    if (*iter != NULL)
350      toolbar_items_.push_back(*iter);
351  }
352  toolbar_items_.insert(toolbar_items_.end(), unsorted.begin(),
353                        unsorted.end());
354
355  // Inform observers.
356  for (size_t i = 0; i < toolbar_items_.size(); i++) {
357    FOR_EACH_OBSERVER(Observer, observers_,
358                      BrowserActionAdded(toolbar_items_[i], i));
359  }
360}
361
362void ExtensionToolbarModel::FillExtensionList(
363    const extensions::ExtensionIdList& order) {
364  toolbar_items_.clear();
365  toolbar_items_.reserve(order.size());
366  for (size_t i = 0; i < order.size(); ++i) {
367    const extensions::Extension* extension =
368        service_->GetExtensionById(order[i], false);
369    if (extension)
370      AddExtension(extension);
371  }
372}
373
374void ExtensionToolbarModel::UpdatePrefs() {
375  if (!service_->extension_prefs())
376    return;
377
378  service_->extension_prefs()->SetToolbarOrder(last_known_positions_);
379}
380
381int ExtensionToolbarModel::IncognitoIndexToOriginal(int incognito_index) {
382  int original_index = 0, i = 0;
383  for (ExtensionList::iterator iter = toolbar_items_.begin();
384       iter != toolbar_items_.end();
385       ++iter, ++original_index) {
386    if (service_->IsIncognitoEnabled((*iter)->id())) {
387      if (incognito_index == i)
388        break;
389      ++i;
390    }
391  }
392  return original_index;
393}
394
395int ExtensionToolbarModel::OriginalIndexToIncognito(int original_index) {
396  int incognito_index = 0, i = 0;
397  for (ExtensionList::iterator iter = toolbar_items_.begin();
398       iter != toolbar_items_.end();
399       ++iter, ++i) {
400    if (original_index == i)
401      break;
402    if (service_->IsIncognitoEnabled((*iter)->id()))
403      ++incognito_index;
404  }
405  return incognito_index;
406}
407