1 // Copyright (c) 2010 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/geolocation/geolocation_exceptions_table_model.h"
6
7 #include "ui/base/l10n/l10n_util.h"
8 #include "base/utf_string_conversions.h"
9 #include "chrome/common/content_settings_helper.h"
10 #include "chrome/common/url_constants.h"
11 #include "grit/generated_resources.h"
12 #include "ui/base/l10n/l10n_util_collator.h"
13 #include "ui/base/models/table_model_observer.h"
14
15 namespace {
16 // Return -1, 0, or 1 depending on whether |origin1| should be sorted before,
17 // equal to, or after |origin2|.
CompareOrigins(const GURL & origin1,const GURL & origin2)18 int CompareOrigins(const GURL& origin1, const GURL& origin2) {
19 if (origin1 == origin2)
20 return 0;
21
22 // Sort alphabetically by host name.
23 std::string origin1_host(origin1.host());
24 std::string origin2_host(origin2.host());
25 if (origin1_host != origin2_host)
26 return origin1_host < origin2_host ? -1 : 1;
27
28 // We'll show non-HTTP schemes, so sort them alphabetically, but put HTTP
29 // first.
30 std::string origin1_scheme(origin1.scheme());
31 std::string origin2_scheme(origin2.scheme());
32 if (origin1_scheme != origin2_scheme) {
33 if (origin1_scheme == chrome::kHttpScheme)
34 return -1;
35 if (origin2_scheme == chrome::kHttpScheme)
36 return 1;
37 return origin1_scheme < origin2_scheme ? -1 : 1;
38 }
39
40 // Sort by port number. This has to differ if the origins are really origins
41 // (and not longer URLs). An unspecified port will be -1 and thus
42 // automatically come first (which is what we want).
43 int origin1_port = origin1.IntPort();
44 int origin2_port = origin2.IntPort();
45 DCHECK(origin1_port != origin2_port);
46 return origin1_port < origin2_port ? -1 : 1;
47 }
48 } // namespace
49
50 struct GeolocationExceptionsTableModel::Entry {
EntryGeolocationExceptionsTableModel::Entry51 Entry(const GURL& in_origin,
52 const GURL& in_embedding_origin,
53 ContentSetting in_setting)
54 : origin(in_origin),
55 embedding_origin(in_embedding_origin),
56 setting(in_setting) {
57 }
58
59 GURL origin;
60 GURL embedding_origin;
61 ContentSetting setting;
62 };
63
GeolocationExceptionsTableModel(GeolocationContentSettingsMap * map)64 GeolocationExceptionsTableModel::GeolocationExceptionsTableModel(
65 GeolocationContentSettingsMap* map)
66 : map_(map),
67 observer_(NULL) {
68 GeolocationContentSettingsMap::AllOriginsSettings settings(
69 map_->GetAllOriginsSettings());
70 GeolocationContentSettingsMap::AllOriginsSettings::const_iterator i;
71 for (i = settings.begin(); i != settings.end(); ++i)
72 AddEntriesForOrigin(i->first, i->second);
73 }
74
~GeolocationExceptionsTableModel()75 GeolocationExceptionsTableModel::~GeolocationExceptionsTableModel() {}
76
CanRemoveRows(const Rows & rows) const77 bool GeolocationExceptionsTableModel::CanRemoveRows(
78 const Rows& rows) const {
79 for (Rows::const_iterator i(rows.begin()); i != rows.end(); ++i) {
80 const Entry& entry = entries_[*i];
81 if ((entry.origin == entry.embedding_origin) &&
82 (entry.setting == CONTENT_SETTING_DEFAULT)) {
83 for (size_t j = (*i) + 1;
84 (j < entries_.size()) && (entries_[j].origin == entry.origin); ++j) {
85 if (!rows.count(j))
86 return false;
87 }
88 }
89 }
90 return !rows.empty();
91 }
92
RemoveRows(const Rows & rows)93 void GeolocationExceptionsTableModel::RemoveRows(const Rows& rows) {
94 for (Rows::const_reverse_iterator i(rows.rbegin()); i != rows.rend(); ++i) {
95 size_t row = *i;
96 Entry* entry = &entries_[row];
97 GURL entry_origin(entry->origin); // Copy, not reference, since we'll erase
98 // |entry| before we're done with this.
99 bool next_has_same_origin = ((row + 1) < entries_.size()) &&
100 (entries_[row + 1].origin == entry_origin);
101 bool has_children = (entry_origin == entry->embedding_origin) &&
102 next_has_same_origin;
103 map_->SetContentSetting(entry_origin, entry->embedding_origin,
104 CONTENT_SETTING_DEFAULT);
105 if (has_children) {
106 entry->setting = CONTENT_SETTING_DEFAULT;
107 if (observer_)
108 observer_->OnItemsChanged(row, 1);
109 continue;
110 }
111 do {
112 entries_.erase(entries_.begin() + row); // Note: |entry| is now garbage.
113 if (observer_)
114 observer_->OnItemsRemoved(row, 1);
115 // If we remove the last non-default child of a default parent, we should
116 // remove the parent too. We do these removals one-at-a-time because the
117 // table view will end up being called back as each row is removed, in
118 // turn calling back to CanRemoveRows(), and if we've already removed
119 // more entries than the view has, we'll have problems.
120 if ((row == 0) || rows.count(row - 1))
121 break;
122 entry = &entries_[--row];
123 } while (!next_has_same_origin && (entry->origin == entry_origin) &&
124 (entry->origin == entry->embedding_origin) &&
125 (entry->setting == CONTENT_SETTING_DEFAULT));
126 }
127 }
128
RemoveAll()129 void GeolocationExceptionsTableModel::RemoveAll() {
130 int old_row_count = RowCount();
131 entries_.clear();
132 map_->ResetToDefault();
133 if (observer_)
134 observer_->OnItemsRemoved(0, old_row_count);
135 }
136
RowCount()137 int GeolocationExceptionsTableModel::RowCount() {
138 return entries_.size();
139 }
140
GetText(int row,int column_id)141 string16 GeolocationExceptionsTableModel::GetText(int row,
142 int column_id) {
143 const Entry& entry = entries_[row];
144 if (column_id == IDS_EXCEPTIONS_HOSTNAME_HEADER) {
145 if (entry.origin == entry.embedding_origin) {
146 return content_settings_helper::OriginToString16(entry.origin);
147 }
148 string16 indent(ASCIIToUTF16(" "));
149 if (entry.embedding_origin.is_empty()) {
150 // NOTE: As long as the user cannot add/edit entries from the exceptions
151 // dialog, it's impossible to actually have a non-default setting for some
152 // origin "embedded on any other site", so this row will never appear. If
153 // we add the ability to add/edit exceptions, we'll need to decide when to
154 // display this and how "removing" it will function.
155 return indent + l10n_util::GetStringUTF16(
156 IDS_EXCEPTIONS_GEOLOCATION_EMBEDDED_ANY_OTHER);
157 }
158 return indent + l10n_util::GetStringFUTF16(
159 IDS_EXCEPTIONS_GEOLOCATION_EMBEDDED_ON_HOST,
160 content_settings_helper::OriginToString16(entry.embedding_origin));
161 }
162
163 if (column_id == IDS_EXCEPTIONS_ACTION_HEADER) {
164 switch (entry.setting) {
165 case CONTENT_SETTING_ALLOW:
166 return l10n_util::GetStringUTF16(IDS_EXCEPTIONS_ALLOW_BUTTON);
167 case CONTENT_SETTING_BLOCK:
168 return l10n_util::GetStringUTF16(IDS_EXCEPTIONS_BLOCK_BUTTON);
169 case CONTENT_SETTING_ASK:
170 return l10n_util::GetStringUTF16(IDS_EXCEPTIONS_ASK_BUTTON);
171 case CONTENT_SETTING_DEFAULT:
172 return l10n_util::GetStringUTF16(IDS_EXCEPTIONS_NOT_SET_BUTTON);
173 default:
174 break;
175 }
176 }
177
178 NOTREACHED();
179 return string16();
180 }
181
SetObserver(ui::TableModelObserver * observer)182 void GeolocationExceptionsTableModel::SetObserver(
183 ui::TableModelObserver* observer) {
184 observer_ = observer;
185 }
186
CompareValues(int row1,int row2,int column_id)187 int GeolocationExceptionsTableModel::CompareValues(int row1,
188 int row2,
189 int column_id) {
190 DCHECK(row1 >= 0 && row1 < RowCount() &&
191 row2 >= 0 && row2 < RowCount());
192
193 const Entry& entry1 = entries_[row1];
194 const Entry& entry2 = entries_[row2];
195
196 // Sort top-level requesting origins, keeping all embedded (child) rules
197 // together.
198 int origin_comparison = CompareOrigins(entry1.origin, entry2.origin);
199 if (origin_comparison == 0) {
200 // The non-embedded rule comes before all embedded rules.
201 bool entry1_origins_same = entry1.origin == entry1.embedding_origin;
202 bool entry2_origins_same = entry2.origin == entry2.embedding_origin;
203 if (entry1_origins_same != entry2_origins_same)
204 return entry1_origins_same ? -1 : 1;
205
206 // The "default" embedded rule comes after all other embedded rules.
207 bool embedding_origin1_empty = entry1.embedding_origin.is_empty();
208 bool embedding_origin2_empty = entry2.embedding_origin.is_empty();
209 if (embedding_origin1_empty != embedding_origin2_empty)
210 return embedding_origin2_empty ? -1 : 1;
211
212 origin_comparison =
213 CompareOrigins(entry1.embedding_origin, entry2.embedding_origin);
214 } else if (column_id == IDS_EXCEPTIONS_ACTION_HEADER) {
215 // The rows are in different origins. We need to find out how the top-level
216 // origins will compare.
217 while (entries_[row1].origin != entries_[row1].embedding_origin)
218 --row1;
219 while (entries_[row2].origin != entries_[row2].embedding_origin)
220 --row2;
221 }
222
223 // The entries are at the same "scope". If we're sorting by action, then do
224 // that now.
225 if (column_id == IDS_EXCEPTIONS_ACTION_HEADER) {
226 int compare_text = l10n_util::CompareString16WithCollator(
227 GetCollator(), GetText(row1, column_id), GetText(row2, column_id));
228 if (compare_text != 0)
229 return compare_text;
230 }
231
232 // Sort by the relevant origin.
233 return origin_comparison;
234 }
235
AddEntriesForOrigin(const GURL & origin,const GeolocationContentSettingsMap::OneOriginSettings & settings)236 void GeolocationExceptionsTableModel::AddEntriesForOrigin(
237 const GURL& origin,
238 const GeolocationContentSettingsMap::OneOriginSettings& settings) {
239 GeolocationContentSettingsMap::OneOriginSettings::const_iterator parent =
240 settings.find(origin);
241
242 // Add the "parent" entry for the non-embedded setting.
243 entries_.push_back(Entry(origin, origin,
244 (parent == settings.end()) ? CONTENT_SETTING_DEFAULT : parent->second));
245
246 // Add the "children" for any embedded settings.
247 GeolocationContentSettingsMap::OneOriginSettings::const_iterator i;
248 for (i = settings.begin(); i != settings.end(); ++i) {
249 // Skip the non-embedded setting which we already added above.
250 if (i == parent)
251 continue;
252
253 entries_.push_back(Entry(origin, i->first, i->second));
254 }
255 }
256