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