Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / toolbar / toolbar_actions_model.cc
blobea803e941025adce35270aa3518d897beb7e581f
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"
7 #include <algorithm>
8 #include <string>
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(
42 Profile* profile,
43 extensions::ExtensionPrefs* extension_prefs)
44 : profile_(profile),
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()
51 ->IsEnabled()),
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()));
59 visible_icon_count_ =
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() {}
75 // static
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)
91 ++pos;
92 if (pos == toolbar_items_.end()) {
93 NOTREACHED();
94 return;
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(),
112 iter->id),
113 id);
114 toolbar_items_.insert(iter, action);
115 } else {
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);
124 UpdatePrefs();
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(
163 Browser* browser,
164 ToolbarActionsBar* bar) {
165 DCHECK(browser);
166 DCHECK(bar);
167 ScopedVector<ToolbarActionViewController> action_list;
169 // Get the component action list.
170 ScopedVector<ToolbarActionViewController> component_actions =
171 ComponentToolbarActionsFactory::GetInstance()->GetComponentToolbarActions(
172 browser);
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()) {
180 switch (item.type) {
181 case EXTENSION_ACTION: {
182 // Get the extension.
183 const extensions::Extension* extension = GetExtensionById(item.id);
184 DCHECK(extension);
186 // Create and add an ExtensionActionViewController for the extension.
187 action_list.push_back(new ExtensionActionViewController(
188 extension, browser, action_manager->GetExtensionAction(*extension),
189 bar));
190 break;
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
205 // |action_list|.
206 component_actions.weak_erase(iter);
207 break;
209 case UNKNOWN_ACTION:
210 NOTREACHED(); // Should never have an UNKNOWN_ACTION in toolbar_items.
211 break;
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
218 // of scope.
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.
227 if (use_redesign_) {
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())
233 return;
235 int new_size = 0;
236 int new_index = 0;
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;
243 } else {
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
247 // overflow menu.
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);
255 if (is_now_visible)
256 AddExtension(extension);
257 else
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())
271 return;
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(),
291 extension->id());
293 if (pos != last_known_positions_.end()) {
294 last_known_positions_.erase(pos);
295 UpdatePrefs();
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
303 // taken from prefs.
304 extension_registry_observer_.Add(extension_registry_);
305 extension_action_observer_.Add(extension_action_api_);
307 if (ExtensionToolbarIconSurfacingBubbleDelegate::ShouldShowForProfile(
308 profile_)) {
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.
332 ++new_index;
333 break;
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_))
347 return false;
349 extensions::ExtensionActionManager* action_manager =
350 extensions::ExtensionActionManager::Get(profile_);
351 if (use_redesign_) {
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))
365 return;
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
382 // extensions.)
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());
396 UpdatePrefs();
397 } else {
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),
442 new_index);
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())
452 return;
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())
477 StopHighlighting();
479 } else {
480 FOR_EACH_OBSERVER(Observer, observers_,
481 OnToolbarActionRemoved(extension->id()));
484 UpdatePrefs();
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
497 // have holes.
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())
505 IncognitoPopulate();
506 else
507 Populate();
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.
522 int hidden = 0;
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 :
530 extensions) {
531 if (!ShouldAddExtension(extension.get())) {
532 if (!extension_action_api_->GetBrowserActionVisibility(extension->id()))
533 ++hidden;
534 continue;
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(),
555 action.id);
556 if (pos != last_known_positions_.end()) {
557 sorted[pos - last_known_positions_.begin()] = action;
558 } else {
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);
566 // Merge the lists.
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;
588 break;
589 case COMPONENT_ACTION:
590 toolbar_items_.push_back(ToolbarItem(action.id, COMPONENT_ACTION));
591 ++component_actions_count;
592 break;
593 case UNKNOWN_ACTION:
594 // Since |sorted| can have holes in it, they will be default-constructed
595 // ToolbarItems with an action type of UNKNOWN. Ignore them.
596 break;
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
614 // max.
615 UMA_HISTOGRAM_COUNTS_100(
616 "ExtensionToolbarModel.BrowserActionsVisible",
617 visible_icon_count_ == -1
618 ? visible_icon_count_
619 : visible_icon_count_ - component_actions_count);
621 if (use_redesign_) {
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));
654 break;
655 case COMPONENT_ACTION:
656 should_add = ComponentToolbarActionsFactory::EnabledIncognito(iter->id);
657 break;
658 case UNKNOWN_ACTION:
659 // We should never have an uninitialized action in the model.
660 NOTREACHED();
661 break;
663 if (!should_add)
664 continue;
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())
673 return;
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,
683 size_t index) {
684 // Component actions don't have prefs to update.
685 if (action.type == COMPONENT_ACTION)
686 return;
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();
692 if (visible !=
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);
701 if (was_registered)
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_)
715 return;
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())
737 continue;
739 if (current_pos != desired_pos) {
740 if (current_pos < desired_pos)
741 std::rotate(current_pos, current_pos + 1, desired_pos + 1);
742 else
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())
747 FOR_EACH_OBSERVER(
748 Observer, observers_,
749 OnToolbarActionMoved(id, desired_pos - toolbar_items_.begin()));
751 ++desired_pos;
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
775 // mode.
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());
789 return true;
792 // Otherwise, we didn't enter highlighting mode (and, in fact, exited it if
793 // we were otherwise in it).
794 if (is_highlighting())
795 StopHighlighting();
796 return false;
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
808 // mode changed.
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))
829 return true;
832 return false;
835 const extensions::Extension* ToolbarActionsModel::GetExtensionById(
836 const std::string& id) const {
837 return extension_registry_->enabled_extensions().GetByID(id);