#include "chrome/browser/extensions/extension_toolbar_model.h"
+#include <algorithm>
#include <string>
+#include "base/message_loop/message_loop.h"
#include "base/metrics/histogram.h"
#include "base/metrics/histogram_base.h"
#include "base/prefs/pref_service.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
-#include "chrome/browser/extensions/extension_action.h"
#include "chrome/browser/extensions/extension_action_manager.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/extensions/extension_toolbar_model_factory.h"
namespace extensions {
-bool ExtensionToolbarModel::Observer::BrowserActionShowPopup(
- const Extension* extension) {
- return false;
-}
-
ExtensionToolbarModel::ExtensionToolbarModel(Profile* profile,
ExtensionPrefs* extension_prefs)
: profile_(profile),
extension_prefs_(extension_prefs),
prefs_(profile_->GetPrefs()),
extensions_initialized_(false),
+ include_all_extensions_(
+ FeatureSwitch::extension_action_redesign()->IsEnabled()),
is_highlighting_(false),
+ extension_action_observer_(this),
extension_registry_observer_(this),
weak_ptr_factory_(this) {
ExtensionSystem::Get(profile_)->ready().Post(
observers_.RemoveObserver(observer);
}
-void ExtensionToolbarModel::MoveBrowserAction(const Extension* extension,
+void ExtensionToolbarModel::MoveExtensionIcon(const Extension* extension,
int index) {
ExtensionList::iterator pos = std::find(toolbar_items_.begin(),
toolbar_items_.end(), extension);
last_known_positions_.push_back(extension->id());
}
- FOR_EACH_OBSERVER(Observer, observers_, BrowserActionMoved(extension, index));
-
+ FOR_EACH_OBSERVER(
+ Observer, observers_, ToolbarExtensionMoved(extension, index));
+ MaybeUpdateVisibilityPref(extension, index);
UpdatePrefs();
}
-ExtensionToolbarModel::Action ExtensionToolbarModel::ExecuteBrowserAction(
- const Extension* extension,
- Browser* browser,
- GURL* popup_url_out,
- bool should_grant) {
- content::WebContents* web_contents = NULL;
- int tab_id = 0;
- if (!ExtensionTabUtil::GetDefaultTab(browser, &web_contents, &tab_id)) {
- return ACTION_NONE;
- }
-
- ExtensionAction* browser_action =
- ExtensionActionManager::Get(profile_)->GetBrowserAction(*extension);
-
- // For browser actions, visibility == enabledness.
- if (!browser_action->GetIsVisible(tab_id))
- return ACTION_NONE;
-
- if (should_grant) {
- TabHelper::FromWebContents(web_contents)
- ->active_tab_permission_granter()
- ->GrantIfRequested(extension);
- }
-
- if (browser_action->HasPopup(tab_id)) {
- if (popup_url_out)
- *popup_url_out = browser_action->GetPopupUrl(tab_id);
- return ACTION_SHOW_POPUP;
- }
-
- ExtensionActionAPI::BrowserActionExecuted(
- browser->profile(), *browser_action, web_contents);
- return ACTION_NONE;
-}
-
void ExtensionToolbarModel::SetVisibleIconCount(int count) {
visible_icon_count_ =
count == static_cast<int>(toolbar_items_.size()) ? -1 : count;
+
// Only set the prefs if we're not in highlight mode. Highlight mode is
// designed to be a transitory state, and should not persist across browser
// restarts (though it may be re-entered).
if (!is_highlighting_) {
+ // Additionally, if we are using the new toolbar, any icons which are in the
+ // overflow menu are considered "hidden". But it so happens that the times
+ // we are likely to call SetVisibleIconCount() are also those when we are
+ // in flux. So wait for things to cool down before setting the prefs.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&ExtensionToolbarModel::MaybeUpdateVisibilityPrefs,
+ weak_ptr_factory_.GetWeakPtr()));
prefs_->SetInteger(pref_names::kToolbarSize, visible_icon_count_);
}
}
+void ExtensionToolbarModel::OnExtensionActionUpdated(
+ ExtensionAction* extension_action,
+ content::WebContents* web_contents,
+ content::BrowserContext* browser_context) {
+ const Extension* extension =
+ ExtensionRegistry::Get(profile_)->enabled_extensions().GetByID(
+ extension_action->extension_id());
+ // Notify observers if the extension exists and is in the model.
+ if (extension &&
+ std::find(toolbar_items_.begin(),
+ toolbar_items_.end(),
+ extension) != toolbar_items_.end()) {
+ FOR_EACH_OBSERVER(Observer, observers_, ToolbarExtensionUpdated(extension));
+ }
+}
+
void ExtensionToolbarModel::OnExtensionLoaded(
content::BrowserContext* browser_context,
const Extension* extension) {
if (toolbar_items_[i].get() == extension)
return;
}
- if (ExtensionActionAPI::GetBrowserActionVisibility(extension_prefs_,
- extension->id())) {
- AddExtension(extension);
- }
+
+ AddExtension(extension);
}
void ExtensionToolbarModel::OnExtensionUnloaded(
RemoveExtension(extension);
}
+void ExtensionToolbarModel::OnExtensionUninstalled(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ extensions::UninstallReason reason) {
+ // Remove the extension id from the ordered list, if it exists (the extension
+ // might not be represented in the list because it might not have an icon).
+ ExtensionIdList::iterator pos =
+ std::find(last_known_positions_.begin(),
+ last_known_positions_.end(), extension->id());
+
+ if (pos != last_known_positions_.end()) {
+ last_known_positions_.erase(pos);
+ UpdatePrefs();
+ }
+}
+
void ExtensionToolbarModel::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
- switch (type) {
- case chrome::NOTIFICATION_EXTENSION_UNINSTALLED: {
- const Extension* extension =
- content::Details<const Extension>(details).ptr();
- UninstalledExtension(extension);
- break;
- }
- case chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED: {
- const Extension* extension =
- ExtensionRegistry::Get(profile_)->GetExtensionById(
- *content::Details<const std::string>(details).ptr(),
- ExtensionRegistry::EVERYTHING);
- if (ExtensionActionAPI::GetBrowserActionVisibility(extension_prefs_,
- extension->id())) {
- AddExtension(extension);
- } else {
- RemoveExtension(extension);
- }
- break;
+ DCHECK_EQ(NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED, type);
+ const Extension* extension =
+ ExtensionRegistry::Get(profile_)->GetExtensionById(
+ *content::Details<const std::string>(details).ptr(),
+ ExtensionRegistry::EVERYTHING);
+
+ bool visible = ExtensionActionAPI::GetBrowserActionVisibility(
+ extension_prefs_, extension->id());
+ // Hiding works differently with the new and old toolbars.
+ if (include_all_extensions_) {
+ int new_size = 0;
+ int new_index = 0;
+ if (visible) {
+ // If this action used to be hidden, we can't possible be showing all.
+ DCHECK_NE(-1, visible_icon_count_);
+ // Grow the bar by one and move the extension to the end of the visibles.
+ new_size = visible_icon_count_ + 1;
+ new_index = new_size - 1;
+ } else {
+ // If we're hiding one, we must be showing at least one.
+ DCHECK_NE(visible_icon_count_, 0);
+ // Shrink the bar by one and move the extension to the beginning of the
+ // overflow menu.
+ new_size = visible_icon_count_ == -1 ?
+ toolbar_items_.size() - 1 : visible_icon_count_ - 1;
+ new_index = new_size;
}
- default:
- NOTREACHED() << "Received unexpected notification";
+ SetVisibleIconCount(new_size);
+ MoveExtensionIcon(extension, new_index);
+ FOR_EACH_OBSERVER(Observer, observers_, ToolbarVisibleCountChanged());
+ } else { // Don't include all extensions.
+ if (visible)
+ AddExtension(extension);
+ else
+ RemoveExtension(extension);
}
}
// changes so that the toolbar buttons can be shown in their stable ordering
// taken from prefs.
extension_registry_observer_.Add(registry);
- registrar_.Add(this,
- chrome::NOTIFICATION_EXTENSION_UNINSTALLED,
- content::Source<Profile>(profile_));
+ extension_action_observer_.Add(ExtensionActionAPI::Get(profile_));
registrar_.Add(
this,
- chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED,
+ extensions::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED,
content::Source<ExtensionPrefs>(extension_prefs_));
}
// See if we have last known good position for this extension.
size_t new_index = 0;
// Loop through the ID list of known positions, to count the number of visible
- // browser action icons preceding |extension|.
+ // extension icons preceding |extension|.
for (ExtensionIdList::const_iterator iter_id = last_known_positions_.begin();
iter_id < last_known_positions_.end(); ++iter_id) {
if ((*iter_id) == extension->id())
}
}
- return -1;
+ // Position not found.
+ return toolbar_items_.size();
+}
+
+bool ExtensionToolbarModel::ShouldAddExtension(const Extension* extension) {
+ ExtensionActionManager* action_manager =
+ ExtensionActionManager::Get(profile_);
+ if (include_all_extensions_) {
+ // In this case, we don't care about the browser action visibility, because
+ // we want to show each extension regardless.
+ // TODO(devlin): Extension actions which are not visible should be moved to
+ // the overflow menu by default.
+ return action_manager->GetBrowserAction(*extension) ||
+ action_manager->GetPageAction(*extension);
+ }
+
+ return action_manager->GetBrowserAction(*extension) &&
+ ExtensionActionAPI::GetBrowserActionVisibility(
+ extension_prefs_, extension->id());
}
void ExtensionToolbarModel::AddExtension(const Extension* extension) {
- // We only care about extensions with browser actions.
- if (!ExtensionActionManager::Get(profile_)->GetBrowserAction(*extension))
+ if (!ShouldAddExtension(extension))
return;
- size_t new_index = -1;
+ size_t new_index = toolbar_items_.size();
// See if we have a last known good position for this extension.
ExtensionIdList::iterator last_pos = std::find(last_known_positions_.begin(),
}
} else {
// This is a never before seen extension, that was added to the end. Make
- // sure to reflect that.
+ // sure to reflect that. (|new_index| was set above.)
toolbar_items_.push_back(make_scoped_refptr(extension));
last_known_positions_.push_back(extension->id());
- new_index = toolbar_items_.size() - 1;
UpdatePrefs();
}
+ MaybeUpdateVisibilityPref(extension, new_index);
+
// If we're currently highlighting, then even though we add a browser action
// to the full list (|toolbar_items_|, there won't be another *visible*
// browser action, which was what the observers care about.
if (!is_highlighting_) {
- FOR_EACH_OBSERVER(Observer, observers_,
- BrowserActionAdded(extension, new_index));
+ FOR_EACH_OBSERVER(
+ Observer, observers_, ToolbarExtensionAdded(extension, new_index));
}
}
extension);
if (pos != highlighted_items_.end()) {
highlighted_items_.erase(pos);
- FOR_EACH_OBSERVER(Observer, observers_, BrowserActionRemoved(extension));
+ FOR_EACH_OBSERVER(
+ Observer, observers_, ToolbarExtensionRemoved(extension));
// If the highlighted list is now empty, we stop highlighting.
if (highlighted_items_.empty())
StopHighlighting();
}
} else {
- FOR_EACH_OBSERVER(Observer, observers_, BrowserActionRemoved(extension));
+ FOR_EACH_OBSERVER(Observer, observers_, ToolbarExtensionRemoved(extension));
}
UpdatePrefs();
}
-void ExtensionToolbarModel::UninstalledExtension(const Extension* extension) {
- // Remove the extension id from the ordered list, if it exists (the extension
- // might not be represented in the list because it might not have an icon).
- ExtensionIdList::iterator pos =
- std::find(last_known_positions_.begin(),
- last_known_positions_.end(), extension->id());
-
- if (pos != last_known_positions_.end()) {
- last_known_positions_.erase(pos);
- UpdatePrefs();
- }
-}
-
// Combine the currently enabled extensions that have browser actions (which
// we get from the ExtensionRegistry) with the ordering we get from the
// pref service. For robustness we use a somewhat inefficient process:
Populate(last_known_positions_, extensions);
extensions_initialized_ = true;
- FOR_EACH_OBSERVER(Observer, observers_, VisibleCountChanged());
+ MaybeUpdateVisibilityPrefs();
+ FOR_EACH_OBSERVER(Observer, observers_, ToolbarVisibleCountChanged());
}
void ExtensionToolbarModel::Populate(const ExtensionIdList& positions,
it != extensions.end();
++it) {
const Extension* extension = it->get();
- if (!extension_action_manager->GetBrowserAction(*extension))
- continue;
- if (!ExtensionActionAPI::GetBrowserActionVisibility(
- extension_prefs_, extension->id())) {
- ++hidden;
+ if (!ShouldAddExtension(extension)) {
+ if (extension_action_manager->GetBrowserAction(*extension))
+ ++hidden;
continue;
}
unsorted.push_back(make_scoped_refptr(extension));
}
- // Erase current icons.
- for (size_t i = 0; i < toolbar_items_.size(); i++) {
+ size_t items_count = toolbar_items_.size();
+ for (size_t i = 0; i < items_count; i++) {
+ const Extension* extension = toolbar_items_.back().get();
+ // By popping the extension here (before calling BrowserActionRemoved),
+ // we will not shrink visible count by one after BrowserActionRemoved
+ // calls SetVisibleCount.
+ toolbar_items_.pop_back();
FOR_EACH_OBSERVER(
- Observer, observers_, BrowserActionRemoved(toolbar_items_[i].get()));
+ Observer, observers_, ToolbarExtensionRemoved(extension));
}
- toolbar_items_.clear();
+ DCHECK(toolbar_items_.empty());
// Merge the lists.
toolbar_items_.reserve(sorted.size() + unsorted.size());
+
for (ExtensionList::const_iterator iter = sorted.begin();
iter != sorted.end(); ++iter) {
// It's possible for the extension order to contain items that aren't
// syncing NPAPI-containing extensions, so if one of those is not actually
// synced, we'll get a NULL in the list. This sort of case can also happen
// if some error prevents an extension from loading.
- if (iter->get() != NULL)
+ if (iter->get() != NULL) {
+ toolbar_items_.push_back(*iter);
+ FOR_EACH_OBSERVER(
+ Observer,
+ observers_,
+ ToolbarExtensionAdded(iter->get(), toolbar_items_.size() - 1));
+ }
+ }
+ for (ExtensionList::const_iterator iter = unsorted.begin();
+ iter != unsorted.end(); ++iter) {
+ if (iter->get() != NULL) {
toolbar_items_.push_back(*iter);
+ FOR_EACH_OBSERVER(
+ Observer,
+ observers_,
+ ToolbarExtensionAdded(iter->get(), toolbar_items_.size() - 1));
+ }
}
- toolbar_items_.insert(toolbar_items_.end(), unsorted.begin(),
- unsorted.end());
UMA_HISTOGRAM_COUNTS_100(
"ExtensionToolbarModel.BrowserActionsPermanentlyHidden", hidden);
base::HistogramBase::kSampleType_MAX :
visible_icon_count_);
}
-
- // Inform observers.
- for (size_t i = 0; i < toolbar_items_.size(); i++) {
- FOR_EACH_OBSERVER(
- Observer, observers_, BrowserActionAdded(toolbar_items_[i].get(), i));
- }
}
void ExtensionToolbarModel::UpdatePrefs() {
pref_change_registrar_.Add(pref_names::kToolbar, pref_change_callback_);
}
+void ExtensionToolbarModel::MaybeUpdateVisibilityPref(
+ const Extension* extension, int index) {
+ // We only update the visibility pref for hidden/not hidden based on the
+ // overflow menu with the new toolbar design.
+ if (include_all_extensions_) {
+ bool visible = index < visible_icon_count_ || visible_icon_count_ == -1;
+ if (visible != ExtensionActionAPI::GetBrowserActionVisibility(
+ extension_prefs_, extension->id())) {
+ // Don't observe changes caused by ourselves.
+ bool was_registered = false;
+ if (registrar_.IsRegistered(
+ this,
+ NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED,
+ content::Source<ExtensionPrefs>(extension_prefs_))) {
+ was_registered = true;
+ registrar_.Remove(
+ this,
+ NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED,
+ content::Source<ExtensionPrefs>(extension_prefs_));
+ }
+ ExtensionActionAPI::SetBrowserActionVisibility(
+ extension_prefs_, extension->id(), visible);
+ if (was_registered) {
+ registrar_.Add(this,
+ NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED,
+ content::Source<ExtensionPrefs>(extension_prefs_));
+ }
+ }
+ }
+}
+
+void ExtensionToolbarModel::MaybeUpdateVisibilityPrefs() {
+ for (size_t i = 0u; i < toolbar_items_.size(); ++i)
+ MaybeUpdateVisibilityPref(toolbar_items_[i].get(), i);
+}
+
int ExtensionToolbarModel::IncognitoIndexToOriginal(int incognito_index) {
int original_index = 0, i = 0;
for (ExtensionList::iterator iter = toolbar_items_.begin();
}
}
-bool ExtensionToolbarModel::ShowBrowserActionPopup(const Extension* extension) {
+bool ExtensionToolbarModel::ShowExtensionActionPopup(
+ const Extension* extension,
+ Browser* browser,
+ bool grant_active_tab) {
ObserverListBase<Observer>::Iterator it(observers_);
Observer* obs = NULL;
+ // Look for the Observer associated with the browser.
+ // This would be cleaner if we had an abstract class for the Toolbar UI
+ // (like we do for LocationBar), but sadly, we don't.
while ((obs = it.GetNext()) != NULL) {
- // Stop after first popup since it should only show in the active window.
- if (obs->BrowserActionShowPopup(extension))
- return true;
+ if (obs->GetBrowser() == browser)
+ return obs->ShowExtensionActionPopup(extension, grant_active_tab);
}
return false;
}
SetVisibleIconCount(extension_ids.size());
// Inform observers.
- FOR_EACH_OBSERVER(Observer, observers_, VisibleCountChanged());
+ FOR_EACH_OBSERVER(Observer, observers_, ToolbarVisibleCountChanged());
}
if (visible_icon_count_ == -1)
extension != toolbar_items_.end(); ++extension) {
if ((*extension)->id() == (*it)) {
if (extension - toolbar_items_.begin() >= visible_icon_count_)
- MoveBrowserAction(*extension, 0);
+ MoveExtensionIcon(extension->get(), 0);
break;
}
}
if (visible_icon_count_ != -1 &&
visible_icon_count_ < static_cast<int>(extension_ids.size())) {
SetVisibleIconCount(extension_ids.size());
- FOR_EACH_OBSERVER(Observer, observers_, VisibleCountChanged());
+ FOR_EACH_OBSERVER(Observer, observers_, ToolbarVisibleCountChanged());
}
- FOR_EACH_OBSERVER(Observer, observers_, HighlightModeChanged(true));
+ FOR_EACH_OBSERVER(Observer, observers_, ToolbarHighlightModeChanged(true));
return true;
}
is_highlighting_ = false;
if (old_visible_icon_count_ != visible_icon_count_) {
SetVisibleIconCount(old_visible_icon_count_);
- FOR_EACH_OBSERVER(Observer, observers_, VisibleCountChanged());
+ FOR_EACH_OBSERVER(Observer, observers_, ToolbarVisibleCountChanged());
}
- FOR_EACH_OBSERVER(Observer, observers_, HighlightModeChanged(false));
+ FOR_EACH_OBSERVER(Observer, observers_, ToolbarHighlightModeChanged(false));
}
-};
+}
+
+void ExtensionToolbarModel::SetVisibleIconCountForTest(size_t visible_icons) {
+ SetVisibleIconCount(visible_icons);
+ FOR_EACH_OBSERVER(Observer, observers_, ToolbarVisibleCountChanged());
+}
} // namespace extensions