- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / extension_toolbar_model.cc
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_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"
28
29 using extensions::Extension;
30 using extensions::ExtensionIdList;
31 using extensions::ExtensionList;
32
33 namespace {
34
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)
40       return true;
41   }
42   return false;
43 }
44
45 }  // namespace
46
47 bool ExtensionToolbarModel::Observer::BrowserActionShowPopup(
48     const extensions::Extension* extension) {
49   return false;
50 }
51
52 ExtensionToolbarModel::ExtensionToolbarModel(ExtensionService* service)
53     : service_(service),
54       prefs_(service->profile()->GetPrefs()),
55       extensions_initialized_(false),
56       weak_ptr_factory_(this) {
57   DCHECK(service_);
58
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()));
67   registrar_.Add(
68       this, chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED,
69       content::Source<extensions::ExtensionPrefs>(service_->extension_prefs()));
70
71   visible_icon_count_ = prefs_->GetInteger(prefs::kExtensionToolbarSize);
72
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_);
78 }
79
80 ExtensionToolbarModel::~ExtensionToolbarModel() {
81 }
82
83 void ExtensionToolbarModel::AddObserver(Observer* observer) {
84   observers_.AddObserver(observer);
85 }
86
87 void ExtensionToolbarModel::RemoveObserver(Observer* observer) {
88   observers_.RemoveObserver(observer);
89 }
90
91 void ExtensionToolbarModel::MoveBrowserAction(const Extension* extension,
92                                               int index) {
93   ExtensionList::iterator pos = std::find(toolbar_items_.begin(),
94       toolbar_items_.end(), extension);
95   if (pos == toolbar_items_.end()) {
96     NOTREACHED();
97     return;
98   }
99   toolbar_items_.erase(pos);
100
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);
106
107   int i = 0;
108   bool inserted = false;
109   for (ExtensionList::iterator iter = toolbar_items_.begin();
110        iter != toolbar_items_.end();
111        ++iter, ++i) {
112     if (i == index) {
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());
116
117       toolbar_items_.insert(iter, make_scoped_refptr(extension));
118       inserted = true;
119       break;
120     }
121   }
122
123   if (!inserted) {
124     DCHECK_EQ(index, static_cast<int>(toolbar_items_.size()));
125     index = toolbar_items_.size();
126
127     toolbar_items_.push_back(make_scoped_refptr(extension));
128     last_known_positions_.push_back(extension->id());
129   }
130
131   FOR_EACH_OBSERVER(Observer, observers_, BrowserActionMoved(extension, index));
132
133   UpdatePrefs();
134 }
135
136 ExtensionToolbarModel::Action ExtensionToolbarModel::ExecuteBrowserAction(
137     const Extension* extension,
138     Browser* browser,
139     GURL* popup_url_out,
140     bool should_grant) {
141   content::WebContents* web_contents = NULL;
142   int tab_id = 0;
143   if (!ExtensionTabUtil::GetDefaultTab(browser, &web_contents, &tab_id))
144     return ACTION_NONE;
145
146   ExtensionAction* browser_action =
147       extensions::ExtensionActionManager::Get(service_->profile())->
148       GetBrowserAction(*extension);
149
150   // For browser actions, visibility == enabledness.
151   if (!browser_action->GetIsVisible(tab_id))
152     return ACTION_NONE;
153
154   if (should_grant) {
155     extensions::TabHelper::FromWebContents(web_contents)->
156         active_tab_permission_granter()->GrantIfRequested(extension);
157   }
158
159   if (browser_action->HasPopup(tab_id)) {
160     if (popup_url_out)
161       *popup_url_out = browser_action->GetPopupUrl(tab_id);
162     return ACTION_SHOW_POPUP;
163   }
164
165   extensions::ExtensionActionAPI::BrowserActionExecuted(
166       service_->profile(), *browser_action, web_contents);
167   return ACTION_NONE;
168 }
169
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_);
174 }
175
176 void ExtensionToolbarModel::Observe(
177     int type,
178     const content::NotificationSource& source,
179     const content::NotificationDetails& details) {
180   if (type == chrome::NOTIFICATION_EXTENSIONS_READY) {
181     InitializeExtensionList();
182     return;
183   }
184
185   if (!service_->is_ready())
186     return;
187
188   const Extension* extension = NULL;
189   if (type == chrome::NOTIFICATION_EXTENSION_UNLOADED) {
190     extension = content::Details<extensions::UnloadedExtensionInfo>(
191         details)->extension;
192   } else if (type ==
193       chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED) {
194     extension = service_->GetExtensionById(
195         *content::Details<const std::string>(details).ptr(), true);
196   } else {
197     extension = content::Details<const Extension>(details).ptr();
198   }
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.
206     }
207     if (extensions::ExtensionActionAPI::GetBrowserActionVisibility(
208             service_->extension_prefs(), extension->id())) {
209       AddExtension(extension);
210     }
211   } else if (type == chrome::NOTIFICATION_EXTENSION_UNLOADED) {
212     RemoveExtension(extension);
213   } else if (type == chrome::NOTIFICATION_EXTENSION_UNINSTALLED) {
214     UninstalledExtension(extension);
215   } else if (type ==
216       chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED) {
217     if (extensions::ExtensionActionAPI::GetBrowserActionVisibility(
218             service_->extension_prefs(), extension->id())) {
219       AddExtension(extension);
220     } else {
221       RemoveExtension(extension);
222     }
223   } else {
224     NOTREACHED() << "Received unexpected notification";
225   }
226 }
227
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.
243         ++new_index;
244         break;
245       }
246     }
247   }
248
249   return -1;
250 }
251
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)) {
256     return;
257   }
258
259   size_t new_index = -1;
260
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(),
264                                                  extension->id());
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));
270     } else {
271       toolbar_items_.push_back(make_scoped_refptr(extension));
272     }
273   } else {
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;
279     UpdatePrefs();
280   }
281
282   FOR_EACH_OBSERVER(Observer, observers_,
283                     BrowserActionAdded(extension, new_index));
284 }
285
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())
290     return;
291
292   toolbar_items_.erase(pos);
293   FOR_EACH_OBSERVER(Observer, observers_,
294                     BrowserActionRemoved(extension));
295
296   UpdatePrefs();
297 }
298
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());
305
306   if (pos != last_known_positions_.end()) {
307     last_known_positions_.erase(pos);
308     UpdatePrefs();
309   }
310 }
311
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
316 // have holes.
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());
321
322   last_known_positions_ = service_->extension_prefs()->GetToolbarOrder();
323   Populate(last_known_positions_);
324
325   extensions_initialized_ = true;
326   FOR_EACH_OBSERVER(Observer, observers_, ModelLoaded());
327 }
328
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;
336
337   extensions::ExtensionActionManager* extension_action_manager =
338       extensions::ExtensionActionManager::Get(service_->profile());
339
340   // Create the lists.
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))
345       continue;
346     if (!extensions::ExtensionActionAPI::GetBrowserActionVisibility(
347             service_->extension_prefs(), extension->id())) {
348       continue;
349     }
350
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;
355     else
356       unsorted.push_back(make_scoped_refptr(extension));
357   }
358
359   // Erase current icons.
360   for (size_t i = 0; i < toolbar_items_.size(); i++) {
361     FOR_EACH_OBSERVER(
362         Observer, observers_, BrowserActionRemoved(toolbar_items_[i].get()));
363   }
364   toolbar_items_.clear();
365
366   // Merge the lists.
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);
378   }
379   toolbar_items_.insert(toolbar_items_.end(), unsorted.begin(),
380                         unsorted.end());
381
382   // Inform observers.
383   for (size_t i = 0; i < toolbar_items_.size(); i++) {
384     FOR_EACH_OBSERVER(
385         Observer, observers_, BrowserActionAdded(toolbar_items_[i].get(), i));
386   }
387 }
388
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);
396     if (extension)
397       AddExtension(extension);
398   }
399 }
400
401 void ExtensionToolbarModel::UpdatePrefs() {
402   if (!service_->extension_prefs())
403     return;
404
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_);
409 }
410
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)
418         break;
419       ++i;
420     }
421   }
422   return original_index;
423 }
424
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();
429        ++iter, ++i) {
430     if (original_index == i)
431       break;
432     if (extension_util::IsIncognitoEnabled((*iter)->id(), service_))
433       ++incognito_index;
434   }
435   return incognito_index;
436 }
437
438 void ExtensionToolbarModel::OnExtensionToolbarPrefChange() {
439   // If extensions are not ready, defer to later Populate() call.
440   if (!extensions_initialized_)
441     return;
442
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]);
452     }
453   }
454   last_known_positions_.swap(pref_positions);
455
456   // Re-populate.
457   Populate(last_known_positions_);
458
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(
463         FROM_HERE,
464         base::Bind(&ExtensionToolbarModel::UpdatePrefs,
465                    weak_ptr_factory_.GetWeakPtr()));
466   }
467 }
468
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))
476       return true;
477   }
478   return false;
479 }