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/ui/toolbar/toolbar_actions_model.h"
10 #include "base/location.h"
11 #include "base/metrics/histogram.h"
12 #include "base/metrics/histogram_base.h"
13 #include "base/prefs/pref_service.h"
14 #include "base/single_thread_task_runner.h"
15 #include "base/thread_task_runner_handle.h"
16 #include "chrome/browser/chrome_notification_types.h"
17 #include "chrome/browser/extensions/extension_action_manager.h"
18 #include "chrome/browser/extensions/extension_tab_util.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/extensions/extension_action_view_controller.h"
24 #include "chrome/browser/ui/extensions/extension_toolbar_icon_surfacing_bubble_delegate.h"
25 #include "chrome/browser/ui/tabs/tab_strip_model.h"
26 #include "chrome/browser/ui/toolbar/component_toolbar_actions_factory.h"
27 #include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
28 #include "chrome/browser/ui/toolbar/toolbar_actions_bar.h"
29 #include "chrome/browser/ui/toolbar/toolbar_actions_model_factory.h"
30 #include "content/public/browser/notification_details.h"
31 #include "content/public/browser/notification_source.h"
32 #include "content/public/browser/web_contents.h"
33 #include "extensions/browser/extension_registry.h"
34 #include "extensions/browser/extension_system.h"
35 #include "extensions/browser/pref_names.h"
36 #include "extensions/common/extension_set.h"
37 #include "extensions/common/feature_switch.h"
38 #include "extensions/common/manifest_constants.h"
39 #include "extensions/common/one_shot_event.h"
41 ToolbarActionsModel::ToolbarActionsModel(
43 extensions::ExtensionPrefs
* extension_prefs
)
45 extension_prefs_(extension_prefs
),
46 prefs_(profile_
->GetPrefs()),
47 extension_action_api_(extensions::ExtensionActionAPI::Get(profile_
)),
48 extension_registry_(extensions::ExtensionRegistry::Get(profile_
)),
49 actions_initialized_(false),
50 use_redesign_(extensions::FeatureSwitch::extension_action_redesign()
52 highlight_type_(HIGHLIGHT_NONE
),
53 extension_action_observer_(this),
54 extension_registry_observer_(this),
55 weak_ptr_factory_(this) {
56 extensions::ExtensionSystem::Get(profile_
)->ready().Post(
57 FROM_HERE
, base::Bind(&ToolbarActionsModel::OnReady
,
58 weak_ptr_factory_
.GetWeakPtr()));
60 prefs_
->GetInteger(extensions::pref_names::kToolbarSize
);
62 // We only care about watching the prefs if not in incognito mode.
63 if (!profile_
->IsOffTheRecord()) {
64 pref_change_registrar_
.Init(prefs_
);
65 pref_change_callback_
=
66 base::Bind(&ToolbarActionsModel::OnActionToolbarPrefChange
,
67 base::Unretained(this));
68 pref_change_registrar_
.Add(extensions::pref_names::kToolbar
,
69 pref_change_callback_
);
73 ToolbarActionsModel::~ToolbarActionsModel() {}
76 ToolbarActionsModel
* ToolbarActionsModel::Get(Profile
* profile
) {
77 return ToolbarActionsModelFactory::GetForProfile(profile
);
80 void ToolbarActionsModel::AddObserver(Observer
* observer
) {
81 observers_
.AddObserver(observer
);
84 void ToolbarActionsModel::RemoveObserver(Observer
* observer
) {
85 observers_
.RemoveObserver(observer
);
88 void ToolbarActionsModel::MoveActionIcon(const std::string
& id
, size_t index
) {
89 std::vector
<ToolbarItem
>::iterator pos
= toolbar_items_
.begin();
90 while (pos
!= toolbar_items_
.end() && (*pos
).id
!= id
)
92 if (pos
== toolbar_items_
.end()) {
97 ToolbarItem action
= *pos
;
98 toolbar_items_
.erase(pos
);
100 std::vector
<std::string
>::iterator pos_id
=
101 std::find(last_known_positions_
.begin(), last_known_positions_
.end(), id
);
102 if (pos_id
!= last_known_positions_
.end())
103 last_known_positions_
.erase(pos_id
);
105 if (index
< toolbar_items_
.size()) {
106 // If the index is not at the end, find the item currently at |index|, and
107 // insert |action| before it in |toolbar_items_| and |action|'s id in
108 // |last_known_positions_|.
109 std::vector
<ToolbarItem
>::iterator iter
= toolbar_items_
.begin() + index
;
110 last_known_positions_
.insert(
111 std::find(last_known_positions_
.begin(), last_known_positions_
.end(),
114 toolbar_items_
.insert(iter
, action
);
116 // Otherwise, put |action| and |id| at the end.
117 DCHECK_EQ(toolbar_items_
.size(), index
);
118 toolbar_items_
.push_back(action
);
119 last_known_positions_
.push_back(id
);
122 FOR_EACH_OBSERVER(Observer
, observers_
, OnToolbarActionMoved(id
, index
));
123 MaybeUpdateVisibilityPref(action
, index
);
127 void ToolbarActionsModel::SetVisibleIconCount(size_t count
) {
128 visible_icon_count_
= (count
>= toolbar_items_
.size()) ? -1 : count
;
130 // Only set the prefs if we're not in highlight mode and the profile is not
131 // incognito. Highlight mode is designed to be a transitory state, and should
132 // not persist across browser restarts (though it may be re-entered), and we
133 // don't store anything in incognito.
134 if (!is_highlighting() && !profile_
->IsOffTheRecord()) {
135 // Additionally, if we are using the new toolbar, any icons which are in the
136 // overflow menu are considered "hidden". But it so happens that the times
137 // we are likely to call SetVisibleIconCount() are also those when we are
138 // in flux. So wait for things to cool down before setting the prefs.
139 base::ThreadTaskRunnerHandle::Get()->PostTask(
140 FROM_HERE
, base::Bind(&ToolbarActionsModel::MaybeUpdateVisibilityPrefs
,
141 weak_ptr_factory_
.GetWeakPtr()));
142 prefs_
->SetInteger(extensions::pref_names::kToolbarSize
,
143 visible_icon_count_
);
146 FOR_EACH_OBSERVER(Observer
, observers_
, OnToolbarVisibleCountChanged());
149 void ToolbarActionsModel::OnExtensionActionUpdated(
150 ExtensionAction
* extension_action
,
151 content::WebContents
* web_contents
,
152 content::BrowserContext
* browser_context
) {
153 // Notify observers if the extension exists and is in the model.
154 if (std::find(toolbar_items_
.begin(), toolbar_items_
.end(),
155 ToolbarItem(extension_action
->extension_id(),
156 EXTENSION_ACTION
)) != toolbar_items_
.end()) {
157 FOR_EACH_OBSERVER(Observer
, observers_
,
158 OnToolbarActionUpdated(extension_action
->extension_id()));
162 ScopedVector
<ToolbarActionViewController
> ToolbarActionsModel::CreateActions(
164 ToolbarActionsBar
* bar
) {
167 ScopedVector
<ToolbarActionViewController
> action_list
;
169 // Get the component action list.
170 ScopedVector
<ToolbarActionViewController
> component_actions
=
171 ComponentToolbarActionsFactory::GetInstance()->GetComponentToolbarActions(
174 extensions::ExtensionActionManager
* action_manager
=
175 extensions::ExtensionActionManager::Get(profile_
);
177 // toolbar_items() might not equate to toolbar_items_ in the case where a
178 // subset are highlighted.
179 for (const ToolbarItem
& item
: toolbar_items()) {
181 case EXTENSION_ACTION
: {
182 // Get the extension.
183 const extensions::Extension
* extension
= GetExtensionById(item
.id
);
186 // Create and add an ExtensionActionViewController for the extension.
187 action_list
.push_back(new ExtensionActionViewController(
188 extension
, browser
, action_manager
->GetExtensionAction(*extension
),
192 case COMPONENT_ACTION
: {
193 DCHECK(use_redesign_
);
194 // Find the index of the component action with the id.
195 auto iter
= std::find_if(
196 component_actions
.begin(), component_actions
.end(),
197 [&item
](const ToolbarActionViewController
* action
) {
198 return action
->GetId() == item
.id
;
200 // We should always find a corresponding action.
201 DCHECK(iter
!= component_actions
.end());
202 action_list
.push_back(*iter
);
204 // We have moved ownership of the action from |component_actions| to
206 component_actions
.weak_erase(iter
);
210 NOTREACHED(); // Should never have an UNKNOWN_ACTION in toolbar_items.
215 // We've moved ownership of the subset of the component actions that we
216 // kept track of via toolbar_items() from |component_actions| to
217 // |action_list|. The rest will be deleted when |component_actions| goes out
220 return action_list
.Pass();
223 void ToolbarActionsModel::OnExtensionActionVisibilityChanged(
224 const std::string
& extension_id
,
225 bool is_now_visible
) {
226 // Hiding works differently with the new and old toolbars.
228 // It's possible that we haven't added this action yet, if its
229 // visibility was adjusted in the course of its initialization.
230 if (std::find(toolbar_items_
.begin(), toolbar_items_
.end(),
231 ToolbarItem(extension_id
, EXTENSION_ACTION
)) ==
232 toolbar_items_
.end())
237 if (is_now_visible
) {
238 // If this action used to be hidden, we can't possibly be showing all.
239 DCHECK_LT(visible_icon_count(), toolbar_items_
.size());
240 // Grow the bar by one and move the action to the end of the visibles.
241 new_size
= visible_icon_count() + 1;
242 new_index
= new_size
- 1;
244 // If we're hiding one, we must be showing at least one.
245 DCHECK_GE(visible_icon_count(), 0u);
246 // Shrink the bar by one and move the action to the beginning of the
248 new_size
= visible_icon_count() - 1;
249 new_index
= new_size
;
251 SetVisibleIconCount(new_size
);
252 MoveActionIcon(extension_id
, new_index
);
253 } else { // Don't include all extensions.
254 const extensions::Extension
* extension
= GetExtensionById(extension_id
);
256 AddExtension(extension
);
258 RemoveExtension(extension
);
262 void ToolbarActionsModel::OnExtensionLoaded(
263 content::BrowserContext
* browser_context
,
264 const extensions::Extension
* extension
) {
265 // We don't want to add the same extension twice. It may have already been
266 // added by EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED below, if the user
267 // hides the browser action and then disables and enables the extension.
268 if (std::find(toolbar_items_
.begin(), toolbar_items_
.end(),
269 ToolbarItem(extension
->id(), EXTENSION_ACTION
)) !=
270 toolbar_items_
.end())
273 AddExtension(extension
);
276 void ToolbarActionsModel::OnExtensionUnloaded(
277 content::BrowserContext
* browser_context
,
278 const extensions::Extension
* extension
,
279 extensions::UnloadedExtensionInfo::Reason reason
) {
280 RemoveExtension(extension
);
283 void ToolbarActionsModel::OnExtensionUninstalled(
284 content::BrowserContext
* browser_context
,
285 const extensions::Extension
* extension
,
286 extensions::UninstallReason reason
) {
287 // Remove the extension id from the ordered list, if it exists (the extension
288 // might not be represented in the list because it might not have an icon).
289 std::vector
<std::string
>::iterator pos
=
290 std::find(last_known_positions_
.begin(), last_known_positions_
.end(),
293 if (pos
!= last_known_positions_
.end()) {
294 last_known_positions_
.erase(pos
);
299 void ToolbarActionsModel::OnReady() {
300 InitializeActionList();
301 // Wait until the extension system is ready before observing any further
302 // changes so that the toolbar buttons can be shown in their stable ordering
304 extension_registry_observer_
.Add(extension_registry_
);
305 extension_action_observer_
.Add(extension_action_api_
);
307 if (ExtensionToolbarIconSurfacingBubbleDelegate::ShouldShowForProfile(
309 std::vector
<std::string
> ids
;
310 for (const ToolbarItem
& action
: toolbar_items_
)
311 ids
.push_back(action
.id
);
312 HighlightActions(ids
, HIGHLIGHT_INFO
);
315 actions_initialized_
= true;
316 FOR_EACH_OBSERVER(Observer
, observers_
, OnToolbarModelInitialized());
319 size_t ToolbarActionsModel::FindNewPositionFromLastKnownGood(
320 const ToolbarItem
& action
) {
321 // See if we have last known good position for this action.
322 size_t new_index
= 0;
323 // Loop through the ID list of known positions, to count the number of
324 // visible action icons preceding |action|'s id.
325 for (const std::string
& last_pos_id
: last_known_positions_
) {
326 if (last_pos_id
== action
.id
)
327 return new_index
; // We've found the right position.
328 // Found an action, need to see if it is visible.
329 for (const ToolbarItem
& item
: toolbar_items_
) {
330 if (item
.id
== last_pos_id
) {
331 // This extension is visible, update the index value.
338 // Position not found.
339 return toolbar_items_
.size();
342 bool ToolbarActionsModel::ShouldAddExtension(
343 const extensions::Extension
* extension
) {
344 // In incognito mode, don't add any extensions that aren't incognito-enabled.
345 if (profile_
->IsOffTheRecord() &&
346 !extensions::util::IsIncognitoEnabled(extension
->id(), profile_
))
349 extensions::ExtensionActionManager
* action_manager
=
350 extensions::ExtensionActionManager::Get(profile_
);
352 // In this case, we don't care about the browser action visibility, because
353 // we want to show each extension regardless.
354 return action_manager
->GetExtensionAction(*extension
) != NULL
;
357 return action_manager
->GetBrowserAction(*extension
) &&
358 extension_action_api_
->GetBrowserActionVisibility(extension
->id());
361 void ToolbarActionsModel::AddExtension(const extensions::Extension
* extension
) {
362 // We only use AddExtension() once the system is initialized.
363 DCHECK(actions_initialized_
);
364 if (!ShouldAddExtension(extension
))
367 // See if we have a last known good position for this extension.
368 bool is_new_extension
=
369 std::find(last_known_positions_
.begin(), last_known_positions_
.end(),
370 extension
->id()) == last_known_positions_
.end();
372 // New extensions go at the right (end) of the visible extensions. Other
373 // extensions go at their previous position.
374 size_t new_index
= 0;
375 if (is_new_extension
) {
376 new_index
= extensions::Manifest::IsComponentLocation(extension
->location())
378 : visible_icon_count();
379 // For the last-known position, we use the index of the extension that is
380 // just before this extension, plus one. (Note that this isn't the same
381 // as new_index + 1, because last_known_positions_ can include disabled
383 int new_last_known_index
=
384 new_index
== 0 ? 0 : std::find(last_known_positions_
.begin(),
385 last_known_positions_
.end(),
386 toolbar_items_
[new_index
- 1].id
) -
387 last_known_positions_
.begin() + 1;
388 // In theory, the extension before this one should always
389 // be in last known positions, but if something funny happened with prefs,
390 // make sure we handle it.
391 // TODO(devlin): Track down these cases so we can CHECK this.
392 new_last_known_index
=
393 std::min
<int>(new_last_known_index
, last_known_positions_
.size());
394 last_known_positions_
.insert(
395 last_known_positions_
.begin() + new_last_known_index
, extension
->id());
398 new_index
= FindNewPositionFromLastKnownGood(
399 ToolbarItem(extension
->id(), EXTENSION_ACTION
));
402 toolbar_items_
.insert(toolbar_items_
.begin() + new_index
,
403 ToolbarItem(extension
->id(), EXTENSION_ACTION
));
405 // If we're currently highlighting, then even though we add a browser action
406 // to the full list (|toolbar_items_|, there won't be another *visible*
407 // browser action, which was what the observers care about.
408 if (!is_highlighting()) {
409 FOR_EACH_OBSERVER(Observer
, observers_
,
410 OnToolbarActionAdded(extension
->id(), new_index
));
412 int visible_count_delta
= 0;
413 if (is_new_extension
&& !all_icons_visible()) {
414 // If this is a new extension (and not all extensions are visible), we
415 // expand the toolbar out so that the new one can be seen.
416 visible_count_delta
= 1;
417 } else if (profile_
->IsOffTheRecord()) {
418 // If this is an incognito profile, we also have to check to make sure the
419 // overflow matches the main bar's status.
420 ToolbarActionsModel
* main_model
=
421 ToolbarActionsModel::Get(profile_
->GetOriginalProfile());
422 // Find what the index will be in the main bar. Because Observer calls are
423 // nondeterministic, we can't just assume the main bar will have the
424 // extension and look it up.
425 size_t main_index
= main_model
->FindNewPositionFromLastKnownGood(
426 ToolbarItem(extension
->id(), EXTENSION_ACTION
));
427 bool visible
= main_index
< main_model
->visible_icon_count();
428 // We may need to adjust the visible count if the incognito bar isn't
429 // showing all icons and this one is visible, or if it is showing all
430 // icons and this is hidden.
431 if (visible
&& !all_icons_visible())
432 visible_count_delta
= 1;
433 else if (!visible
&& all_icons_visible())
434 visible_count_delta
= -1;
437 if (visible_count_delta
)
438 SetVisibleIconCount(visible_icon_count() + visible_count_delta
);
441 MaybeUpdateVisibilityPref(ToolbarItem(extension
->id(), EXTENSION_ACTION
),
445 void ToolbarActionsModel::RemoveExtension(
446 const extensions::Extension
* extension
) {
447 std::vector
<ToolbarItem
>::iterator pos
=
448 std::find(toolbar_items_
.begin(), toolbar_items_
.end(),
449 ToolbarItem(extension
->id(), EXTENSION_ACTION
));
451 if (pos
== toolbar_items_
.end())
454 size_t index
= pos
- toolbar_items_
.begin();
455 // If the removed extension was on the toolbar, a new one will take its place
456 // if there are any in overflow.
457 bool new_extension_shown
=
458 !all_icons_visible() && index
< visible_icon_count();
460 // If our visible count is set to the current size, we need to decrement it.
461 if (visible_icon_count_
== static_cast<int>(toolbar_items_
.size()))
462 SetVisibleIconCount(toolbar_items_
.size() - 1);
464 toolbar_items_
.erase(pos
);
466 // If we're in highlight mode, we also have to remove the extension from
467 // the highlighted list.
468 if (is_highlighting()) {
469 pos
= std::find(highlighted_items_
.begin(), highlighted_items_
.end(),
470 ToolbarItem(extension
->id(), EXTENSION_ACTION
));
471 if (pos
!= highlighted_items_
.end()) {
472 highlighted_items_
.erase(pos
);
473 FOR_EACH_OBSERVER(Observer
, observers_
,
474 OnToolbarActionRemoved(extension
->id()));
475 // If the highlighted list is now empty, we stop highlighting.
476 if (highlighted_items_
.empty())
480 FOR_EACH_OBSERVER(Observer
, observers_
,
481 OnToolbarActionRemoved(extension
->id()));
485 if (new_extension_shown
) {
486 size_t newly_visible_index
= visible_icon_count() - 1;
487 MaybeUpdateVisibilityPref(toolbar_items_
[newly_visible_index
],
488 newly_visible_index
);
492 // Combine the currently enabled extensions that have browser actions (which
493 // we get from the ExtensionRegistry) and component actions (which we get from
494 // ComponentToolbarActionsFactory) with the ordering we get from the pref
495 // service. For robustness we use a somewhat inefficient process:
496 // 1. Create a vector of actions sorted by their pref values. This vector may
498 // 2. Create a vector of actions that did not have a pref value.
499 // 3. Remove holes from the sorted vector and append the unsorted vector.
500 void ToolbarActionsModel::InitializeActionList() {
501 DCHECK(toolbar_items_
.empty()); // We shouldn't have any items yet.
503 last_known_positions_
= extension_prefs_
->GetToolbarOrder();
504 if (profile_
->IsOffTheRecord())
509 MaybeUpdateVisibilityPrefs();
512 void ToolbarActionsModel::Populate() {
513 DCHECK(!profile_
->IsOffTheRecord());
515 std::vector
<ToolbarItem
> all_actions
;
516 // Ids of actions that have explicit positions.
517 std::vector
<ToolbarItem
> sorted(last_known_positions_
.size(), ToolbarItem());
518 // Ids of actions that don't have explicit positions.
519 std::vector
<ToolbarItem
> unsorted
;
521 // Populate the lists.
523 int browser_actions_count
= 0;
524 int component_actions_count
= 0;
526 // First, add the extension action ids to all_actions.
527 const extensions::ExtensionSet
& extensions
=
528 extension_registry_
->enabled_extensions();
529 for (const scoped_refptr
<const extensions::Extension
>& extension
:
531 if (!ShouldAddExtension(extension
.get())) {
532 if (!extension_action_api_
->GetBrowserActionVisibility(extension
->id()))
537 all_actions
.push_back(ToolbarItem(extension
->id(), EXTENSION_ACTION
));
540 // Next, add the component action ids.
541 std::vector
<std::string
> component_ids
=
542 ComponentToolbarActionsFactory::GetComponentIds();
543 for (const std::string
& id
: component_ids
)
544 all_actions
.push_back(ToolbarItem(id
, COMPONENT_ACTION
));
546 // Add each action id to the appropriate list. Since the |sorted| list is
547 // created with enough room for each id in |positions| (which helps with
548 // proper order insertion), holes can be present if there isn't an action
549 // for each id. This is handled below when we add the actions to
550 // |toolbar_items_| to ensure that there are never any holes in
551 // |toolbar_items_| itself (or, relatedly, CreateActions()).
552 for (const ToolbarItem
& action
: all_actions
) {
553 std::vector
<std::string
>::const_iterator pos
=
554 std::find(last_known_positions_
.begin(), last_known_positions_
.end(),
556 if (pos
!= last_known_positions_
.end()) {
557 sorted
[pos
- last_known_positions_
.begin()] = action
;
559 // Unknown action - push it to the back of unsorted, and add it to the
560 // list of ids at the end.
561 unsorted
.push_back(action
);
562 last_known_positions_
.push_back(action
.id
);
567 sorted
.insert(sorted
.end(), unsorted
.begin(), unsorted
.end());
568 toolbar_items_
.reserve(sorted
.size());
570 // We don't notify observers of the added extension yet. Rather, observers
571 // should wait for the "OnToolbarModelInitialized" notification, and then
572 // bulk-update. (This saves a lot of bouncing-back-and-forth here, and allows
573 // observers to ensure that the extension system is always initialized before
574 // using the extensions).
575 for (const ToolbarItem
& action
: sorted
) {
576 switch (action
.type
) {
577 case EXTENSION_ACTION
:
578 // It's possible for the extension order to contain items that aren't
579 // actually loaded on this machine. For example, when extension sync is
580 // on, we sync the extension order as-is but double-check with the user
581 // before syncing NPAPI-containing extensions, so if one of those is not
582 // actually synced, we'll get a NULL in the list. This sort of case can
583 // also happen if some error prevents an extension from loading.
584 if (GetExtensionById(action
.id
)) {
585 toolbar_items_
.push_back(ToolbarItem(action
.id
, EXTENSION_ACTION
));
586 ++browser_actions_count
;
589 case COMPONENT_ACTION
:
590 toolbar_items_
.push_back(ToolbarItem(action
.id
, COMPONENT_ACTION
));
591 ++component_actions_count
;
594 // Since |sorted| can have holes in it, they will be default-constructed
595 // ToolbarItems with an action type of UNKNOWN. Ignore them.
600 // Histogram names are prefixed with "ExtensionToolbarModel" rather than
601 // "ToolbarActionsModel" for historical reasons.
602 UMA_HISTOGRAM_COUNTS_100(
603 "ExtensionToolbarModel.BrowserActionsPermanentlyHidden", hidden
);
604 UMA_HISTOGRAM_COUNTS_100("ExtensionToolbarModel.BrowserActionsCount",
605 browser_actions_count
);
606 UMA_HISTOGRAM_COUNTS_100("Toolbar.ActionsModel.ComponentActionsCount",
607 component_actions_count
);
608 UMA_HISTOGRAM_COUNTS_100("Toolbar.ActionsModel.OverallActionsCount",
609 toolbar_items_
.size());
611 if (!toolbar_items_
.empty()) {
612 // Visible count can be -1, meaning: 'show all'. Since UMA converts negative
613 // values to 0, this would be counted as 'show none' unless we convert it to
615 UMA_HISTOGRAM_COUNTS_100(
616 "ExtensionToolbarModel.BrowserActionsVisible",
617 visible_icon_count_
== -1
618 ? visible_icon_count_
619 : visible_icon_count_
- component_actions_count
);
622 // The only time this will useful and possibly vary from
623 // BrowserActionsVisible is when the redesign has been enabled.
624 UMA_HISTOGRAM_COUNTS_100("Toolbar.ActionsModel.ToolbarActionsVisible",
625 visible_icon_count_
== -1
626 ? base::HistogramBase::kSampleType_MAX
627 : visible_icon_count_
);
632 void ToolbarActionsModel::IncognitoPopulate() {
633 DCHECK(profile_
->IsOffTheRecord());
634 const ToolbarActionsModel
* original_model
=
635 ToolbarActionsModel::Get(profile_
->GetOriginalProfile());
637 // Find the absolute value of the original model's count.
638 int original_visible
= original_model
->visible_icon_count();
640 // In incognito mode, we show only those actions that are incognito-enabled
641 // Further, any actions that were overflowed in regular mode are still
642 // overflowed. Order is the same as in regular mode.
643 visible_icon_count_
= 0;
645 for (std::vector
<ToolbarItem
>::const_iterator iter
=
646 original_model
->toolbar_items_
.begin();
647 iter
!= original_model
->toolbar_items_
.end(); ++iter
) {
648 // The extension might not be shown in incognito mode.
649 // We may also disable certain component actions in incognito mode.
650 bool should_add
= false;
651 switch (iter
->type
) {
652 case EXTENSION_ACTION
:
653 should_add
= ShouldAddExtension(GetExtensionById(iter
->id
));
655 case COMPONENT_ACTION
:
656 should_add
= ComponentToolbarActionsFactory::EnabledIncognito(iter
->id
);
659 // We should never have an uninitialized action in the model.
665 toolbar_items_
.push_back(*iter
);
666 if (iter
- original_model
->toolbar_items_
.begin() < original_visible
)
667 ++visible_icon_count_
;
671 void ToolbarActionsModel::UpdatePrefs() {
672 if (!extension_prefs_
|| profile_
->IsOffTheRecord())
675 // Don't observe change caused by self.
676 pref_change_registrar_
.Remove(extensions::pref_names::kToolbar
);
677 extension_prefs_
->SetToolbarOrder(last_known_positions_
);
678 pref_change_registrar_
.Add(extensions::pref_names::kToolbar
,
679 pref_change_callback_
);
682 void ToolbarActionsModel::MaybeUpdateVisibilityPref(const ToolbarItem
& action
,
684 // Component actions don't have prefs to update.
685 if (action
.type
== COMPONENT_ACTION
)
688 // We only update the visibility pref for hidden/not hidden based on the
689 // overflow menu with the new toolbar design.
690 if (use_redesign_
&& !profile_
->IsOffTheRecord()) {
691 bool visible
= index
< visible_icon_count();
693 extension_action_api_
->GetBrowserActionVisibility(action
.id
)) {
694 // Don't observe changes caused by ourselves.
695 bool was_registered
= false;
696 if (extension_action_observer_
.IsObserving(extension_action_api_
)) {
697 was_registered
= true;
698 extension_action_observer_
.RemoveAll();
700 extension_action_api_
->SetBrowserActionVisibility(action
.id
, visible
);
702 extension_action_observer_
.Add(extension_action_api_
);
707 void ToolbarActionsModel::MaybeUpdateVisibilityPrefs() {
708 for (size_t i
= 0u; i
< toolbar_items_
.size(); ++i
)
709 MaybeUpdateVisibilityPref(toolbar_items_
[i
], i
);
712 void ToolbarActionsModel::OnActionToolbarPrefChange() {
713 // If extensions are not ready, defer to later Populate() call.
714 if (!actions_initialized_
)
717 // Recalculate |last_known_positions_| to be |pref_positions| followed by
718 // ones that are only in |last_known_positions_|.
719 std::vector
<std::string
> pref_positions
= extension_prefs_
->GetToolbarOrder();
720 size_t pref_position_size
= pref_positions
.size();
721 for (size_t i
= 0; i
< last_known_positions_
.size(); ++i
) {
722 if (std::find(pref_positions
.begin(), pref_positions
.end(),
723 last_known_positions_
[i
]) == pref_positions
.end()) {
724 pref_positions
.push_back(last_known_positions_
[i
]);
727 last_known_positions_
.swap(pref_positions
);
729 // Loop over the updated list of last known positions, moving any extensions
730 // that are in the wrong place.
731 auto desired_pos
= toolbar_items_
.begin();
732 for (const std::string
& id
: last_known_positions_
) {
733 auto current_pos
= std::find_if(
734 toolbar_items_
.begin(), toolbar_items_
.end(),
735 [&id
](const ToolbarItem
& item
) { return item
.id
== id
; });
736 if (current_pos
== toolbar_items_
.end())
739 if (current_pos
!= desired_pos
) {
740 if (current_pos
< desired_pos
)
741 std::rotate(current_pos
, current_pos
+ 1, desired_pos
+ 1);
743 std::rotate(desired_pos
, current_pos
, current_pos
+ 1);
744 // Notify the observers to keep them up-to-date, unless we're highlighting
745 // (in which case we're deliberately only showing a subset of actions).
746 if (!is_highlighting())
748 Observer
, observers_
,
749 OnToolbarActionMoved(id
, desired_pos
- toolbar_items_
.begin()));
754 if (last_known_positions_
.size() > pref_position_size
) {
755 // Need to update pref because we have extra icons. But can't call
756 // UpdatePrefs() directly within observation closure.
757 base::ThreadTaskRunnerHandle::Get()->PostTask(
758 FROM_HERE
, base::Bind(&ToolbarActionsModel::UpdatePrefs
,
759 weak_ptr_factory_
.GetWeakPtr()));
763 bool ToolbarActionsModel::HighlightActions(const std::vector
<std::string
>& ids
,
764 HighlightType highlight_type
) {
765 highlighted_items_
.clear();
767 for (const std::string
& action_id
: ids
) {
768 for (const ToolbarItem
& item
: toolbar_items_
) {
769 if (action_id
== item
.id
)
770 highlighted_items_
.push_back(item
);
774 // If we have any items in |highlighted_items_|, then we entered highlighting
776 if (highlighted_items_
.size()) {
777 // It's important that is_highlighting_ is changed immediately before the
778 // observers are notified since it changes the result of toolbar_items().
779 highlight_type_
= highlight_type
;
780 FOR_EACH_OBSERVER(Observer
, observers_
,
781 OnToolbarHighlightModeChanged(true));
783 // We set the visible icon count after the highlight mode change because
784 // the UI actions are created/destroyed during highlight, and doing that
785 // prior to changing the size allows us to still have smooth animations.
786 if (visible_icon_count() < ids
.size())
787 SetVisibleIconCount(ids
.size());
792 // Otherwise, we didn't enter highlighting mode (and, in fact, exited it if
793 // we were otherwise in it).
794 if (is_highlighting())
799 void ToolbarActionsModel::StopHighlighting() {
800 if (is_highlighting()) {
801 // It's important that is_highlighting_ is changed immediately before the
802 // observers are notified since it changes the result of toolbar_items().
803 highlight_type_
= HIGHLIGHT_NONE
;
804 FOR_EACH_OBSERVER(Observer
, observers_
,
805 OnToolbarHighlightModeChanged(false));
807 // For the same reason, we don't clear highlighted_items_ until after the
809 highlighted_items_
.clear();
811 // We set the visible icon count after the highlight mode change because
812 // the UI actions are created/destroyed during highlight, and doing that
813 // prior to changing the size allows us to still have smooth animations.
814 int saved_icon_count
=
815 prefs_
->GetInteger(extensions::pref_names::kToolbarSize
);
816 if (saved_icon_count
!= visible_icon_count_
)
817 SetVisibleIconCount(saved_icon_count
);
821 bool ToolbarActionsModel::RedesignIsShowingNewIcons() const {
822 for (const ToolbarItem
& action
: toolbar_items_
) {
823 if (action
.type
== EXTENSION_ACTION
) {
824 // Without the redesign, we only show extensions with browser actions.
825 // Any extension without a browser action is an indication that we're
826 // showing something new.
827 if (!GetExtensionById(action
.id
)->manifest()->HasKey(
828 extensions::manifest_keys::kBrowserAction
))
835 const extensions::Extension
* ToolbarActionsModel::GetExtensionById(
836 const std::string
& id
) const {
837 return extension_registry_
->enabled_extensions().GetByID(id
);