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"
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
) {
43 ExtensionToolbarModel::ExtensionToolbarModel(
45 extensions::ExtensionPrefs
* extension_prefs
)
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_
));
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() {
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
,
91 ExtensionList::iterator pos
= std::find(toolbar_items_
.begin(),
92 toolbar_items_
.end(), extension
);
93 if (pos
== toolbar_items_
.end()) {
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
);
106 bool inserted
= false;
107 for (ExtensionList::iterator iter
= toolbar_items_
.begin();
108 iter
!= toolbar_items_
.end();
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
));
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
));
134 ExtensionToolbarModel::Action
ExtensionToolbarModel::ExecuteBrowserAction(
135 const Extension
* extension
,
139 content::WebContents
* web_contents
= NULL
;
141 if (!extensions::ExtensionTabUtil::GetDefaultTab(
142 browser
, &web_contents
, &tab_id
)) {
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
))
155 extensions::TabHelper::FromWebContents(web_contents
)->
156 active_tab_permission_granter()->GrantIfRequested(extension
);
159 if (browser_action
->HasPopup(tab_id
)) {
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
);
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
)
192 if (ExtensionActionAPI::GetBrowserActionVisibility(extension_prefs_
,
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(
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())
216 case chrome::NOTIFICATION_EXTENSIONS_READY
:
217 InitializeExtensionList(extension_service
);
219 case chrome::NOTIFICATION_EXTENSION_UNINSTALLED
: {
220 const Extension
* extension
=
221 content::Details
<const Extension
>(details
).ptr();
222 UninstalledExtension(extension
);
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_
,
230 AddExtension(extension
);
232 RemoveExtension(extension
);
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.
265 void ExtensionToolbarModel::AddExtension(const Extension
* extension
) {
266 // We only care about extensions with browser actions.
267 if (!ExtensionActionManager::Get(profile_
)->GetBrowserAction(*extension
))
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(),
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
));
282 toolbar_items_
.push_back(make_scoped_refptr(extension
));
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;
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())
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(),
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())
324 FOR_EACH_OBSERVER(Observer
, observers_
, BrowserActionRemoved(extension
));
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
);
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
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_
);
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
))
380 if (!ExtensionActionAPI::GetBrowserActionVisibility(
381 extension_prefs_
, extension
->id())) {
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
;
391 unsorted
.push_back(make_scoped_refptr(extension
));
394 // Erase current icons.
395 for (size_t i
= 0; i
< toolbar_items_
.size(); i
++) {
397 Observer
, observers_
, BrowserActionRemoved(toolbar_items_
[i
].get()));
399 toolbar_items_
.clear();
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(),
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
426 UMA_HISTOGRAM_COUNTS_100("ExtensionToolbarModel.BrowserActionsVisible",
427 visible_icon_count_
== -1 ?
428 base::HistogramBase::kSampleType_MAX
:
429 visible_icon_count_
);
433 for (size_t i
= 0; i
< toolbar_items_
.size(); i
++) {
435 Observer
, observers_
, BrowserActionAdded(toolbar_items_
[i
].get(), i
));
439 void ExtensionToolbarModel::UpdatePrefs() {
440 if (!extension_prefs_
)
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
)
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();
468 if (original_index
== i
)
470 if (util::IsIncognitoEnabled((*iter
)->id(), profile_
))
473 return incognito_index
;
476 void ExtensionToolbarModel::OnExtensionToolbarPrefChange() {
477 // If extensions are not ready, defer to later Populate() call.
478 if (!extensions_initialized_
)
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
);
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(
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
))
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
525 if (visible_icon_count_
< static_cast<int>(extension_ids
.size())) {
526 SetVisibleIconCount(extension_ids
.size());
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);
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();
556 for (ExtensionList::const_iterator extension
= toolbar_items_
.begin();
557 extension
!= toolbar_items_
.end();
559 if (*id
== (*extension
)->id())
560 highlighted_items_
.push_back(*extension
);
564 // If we have any items in |highlighted_items_|, then we entered highlighting
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));
579 // Otherwise, we didn't enter highlighting mode (and, in fact, exited it if
580 // we were otherwise in it).
581 if (is_highlighting_
)
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