1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/custom_home_pages_table_model.h"
8 #include "base/bind_helpers.h"
9 #include "base/i18n/rtl.h"
10 #include "base/prefs/pref_service.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/browser/history/history_service_factory.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/browser/ui/browser.h"
15 #include "chrome/browser/ui/browser_iterator.h"
16 #include "chrome/browser/ui/browser_list.h"
17 #include "chrome/browser/ui/tabs/tab_strip_model.h"
18 #include "chrome/common/pref_names.h"
19 #include "chrome/common/url_constants.h"
20 #include "chrome/grit/generated_resources.h"
21 #include "components/history/core/browser/history_service.h"
22 #include "content/public/browser/web_contents.h"
23 #include "net/base/net_util.h"
24 #include "ui/base/l10n/l10n_util.h"
25 #include "ui/base/models/table_model_observer.h"
26 #include "ui/gfx/codec/png_codec.h"
31 // Checks whether the given URL should count as one of the "current" pages.
32 // Returns true for all pages except dev tools and settings.
33 bool ShouldAddPage(const GURL
& url
) {
37 if (url
.SchemeIs(content::kChromeDevToolsScheme
))
40 if (url
.SchemeIs(content::kChromeUIScheme
)) {
41 if (url
.host() == chrome::kChromeUISettingsHost
)
44 // For a settings page, the path will start with "/settings" not "settings"
45 // so find() will return 1, not 0.
46 if (url
.host() == chrome::kChromeUIUberHost
&&
47 url
.path().find(chrome::kChromeUISettingsHost
) == 1) {
57 struct CustomHomePagesTableModel::Entry
{
58 Entry() : task_id(base::CancelableTaskTracker::kBadTaskId
) {}
63 // Page title. If this is empty, we'll display the URL as the entry.
66 // If not |base::CancelableTaskTracker::kBadTaskId|, indicates we're loading
67 // the title for the page.
68 base::CancelableTaskTracker::TaskId task_id
;
71 CustomHomePagesTableModel::CustomHomePagesTableModel(Profile
* profile
)
74 num_outstanding_title_lookups_(0) {
77 CustomHomePagesTableModel::~CustomHomePagesTableModel() {
80 void CustomHomePagesTableModel::SetURLs(const std::vector
<GURL
>& urls
) {
81 entries_
.resize(urls
.size());
82 for (size_t i
= 0; i
< urls
.size(); ++i
) {
83 entries_
[i
].url
= urls
[i
];
84 entries_
[i
].title
.erase();
90 * Move a number of existing entries to a new position, reordering the table.
92 * We determine the range of elements affected by the move, save the moved
93 * elements, compact the remaining ones, and re-insert moved elements.
94 * Expects |index_list| to be ordered ascending.
96 void CustomHomePagesTableModel::MoveURLs(int insert_before
,
97 const std::vector
<int>& index_list
) {
98 if (index_list
.empty()) return;
99 DCHECK(insert_before
>= 0 && insert_before
<= RowCount());
101 // The range of elements that needs to be reshuffled is [ |first|, |last| ).
102 int first
= std::min(insert_before
, index_list
.front());
103 int last
= std::max(insert_before
, index_list
.back() + 1);
105 // Save the dragged elements. Also, adjust insertion point if it is before a
107 std::vector
<Entry
> moved_entries
;
108 for (size_t i
= 0; i
< index_list
.size(); ++i
) {
109 moved_entries
.push_back(entries_
[index_list
[i
]]);
110 if (index_list
[i
] == insert_before
)
114 // Compact the range between beginning and insertion point, moving downwards.
115 size_t skip_count
= 0;
116 for (int i
= first
; i
< insert_before
; ++i
) {
117 if (skip_count
< index_list
.size() && index_list
[skip_count
] == i
)
120 entries_
[i
- skip_count
] = entries_
[i
];
123 // Moving items down created a gap. We start compacting up after it.
124 first
= insert_before
;
125 insert_before
-= skip_count
;
127 // Now compact up for elements after the insertion point.
129 for (int i
= last
- 1; i
>= first
; --i
) {
130 if (skip_count
< index_list
.size() &&
131 index_list
[index_list
.size() - skip_count
- 1] == i
) {
134 entries_
[i
+ skip_count
] = entries_
[i
];
138 // Insert moved elements.
139 std::copy(moved_entries
.begin(), moved_entries
.end(),
140 entries_
.begin() + insert_before
);
142 // Possibly large change, so tell the view to just rebuild itself.
144 observer_
->OnModelChanged();
147 void CustomHomePagesTableModel::AddWithoutNotification(
148 int index
, const GURL
& url
) {
149 DCHECK(index
>= 0 && index
<= RowCount());
150 entries_
.insert(entries_
.begin() + static_cast<size_t>(index
), Entry());
151 entries_
[index
].url
= url
;
154 void CustomHomePagesTableModel::Add(int index
, const GURL
& url
) {
155 AddWithoutNotification(index
, url
);
156 LoadTitle(&(entries_
[index
]));
158 observer_
->OnItemsAdded(index
, 1);
161 void CustomHomePagesTableModel::RemoveWithoutNotification(int index
) {
162 DCHECK(index
>= 0 && index
< RowCount());
163 Entry
* entry
= &(entries_
[index
]);
164 // Cancel any pending load requests now so we don't deref a bogus pointer when
165 // we get the loaded notification.
166 if (entry
->task_id
!= base::CancelableTaskTracker::kBadTaskId
) {
167 task_tracker_
.TryCancel(entry
->task_id
);
168 entry
->task_id
= base::CancelableTaskTracker::kBadTaskId
;
170 entries_
.erase(entries_
.begin() + static_cast<size_t>(index
));
173 void CustomHomePagesTableModel::Remove(int index
) {
174 RemoveWithoutNotification(index
);
176 observer_
->OnItemsRemoved(index
, 1);
179 void CustomHomePagesTableModel::SetToCurrentlyOpenPages() {
180 // Remove the current entries.
182 RemoveWithoutNotification(0);
184 // And add all tabs for all open browsers with our profile.
186 for (chrome::BrowserIterator it
; !it
.done(); it
.Next()) {
187 Browser
* browser
= *it
;
188 if (browser
->profile() != profile_
)
189 continue; // Skip incognito browsers.
191 for (int tab_index
= 0;
192 tab_index
< browser
->tab_strip_model()->count();
195 browser
->tab_strip_model()->GetWebContentsAt(tab_index
)->GetURL();
196 if (ShouldAddPage(url
))
197 AddWithoutNotification(add_index
++, url
);
203 std::vector
<GURL
> CustomHomePagesTableModel::GetURLs() {
204 std::vector
<GURL
> urls(entries_
.size());
205 for (size_t i
= 0; i
< entries_
.size(); ++i
)
206 urls
[i
] = entries_
[i
].url
;
210 int CustomHomePagesTableModel::RowCount() {
211 return static_cast<int>(entries_
.size());
214 base::string16
CustomHomePagesTableModel::GetText(int row
, int column_id
) {
215 DCHECK(column_id
== 0);
216 DCHECK(row
>= 0 && row
< RowCount());
217 return entries_
[row
].title
.empty() ? FormattedURL(row
) : entries_
[row
].title
;
220 base::string16
CustomHomePagesTableModel::GetTooltip(int row
) {
221 return entries_
[row
].title
.empty() ? base::string16() :
222 l10n_util::GetStringFUTF16(IDS_OPTIONS_STARTUP_PAGE_TOOLTIP
,
223 entries_
[row
].title
, FormattedURL(row
));
226 void CustomHomePagesTableModel::SetObserver(ui::TableModelObserver
* observer
) {
227 observer_
= observer
;
230 void CustomHomePagesTableModel::LoadTitle(Entry
* entry
) {
231 history::HistoryService
* history_service
=
232 HistoryServiceFactory::GetForProfile(profile_
,
233 ServiceAccessType::EXPLICIT_ACCESS
);
234 if (history_service
) {
235 entry
->task_id
= history_service
->QueryURL(
238 base::Bind(&CustomHomePagesTableModel::OnGotTitle
,
239 base::Unretained(this),
246 void CustomHomePagesTableModel::LoadAllTitles() {
247 history::HistoryService
* history_service
=
248 HistoryServiceFactory::GetForProfile(profile_
,
249 ServiceAccessType::EXPLICIT_ACCESS
);
250 // It's possible for multiple LoadAllTitles() queries to be inflight we want
251 // to make sure everything is resolved before updating the observer or we risk
252 // getting rendering glitches.
253 num_outstanding_title_lookups_
+= entries_
.size();
254 for (Entry
& entry
: entries_
) {
255 if (history_service
) {
256 entry
.task_id
= history_service
->QueryURL(
259 base::Bind(&CustomHomePagesTableModel::OnGotOneOfManyTitles
,
260 base::Unretained(this),
265 if (entries_
.empty())
266 observer_
->OnModelChanged();
269 void CustomHomePagesTableModel::OnGotOneOfManyTitles(const GURL
& entry_url
,
271 const history::URLRow
& row
,
272 const history::VisitVector
& visits
) {
273 OnGotTitle(entry_url
, false, found_url
, row
, visits
);
274 DCHECK_GE(num_outstanding_title_lookups_
, 1);
275 if (--num_outstanding_title_lookups_
== 0 && observer_
)
276 observer_
->OnModelChanged();
279 void CustomHomePagesTableModel::OnGotTitle(const GURL
& entry_url
,
282 const history::URLRow
& row
,
283 const history::VisitVector
& visits
) {
285 size_t entry_index
= 0;
286 for (size_t i
= 0; i
< entries_
.size(); ++i
) {
287 if (entries_
[i
].url
== entry_url
) {
288 entry
= &entries_
[i
];
294 // The URLs changed before we were called back.
297 entry
->task_id
= base::CancelableTaskTracker::kBadTaskId
;
298 if (found_url
&& !row
.title().empty()) {
299 entry
->title
= row
.title();
300 if (observer_
&& observable
)
301 observer_
->OnItemsChanged(static_cast<int>(entry_index
), 1);
305 base::string16
CustomHomePagesTableModel::FormattedURL(int row
) const {
306 std::string languages
=
307 profile_
->GetPrefs()->GetString(prefs::kAcceptLanguages
);
308 base::string16 url
= net::FormatUrl(entries_
[row
].url
, languages
);
309 url
= base::i18n::GetDisplayStringInLTRDirectionality(url
);