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