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/prefs/pref_service.h"
10 #include "chrome/browser/chrome_notification_types.h"
11 #include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
12 #include "chrome/browser/extensions/extension_action.h"
13 #include "chrome/browser/extensions/extension_action_manager.h"
14 #include "chrome/browser/extensions/extension_prefs.h"
15 #include "chrome/browser/extensions/extension_service.h"
16 #include "chrome/browser/extensions/extension_tab_util.h"
17 #include "chrome/browser/extensions/extension_toolbar_model_factory.h"
18 #include "chrome/browser/extensions/extension_util.h"
19 #include "chrome/browser/extensions/tab_helper.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/browser/ui/browser.h"
22 #include "chrome/browser/ui/tabs/tab_strip_model.h"
23 #include "chrome/common/pref_names.h"
24 #include "content/public/browser/notification_details.h"
25 #include "content/public/browser/notification_source.h"
26 #include "content/public/browser/web_contents.h"
27 #include "extensions/browser/pref_names.h"
28 #include "extensions/common/extension.h"
29 #include "extensions/common/extension_set.h"
30 #include "extensions/common/feature_switch.h"
32 using extensions::Extension
;
33 using extensions::ExtensionIdList
;
34 using extensions::ExtensionList
;
36 bool ExtensionToolbarModel::Observer::BrowserActionShowPopup(
37 const extensions::Extension
* extension
) {
41 ExtensionToolbarModel::ExtensionToolbarModel(
43 extensions::ExtensionPrefs
* extension_prefs
)
45 extension_prefs_(extension_prefs
),
46 prefs_(profile_
->GetPrefs()),
47 extensions_initialized_(false),
48 weak_ptr_factory_(this) {
49 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED
,
50 content::Source
<Profile
>(profile_
));
51 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED
,
52 content::Source
<Profile
>(profile_
));
53 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY
,
54 content::Source
<Profile
>(profile_
));
55 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED
,
56 content::Source
<Profile
>(profile_
));
58 this, chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED
,
59 content::Source
<extensions::ExtensionPrefs
>(extension_prefs_
));
61 visible_icon_count_
= prefs_
->GetInteger(
62 extensions::pref_names::kToolbarSize
);
64 pref_change_registrar_
.Init(prefs_
);
65 pref_change_callback_
=
66 base::Bind(&ExtensionToolbarModel::OnExtensionToolbarPrefChange
,
67 base::Unretained(this));
68 pref_change_registrar_
.Add(extensions::pref_names::kToolbar
,
69 pref_change_callback_
);
72 ExtensionToolbarModel::~ExtensionToolbarModel() {
76 ExtensionToolbarModel
* ExtensionToolbarModel::Get(Profile
* profile
) {
77 return ExtensionToolbarModelFactory::GetForProfile(profile
);
80 void ExtensionToolbarModel::AddObserver(Observer
* observer
) {
81 observers_
.AddObserver(observer
);
84 void ExtensionToolbarModel::RemoveObserver(Observer
* observer
) {
85 observers_
.RemoveObserver(observer
);
88 void ExtensionToolbarModel::MoveBrowserAction(const Extension
* extension
,
90 ExtensionList::iterator pos
= std::find(toolbar_items_
.begin(),
91 toolbar_items_
.end(), extension
);
92 if (pos
== toolbar_items_
.end()) {
96 toolbar_items_
.erase(pos
);
98 ExtensionIdList::iterator pos_id
;
99 pos_id
= std::find(last_known_positions_
.begin(),
100 last_known_positions_
.end(), extension
->id());
101 if (pos_id
!= last_known_positions_
.end())
102 last_known_positions_
.erase(pos_id
);
105 bool inserted
= false;
106 for (ExtensionList::iterator iter
= toolbar_items_
.begin();
107 iter
!= toolbar_items_
.end();
110 pos_id
= std::find(last_known_positions_
.begin(),
111 last_known_positions_
.end(), (*iter
)->id());
112 last_known_positions_
.insert(pos_id
, extension
->id());
114 toolbar_items_
.insert(iter
, make_scoped_refptr(extension
));
121 DCHECK_EQ(index
, static_cast<int>(toolbar_items_
.size()));
122 index
= toolbar_items_
.size();
124 toolbar_items_
.push_back(make_scoped_refptr(extension
));
125 last_known_positions_
.push_back(extension
->id());
128 FOR_EACH_OBSERVER(Observer
, observers_
, BrowserActionMoved(extension
, index
));
133 ExtensionToolbarModel::Action
ExtensionToolbarModel::ExecuteBrowserAction(
134 const Extension
* extension
,
138 content::WebContents
* web_contents
= NULL
;
140 if (!extensions::ExtensionTabUtil::GetDefaultTab(
141 browser
, &web_contents
, &tab_id
)) {
145 ExtensionAction
* browser_action
=
146 extensions::ExtensionActionManager::Get(profile_
)->
147 GetBrowserAction(*extension
);
149 // For browser actions, visibility == enabledness.
150 if (!browser_action
->GetIsVisible(tab_id
))
154 extensions::TabHelper::FromWebContents(web_contents
)->
155 active_tab_permission_granter()->GrantIfRequested(extension
);
158 if (browser_action
->HasPopup(tab_id
)) {
160 *popup_url_out
= browser_action
->GetPopupUrl(tab_id
);
161 return ACTION_SHOW_POPUP
;
164 extensions::ExtensionActionAPI::BrowserActionExecuted(
165 browser
->profile(), *browser_action
, web_contents
);
169 void ExtensionToolbarModel::SetVisibleIconCount(int count
) {
170 visible_icon_count_
=
171 count
== static_cast<int>(toolbar_items_
.size()) ? -1 : count
;
172 prefs_
->SetInteger(extensions::pref_names::kToolbarSize
, visible_icon_count_
);
175 void ExtensionToolbarModel::Observe(
177 const content::NotificationSource
& source
,
178 const content::NotificationDetails
& details
) {
179 ExtensionService
* extension_service
=
180 extensions::ExtensionSystem::Get(profile_
)->extension_service();
181 if (!extension_service
|| !extension_service
->is_ready())
184 if (type
== chrome::NOTIFICATION_EXTENSIONS_READY
) {
185 InitializeExtensionList(extension_service
);
189 const Extension
* extension
= NULL
;
190 if (type
== chrome::NOTIFICATION_EXTENSION_UNLOADED
) {
191 extension
= content::Details
<extensions::UnloadedExtensionInfo
>(
194 chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED
) {
195 extension
= extension_service
->GetExtensionById(
196 *content::Details
<const std::string
>(details
).ptr(), true);
198 extension
= content::Details
<const Extension
>(details
).ptr();
200 if (type
== chrome::NOTIFICATION_EXTENSION_LOADED
) {
201 // We don't want to add the same extension twice. It may have already been
202 // added by EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED below, if the user
203 // hides the browser action and then disables and enables the extension.
204 for (size_t i
= 0; i
< toolbar_items_
.size(); i
++) {
205 if (toolbar_items_
[i
].get() == extension
)
206 return; // Already exists.
208 if (extensions::ExtensionActionAPI::GetBrowserActionVisibility(
209 extension_prefs_
, extension
->id())) {
210 AddExtension(extension
);
212 } else if (type
== chrome::NOTIFICATION_EXTENSION_UNLOADED
) {
213 RemoveExtension(extension
);
214 } else if (type
== chrome::NOTIFICATION_EXTENSION_UNINSTALLED
) {
215 UninstalledExtension(extension
);
217 chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED
) {
218 if (extensions::ExtensionActionAPI::GetBrowserActionVisibility(
219 extension_prefs_
, extension
->id())) {
220 AddExtension(extension
);
222 RemoveExtension(extension
);
225 NOTREACHED() << "Received unexpected notification";
229 size_t ExtensionToolbarModel::FindNewPositionFromLastKnownGood(
230 const Extension
* extension
) {
231 // See if we have last known good position for this extension.
232 size_t new_index
= 0;
233 // Loop through the ID list of known positions, to count the number of visible
234 // browser action icons preceding |extension|.
235 for (ExtensionIdList::const_iterator iter_id
= last_known_positions_
.begin();
236 iter_id
< last_known_positions_
.end(); ++iter_id
) {
237 if ((*iter_id
) == extension
->id())
238 return new_index
; // We've found the right position.
239 // Found an id, need to see if it is visible.
240 for (ExtensionList::const_iterator iter_ext
= toolbar_items_
.begin();
241 iter_ext
< toolbar_items_
.end(); ++iter_ext
) {
242 if ((*iter_ext
)->id().compare(*iter_id
) == 0) {
243 // This extension is visible, update the index value.
253 void ExtensionToolbarModel::AddExtension(const Extension
* extension
) {
254 // We only care about extensions with browser actions.
255 if (!extensions::ExtensionActionManager::Get(profile_
)->
256 GetBrowserAction(*extension
)) {
260 size_t new_index
= -1;
262 // See if we have a last known good position for this extension.
263 ExtensionIdList::iterator last_pos
= std::find(last_known_positions_
.begin(),
264 last_known_positions_
.end(),
266 if (last_pos
!= last_known_positions_
.end()) {
267 new_index
= FindNewPositionFromLastKnownGood(extension
);
268 if (new_index
!= toolbar_items_
.size()) {
269 toolbar_items_
.insert(toolbar_items_
.begin() + new_index
,
270 make_scoped_refptr(extension
));
272 toolbar_items_
.push_back(make_scoped_refptr(extension
));
275 // This is a never before seen extension, that was added to the end. Make
276 // sure to reflect that.
277 toolbar_items_
.push_back(make_scoped_refptr(extension
));
278 last_known_positions_
.push_back(extension
->id());
279 new_index
= toolbar_items_
.size() - 1;
283 FOR_EACH_OBSERVER(Observer
, observers_
,
284 BrowserActionAdded(extension
, new_index
));
287 void ExtensionToolbarModel::RemoveExtension(const Extension
* extension
) {
288 ExtensionList::iterator pos
=
289 std::find(toolbar_items_
.begin(), toolbar_items_
.end(), extension
);
290 if (pos
== toolbar_items_
.end())
293 toolbar_items_
.erase(pos
);
294 FOR_EACH_OBSERVER(Observer
, observers_
,
295 BrowserActionRemoved(extension
));
300 void ExtensionToolbarModel::UninstalledExtension(const Extension
* extension
) {
301 // Remove the extension id from the ordered list, if it exists (the extension
302 // might not be represented in the list because it might not have an icon).
303 ExtensionIdList::iterator pos
=
304 std::find(last_known_positions_
.begin(),
305 last_known_positions_
.end(), extension
->id());
307 if (pos
!= last_known_positions_
.end()) {
308 last_known_positions_
.erase(pos
);
313 // Combine the currently enabled extensions that have browser actions (which
314 // we get from the ExtensionService) with the ordering we get from the
315 // pref service. For robustness we use a somewhat inefficient process:
316 // 1. Create a vector of extensions sorted by their pref values. This vector may
318 // 2. Create a vector of extensions that did not have a pref value.
319 // 3. Remove holes from the sorted vector and append the unsorted vector.
320 void ExtensionToolbarModel::InitializeExtensionList(ExtensionService
* service
) {
321 DCHECK(service
->is_ready());
323 last_known_positions_
= extension_prefs_
->GetToolbarOrder();
324 Populate(last_known_positions_
, service
);
326 extensions_initialized_
= true;
327 FOR_EACH_OBSERVER(Observer
, observers_
, VisibleCountChanged());
330 void ExtensionToolbarModel::Populate(
331 const extensions::ExtensionIdList
& positions
,
332 ExtensionService
* service
) {
333 // Items that have explicit positions.
334 ExtensionList sorted
;
335 sorted
.resize(positions
.size(), NULL
);
336 // The items that don't have explicit positions.
337 ExtensionList unsorted
;
339 extensions::ExtensionActionManager
* extension_action_manager
=
340 extensions::ExtensionActionManager::Get(profile_
);
343 for (extensions::ExtensionSet::const_iterator it
=
344 service
->extensions()->begin();
345 it
!= service
->extensions()->end(); ++it
) {
346 const Extension
* extension
= it
->get();
347 if (!extension_action_manager
->GetBrowserAction(*extension
))
349 if (!extensions::ExtensionActionAPI::GetBrowserActionVisibility(
350 extension_prefs_
, extension
->id())) {
354 extensions::ExtensionIdList::const_iterator pos
=
355 std::find(positions
.begin(), positions
.end(), extension
->id());
356 if (pos
!= positions
.end())
357 sorted
[pos
- positions
.begin()] = extension
;
359 unsorted
.push_back(make_scoped_refptr(extension
));
362 // Erase current icons.
363 for (size_t i
= 0; i
< toolbar_items_
.size(); i
++) {
365 Observer
, observers_
, BrowserActionRemoved(toolbar_items_
[i
].get()));
367 toolbar_items_
.clear();
370 toolbar_items_
.reserve(sorted
.size() + unsorted
.size());
371 for (ExtensionList::const_iterator iter
= sorted
.begin();
372 iter
!= sorted
.end(); ++iter
) {
373 // It's possible for the extension order to contain items that aren't
374 // actually loaded on this machine. For example, when extension sync is on,
375 // we sync the extension order as-is but double-check with the user before
376 // syncing NPAPI-containing extensions, so if one of those is not actually
377 // synced, we'll get a NULL in the list. This sort of case can also happen
378 // if some error prevents an extension from loading.
379 if (iter
->get() != NULL
)
380 toolbar_items_
.push_back(*iter
);
382 toolbar_items_
.insert(toolbar_items_
.end(), unsorted
.begin(),
386 for (size_t i
= 0; i
< toolbar_items_
.size(); i
++) {
388 Observer
, observers_
, BrowserActionAdded(toolbar_items_
[i
].get(), i
));
392 void ExtensionToolbarModel::UpdatePrefs() {
393 if (!extension_prefs_
)
396 // Don't observe change caused by self.
397 pref_change_registrar_
.Remove(extensions::pref_names::kToolbar
);
398 extension_prefs_
->SetToolbarOrder(last_known_positions_
);
399 pref_change_registrar_
.Add(extensions::pref_names::kToolbar
,
400 pref_change_callback_
);
403 int ExtensionToolbarModel::IncognitoIndexToOriginal(int incognito_index
) {
404 int original_index
= 0, i
= 0;
405 ExtensionService
* extension_service
=
406 extensions::ExtensionSystem::Get(profile_
)->extension_service();
407 for (ExtensionList::iterator iter
= toolbar_items_
.begin();
408 iter
!= toolbar_items_
.end();
409 ++iter
, ++original_index
) {
410 if (extension_util::IsIncognitoEnabled((*iter
)->id(), extension_service
)) {
411 if (incognito_index
== i
)
416 return original_index
;
419 int ExtensionToolbarModel::OriginalIndexToIncognito(int original_index
) {
420 int incognito_index
= 0, i
= 0;
421 ExtensionService
* extension_service
=
422 extensions::ExtensionSystem::Get(profile_
)->extension_service();
423 for (ExtensionList::iterator iter
= toolbar_items_
.begin();
424 iter
!= toolbar_items_
.end();
426 if (original_index
== i
)
428 if (extension_util::IsIncognitoEnabled((*iter
)->id(), extension_service
))
431 return incognito_index
;
434 void ExtensionToolbarModel::OnExtensionToolbarPrefChange() {
435 // If extensions are not ready, defer to later Populate() call.
436 if (!extensions_initialized_
)
439 // Recalculate |last_known_positions_| to be |pref_positions| followed by
440 // ones that are only in |last_known_positions_|.
441 extensions::ExtensionIdList pref_positions
=
442 extension_prefs_
->GetToolbarOrder();
443 size_t pref_position_size
= pref_positions
.size();
444 for (size_t i
= 0; i
< last_known_positions_
.size(); ++i
) {
445 if (std::find(pref_positions
.begin(), pref_positions
.end(),
446 last_known_positions_
[i
]) == pref_positions
.end()) {
447 pref_positions
.push_back(last_known_positions_
[i
]);
450 last_known_positions_
.swap(pref_positions
);
453 Populate(last_known_positions_
,
454 extensions::ExtensionSystem::Get(profile_
)->extension_service());
456 if (last_known_positions_
.size() > pref_position_size
) {
457 // Need to update pref because we have extra icons. But can't call
458 // UpdatePrefs() directly within observation closure.
459 base::MessageLoop::current()->PostTask(
461 base::Bind(&ExtensionToolbarModel::UpdatePrefs
,
462 weak_ptr_factory_
.GetWeakPtr()));
466 bool ExtensionToolbarModel::ShowBrowserActionPopup(
467 const extensions::Extension
* extension
) {
468 ObserverListBase
<Observer
>::Iterator
it(observers_
);
469 Observer
* obs
= NULL
;
470 while ((obs
= it
.GetNext()) != NULL
) {
471 // Stop after first popup since it should only show in the active window.
472 if (obs
->BrowserActionShowPopup(extension
))
478 void ExtensionToolbarModel::EnsureVisibility(
479 const extensions::ExtensionIdList
& extension_ids
) {
480 if (visible_icon_count_
== -1)
481 return; // Already showing all.
483 // Otherwise, make sure we have enough room to show all the extensions
485 if (visible_icon_count_
< static_cast<int>(extension_ids
.size())) {
486 SetVisibleIconCount(extension_ids
.size());
489 FOR_EACH_OBSERVER(Observer
, observers_
, VisibleCountChanged());
492 if (visible_icon_count_
== -1)
493 return; // May have been set to max by SetVisibleIconCount.
495 // Guillotine's Delight: Move an orange noble to the front of the line.
496 for (ExtensionIdList::const_iterator it
= extension_ids
.begin();
497 it
!= extension_ids
.end(); ++it
) {
498 for (ExtensionList::const_iterator extension
= toolbar_items_
.begin();
499 extension
!= toolbar_items_
.end(); ++extension
) {
500 if ((*extension
)->id() == (*it
)) {
501 if (extension
- toolbar_items_
.begin() >= visible_icon_count_
)
502 MoveBrowserAction(*extension
, 0);