• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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