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|. 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 { 51 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 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 75 GeolocationExceptionsTableModel::~GeolocationExceptionsTableModel() {} 76 77 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 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 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 137 int GeolocationExceptionsTableModel::RowCount() { 138 return entries_.size(); 139 } 140 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 182 void GeolocationExceptionsTableModel::SetObserver( 183 ui::TableModelObserver* observer) { 184 observer_ = observer; 185 } 186 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 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