OLD | NEW |
| (Empty) |
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 <algorithm> | |
8 #include <string> | |
9 | |
10 #include "base/location.h" | |
11 #include "base/metrics/histogram.h" | |
12 #include "base/metrics/histogram_base.h" | |
13 #include "base/prefs/pref_service.h" | |
14 #include "base/single_thread_task_runner.h" | |
15 #include "base/thread_task_runner_handle.h" | |
16 #include "chrome/browser/chrome_notification_types.h" | |
17 #include "chrome/browser/extensions/api/extension_action/extension_action_api.h" | |
18 #include "chrome/browser/extensions/extension_action_manager.h" | |
19 #include "chrome/browser/extensions/extension_tab_util.h" | |
20 #include "chrome/browser/extensions/extension_toolbar_model_factory.h" | |
21 #include "chrome/browser/extensions/extension_util.h" | |
22 #include "chrome/browser/extensions/tab_helper.h" | |
23 #include "chrome/browser/profiles/profile.h" | |
24 #include "chrome/browser/ui/browser.h" | |
25 #include "chrome/browser/ui/extensions/extension_toolbar_icon_surfacing_bubble_d
elegate.h" | |
26 #include "chrome/browser/ui/tabs/tab_strip_model.h" | |
27 #include "content/public/browser/notification_details.h" | |
28 #include "content/public/browser/notification_source.h" | |
29 #include "content/public/browser/web_contents.h" | |
30 #include "extensions/browser/extension_prefs.h" | |
31 #include "extensions/browser/extension_registry.h" | |
32 #include "extensions/browser/extension_system.h" | |
33 #include "extensions/browser/pref_names.h" | |
34 #include "extensions/common/extension.h" | |
35 #include "extensions/common/extension_set.h" | |
36 #include "extensions/common/feature_switch.h" | |
37 #include "extensions/common/manifest_constants.h" | |
38 #include "extensions/common/one_shot_event.h" | |
39 | |
40 namespace extensions { | |
41 | |
42 ExtensionToolbarModel::ExtensionToolbarModel(Profile* profile, | |
43 ExtensionPrefs* extension_prefs) | |
44 : profile_(profile), | |
45 extension_prefs_(extension_prefs), | |
46 prefs_(profile_->GetPrefs()), | |
47 extension_action_api_(ExtensionActionAPI::Get(profile_)), | |
48 extensions_initialized_(false), | |
49 include_all_extensions_(FeatureSwitch::extension_action_redesign() | |
50 ->IsEnabled()), | |
51 highlight_type_(HIGHLIGHT_NONE), | |
52 extension_action_observer_(this), | |
53 extension_registry_observer_(this), | |
54 weak_ptr_factory_(this) { | |
55 ExtensionSystem::Get(profile_)->ready().Post( | |
56 FROM_HERE, | |
57 base::Bind(&ExtensionToolbarModel::OnReady, | |
58 weak_ptr_factory_.GetWeakPtr())); | |
59 visible_icon_count_ = prefs_->GetInteger(pref_names::kToolbarSize); | |
60 | |
61 // We only care about watching the prefs if not in incognito mode. | |
62 if (!profile_->IsOffTheRecord()) { | |
63 pref_change_registrar_.Init(prefs_); | |
64 pref_change_callback_ = | |
65 base::Bind(&ExtensionToolbarModel::OnExtensionToolbarPrefChange, | |
66 base::Unretained(this)); | |
67 pref_change_registrar_.Add(pref_names::kToolbar, pref_change_callback_); | |
68 } | |
69 } | |
70 | |
71 ExtensionToolbarModel::~ExtensionToolbarModel() { | |
72 } | |
73 | |
74 // static | |
75 ExtensionToolbarModel* ExtensionToolbarModel::Get(Profile* profile) { | |
76 return ExtensionToolbarModelFactory::GetForProfile(profile); | |
77 } | |
78 | |
79 void ExtensionToolbarModel::AddObserver(Observer* observer) { | |
80 observers_.AddObserver(observer); | |
81 } | |
82 | |
83 void ExtensionToolbarModel::RemoveObserver(Observer* observer) { | |
84 observers_.RemoveObserver(observer); | |
85 } | |
86 | |
87 void ExtensionToolbarModel::MoveExtensionIcon(const std::string& id, | |
88 size_t index) { | |
89 ExtensionList::iterator pos = toolbar_items_.begin(); | |
90 while (pos != toolbar_items_.end() && (*pos)->id() != id) | |
91 ++pos; | |
92 if (pos == toolbar_items_.end()) { | |
93 NOTREACHED(); | |
94 return; | |
95 } | |
96 scoped_refptr<const Extension> extension = *pos; | |
97 toolbar_items_.erase(pos); | |
98 | |
99 ExtensionIdList::iterator pos_id = std::find(last_known_positions_.begin(), | |
100 last_known_positions_.end(), | |
101 id); | |
102 if (pos_id != last_known_positions_.end()) | |
103 last_known_positions_.erase(pos_id); | |
104 | |
105 if (index < toolbar_items_.size()) { | |
106 // If the index is not at the end, find the item currently at |index|, and | |
107 // insert |extension| before it in both |toolbar_items_| and | |
108 // |last_known_positions_|. | |
109 ExtensionList::iterator iter = toolbar_items_.begin() + index; | |
110 last_known_positions_.insert(std::find(last_known_positions_.begin(), | |
111 last_known_positions_.end(), | |
112 (*iter)->id()), | |
113 id); | |
114 toolbar_items_.insert(iter, extension); | |
115 } else { | |
116 // Otherwise, put |extension| at the end. | |
117 DCHECK_EQ(toolbar_items_.size(), index); | |
118 index = toolbar_items_.size(); | |
119 toolbar_items_.push_back(extension); | |
120 last_known_positions_.push_back(id); | |
121 } | |
122 | |
123 FOR_EACH_OBSERVER(Observer, observers_, | |
124 OnToolbarExtensionMoved(extension.get(), index)); | |
125 MaybeUpdateVisibilityPref(extension.get(), index); | |
126 UpdatePrefs(); | |
127 } | |
128 | |
129 void ExtensionToolbarModel::SetVisibleIconCount(size_t count) { | |
130 visible_icon_count_ = (count >= toolbar_items_.size()) ? -1 : count; | |
131 | |
132 // Only set the prefs if we're not in highlight mode and the profile is not | |
133 // incognito. Highlight mode is designed to be a transitory state, and should | |
134 // not persist across browser restarts (though it may be re-entered), and we | |
135 // don't store anything in incognito. | |
136 if (!is_highlighting() && !profile_->IsOffTheRecord()) { | |
137 // Additionally, if we are using the new toolbar, any icons which are in the | |
138 // overflow menu are considered "hidden". But it so happens that the times | |
139 // we are likely to call SetVisibleIconCount() are also those when we are | |
140 // in flux. So wait for things to cool down before setting the prefs. | |
141 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
142 FROM_HERE, | |
143 base::Bind(&ExtensionToolbarModel::MaybeUpdateVisibilityPrefs, | |
144 weak_ptr_factory_.GetWeakPtr())); | |
145 prefs_->SetInteger(pref_names::kToolbarSize, visible_icon_count_); | |
146 } | |
147 | |
148 FOR_EACH_OBSERVER(Observer, observers_, OnToolbarVisibleCountChanged()); | |
149 } | |
150 | |
151 void ExtensionToolbarModel::OnExtensionActionUpdated( | |
152 ExtensionAction* extension_action, | |
153 content::WebContents* web_contents, | |
154 content::BrowserContext* browser_context) { | |
155 const Extension* extension = | |
156 ExtensionRegistry::Get(profile_)->enabled_extensions().GetByID( | |
157 extension_action->extension_id()); | |
158 // Notify observers if the extension exists and is in the model. | |
159 if (std::find(toolbar_items_.begin(), toolbar_items_.end(), extension) != | |
160 toolbar_items_.end()) { | |
161 FOR_EACH_OBSERVER(Observer, observers_, | |
162 OnToolbarExtensionUpdated(extension)); | |
163 } | |
164 } | |
165 | |
166 void ExtensionToolbarModel::OnExtensionActionVisibilityChanged( | |
167 const std::string& extension_id, | |
168 bool is_now_visible) { | |
169 const Extension* extension = | |
170 ExtensionRegistry::Get(profile_)->GetExtensionById( | |
171 extension_id, ExtensionRegistry::EVERYTHING); | |
172 | |
173 // Hiding works differently with the new and old toolbars. | |
174 if (include_all_extensions_) { | |
175 // It's possible that we haven't added this extension yet, if its | |
176 // visibility was adjusted in the course of its initialization. | |
177 if (std::find(toolbar_items_.begin(), toolbar_items_.end(), extension) == | |
178 toolbar_items_.end()) | |
179 return; | |
180 | |
181 int new_size = 0; | |
182 int new_index = 0; | |
183 if (is_now_visible) { | |
184 // If this action used to be hidden, we can't possibly be showing all. | |
185 DCHECK_LT(visible_icon_count(), toolbar_items_.size()); | |
186 // Grow the bar by one and move the extension to the end of the visibles. | |
187 new_size = visible_icon_count() + 1; | |
188 new_index = new_size - 1; | |
189 } else { | |
190 // If we're hiding one, we must be showing at least one. | |
191 DCHECK_GE(visible_icon_count(), 0u); | |
192 // Shrink the bar by one and move the extension to the beginning of the | |
193 // overflow menu. | |
194 new_size = visible_icon_count() - 1; | |
195 new_index = new_size; | |
196 } | |
197 SetVisibleIconCount(new_size); | |
198 MoveExtensionIcon(extension->id(), new_index); | |
199 } else { // Don't include all extensions. | |
200 if (is_now_visible) | |
201 AddExtension(extension); | |
202 else | |
203 RemoveExtension(extension); | |
204 } | |
205 } | |
206 | |
207 void ExtensionToolbarModel::OnExtensionLoaded( | |
208 content::BrowserContext* browser_context, | |
209 const Extension* extension) { | |
210 // We don't want to add the same extension twice. It may have already been | |
211 // added by EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED below, if the user | |
212 // hides the browser action and then disables and enables the extension. | |
213 for (size_t i = 0; i < toolbar_items_.size(); i++) { | |
214 if (toolbar_items_[i].get() == extension) | |
215 return; | |
216 } | |
217 | |
218 AddExtension(extension); | |
219 } | |
220 | |
221 void ExtensionToolbarModel::OnExtensionUnloaded( | |
222 content::BrowserContext* browser_context, | |
223 const Extension* extension, | |
224 UnloadedExtensionInfo::Reason reason) { | |
225 RemoveExtension(extension); | |
226 } | |
227 | |
228 void ExtensionToolbarModel::OnExtensionUninstalled( | |
229 content::BrowserContext* browser_context, | |
230 const Extension* extension, | |
231 extensions::UninstallReason reason) { | |
232 // Remove the extension id from the ordered list, if it exists (the extension | |
233 // might not be represented in the list because it might not have an icon). | |
234 ExtensionIdList::iterator pos = | |
235 std::find(last_known_positions_.begin(), | |
236 last_known_positions_.end(), extension->id()); | |
237 | |
238 if (pos != last_known_positions_.end()) { | |
239 last_known_positions_.erase(pos); | |
240 UpdatePrefs(); | |
241 } | |
242 } | |
243 | |
244 void ExtensionToolbarModel::OnReady() { | |
245 ExtensionRegistry* registry = ExtensionRegistry::Get(profile_); | |
246 InitializeExtensionList(); | |
247 // Wait until the extension system is ready before observing any further | |
248 // changes so that the toolbar buttons can be shown in their stable ordering | |
249 // taken from prefs. | |
250 extension_registry_observer_.Add(registry); | |
251 extension_action_observer_.Add(extension_action_api_); | |
252 | |
253 if (ExtensionToolbarIconSurfacingBubbleDelegate::ShouldShowForProfile( | |
254 profile_)) { | |
255 ExtensionIdList ids; | |
256 for (const auto& extension : toolbar_items_) | |
257 ids.push_back(extension->id()); | |
258 HighlightExtensions(ids, HIGHLIGHT_INFO); | |
259 } | |
260 | |
261 extensions_initialized_ = true; | |
262 FOR_EACH_OBSERVER(Observer, observers_, OnToolbarModelInitialized()); | |
263 } | |
264 | |
265 size_t ExtensionToolbarModel::FindNewPositionFromLastKnownGood( | |
266 const Extension* extension) { | |
267 // See if we have last known good position for this extension. | |
268 size_t new_index = 0; | |
269 // Loop through the ID list of known positions, to count the number of visible | |
270 // extension icons preceding |extension|. | |
271 for (ExtensionIdList::const_iterator iter_id = last_known_positions_.begin(); | |
272 iter_id < last_known_positions_.end(); ++iter_id) { | |
273 if ((*iter_id) == extension->id()) | |
274 return new_index; // We've found the right position. | |
275 // Found an id, need to see if it is visible. | |
276 for (ExtensionList::const_iterator iter_ext = toolbar_items_.begin(); | |
277 iter_ext < toolbar_items_.end(); ++iter_ext) { | |
278 if ((*iter_ext)->id() == (*iter_id)) { | |
279 // This extension is visible, update the index value. | |
280 ++new_index; | |
281 break; | |
282 } | |
283 } | |
284 } | |
285 | |
286 // Position not found. | |
287 return toolbar_items_.size(); | |
288 } | |
289 | |
290 bool ExtensionToolbarModel::ShouldAddExtension(const Extension* extension) { | |
291 // In incognito mode, don't add any extensions that aren't incognito-enabled. | |
292 if (profile_->IsOffTheRecord() && | |
293 !util::IsIncognitoEnabled(extension->id(), profile_)) | |
294 return false; | |
295 | |
296 ExtensionActionManager* action_manager = | |
297 ExtensionActionManager::Get(profile_); | |
298 if (include_all_extensions_) { | |
299 // In this case, we don't care about the browser action visibility, because | |
300 // we want to show each extension regardless. | |
301 // TODO(devlin): Extension actions which are not visible should be moved to | |
302 // the overflow menu by default. | |
303 return action_manager->GetExtensionAction(*extension) != NULL; | |
304 } | |
305 | |
306 return action_manager->GetBrowserAction(*extension) && | |
307 extension_action_api_->GetBrowserActionVisibility(extension->id()); | |
308 } | |
309 | |
310 void ExtensionToolbarModel::AddExtension(const Extension* extension) { | |
311 // We only use AddExtension() once the system is initialized. | |
312 DCHECK(extensions_initialized_); | |
313 if (!ShouldAddExtension(extension)) | |
314 return; | |
315 | |
316 // See if we have a last known good position for this extension. | |
317 bool is_new_extension = | |
318 std::find(last_known_positions_.begin(), | |
319 last_known_positions_.end(), | |
320 extension->id()) == last_known_positions_.end(); | |
321 | |
322 // New extensions go at the right (end) of the visible extensions. Other | |
323 // extensions go at their previous position. | |
324 size_t new_index = 0; | |
325 if (is_new_extension) { | |
326 new_index = Manifest::IsComponentLocation(extension->location()) ? | |
327 0 : visible_icon_count(); | |
328 // For the last-known position, we use the index of the extension that is | |
329 // just before this extension, plus one. (Note that this isn't the same | |
330 // as new_index + 1, because last_known_positions_ can include disabled | |
331 // extensions.) | |
332 int new_last_known_index = | |
333 new_index == 0 ? 0 : | |
334 std::find(last_known_positions_.begin(), | |
335 last_known_positions_.end(), | |
336 toolbar_items_[new_index - 1]->id()) - | |
337 last_known_positions_.begin() + 1; | |
338 // In theory, the extension before this one should always | |
339 // be in last known positions, but if something funny happened with prefs, | |
340 // make sure we handle it. | |
341 // TODO(devlin): Track down these cases so we can CHECK this. | |
342 new_last_known_index = | |
343 std::min<int>(new_last_known_index, last_known_positions_.size()); | |
344 last_known_positions_.insert( | |
345 last_known_positions_.begin() + new_last_known_index, extension->id()); | |
346 UpdatePrefs(); | |
347 } else { | |
348 new_index = FindNewPositionFromLastKnownGood(extension); | |
349 } | |
350 | |
351 toolbar_items_.insert(toolbar_items_.begin() + new_index, extension); | |
352 | |
353 // If we're currently highlighting, then even though we add a browser action | |
354 // to the full list (|toolbar_items_|, there won't be another *visible* | |
355 // browser action, which was what the observers care about. | |
356 if (!is_highlighting()) { | |
357 FOR_EACH_OBSERVER(Observer, observers_, | |
358 OnToolbarExtensionAdded(extension, new_index)); | |
359 | |
360 int visible_count_delta = 0; | |
361 if (is_new_extension && !all_icons_visible()) { | |
362 // If this is a new extension (and not all extensions are visible), we | |
363 // expand the toolbar out so that the new one can be seen. | |
364 visible_count_delta = 1; | |
365 } else if (profile_->IsOffTheRecord()) { | |
366 // If this is an incognito profile, we also have to check to make sure the | |
367 // overflow matches the main bar's status. | |
368 ExtensionToolbarModel* main_model = | |
369 ExtensionToolbarModel::Get(profile_->GetOriginalProfile()); | |
370 // Find what the index will be in the main bar. Because Observer calls are | |
371 // nondeterministic, we can't just assume the main bar will have the | |
372 // extension and look it up. | |
373 size_t main_index = | |
374 main_model->FindNewPositionFromLastKnownGood(extension); | |
375 bool visible = main_index < main_model->visible_icon_count(); | |
376 // We may need to adjust the visible count if the incognito bar isn't | |
377 // showing all icons and this one is visible, or if it is showing all | |
378 // icons and this is hidden. | |
379 if (visible && !all_icons_visible()) | |
380 visible_count_delta = 1; | |
381 else if (!visible && all_icons_visible()) | |
382 visible_count_delta = -1; | |
383 } | |
384 | |
385 if (visible_count_delta) | |
386 SetVisibleIconCount(visible_icon_count() + visible_count_delta); | |
387 } | |
388 | |
389 MaybeUpdateVisibilityPref(extension, new_index); | |
390 } | |
391 | |
392 void ExtensionToolbarModel::RemoveExtension(const Extension* extension) { | |
393 ExtensionList::iterator pos = | |
394 std::find(toolbar_items_.begin(), toolbar_items_.end(), extension); | |
395 if (pos == toolbar_items_.end()) | |
396 return; | |
397 | |
398 size_t index = pos - toolbar_items_.begin(); | |
399 // If the removed extension was on the toolbar, a new one will take its place | |
400 // if there are any in overflow. | |
401 bool new_extension_shown = | |
402 !all_icons_visible() && index < visible_icon_count(); | |
403 | |
404 // If our visible count is set to the current size, we need to decrement it. | |
405 if (visible_icon_count_ == static_cast<int>(toolbar_items_.size())) | |
406 SetVisibleIconCount(toolbar_items_.size() - 1); | |
407 | |
408 toolbar_items_.erase(pos); | |
409 | |
410 // If we're in highlight mode, we also have to remove the extension from | |
411 // the highlighted list. | |
412 if (is_highlighting()) { | |
413 pos = std::find(highlighted_items_.begin(), | |
414 highlighted_items_.end(), | |
415 extension); | |
416 if (pos != highlighted_items_.end()) { | |
417 highlighted_items_.erase(pos); | |
418 FOR_EACH_OBSERVER(Observer, observers_, | |
419 OnToolbarExtensionRemoved(extension)); | |
420 // If the highlighted list is now empty, we stop highlighting. | |
421 if (highlighted_items_.empty()) | |
422 StopHighlighting(); | |
423 } | |
424 } else { | |
425 FOR_EACH_OBSERVER(Observer, observers_, | |
426 OnToolbarExtensionRemoved(extension)); | |
427 } | |
428 | |
429 UpdatePrefs(); | |
430 if (new_extension_shown) { | |
431 size_t newly_visible_index = visible_icon_count() - 1; | |
432 MaybeUpdateVisibilityPref(toolbar_items_[newly_visible_index].get(), | |
433 newly_visible_index); | |
434 } | |
435 } | |
436 | |
437 // Combine the currently enabled extensions that have browser actions (which | |
438 // we get from the ExtensionRegistry) with the ordering we get from the | |
439 // pref service. For robustness we use a somewhat inefficient process: | |
440 // 1. Create a vector of extensions sorted by their pref values. This vector may | |
441 // have holes. | |
442 // 2. Create a vector of extensions that did not have a pref value. | |
443 // 3. Remove holes from the sorted vector and append the unsorted vector. | |
444 void ExtensionToolbarModel::InitializeExtensionList() { | |
445 DCHECK(toolbar_items_.empty()); // We shouldn't have any items yet. | |
446 | |
447 last_known_positions_ = extension_prefs_->GetToolbarOrder(); | |
448 if (profile_->IsOffTheRecord()) | |
449 IncognitoPopulate(); | |
450 else | |
451 Populate(&last_known_positions_); | |
452 | |
453 MaybeUpdateVisibilityPrefs(); | |
454 } | |
455 | |
456 void ExtensionToolbarModel::Populate(ExtensionIdList* positions) { | |
457 DCHECK(!profile_->IsOffTheRecord()); | |
458 const ExtensionSet& extensions = | |
459 ExtensionRegistry::Get(profile_)->enabled_extensions(); | |
460 // Items that have explicit positions. | |
461 ExtensionList sorted(positions->size(), NULL); | |
462 // The items that don't have explicit positions. | |
463 ExtensionList unsorted; | |
464 | |
465 // Create the lists. | |
466 int hidden = 0; | |
467 for (const scoped_refptr<const Extension>& extension : extensions) { | |
468 if (!ShouldAddExtension(extension.get())) { | |
469 if (!extension_action_api_->GetBrowserActionVisibility(extension->id())) | |
470 ++hidden; | |
471 continue; | |
472 } | |
473 | |
474 ExtensionIdList::const_iterator pos = | |
475 std::find(positions->begin(), positions->end(), extension->id()); | |
476 if (pos != positions->end()) { | |
477 sorted[pos - positions->begin()] = extension; | |
478 } else { | |
479 // Unknown extension - push it to the back of unsorted, and add it to the | |
480 // list of ids at the end. | |
481 unsorted.push_back(extension); | |
482 positions->push_back(extension->id()); | |
483 } | |
484 } | |
485 | |
486 // Merge the lists. | |
487 sorted.insert(sorted.end(), unsorted.begin(), unsorted.end()); | |
488 toolbar_items_.reserve(sorted.size()); | |
489 | |
490 for (const scoped_refptr<const Extension>& extension : sorted) { | |
491 // It's possible for the extension order to contain items that aren't | |
492 // actually loaded on this machine. For example, when extension sync is on, | |
493 // we sync the extension order as-is but double-check with the user before | |
494 // syncing NPAPI-containing extensions, so if one of those is not actually | |
495 // synced, we'll get a NULL in the list. This sort of case can also happen | |
496 // if some error prevents an extension from loading. | |
497 if (extension.get()) { | |
498 // We don't notify observers of the added extension yet. Rather, observers | |
499 // should wait for the "OnToolbarModelInitialized" notification, and then | |
500 // bulk-update. (This saves a lot of bouncing-back-and-forth here, and | |
501 // allows observers to ensure that the extension system is always | |
502 // initialized before using the extensions). | |
503 toolbar_items_.push_back(extension); | |
504 } | |
505 } | |
506 | |
507 UMA_HISTOGRAM_COUNTS_100( | |
508 "ExtensionToolbarModel.BrowserActionsPermanentlyHidden", hidden); | |
509 UMA_HISTOGRAM_COUNTS_100("ExtensionToolbarModel.BrowserActionsCount", | |
510 toolbar_items_.size()); | |
511 | |
512 if (!toolbar_items_.empty()) { | |
513 // Visible count can be -1, meaning: 'show all'. Since UMA converts negative | |
514 // values to 0, this would be counted as 'show none' unless we convert it to | |
515 // max. | |
516 UMA_HISTOGRAM_COUNTS_100("ExtensionToolbarModel.BrowserActionsVisible", | |
517 visible_icon_count_ == -1 ? | |
518 base::HistogramBase::kSampleType_MAX : | |
519 visible_icon_count_); | |
520 } | |
521 } | |
522 | |
523 void ExtensionToolbarModel::IncognitoPopulate() { | |
524 DCHECK(profile_->IsOffTheRecord()); | |
525 const ExtensionToolbarModel* original_model = | |
526 ExtensionToolbarModel::Get(profile_->GetOriginalProfile()); | |
527 | |
528 // Find the absolute value of the original model's count. | |
529 int original_visible = original_model->visible_icon_count(); | |
530 | |
531 // In incognito mode, we show only those extensions that are | |
532 // incognito-enabled. Further, any actions that were overflowed in regular | |
533 // mode are still overflowed. Order is the same as in regular mode. | |
534 visible_icon_count_ = 0; | |
535 for (ExtensionList::const_iterator iter = | |
536 original_model->toolbar_items_.begin(); | |
537 iter != original_model->toolbar_items_.end(); ++iter) { | |
538 if (ShouldAddExtension(iter->get())) { | |
539 toolbar_items_.push_back(*iter); | |
540 if (iter - original_model->toolbar_items_.begin() < original_visible) | |
541 ++visible_icon_count_; | |
542 } | |
543 } | |
544 } | |
545 | |
546 void ExtensionToolbarModel::UpdatePrefs() { | |
547 if (!extension_prefs_ || profile_->IsOffTheRecord()) | |
548 return; | |
549 | |
550 // Don't observe change caused by self. | |
551 pref_change_registrar_.Remove(pref_names::kToolbar); | |
552 extension_prefs_->SetToolbarOrder(last_known_positions_); | |
553 pref_change_registrar_.Add(pref_names::kToolbar, pref_change_callback_); | |
554 } | |
555 | |
556 void ExtensionToolbarModel::MaybeUpdateVisibilityPref( | |
557 const Extension* extension, size_t index) { | |
558 // We only update the visibility pref for hidden/not hidden based on the | |
559 // overflow menu with the new toolbar design. | |
560 if (include_all_extensions_ && !profile_->IsOffTheRecord()) { | |
561 bool visible = index < visible_icon_count(); | |
562 if (visible != extension_action_api_->GetBrowserActionVisibility( | |
563 extension->id())) { | |
564 // Don't observe changes caused by ourselves. | |
565 bool was_registered = false; | |
566 if (extension_action_observer_.IsObserving(extension_action_api_)) { | |
567 was_registered = true; | |
568 extension_action_observer_.RemoveAll(); | |
569 } | |
570 extension_action_api_->SetBrowserActionVisibility(extension->id(), | |
571 visible); | |
572 if (was_registered) | |
573 extension_action_observer_.Add(extension_action_api_); | |
574 } | |
575 } | |
576 } | |
577 | |
578 void ExtensionToolbarModel::MaybeUpdateVisibilityPrefs() { | |
579 for (size_t i = 0u; i < toolbar_items_.size(); ++i) | |
580 MaybeUpdateVisibilityPref(toolbar_items_[i].get(), i); | |
581 } | |
582 | |
583 void ExtensionToolbarModel::OnExtensionToolbarPrefChange() { | |
584 // If extensions are not ready, defer to later Populate() call. | |
585 if (!extensions_initialized_) | |
586 return; | |
587 | |
588 // Recalculate |last_known_positions_| to be |pref_positions| followed by | |
589 // ones that are only in |last_known_positions_|. | |
590 ExtensionIdList pref_positions = extension_prefs_->GetToolbarOrder(); | |
591 size_t pref_position_size = pref_positions.size(); | |
592 for (size_t i = 0; i < last_known_positions_.size(); ++i) { | |
593 if (std::find(pref_positions.begin(), pref_positions.end(), | |
594 last_known_positions_[i]) == pref_positions.end()) { | |
595 pref_positions.push_back(last_known_positions_[i]); | |
596 } | |
597 } | |
598 last_known_positions_.swap(pref_positions); | |
599 | |
600 int desired_index = 0; | |
601 // Loop over the updated list of last known positions, moving any extensions | |
602 // that are in the wrong place. | |
603 for (const std::string& id : last_known_positions_) { | |
604 int current_index = GetIndexForId(id); | |
605 if (current_index == -1) | |
606 continue; | |
607 if (current_index != desired_index) { | |
608 scoped_refptr<const Extension> extension = toolbar_items_[current_index]; | |
609 toolbar_items_.erase(toolbar_items_.begin() + current_index); | |
610 toolbar_items_.insert(toolbar_items_.begin() + desired_index, extension); | |
611 // Notify the observers to keep them up-to-date. | |
612 FOR_EACH_OBSERVER( | |
613 Observer, observers_, | |
614 OnToolbarExtensionMoved(extension.get(), desired_index)); | |
615 } | |
616 ++desired_index; | |
617 } | |
618 | |
619 if (last_known_positions_.size() > pref_position_size) { | |
620 // Need to update pref because we have extra icons. But can't call | |
621 // UpdatePrefs() directly within observation closure. | |
622 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
623 FROM_HERE, base::Bind(&ExtensionToolbarModel::UpdatePrefs, | |
624 weak_ptr_factory_.GetWeakPtr())); | |
625 } | |
626 } | |
627 | |
628 int ExtensionToolbarModel::GetIndexForId(const std::string& id) const { | |
629 for (size_t i = 0; i < toolbar_items().size(); ++i) { | |
630 if (toolbar_items()[i]->id() == id) | |
631 return i; | |
632 } | |
633 return -1; | |
634 } | |
635 | |
636 bool ExtensionToolbarModel::ShowExtensionActionPopup( | |
637 const Extension* extension, | |
638 Browser* browser, | |
639 bool grant_active_tab) { | |
640 base::ObserverListBase<Observer>::Iterator it(&observers_); | |
641 Observer* obs = NULL; | |
642 // Look for the Observer associated with the browser. | |
643 // This would be cleaner if we had an abstract class for the Toolbar UI | |
644 // (like we do for LocationBar), but sadly, we don't. | |
645 while ((obs = it.GetNext()) != NULL) { | |
646 if (obs->GetBrowser() == browser) | |
647 return obs->ShowExtensionActionPopup(extension, grant_active_tab); | |
648 } | |
649 return false; | |
650 } | |
651 | |
652 void ExtensionToolbarModel::EnsureVisibility( | |
653 const ExtensionIdList& extension_ids) { | |
654 if (all_icons_visible()) | |
655 return; // Already showing all. | |
656 | |
657 // Otherwise, make sure we have enough room to show all the extensions | |
658 // requested. | |
659 if (visible_icon_count() < extension_ids.size()) | |
660 SetVisibleIconCount(extension_ids.size()); | |
661 | |
662 if (all_icons_visible()) | |
663 return; // May have been set to max by SetVisibleIconCount. | |
664 | |
665 // Guillotine's Delight: Move an orange noble to the front of the line. | |
666 for (ExtensionIdList::const_iterator it = extension_ids.begin(); | |
667 it != extension_ids.end(); ++it) { | |
668 for (ExtensionList::const_iterator extension = toolbar_items_.begin(); | |
669 extension != toolbar_items_.end(); ++extension) { | |
670 if ((*extension)->id() == (*it)) { | |
671 if (extension - toolbar_items_.begin() >= | |
672 static_cast<int>(visible_icon_count())) | |
673 MoveExtensionIcon((*extension)->id(), 0); | |
674 break; | |
675 } | |
676 } | |
677 } | |
678 } | |
679 | |
680 bool ExtensionToolbarModel::HighlightExtensions( | |
681 const ExtensionIdList& extension_ids, | |
682 HighlightType highlight_type) { | |
683 highlighted_items_.clear(); | |
684 | |
685 for (ExtensionIdList::const_iterator id = extension_ids.begin(); | |
686 id != extension_ids.end(); | |
687 ++id) { | |
688 for (ExtensionList::const_iterator extension = toolbar_items_.begin(); | |
689 extension != toolbar_items_.end(); | |
690 ++extension) { | |
691 if (*id == (*extension)->id()) | |
692 highlighted_items_.push_back(*extension); | |
693 } | |
694 } | |
695 | |
696 // If we have any items in |highlighted_items_|, then we entered highlighting | |
697 // mode. | |
698 if (highlighted_items_.size()) { | |
699 // It's important that is_highlighting_ is changed immediately before the | |
700 // observers are notified since it changes the result of toolbar_items(). | |
701 highlight_type_ = highlight_type; | |
702 FOR_EACH_OBSERVER(Observer, observers_, | |
703 OnToolbarHighlightModeChanged(true)); | |
704 | |
705 // We set the visible icon count after the highlight mode change because | |
706 // the UI actions are created/destroyed during highlight, and doing that | |
707 // prior to changing the size allows us to still have smooth animations. | |
708 if (visible_icon_count() < extension_ids.size()) | |
709 SetVisibleIconCount(extension_ids.size()); | |
710 | |
711 return true; | |
712 } | |
713 | |
714 // Otherwise, we didn't enter highlighting mode (and, in fact, exited it if | |
715 // we were otherwise in it). | |
716 if (is_highlighting()) | |
717 StopHighlighting(); | |
718 return false; | |
719 } | |
720 | |
721 void ExtensionToolbarModel::StopHighlighting() { | |
722 if (is_highlighting()) { | |
723 // It's important that is_highlighting_ is changed immediately before the | |
724 // observers are notified since it changes the result of toolbar_items(). | |
725 highlight_type_ = HIGHLIGHT_NONE; | |
726 FOR_EACH_OBSERVER(Observer, observers_, | |
727 OnToolbarHighlightModeChanged(false)); | |
728 | |
729 // For the same reason, we don't clear highlighted_items_ until after the | |
730 // mode changed. | |
731 highlighted_items_.clear(); | |
732 | |
733 // We set the visible icon count after the highlight mode change because | |
734 // the UI actions are created/destroyed during highlight, and doing that | |
735 // prior to changing the size allows us to still have smooth animations. | |
736 int saved_icon_count = prefs_->GetInteger(pref_names::kToolbarSize); | |
737 if (saved_icon_count != visible_icon_count_) | |
738 SetVisibleIconCount(saved_icon_count); | |
739 } | |
740 } | |
741 | |
742 bool ExtensionToolbarModel::RedesignIsShowingNewIcons() const { | |
743 for (const scoped_refptr<const Extension>& extension : toolbar_items_) { | |
744 // Without the redesign, we only show extensions with browser actions. | |
745 // Any extension without a browser action is an indication that we're | |
746 // showing something new. | |
747 if (!extension->manifest()->HasKey(manifest_keys::kBrowserAction)) | |
748 return true; | |
749 } | |
750 return false; | |
751 } | |
752 | |
753 } // namespace extensions | |
OLD | NEW |