Remove some NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED from c/b/extensions Part2
[chromium-blink-merge.git] / chrome / browser / extensions / extension_toolbar_model.cc
blob90c065a53afcacc1fa326f4b6ebc0db386869f09
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.
5 #include "chrome/browser/extensions/extension_toolbar_model.h"
7 #include <string>
9 #include "base/metrics/histogram.h"
10 #include "base/metrics/histogram_base.h"
11 #include "base/prefs/pref_service.h"
12 #include "chrome/browser/chrome_notification_types.h"
13 #include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
14 #include "chrome/browser/extensions/extension_action.h"
15 #include "chrome/browser/extensions/extension_action_manager.h"
16 #include "chrome/browser/extensions/extension_service.h"
17 #include "chrome/browser/extensions/extension_tab_util.h"
18 #include "chrome/browser/extensions/extension_toolbar_model_factory.h"
19 #include "chrome/browser/extensions/extension_util.h"
20 #include "chrome/browser/extensions/tab_helper.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/browser/ui/browser.h"
23 #include "chrome/browser/ui/tabs/tab_strip_model.h"
24 #include "chrome/common/pref_names.h"
25 #include "content/public/browser/notification_details.h"
26 #include "content/public/browser/notification_source.h"
27 #include "content/public/browser/web_contents.h"
28 #include "extensions/browser/extension_prefs.h"
29 #include "extensions/browser/extension_registry.h"
30 #include "extensions/browser/extension_system.h"
31 #include "extensions/browser/pref_names.h"
32 #include "extensions/common/extension.h"
33 #include "extensions/common/extension_set.h"
34 #include "extensions/common/feature_switch.h"
36 namespace extensions {
38 bool ExtensionToolbarModel::Observer::BrowserActionShowPopup(
39 const extensions::Extension* extension) {
40 return false;
43 ExtensionToolbarModel::ExtensionToolbarModel(
44 Profile* profile,
45 extensions::ExtensionPrefs* extension_prefs)
46 : profile_(profile),
47 extension_prefs_(extension_prefs),
48 prefs_(profile_->GetPrefs()),
49 extensions_initialized_(false),
50 is_highlighting_(false),
51 extension_registry_observer_(this),
52 weak_ptr_factory_(this) {
53 extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
55 registrar_.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY,
56 content::Source<Profile>(profile_));
57 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED,
58 content::Source<Profile>(profile_));
59 registrar_.Add(
60 this, chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED,
61 content::Source<extensions::ExtensionPrefs>(extension_prefs_));
63 visible_icon_count_ = prefs_->GetInteger(
64 extensions::pref_names::kToolbarSize);
65 pref_change_registrar_.Init(prefs_);
66 pref_change_callback_ =
67 base::Bind(&ExtensionToolbarModel::OnExtensionToolbarPrefChange,
68 base::Unretained(this));
69 pref_change_registrar_.Add(extensions::pref_names::kToolbar,
70 pref_change_callback_);
73 ExtensionToolbarModel::~ExtensionToolbarModel() {
76 // static
77 ExtensionToolbarModel* ExtensionToolbarModel::Get(Profile* profile) {
78 return ExtensionToolbarModelFactory::GetForProfile(profile);
81 void ExtensionToolbarModel::AddObserver(Observer* observer) {
82 observers_.AddObserver(observer);
85 void ExtensionToolbarModel::RemoveObserver(Observer* observer) {
86 observers_.RemoveObserver(observer);
89 void ExtensionToolbarModel::MoveBrowserAction(const Extension* extension,
90 int index) {
91 ExtensionList::iterator pos = std::find(toolbar_items_.begin(),
92 toolbar_items_.end(), extension);
93 if (pos == toolbar_items_.end()) {
94 NOTREACHED();
95 return;
97 toolbar_items_.erase(pos);
99 ExtensionIdList::iterator pos_id;
100 pos_id = std::find(last_known_positions_.begin(),
101 last_known_positions_.end(), extension->id());
102 if (pos_id != last_known_positions_.end())
103 last_known_positions_.erase(pos_id);
105 int i = 0;
106 bool inserted = false;
107 for (ExtensionList::iterator iter = toolbar_items_.begin();
108 iter != toolbar_items_.end();
109 ++iter, ++i) {
110 if (i == index) {
111 pos_id = std::find(last_known_positions_.begin(),
112 last_known_positions_.end(), (*iter)->id());
113 last_known_positions_.insert(pos_id, extension->id());
115 toolbar_items_.insert(iter, make_scoped_refptr(extension));
116 inserted = true;
117 break;
121 if (!inserted) {
122 DCHECK_EQ(index, static_cast<int>(toolbar_items_.size()));
123 index = toolbar_items_.size();
125 toolbar_items_.push_back(make_scoped_refptr(extension));
126 last_known_positions_.push_back(extension->id());
129 FOR_EACH_OBSERVER(Observer, observers_, BrowserActionMoved(extension, index));
131 UpdatePrefs();
134 ExtensionToolbarModel::Action ExtensionToolbarModel::ExecuteBrowserAction(
135 const Extension* extension,
136 Browser* browser,
137 GURL* popup_url_out,
138 bool should_grant) {
139 content::WebContents* web_contents = NULL;
140 int tab_id = 0;
141 if (!extensions::ExtensionTabUtil::GetDefaultTab(
142 browser, &web_contents, &tab_id)) {
143 return ACTION_NONE;
146 ExtensionAction* browser_action =
147 extensions::ExtensionActionManager::Get(profile_)->
148 GetBrowserAction(*extension);
150 // For browser actions, visibility == enabledness.
151 if (!browser_action->GetIsVisible(tab_id))
152 return ACTION_NONE;
154 if (should_grant) {
155 extensions::TabHelper::FromWebContents(web_contents)->
156 active_tab_permission_granter()->GrantIfRequested(extension);
159 if (browser_action->HasPopup(tab_id)) {
160 if (popup_url_out)
161 *popup_url_out = browser_action->GetPopupUrl(tab_id);
162 return ACTION_SHOW_POPUP;
165 extensions::ExtensionActionAPI::BrowserActionExecuted(
166 browser->profile(), *browser_action, web_contents);
167 return ACTION_NONE;
170 void ExtensionToolbarModel::SetVisibleIconCount(int count) {
171 visible_icon_count_ =
172 count == static_cast<int>(toolbar_items_.size()) ? -1 : count;
173 // Only set the prefs if we're not in highlight mode. Highlight mode is
174 // designed to be a transitory state, and should not persist across browser
175 // restarts (though it may be re-entered).
176 if (!is_highlighting_) {
177 prefs_->SetInteger(extensions::pref_names::kToolbarSize,
178 visible_icon_count_);
182 void ExtensionToolbarModel::OnExtensionLoaded(
183 content::BrowserContext* browser_context,
184 const Extension* extension) {
185 // We don't want to add the same extension twice. It may have already been
186 // added by EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED below, if the user
187 // hides the browser action and then disables and enables the extension.
188 for (size_t i = 0; i < toolbar_items_.size(); i++) {
189 if (toolbar_items_[i].get() == extension)
190 return;
192 if (ExtensionActionAPI::GetBrowserActionVisibility(extension_prefs_,
193 extension->id())) {
194 AddExtension(extension);
198 void ExtensionToolbarModel::OnExtensionUnloaded(
199 content::BrowserContext* browser_context,
200 const Extension* extension,
201 UnloadedExtensionInfo::Reason reason) {
202 RemoveExtension(extension);
205 void ExtensionToolbarModel::Observe(
206 int type,
207 const content::NotificationSource& source,
208 const content::NotificationDetails& details) {
209 ExtensionService* extension_service =
210 ExtensionSystem::Get(profile_)->extension_service();
211 DCHECK(extension_service);
212 if (!extension_service->is_ready())
213 return;
215 switch (type) {
216 case chrome::NOTIFICATION_EXTENSIONS_READY:
217 InitializeExtensionList(extension_service);
218 break;
219 case chrome::NOTIFICATION_EXTENSION_UNINSTALLED: {
220 const Extension* extension =
221 content::Details<const Extension>(details).ptr();
222 UninstalledExtension(extension);
223 break;
225 case chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED: {
226 const Extension* extension = extension_service->GetExtensionById(
227 *content::Details<const std::string>(details).ptr(), true);
228 if (ExtensionActionAPI::GetBrowserActionVisibility(extension_prefs_,
229 extension->id())) {
230 AddExtension(extension);
231 } else {
232 RemoveExtension(extension);
234 break;
236 default:
237 NOTREACHED() << "Received unexpected notification";
241 size_t ExtensionToolbarModel::FindNewPositionFromLastKnownGood(
242 const Extension* extension) {
243 // See if we have last known good position for this extension.
244 size_t new_index = 0;
245 // Loop through the ID list of known positions, to count the number of visible
246 // browser action icons preceding |extension|.
247 for (ExtensionIdList::const_iterator iter_id = last_known_positions_.begin();
248 iter_id < last_known_positions_.end(); ++iter_id) {
249 if ((*iter_id) == extension->id())
250 return new_index; // We've found the right position.
251 // Found an id, need to see if it is visible.
252 for (ExtensionList::const_iterator iter_ext = toolbar_items_.begin();
253 iter_ext < toolbar_items_.end(); ++iter_ext) {
254 if ((*iter_ext)->id().compare(*iter_id) == 0) {
255 // This extension is visible, update the index value.
256 ++new_index;
257 break;
262 return -1;
265 void ExtensionToolbarModel::AddExtension(const Extension* extension) {
266 // We only care about extensions with browser actions.
267 if (!ExtensionActionManager::Get(profile_)->GetBrowserAction(*extension))
268 return;
270 size_t new_index = -1;
272 // See if we have a last known good position for this extension.
273 ExtensionIdList::iterator last_pos = std::find(last_known_positions_.begin(),
274 last_known_positions_.end(),
275 extension->id());
276 if (last_pos != last_known_positions_.end()) {
277 new_index = FindNewPositionFromLastKnownGood(extension);
278 if (new_index != toolbar_items_.size()) {
279 toolbar_items_.insert(toolbar_items_.begin() + new_index,
280 make_scoped_refptr(extension));
281 } else {
282 toolbar_items_.push_back(make_scoped_refptr(extension));
284 } else {
285 // This is a never before seen extension, that was added to the end. Make
286 // sure to reflect that.
287 toolbar_items_.push_back(make_scoped_refptr(extension));
288 last_known_positions_.push_back(extension->id());
289 new_index = toolbar_items_.size() - 1;
290 UpdatePrefs();
293 // If we're currently highlighting, then even though we add a browser action
294 // to the full list (|toolbar_items_|, there won't be another *visible*
295 // browser action, which was what the observers care about.
296 if (!is_highlighting_) {
297 FOR_EACH_OBSERVER(Observer, observers_,
298 BrowserActionAdded(extension, new_index));
302 void ExtensionToolbarModel::RemoveExtension(const Extension* extension) {
303 ExtensionList::iterator pos =
304 std::find(toolbar_items_.begin(), toolbar_items_.end(), extension);
305 if (pos == toolbar_items_.end())
306 return;
308 toolbar_items_.erase(pos);
310 // If we're in highlight mode, we also have to remove the extension from
311 // the highlighted list.
312 if (is_highlighting_) {
313 pos = std::find(highlighted_items_.begin(),
314 highlighted_items_.end(),
315 extension);
316 if (pos != highlighted_items_.end()) {
317 highlighted_items_.erase(pos);
318 FOR_EACH_OBSERVER(Observer, observers_, BrowserActionRemoved(extension));
319 // If the highlighted list is now empty, we stop highlighting.
320 if (highlighted_items_.empty())
321 StopHighlighting();
323 } else {
324 FOR_EACH_OBSERVER(Observer, observers_, BrowserActionRemoved(extension));
327 UpdatePrefs();
330 void ExtensionToolbarModel::UninstalledExtension(const Extension* extension) {
331 // Remove the extension id from the ordered list, if it exists (the extension
332 // might not be represented in the list because it might not have an icon).
333 ExtensionIdList::iterator pos =
334 std::find(last_known_positions_.begin(),
335 last_known_positions_.end(), extension->id());
337 if (pos != last_known_positions_.end()) {
338 last_known_positions_.erase(pos);
339 UpdatePrefs();
343 // Combine the currently enabled extensions that have browser actions (which
344 // we get from the ExtensionService) with the ordering we get from the
345 // pref service. For robustness we use a somewhat inefficient process:
346 // 1. Create a vector of extensions sorted by their pref values. This vector may
347 // have holes.
348 // 2. Create a vector of extensions that did not have a pref value.
349 // 3. Remove holes from the sorted vector and append the unsorted vector.
350 void ExtensionToolbarModel::InitializeExtensionList(ExtensionService* service) {
351 DCHECK(service->is_ready());
353 last_known_positions_ = extension_prefs_->GetToolbarOrder();
354 Populate(last_known_positions_, service);
356 extensions_initialized_ = true;
357 FOR_EACH_OBSERVER(Observer, observers_, VisibleCountChanged());
360 void ExtensionToolbarModel::Populate(
361 const ExtensionIdList& positions,
362 ExtensionService* service) {
363 // Items that have explicit positions.
364 ExtensionList sorted;
365 sorted.resize(positions.size(), NULL);
366 // The items that don't have explicit positions.
367 ExtensionList unsorted;
369 ExtensionActionManager* extension_action_manager =
370 ExtensionActionManager::Get(profile_);
372 // Create the lists.
373 int hidden = 0;
374 for (extensions::ExtensionSet::const_iterator it =
375 service->extensions()->begin();
376 it != service->extensions()->end(); ++it) {
377 const Extension* extension = it->get();
378 if (!extension_action_manager->GetBrowserAction(*extension))
379 continue;
380 if (!ExtensionActionAPI::GetBrowserActionVisibility(
381 extension_prefs_, extension->id())) {
382 ++hidden;
383 continue;
386 ExtensionIdList::const_iterator pos =
387 std::find(positions.begin(), positions.end(), extension->id());
388 if (pos != positions.end())
389 sorted[pos - positions.begin()] = extension;
390 else
391 unsorted.push_back(make_scoped_refptr(extension));
394 // Erase current icons.
395 for (size_t i = 0; i < toolbar_items_.size(); i++) {
396 FOR_EACH_OBSERVER(
397 Observer, observers_, BrowserActionRemoved(toolbar_items_[i].get()));
399 toolbar_items_.clear();
401 // Merge the lists.
402 toolbar_items_.reserve(sorted.size() + unsorted.size());
403 for (ExtensionList::const_iterator iter = sorted.begin();
404 iter != sorted.end(); ++iter) {
405 // It's possible for the extension order to contain items that aren't
406 // actually loaded on this machine. For example, when extension sync is on,
407 // we sync the extension order as-is but double-check with the user before
408 // syncing NPAPI-containing extensions, so if one of those is not actually
409 // synced, we'll get a NULL in the list. This sort of case can also happen
410 // if some error prevents an extension from loading.
411 if (iter->get() != NULL)
412 toolbar_items_.push_back(*iter);
414 toolbar_items_.insert(toolbar_items_.end(), unsorted.begin(),
415 unsorted.end());
417 UMA_HISTOGRAM_COUNTS_100(
418 "ExtensionToolbarModel.BrowserActionsPermanentlyHidden", hidden);
419 UMA_HISTOGRAM_COUNTS_100("ExtensionToolbarModel.BrowserActionsCount",
420 toolbar_items_.size());
422 if (!toolbar_items_.empty()) {
423 // Visible count can be -1, meaning: 'show all'. Since UMA converts negative
424 // values to 0, this would be counted as 'show none' unless we convert it to
425 // max.
426 UMA_HISTOGRAM_COUNTS_100("ExtensionToolbarModel.BrowserActionsVisible",
427 visible_icon_count_ == -1 ?
428 base::HistogramBase::kSampleType_MAX :
429 visible_icon_count_);
432 // Inform observers.
433 for (size_t i = 0; i < toolbar_items_.size(); i++) {
434 FOR_EACH_OBSERVER(
435 Observer, observers_, BrowserActionAdded(toolbar_items_[i].get(), i));
439 void ExtensionToolbarModel::UpdatePrefs() {
440 if (!extension_prefs_)
441 return;
443 // Don't observe change caused by self.
444 pref_change_registrar_.Remove(pref_names::kToolbar);
445 extension_prefs_->SetToolbarOrder(last_known_positions_);
446 pref_change_registrar_.Add(pref_names::kToolbar, pref_change_callback_);
449 int ExtensionToolbarModel::IncognitoIndexToOriginal(int incognito_index) {
450 int original_index = 0, i = 0;
451 for (ExtensionList::iterator iter = toolbar_items_.begin();
452 iter != toolbar_items_.end();
453 ++iter, ++original_index) {
454 if (util::IsIncognitoEnabled((*iter)->id(), profile_)) {
455 if (incognito_index == i)
456 break;
457 ++i;
460 return original_index;
463 int ExtensionToolbarModel::OriginalIndexToIncognito(int original_index) {
464 int incognito_index = 0, i = 0;
465 for (ExtensionList::iterator iter = toolbar_items_.begin();
466 iter != toolbar_items_.end();
467 ++iter, ++i) {
468 if (original_index == i)
469 break;
470 if (util::IsIncognitoEnabled((*iter)->id(), profile_))
471 ++incognito_index;
473 return incognito_index;
476 void ExtensionToolbarModel::OnExtensionToolbarPrefChange() {
477 // If extensions are not ready, defer to later Populate() call.
478 if (!extensions_initialized_)
479 return;
481 // Recalculate |last_known_positions_| to be |pref_positions| followed by
482 // ones that are only in |last_known_positions_|.
483 ExtensionIdList pref_positions = extension_prefs_->GetToolbarOrder();
484 size_t pref_position_size = pref_positions.size();
485 for (size_t i = 0; i < last_known_positions_.size(); ++i) {
486 if (std::find(pref_positions.begin(), pref_positions.end(),
487 last_known_positions_[i]) == pref_positions.end()) {
488 pref_positions.push_back(last_known_positions_[i]);
491 last_known_positions_.swap(pref_positions);
493 // Re-populate.
494 Populate(last_known_positions_,
495 ExtensionSystem::Get(profile_)->extension_service());
497 if (last_known_positions_.size() > pref_position_size) {
498 // Need to update pref because we have extra icons. But can't call
499 // UpdatePrefs() directly within observation closure.
500 base::MessageLoop::current()->PostTask(
501 FROM_HERE,
502 base::Bind(&ExtensionToolbarModel::UpdatePrefs,
503 weak_ptr_factory_.GetWeakPtr()));
507 bool ExtensionToolbarModel::ShowBrowserActionPopup(const Extension* extension) {
508 ObserverListBase<Observer>::Iterator it(observers_);
509 Observer* obs = NULL;
510 while ((obs = it.GetNext()) != NULL) {
511 // Stop after first popup since it should only show in the active window.
512 if (obs->BrowserActionShowPopup(extension))
513 return true;
515 return false;
518 void ExtensionToolbarModel::EnsureVisibility(
519 const ExtensionIdList& extension_ids) {
520 if (visible_icon_count_ == -1)
521 return; // Already showing all.
523 // Otherwise, make sure we have enough room to show all the extensions
524 // requested.
525 if (visible_icon_count_ < static_cast<int>(extension_ids.size())) {
526 SetVisibleIconCount(extension_ids.size());
528 // Inform observers.
529 FOR_EACH_OBSERVER(Observer, observers_, VisibleCountChanged());
532 if (visible_icon_count_ == -1)
533 return; // May have been set to max by SetVisibleIconCount.
535 // Guillotine's Delight: Move an orange noble to the front of the line.
536 for (ExtensionIdList::const_iterator it = extension_ids.begin();
537 it != extension_ids.end(); ++it) {
538 for (ExtensionList::const_iterator extension = toolbar_items_.begin();
539 extension != toolbar_items_.end(); ++extension) {
540 if ((*extension)->id() == (*it)) {
541 if (extension - toolbar_items_.begin() >= visible_icon_count_)
542 MoveBrowserAction(*extension, 0);
543 break;
549 bool ExtensionToolbarModel::HighlightExtensions(
550 const ExtensionIdList& extension_ids) {
551 highlighted_items_.clear();
553 for (ExtensionIdList::const_iterator id = extension_ids.begin();
554 id != extension_ids.end();
555 ++id) {
556 for (ExtensionList::const_iterator extension = toolbar_items_.begin();
557 extension != toolbar_items_.end();
558 ++extension) {
559 if (*id == (*extension)->id())
560 highlighted_items_.push_back(*extension);
564 // If we have any items in |highlighted_items_|, then we entered highlighting
565 // mode.
566 if (highlighted_items_.size()) {
567 old_visible_icon_count_ = visible_icon_count_;
568 is_highlighting_ = true;
569 if (visible_icon_count_ != -1 &&
570 visible_icon_count_ < static_cast<int>(extension_ids.size())) {
571 SetVisibleIconCount(extension_ids.size());
572 FOR_EACH_OBSERVER(Observer, observers_, VisibleCountChanged());
575 FOR_EACH_OBSERVER(Observer, observers_, HighlightModeChanged(true));
576 return true;
579 // Otherwise, we didn't enter highlighting mode (and, in fact, exited it if
580 // we were otherwise in it).
581 if (is_highlighting_)
582 StopHighlighting();
583 return false;
586 void ExtensionToolbarModel::StopHighlighting() {
587 if (is_highlighting_) {
588 highlighted_items_.clear();
589 is_highlighting_ = false;
590 if (old_visible_icon_count_ != visible_icon_count_) {
591 SetVisibleIconCount(old_visible_icon_count_);
592 FOR_EACH_OBSERVER(Observer, observers_, VisibleCountChanged());
594 FOR_EACH_OBSERVER(Observer, observers_, HighlightModeChanged(false));
598 } // namespace extensions