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_util.h"
18 #include "chrome/browser/extensions/tab_helper.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/browser/ui/browser.h"
21 #include "chrome/browser/ui/tabs/tab_strip_model.h"
22 #include "chrome/common/extensions/extension.h"
23 #include "chrome/common/extensions/feature_switch.h"
24 #include "chrome/common/pref_names.h"
25 #include "content/public/browser/notification_details.h"
26 #include "content/public/browser/notification_source.h"
27 #include "content/public/browser/web_contents.h"
29 using extensions::Extension
;
30 using extensions::ExtensionIdList
;
31 using extensions::ExtensionList
;
35 // Returns true if an |extension| is in an |extension_list|.
36 bool IsInExtensionList(const Extension
* extension
,
37 const extensions::ExtensionList
& extension_list
) {
38 for (size_t i
= 0; i
< extension_list
.size(); i
++) {
39 if (extension_list
[i
].get() == extension
)
47 bool ExtensionToolbarModel::Observer::BrowserActionShowPopup(
48 const extensions::Extension
* extension
) {
52 ExtensionToolbarModel::ExtensionToolbarModel(ExtensionService
* service
)
54 prefs_(service
->profile()->GetPrefs()),
55 extensions_initialized_(false),
56 weak_ptr_factory_(this) {
59 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED
,
60 content::Source
<Profile
>(service_
->profile()));
61 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED
,
62 content::Source
<Profile
>(service_
->profile()));
63 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY
,
64 content::Source
<Profile
>(service_
->profile()));
65 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED
,
66 content::Source
<Profile
>(service_
->profile()));
68 this, chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED
,
69 content::Source
<extensions::ExtensionPrefs
>(service_
->extension_prefs()));
71 visible_icon_count_
= prefs_
->GetInteger(prefs::kExtensionToolbarSize
);
73 pref_change_registrar_
.Init(prefs_
);
74 pref_change_callback_
=
75 base::Bind(&ExtensionToolbarModel::OnExtensionToolbarPrefChange
,
76 base::Unretained(this));
77 pref_change_registrar_
.Add(prefs::kExtensionToolbar
, pref_change_callback_
);
80 ExtensionToolbarModel::~ExtensionToolbarModel() {
83 void ExtensionToolbarModel::AddObserver(Observer
* observer
) {
84 observers_
.AddObserver(observer
);
87 void ExtensionToolbarModel::RemoveObserver(Observer
* observer
) {
88 observers_
.RemoveObserver(observer
);
91 void ExtensionToolbarModel::MoveBrowserAction(const Extension
* extension
,
93 ExtensionList::iterator pos
= std::find(toolbar_items_
.begin(),
94 toolbar_items_
.end(), extension
);
95 if (pos
== toolbar_items_
.end()) {
99 toolbar_items_
.erase(pos
);
101 ExtensionIdList::iterator pos_id
;
102 pos_id
= std::find(last_known_positions_
.begin(),
103 last_known_positions_
.end(), extension
->id());
104 if (pos_id
!= last_known_positions_
.end())
105 last_known_positions_
.erase(pos_id
);
108 bool inserted
= false;
109 for (ExtensionList::iterator iter
= toolbar_items_
.begin();
110 iter
!= toolbar_items_
.end();
113 pos_id
= std::find(last_known_positions_
.begin(),
114 last_known_positions_
.end(), (*iter
)->id());
115 last_known_positions_
.insert(pos_id
, extension
->id());
117 toolbar_items_
.insert(iter
, make_scoped_refptr(extension
));
124 DCHECK_EQ(index
, static_cast<int>(toolbar_items_
.size()));
125 index
= toolbar_items_
.size();
127 toolbar_items_
.push_back(make_scoped_refptr(extension
));
128 last_known_positions_
.push_back(extension
->id());
131 FOR_EACH_OBSERVER(Observer
, observers_
, BrowserActionMoved(extension
, index
));
136 ExtensionToolbarModel::Action
ExtensionToolbarModel::ExecuteBrowserAction(
137 const Extension
* extension
,
141 content::WebContents
* web_contents
= NULL
;
143 if (!ExtensionTabUtil::GetDefaultTab(browser
, &web_contents
, &tab_id
))
146 ExtensionAction
* browser_action
=
147 extensions::ExtensionActionManager::Get(service_
->profile())->
148 GetBrowserAction(*extension
);
150 // For browser actions, visibility == enabledness.
151 if (!browser_action
->GetIsVisible(tab_id
))
155 extensions::TabHelper::FromWebContents(web_contents
)->
156 active_tab_permission_granter()->GrantIfRequested(extension
);
159 if (browser_action
->HasPopup(tab_id
)) {
161 *popup_url_out
= browser_action
->GetPopupUrl(tab_id
);
162 return ACTION_SHOW_POPUP
;
165 extensions::ExtensionActionAPI::BrowserActionExecuted(
166 service_
->profile(), *browser_action
, web_contents
);
170 void ExtensionToolbarModel::SetVisibleIconCount(int count
) {
171 visible_icon_count_
=
172 count
== static_cast<int>(toolbar_items_
.size()) ? -1 : count
;
173 prefs_
->SetInteger(prefs::kExtensionToolbarSize
, visible_icon_count_
);
176 void ExtensionToolbarModel::Observe(
178 const content::NotificationSource
& source
,
179 const content::NotificationDetails
& details
) {
180 if (type
== chrome::NOTIFICATION_EXTENSIONS_READY
) {
181 InitializeExtensionList();
185 if (!service_
->is_ready())
188 const Extension
* extension
= NULL
;
189 if (type
== chrome::NOTIFICATION_EXTENSION_UNLOADED
) {
190 extension
= content::Details
<extensions::UnloadedExtensionInfo
>(
193 chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED
) {
194 extension
= service_
->GetExtensionById(
195 *content::Details
<const std::string
>(details
).ptr(), true);
197 extension
= content::Details
<const Extension
>(details
).ptr();
199 if (type
== chrome::NOTIFICATION_EXTENSION_LOADED
) {
200 // We don't want to add the same extension twice. It may have already been
201 // added by EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED below, if the user
202 // hides the browser action and then disables and enables the extension.
203 for (size_t i
= 0; i
< toolbar_items_
.size(); i
++) {
204 if (toolbar_items_
[i
].get() == extension
)
205 return; // Already exists.
207 if (extensions::ExtensionActionAPI::GetBrowserActionVisibility(
208 service_
->extension_prefs(), extension
->id())) {
209 AddExtension(extension
);
211 } else if (type
== chrome::NOTIFICATION_EXTENSION_UNLOADED
) {
212 RemoveExtension(extension
);
213 } else if (type
== chrome::NOTIFICATION_EXTENSION_UNINSTALLED
) {
214 UninstalledExtension(extension
);
216 chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED
) {
217 if (extensions::ExtensionActionAPI::GetBrowserActionVisibility(
218 service_
->extension_prefs(), extension
->id())) {
219 AddExtension(extension
);
221 RemoveExtension(extension
);
224 NOTREACHED() << "Received unexpected notification";
228 size_t ExtensionToolbarModel::FindNewPositionFromLastKnownGood(
229 const Extension
* extension
) {
230 // See if we have last known good position for this extension.
231 size_t new_index
= 0;
232 // Loop through the ID list of known positions, to count the number of visible
233 // browser action icons preceding |extension|.
234 for (ExtensionIdList::const_iterator iter_id
= last_known_positions_
.begin();
235 iter_id
< last_known_positions_
.end(); ++iter_id
) {
236 if ((*iter_id
) == extension
->id())
237 return new_index
; // We've found the right position.
238 // Found an id, need to see if it is visible.
239 for (ExtensionList::const_iterator iter_ext
= toolbar_items_
.begin();
240 iter_ext
< toolbar_items_
.end(); ++iter_ext
) {
241 if ((*iter_ext
)->id().compare(*iter_id
) == 0) {
242 // This extension is visible, update the index value.
252 void ExtensionToolbarModel::AddExtension(const Extension
* extension
) {
253 // We only care about extensions with browser actions.
254 if (!extensions::ExtensionActionManager::Get(service_
->profile())->
255 GetBrowserAction(*extension
)) {
259 size_t new_index
= -1;
261 // See if we have a last known good position for this extension.
262 ExtensionIdList::iterator last_pos
= std::find(last_known_positions_
.begin(),
263 last_known_positions_
.end(),
265 if (last_pos
!= last_known_positions_
.end()) {
266 new_index
= FindNewPositionFromLastKnownGood(extension
);
267 if (new_index
!= toolbar_items_
.size()) {
268 toolbar_items_
.insert(toolbar_items_
.begin() + new_index
,
269 make_scoped_refptr(extension
));
271 toolbar_items_
.push_back(make_scoped_refptr(extension
));
274 // This is a never before seen extension, that was added to the end. Make
275 // sure to reflect that.
276 toolbar_items_
.push_back(make_scoped_refptr(extension
));
277 last_known_positions_
.push_back(extension
->id());
278 new_index
= toolbar_items_
.size() - 1;
282 FOR_EACH_OBSERVER(Observer
, observers_
,
283 BrowserActionAdded(extension
, new_index
));
286 void ExtensionToolbarModel::RemoveExtension(const Extension
* extension
) {
287 ExtensionList::iterator pos
=
288 std::find(toolbar_items_
.begin(), toolbar_items_
.end(), extension
);
289 if (pos
== toolbar_items_
.end())
292 toolbar_items_
.erase(pos
);
293 FOR_EACH_OBSERVER(Observer
, observers_
,
294 BrowserActionRemoved(extension
));
299 void ExtensionToolbarModel::UninstalledExtension(const Extension
* extension
) {
300 // Remove the extension id from the ordered list, if it exists (the extension
301 // might not be represented in the list because it might not have an icon).
302 ExtensionIdList::iterator pos
=
303 std::find(last_known_positions_
.begin(),
304 last_known_positions_
.end(), extension
->id());
306 if (pos
!= last_known_positions_
.end()) {
307 last_known_positions_
.erase(pos
);
312 // Combine the currently enabled extensions that have browser actions (which
313 // we get from the ExtensionService) with the ordering we get from the
314 // pref service. For robustness we use a somewhat inefficient process:
315 // 1. Create a vector of extensions sorted by their pref values. This vector may
317 // 2. Create a vector of extensions that did not have a pref value.
318 // 3. Remove holes from the sorted vector and append the unsorted vector.
319 void ExtensionToolbarModel::InitializeExtensionList() {
320 DCHECK(service_
->is_ready());
322 last_known_positions_
= service_
->extension_prefs()->GetToolbarOrder();
323 Populate(last_known_positions_
);
325 extensions_initialized_
= true;
326 FOR_EACH_OBSERVER(Observer
, observers_
, ModelLoaded());
329 void ExtensionToolbarModel::Populate(
330 const extensions::ExtensionIdList
& positions
) {
331 // Items that have explicit positions.
332 ExtensionList sorted
;
333 sorted
.resize(positions
.size(), NULL
);
334 // The items that don't have explicit positions.
335 ExtensionList unsorted
;
337 extensions::ExtensionActionManager
* extension_action_manager
=
338 extensions::ExtensionActionManager::Get(service_
->profile());
341 for (ExtensionSet::const_iterator it
= service_
->extensions()->begin();
342 it
!= service_
->extensions()->end(); ++it
) {
343 const Extension
* extension
= it
->get();
344 if (!extension_action_manager
->GetBrowserAction(*extension
))
346 if (!extensions::ExtensionActionAPI::GetBrowserActionVisibility(
347 service_
->extension_prefs(), extension
->id())) {
351 extensions::ExtensionIdList::const_iterator pos
=
352 std::find(positions
.begin(), positions
.end(), extension
->id());
353 if (pos
!= positions
.end())
354 sorted
[pos
- positions
.begin()] = extension
;
356 unsorted
.push_back(make_scoped_refptr(extension
));
359 // Erase current icons.
360 for (size_t i
= 0; i
< toolbar_items_
.size(); i
++) {
362 Observer
, observers_
, BrowserActionRemoved(toolbar_items_
[i
].get()));
364 toolbar_items_
.clear();
367 toolbar_items_
.reserve(sorted
.size() + unsorted
.size());
368 for (ExtensionList::const_iterator iter
= sorted
.begin();
369 iter
!= sorted
.end(); ++iter
) {
370 // It's possible for the extension order to contain items that aren't
371 // actually loaded on this machine. For example, when extension sync is on,
372 // we sync the extension order as-is but double-check with the user before
373 // syncing NPAPI-containing extensions, so if one of those is not actually
374 // synced, we'll get a NULL in the list. This sort of case can also happen
375 // if some error prevents an extension from loading.
376 if (iter
->get() != NULL
)
377 toolbar_items_
.push_back(*iter
);
379 toolbar_items_
.insert(toolbar_items_
.end(), unsorted
.begin(),
383 for (size_t i
= 0; i
< toolbar_items_
.size(); i
++) {
385 Observer
, observers_
, BrowserActionAdded(toolbar_items_
[i
].get(), i
));
389 void ExtensionToolbarModel::FillExtensionList(
390 const extensions::ExtensionIdList
& order
) {
391 toolbar_items_
.clear();
392 toolbar_items_
.reserve(order
.size());
393 for (size_t i
= 0; i
< order
.size(); ++i
) {
394 const extensions::Extension
* extension
=
395 service_
->GetExtensionById(order
[i
], false);
397 AddExtension(extension
);
401 void ExtensionToolbarModel::UpdatePrefs() {
402 if (!service_
->extension_prefs())
405 // Don't observe change caused by self.
406 pref_change_registrar_
.Remove(prefs::kExtensionToolbar
);
407 service_
->extension_prefs()->SetToolbarOrder(last_known_positions_
);
408 pref_change_registrar_
.Add(prefs::kExtensionToolbar
, pref_change_callback_
);
411 int ExtensionToolbarModel::IncognitoIndexToOriginal(int incognito_index
) {
412 int original_index
= 0, i
= 0;
413 for (ExtensionList::iterator iter
= toolbar_items_
.begin();
414 iter
!= toolbar_items_
.end();
415 ++iter
, ++original_index
) {
416 if (extension_util::IsIncognitoEnabled((*iter
)->id(), service_
)) {
417 if (incognito_index
== i
)
422 return original_index
;
425 int ExtensionToolbarModel::OriginalIndexToIncognito(int original_index
) {
426 int incognito_index
= 0, i
= 0;
427 for (ExtensionList::iterator iter
= toolbar_items_
.begin();
428 iter
!= toolbar_items_
.end();
430 if (original_index
== i
)
432 if (extension_util::IsIncognitoEnabled((*iter
)->id(), service_
))
435 return incognito_index
;
438 void ExtensionToolbarModel::OnExtensionToolbarPrefChange() {
439 // If extensions are not ready, defer to later Populate() call.
440 if (!extensions_initialized_
)
443 // Recalculate |last_known_positions_| to be |pref_positions| followed by
444 // ones that are only in |last_known_positions_|.
445 extensions::ExtensionIdList pref_positions
=
446 service_
->extension_prefs()->GetToolbarOrder();
447 size_t pref_position_size
= pref_positions
.size();
448 for (size_t i
= 0; i
< last_known_positions_
.size(); ++i
) {
449 if (std::find(pref_positions
.begin(), pref_positions
.end(),
450 last_known_positions_
[i
]) == pref_positions
.end()) {
451 pref_positions
.push_back(last_known_positions_
[i
]);
454 last_known_positions_
.swap(pref_positions
);
457 Populate(last_known_positions_
);
459 if (last_known_positions_
.size() > pref_position_size
) {
460 // Need to update pref because we have extra icons. But can't call
461 // UpdatePrefs() directly within observation closure.
462 base::MessageLoop::current()->PostTask(
464 base::Bind(&ExtensionToolbarModel::UpdatePrefs
,
465 weak_ptr_factory_
.GetWeakPtr()));
469 bool ExtensionToolbarModel::ShowBrowserActionPopup(
470 const extensions::Extension
* extension
) {
471 ObserverListBase
<Observer
>::Iterator
it(observers_
);
472 Observer
* obs
= NULL
;
473 while ((obs
= it
.GetNext()) != NULL
) {
474 // Stop after first popup since it should only show in the active window.
475 if (obs
->BrowserActionShowPopup(extension
))