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