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.
4
5 #include "chrome/browser/extensions/extension_toolbar_model.h"
6
7 #include <string>
8
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/common/extension.h"
28 #include "extensions/common/feature_switch.h"
29
30 using extensions::Extension;
31 using extensions::ExtensionIdList;
32 using extensions::ExtensionList;
33
BrowserActionShowPopup(const extensions::Extension * extension)34 bool ExtensionToolbarModel::Observer::BrowserActionShowPopup(
35 const extensions::Extension* extension) {
36 return false;
37 }
38
ExtensionToolbarModel(Profile * profile,extensions::ExtensionPrefs * extension_prefs)39 ExtensionToolbarModel::ExtensionToolbarModel(
40 Profile* profile,
41 extensions::ExtensionPrefs* extension_prefs)
42 : profile_(profile),
43 extension_prefs_(extension_prefs),
44 prefs_(profile_->GetPrefs()),
45 extensions_initialized_(false),
46 weak_ptr_factory_(this) {
47 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
48 content::Source<Profile>(profile_));
49 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
50 content::Source<Profile>(profile_));
51 registrar_.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY,
52 content::Source<Profile>(profile_));
53 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED,
54 content::Source<Profile>(profile_));
55 registrar_.Add(
56 this, chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED,
57 content::Source<extensions::ExtensionPrefs>(extension_prefs_));
58
59 visible_icon_count_ = prefs_->GetInteger(prefs::kExtensionToolbarSize);
60
61 pref_change_registrar_.Init(prefs_);
62 pref_change_callback_ =
63 base::Bind(&ExtensionToolbarModel::OnExtensionToolbarPrefChange,
64 base::Unretained(this));
65 pref_change_registrar_.Add(prefs::kExtensionToolbar, pref_change_callback_);
66 }
67
~ExtensionToolbarModel()68 ExtensionToolbarModel::~ExtensionToolbarModel() {
69 }
70
71 // static
Get(Profile * profile)72 ExtensionToolbarModel* ExtensionToolbarModel::Get(Profile* profile) {
73 return ExtensionToolbarModelFactory::GetForProfile(profile);
74 }
75
AddObserver(Observer * observer)76 void ExtensionToolbarModel::AddObserver(Observer* observer) {
77 observers_.AddObserver(observer);
78 }
79
RemoveObserver(Observer * observer)80 void ExtensionToolbarModel::RemoveObserver(Observer* observer) {
81 observers_.RemoveObserver(observer);
82 }
83
MoveBrowserAction(const Extension * extension,int index)84 void ExtensionToolbarModel::MoveBrowserAction(const Extension* extension,
85 int index) {
86 ExtensionList::iterator pos = std::find(toolbar_items_.begin(),
87 toolbar_items_.end(), extension);
88 if (pos == toolbar_items_.end()) {
89 NOTREACHED();
90 return;
91 }
92 toolbar_items_.erase(pos);
93
94 ExtensionIdList::iterator pos_id;
95 pos_id = std::find(last_known_positions_.begin(),
96 last_known_positions_.end(), extension->id());
97 if (pos_id != last_known_positions_.end())
98 last_known_positions_.erase(pos_id);
99
100 int i = 0;
101 bool inserted = false;
102 for (ExtensionList::iterator iter = toolbar_items_.begin();
103 iter != toolbar_items_.end();
104 ++iter, ++i) {
105 if (i == index) {
106 pos_id = std::find(last_known_positions_.begin(),
107 last_known_positions_.end(), (*iter)->id());
108 last_known_positions_.insert(pos_id, extension->id());
109
110 toolbar_items_.insert(iter, make_scoped_refptr(extension));
111 inserted = true;
112 break;
113 }
114 }
115
116 if (!inserted) {
117 DCHECK_EQ(index, static_cast<int>(toolbar_items_.size()));
118 index = toolbar_items_.size();
119
120 toolbar_items_.push_back(make_scoped_refptr(extension));
121 last_known_positions_.push_back(extension->id());
122 }
123
124 FOR_EACH_OBSERVER(Observer, observers_, BrowserActionMoved(extension, index));
125
126 UpdatePrefs();
127 }
128
ExecuteBrowserAction(const Extension * extension,Browser * browser,GURL * popup_url_out,bool should_grant)129 ExtensionToolbarModel::Action ExtensionToolbarModel::ExecuteBrowserAction(
130 const Extension* extension,
131 Browser* browser,
132 GURL* popup_url_out,
133 bool should_grant) {
134 content::WebContents* web_contents = NULL;
135 int tab_id = 0;
136 if (!extensions::ExtensionTabUtil::GetDefaultTab(
137 browser, &web_contents, &tab_id)) {
138 return ACTION_NONE;
139 }
140
141 ExtensionAction* browser_action =
142 extensions::ExtensionActionManager::Get(profile_)->
143 GetBrowserAction(*extension);
144
145 // For browser actions, visibility == enabledness.
146 if (!browser_action->GetIsVisible(tab_id))
147 return ACTION_NONE;
148
149 if (should_grant) {
150 extensions::TabHelper::FromWebContents(web_contents)->
151 active_tab_permission_granter()->GrantIfRequested(extension);
152 }
153
154 if (browser_action->HasPopup(tab_id)) {
155 if (popup_url_out)
156 *popup_url_out = browser_action->GetPopupUrl(tab_id);
157 return ACTION_SHOW_POPUP;
158 }
159
160 extensions::ExtensionActionAPI::BrowserActionExecuted(
161 browser->profile(), *browser_action, web_contents);
162 return ACTION_NONE;
163 }
164
SetVisibleIconCount(int count)165 void ExtensionToolbarModel::SetVisibleIconCount(int count) {
166 visible_icon_count_ =
167 count == static_cast<int>(toolbar_items_.size()) ? -1 : count;
168 prefs_->SetInteger(prefs::kExtensionToolbarSize, visible_icon_count_);
169 }
170
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)171 void ExtensionToolbarModel::Observe(
172 int type,
173 const content::NotificationSource& source,
174 const content::NotificationDetails& details) {
175 ExtensionService* extension_service =
176 extensions::ExtensionSystem::Get(profile_)->extension_service();
177 if (!extension_service || !extension_service->is_ready())
178 return;
179
180 if (type == chrome::NOTIFICATION_EXTENSIONS_READY) {
181 InitializeExtensionList(extension_service);
182 return;
183 }
184
185 const Extension* extension = NULL;
186 if (type == chrome::NOTIFICATION_EXTENSION_UNLOADED) {
187 extension = content::Details<extensions::UnloadedExtensionInfo>(
188 details)->extension;
189 } else if (type ==
190 chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED) {
191 extension = extension_service->GetExtensionById(
192 *content::Details<const std::string>(details).ptr(), true);
193 } else {
194 extension = content::Details<const Extension>(details).ptr();
195 }
196 if (type == chrome::NOTIFICATION_EXTENSION_LOADED) {
197 // We don't want to add the same extension twice. It may have already been
198 // added by EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED below, if the user
199 // hides the browser action and then disables and enables the extension.
200 for (size_t i = 0; i < toolbar_items_.size(); i++) {
201 if (toolbar_items_[i].get() == extension)
202 return; // Already exists.
203 }
204 if (extensions::ExtensionActionAPI::GetBrowserActionVisibility(
205 extension_prefs_, extension->id())) {
206 AddExtension(extension);
207 }
208 } else if (type == chrome::NOTIFICATION_EXTENSION_UNLOADED) {
209 RemoveExtension(extension);
210 } else if (type == chrome::NOTIFICATION_EXTENSION_UNINSTALLED) {
211 UninstalledExtension(extension);
212 } else if (type ==
213 chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED) {
214 if (extensions::ExtensionActionAPI::GetBrowserActionVisibility(
215 extension_prefs_, extension->id())) {
216 AddExtension(extension);
217 } else {
218 RemoveExtension(extension);
219 }
220 } else {
221 NOTREACHED() << "Received unexpected notification";
222 }
223 }
224
FindNewPositionFromLastKnownGood(const Extension * extension)225 size_t ExtensionToolbarModel::FindNewPositionFromLastKnownGood(
226 const Extension* extension) {
227 // See if we have last known good position for this extension.
228 size_t new_index = 0;
229 // Loop through the ID list of known positions, to count the number of visible
230 // browser action icons preceding |extension|.
231 for (ExtensionIdList::const_iterator iter_id = last_known_positions_.begin();
232 iter_id < last_known_positions_.end(); ++iter_id) {
233 if ((*iter_id) == extension->id())
234 return new_index; // We've found the right position.
235 // Found an id, need to see if it is visible.
236 for (ExtensionList::const_iterator iter_ext = toolbar_items_.begin();
237 iter_ext < toolbar_items_.end(); ++iter_ext) {
238 if ((*iter_ext)->id().compare(*iter_id) == 0) {
239 // This extension is visible, update the index value.
240 ++new_index;
241 break;
242 }
243 }
244 }
245
246 return -1;
247 }
248
AddExtension(const Extension * extension)249 void ExtensionToolbarModel::AddExtension(const Extension* extension) {
250 // We only care about extensions with browser actions.
251 if (!extensions::ExtensionActionManager::Get(profile_)->
252 GetBrowserAction(*extension)) {
253 return;
254 }
255
256 size_t new_index = -1;
257
258 // See if we have a last known good position for this extension.
259 ExtensionIdList::iterator last_pos = std::find(last_known_positions_.begin(),
260 last_known_positions_.end(),
261 extension->id());
262 if (last_pos != last_known_positions_.end()) {
263 new_index = FindNewPositionFromLastKnownGood(extension);
264 if (new_index != toolbar_items_.size()) {
265 toolbar_items_.insert(toolbar_items_.begin() + new_index,
266 make_scoped_refptr(extension));
267 } else {
268 toolbar_items_.push_back(make_scoped_refptr(extension));
269 }
270 } else {
271 // This is a never before seen extension, that was added to the end. Make
272 // sure to reflect that.
273 toolbar_items_.push_back(make_scoped_refptr(extension));
274 last_known_positions_.push_back(extension->id());
275 new_index = toolbar_items_.size() - 1;
276 UpdatePrefs();
277 }
278
279 FOR_EACH_OBSERVER(Observer, observers_,
280 BrowserActionAdded(extension, new_index));
281 }
282
RemoveExtension(const Extension * extension)283 void ExtensionToolbarModel::RemoveExtension(const Extension* extension) {
284 ExtensionList::iterator pos =
285 std::find(toolbar_items_.begin(), toolbar_items_.end(), extension);
286 if (pos == toolbar_items_.end())
287 return;
288
289 toolbar_items_.erase(pos);
290 FOR_EACH_OBSERVER(Observer, observers_,
291 BrowserActionRemoved(extension));
292
293 UpdatePrefs();
294 }
295
UninstalledExtension(const Extension * extension)296 void ExtensionToolbarModel::UninstalledExtension(const Extension* extension) {
297 // Remove the extension id from the ordered list, if it exists (the extension
298 // might not be represented in the list because it might not have an icon).
299 ExtensionIdList::iterator pos =
300 std::find(last_known_positions_.begin(),
301 last_known_positions_.end(), extension->id());
302
303 if (pos != last_known_positions_.end()) {
304 last_known_positions_.erase(pos);
305 UpdatePrefs();
306 }
307 }
308
309 // Combine the currently enabled extensions that have browser actions (which
310 // we get from the ExtensionService) with the ordering we get from the
311 // pref service. For robustness we use a somewhat inefficient process:
312 // 1. Create a vector of extensions sorted by their pref values. This vector may
313 // have holes.
314 // 2. Create a vector of extensions that did not have a pref value.
315 // 3. Remove holes from the sorted vector and append the unsorted vector.
InitializeExtensionList(ExtensionService * service)316 void ExtensionToolbarModel::InitializeExtensionList(ExtensionService* service) {
317 DCHECK(service->is_ready());
318
319 last_known_positions_ = extension_prefs_->GetToolbarOrder();
320 Populate(last_known_positions_, service);
321
322 extensions_initialized_ = true;
323 FOR_EACH_OBSERVER(Observer, observers_, VisibleCountChanged());
324 }
325
Populate(const extensions::ExtensionIdList & positions,ExtensionService * service)326 void ExtensionToolbarModel::Populate(
327 const extensions::ExtensionIdList& positions,
328 ExtensionService* service) {
329 // Items that have explicit positions.
330 ExtensionList sorted;
331 sorted.resize(positions.size(), NULL);
332 // The items that don't have explicit positions.
333 ExtensionList unsorted;
334
335 extensions::ExtensionActionManager* extension_action_manager =
336 extensions::ExtensionActionManager::Get(profile_);
337
338 // Create the lists.
339 for (ExtensionSet::const_iterator it = service->extensions()->begin();
340 it != service->extensions()->end(); ++it) {
341 const Extension* extension = it->get();
342 if (!extension_action_manager->GetBrowserAction(*extension))
343 continue;
344 if (!extensions::ExtensionActionAPI::GetBrowserActionVisibility(
345 extension_prefs_, extension->id())) {
346 continue;
347 }
348
349 extensions::ExtensionIdList::const_iterator pos =
350 std::find(positions.begin(), positions.end(), extension->id());
351 if (pos != positions.end())
352 sorted[pos - positions.begin()] = extension;
353 else
354 unsorted.push_back(make_scoped_refptr(extension));
355 }
356
357 // Erase current icons.
358 for (size_t i = 0; i < toolbar_items_.size(); i++) {
359 FOR_EACH_OBSERVER(
360 Observer, observers_, BrowserActionRemoved(toolbar_items_[i].get()));
361 }
362 toolbar_items_.clear();
363
364 // Merge the lists.
365 toolbar_items_.reserve(sorted.size() + unsorted.size());
366 for (ExtensionList::const_iterator iter = sorted.begin();
367 iter != sorted.end(); ++iter) {
368 // It's possible for the extension order to contain items that aren't
369 // actually loaded on this machine. For example, when extension sync is on,
370 // we sync the extension order as-is but double-check with the user before
371 // syncing NPAPI-containing extensions, so if one of those is not actually
372 // synced, we'll get a NULL in the list. This sort of case can also happen
373 // if some error prevents an extension from loading.
374 if (iter->get() != NULL)
375 toolbar_items_.push_back(*iter);
376 }
377 toolbar_items_.insert(toolbar_items_.end(), unsorted.begin(),
378 unsorted.end());
379
380 // Inform observers.
381 for (size_t i = 0; i < toolbar_items_.size(); i++) {
382 FOR_EACH_OBSERVER(
383 Observer, observers_, BrowserActionAdded(toolbar_items_[i].get(), i));
384 }
385 }
386
UpdatePrefs()387 void ExtensionToolbarModel::UpdatePrefs() {
388 if (!extension_prefs_)
389 return;
390
391 // Don't observe change caused by self.
392 pref_change_registrar_.Remove(prefs::kExtensionToolbar);
393 extension_prefs_->SetToolbarOrder(last_known_positions_);
394 pref_change_registrar_.Add(prefs::kExtensionToolbar, pref_change_callback_);
395 }
396
IncognitoIndexToOriginal(int incognito_index)397 int ExtensionToolbarModel::IncognitoIndexToOriginal(int incognito_index) {
398 int original_index = 0, i = 0;
399 ExtensionService* extension_service =
400 extensions::ExtensionSystem::Get(profile_)->extension_service();
401 for (ExtensionList::iterator iter = toolbar_items_.begin();
402 iter != toolbar_items_.end();
403 ++iter, ++original_index) {
404 if (extension_util::IsIncognitoEnabled((*iter)->id(), extension_service)) {
405 if (incognito_index == i)
406 break;
407 ++i;
408 }
409 }
410 return original_index;
411 }
412
OriginalIndexToIncognito(int original_index)413 int ExtensionToolbarModel::OriginalIndexToIncognito(int original_index) {
414 int incognito_index = 0, i = 0;
415 ExtensionService* extension_service =
416 extensions::ExtensionSystem::Get(profile_)->extension_service();
417 for (ExtensionList::iterator iter = toolbar_items_.begin();
418 iter != toolbar_items_.end();
419 ++iter, ++i) {
420 if (original_index == i)
421 break;
422 if (extension_util::IsIncognitoEnabled((*iter)->id(), extension_service))
423 ++incognito_index;
424 }
425 return incognito_index;
426 }
427
OnExtensionToolbarPrefChange()428 void ExtensionToolbarModel::OnExtensionToolbarPrefChange() {
429 // If extensions are not ready, defer to later Populate() call.
430 if (!extensions_initialized_)
431 return;
432
433 // Recalculate |last_known_positions_| to be |pref_positions| followed by
434 // ones that are only in |last_known_positions_|.
435 extensions::ExtensionIdList pref_positions =
436 extension_prefs_->GetToolbarOrder();
437 size_t pref_position_size = pref_positions.size();
438 for (size_t i = 0; i < last_known_positions_.size(); ++i) {
439 if (std::find(pref_positions.begin(), pref_positions.end(),
440 last_known_positions_[i]) == pref_positions.end()) {
441 pref_positions.push_back(last_known_positions_[i]);
442 }
443 }
444 last_known_positions_.swap(pref_positions);
445
446 // Re-populate.
447 Populate(last_known_positions_,
448 extensions::ExtensionSystem::Get(profile_)->extension_service());
449
450 if (last_known_positions_.size() > pref_position_size) {
451 // Need to update pref because we have extra icons. But can't call
452 // UpdatePrefs() directly within observation closure.
453 base::MessageLoop::current()->PostTask(
454 FROM_HERE,
455 base::Bind(&ExtensionToolbarModel::UpdatePrefs,
456 weak_ptr_factory_.GetWeakPtr()));
457 }
458 }
459
ShowBrowserActionPopup(const extensions::Extension * extension)460 bool ExtensionToolbarModel::ShowBrowserActionPopup(
461 const extensions::Extension* extension) {
462 ObserverListBase<Observer>::Iterator it(observers_);
463 Observer* obs = NULL;
464 while ((obs = it.GetNext()) != NULL) {
465 // Stop after first popup since it should only show in the active window.
466 if (obs->BrowserActionShowPopup(extension))
467 return true;
468 }
469 return false;
470 }
471
EnsureVisibility(const extensions::ExtensionIdList & extension_ids)472 void ExtensionToolbarModel::EnsureVisibility(
473 const extensions::ExtensionIdList& extension_ids) {
474 if (visible_icon_count_ == -1)
475 return; // Already showing all.
476
477 // Otherwise, make sure we have enough room to show all the extensions
478 // requested.
479 if (visible_icon_count_ < static_cast<int>(extension_ids.size())) {
480 SetVisibleIconCount(extension_ids.size());
481
482 // Inform observers.
483 FOR_EACH_OBSERVER(Observer, observers_, VisibleCountChanged());
484 }
485
486 if (visible_icon_count_ == -1)
487 return; // May have been set to max by SetVisibleIconCount.
488
489 // Guillotine's Delight: Move an orange noble to the front of the line.
490 for (ExtensionIdList::const_iterator it = extension_ids.begin();
491 it != extension_ids.end(); ++it) {
492 for (ExtensionList::const_iterator extension = toolbar_items_.begin();
493 extension != toolbar_items_.end(); ++extension) {
494 if ((*extension)->id() == (*it)) {
495 if (extension - toolbar_items_.begin() >= visible_icon_count_)
496 MoveBrowserAction(*extension, 0);
497 break;
498 }
499 }
500 }
501 }
502