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