| // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/geolocation/geolocation_exceptions_table_model.h" |
| |
| #include "ui/base/l10n/l10n_util.h" |
| #include "base/utf_string_conversions.h" |
| #include "chrome/common/content_settings_helper.h" |
| #include "chrome/common/url_constants.h" |
| #include "grit/generated_resources.h" |
| #include "ui/base/l10n/l10n_util_collator.h" |
| #include "ui/base/models/table_model_observer.h" |
| |
| namespace { |
| // Return -1, 0, or 1 depending on whether |origin1| should be sorted before, |
| // equal to, or after |origin2|. |
| int CompareOrigins(const GURL& origin1, const GURL& origin2) { |
| if (origin1 == origin2) |
| return 0; |
| |
| // Sort alphabetically by host name. |
| std::string origin1_host(origin1.host()); |
| std::string origin2_host(origin2.host()); |
| if (origin1_host != origin2_host) |
| return origin1_host < origin2_host ? -1 : 1; |
| |
| // We'll show non-HTTP schemes, so sort them alphabetically, but put HTTP |
| // first. |
| std::string origin1_scheme(origin1.scheme()); |
| std::string origin2_scheme(origin2.scheme()); |
| if (origin1_scheme != origin2_scheme) { |
| if (origin1_scheme == chrome::kHttpScheme) |
| return -1; |
| if (origin2_scheme == chrome::kHttpScheme) |
| return 1; |
| return origin1_scheme < origin2_scheme ? -1 : 1; |
| } |
| |
| // Sort by port number. This has to differ if the origins are really origins |
| // (and not longer URLs). An unspecified port will be -1 and thus |
| // automatically come first (which is what we want). |
| int origin1_port = origin1.IntPort(); |
| int origin2_port = origin2.IntPort(); |
| DCHECK(origin1_port != origin2_port); |
| return origin1_port < origin2_port ? -1 : 1; |
| } |
| } // namespace |
| |
| struct GeolocationExceptionsTableModel::Entry { |
| Entry(const GURL& in_origin, |
| const GURL& in_embedding_origin, |
| ContentSetting in_setting) |
| : origin(in_origin), |
| embedding_origin(in_embedding_origin), |
| setting(in_setting) { |
| } |
| |
| GURL origin; |
| GURL embedding_origin; |
| ContentSetting setting; |
| }; |
| |
| GeolocationExceptionsTableModel::GeolocationExceptionsTableModel( |
| GeolocationContentSettingsMap* map) |
| : map_(map), |
| observer_(NULL) { |
| GeolocationContentSettingsMap::AllOriginsSettings settings( |
| map_->GetAllOriginsSettings()); |
| GeolocationContentSettingsMap::AllOriginsSettings::const_iterator i; |
| for (i = settings.begin(); i != settings.end(); ++i) |
| AddEntriesForOrigin(i->first, i->second); |
| } |
| |
| GeolocationExceptionsTableModel::~GeolocationExceptionsTableModel() {} |
| |
| bool GeolocationExceptionsTableModel::CanRemoveRows( |
| const Rows& rows) const { |
| for (Rows::const_iterator i(rows.begin()); i != rows.end(); ++i) { |
| const Entry& entry = entries_[*i]; |
| if ((entry.origin == entry.embedding_origin) && |
| (entry.setting == CONTENT_SETTING_DEFAULT)) { |
| for (size_t j = (*i) + 1; |
| (j < entries_.size()) && (entries_[j].origin == entry.origin); ++j) { |
| if (!rows.count(j)) |
| return false; |
| } |
| } |
| } |
| return !rows.empty(); |
| } |
| |
| void GeolocationExceptionsTableModel::RemoveRows(const Rows& rows) { |
| for (Rows::const_reverse_iterator i(rows.rbegin()); i != rows.rend(); ++i) { |
| size_t row = *i; |
| Entry* entry = &entries_[row]; |
| GURL entry_origin(entry->origin); // Copy, not reference, since we'll erase |
| // |entry| before we're done with this. |
| bool next_has_same_origin = ((row + 1) < entries_.size()) && |
| (entries_[row + 1].origin == entry_origin); |
| bool has_children = (entry_origin == entry->embedding_origin) && |
| next_has_same_origin; |
| map_->SetContentSetting(entry_origin, entry->embedding_origin, |
| CONTENT_SETTING_DEFAULT); |
| if (has_children) { |
| entry->setting = CONTENT_SETTING_DEFAULT; |
| if (observer_) |
| observer_->OnItemsChanged(row, 1); |
| continue; |
| } |
| do { |
| entries_.erase(entries_.begin() + row); // Note: |entry| is now garbage. |
| if (observer_) |
| observer_->OnItemsRemoved(row, 1); |
| // If we remove the last non-default child of a default parent, we should |
| // remove the parent too. We do these removals one-at-a-time because the |
| // table view will end up being called back as each row is removed, in |
| // turn calling back to CanRemoveRows(), and if we've already removed |
| // more entries than the view has, we'll have problems. |
| if ((row == 0) || rows.count(row - 1)) |
| break; |
| entry = &entries_[--row]; |
| } while (!next_has_same_origin && (entry->origin == entry_origin) && |
| (entry->origin == entry->embedding_origin) && |
| (entry->setting == CONTENT_SETTING_DEFAULT)); |
| } |
| } |
| |
| void GeolocationExceptionsTableModel::RemoveAll() { |
| int old_row_count = RowCount(); |
| entries_.clear(); |
| map_->ResetToDefault(); |
| if (observer_) |
| observer_->OnItemsRemoved(0, old_row_count); |
| } |
| |
| int GeolocationExceptionsTableModel::RowCount() { |
| return entries_.size(); |
| } |
| |
| string16 GeolocationExceptionsTableModel::GetText(int row, |
| int column_id) { |
| const Entry& entry = entries_[row]; |
| if (column_id == IDS_EXCEPTIONS_HOSTNAME_HEADER) { |
| if (entry.origin == entry.embedding_origin) { |
| return content_settings_helper::OriginToString16(entry.origin); |
| } |
| string16 indent(ASCIIToUTF16(" ")); |
| if (entry.embedding_origin.is_empty()) { |
| // NOTE: As long as the user cannot add/edit entries from the exceptions |
| // dialog, it's impossible to actually have a non-default setting for some |
| // origin "embedded on any other site", so this row will never appear. If |
| // we add the ability to add/edit exceptions, we'll need to decide when to |
| // display this and how "removing" it will function. |
| return indent + l10n_util::GetStringUTF16( |
| IDS_EXCEPTIONS_GEOLOCATION_EMBEDDED_ANY_OTHER); |
| } |
| return indent + l10n_util::GetStringFUTF16( |
| IDS_EXCEPTIONS_GEOLOCATION_EMBEDDED_ON_HOST, |
| content_settings_helper::OriginToString16(entry.embedding_origin)); |
| } |
| |
| if (column_id == IDS_EXCEPTIONS_ACTION_HEADER) { |
| switch (entry.setting) { |
| case CONTENT_SETTING_ALLOW: |
| return l10n_util::GetStringUTF16(IDS_EXCEPTIONS_ALLOW_BUTTON); |
| case CONTENT_SETTING_BLOCK: |
| return l10n_util::GetStringUTF16(IDS_EXCEPTIONS_BLOCK_BUTTON); |
| case CONTENT_SETTING_ASK: |
| return l10n_util::GetStringUTF16(IDS_EXCEPTIONS_ASK_BUTTON); |
| case CONTENT_SETTING_DEFAULT: |
| return l10n_util::GetStringUTF16(IDS_EXCEPTIONS_NOT_SET_BUTTON); |
| default: |
| break; |
| } |
| } |
| |
| NOTREACHED(); |
| return string16(); |
| } |
| |
| void GeolocationExceptionsTableModel::SetObserver( |
| ui::TableModelObserver* observer) { |
| observer_ = observer; |
| } |
| |
| int GeolocationExceptionsTableModel::CompareValues(int row1, |
| int row2, |
| int column_id) { |
| DCHECK(row1 >= 0 && row1 < RowCount() && |
| row2 >= 0 && row2 < RowCount()); |
| |
| const Entry& entry1 = entries_[row1]; |
| const Entry& entry2 = entries_[row2]; |
| |
| // Sort top-level requesting origins, keeping all embedded (child) rules |
| // together. |
| int origin_comparison = CompareOrigins(entry1.origin, entry2.origin); |
| if (origin_comparison == 0) { |
| // The non-embedded rule comes before all embedded rules. |
| bool entry1_origins_same = entry1.origin == entry1.embedding_origin; |
| bool entry2_origins_same = entry2.origin == entry2.embedding_origin; |
| if (entry1_origins_same != entry2_origins_same) |
| return entry1_origins_same ? -1 : 1; |
| |
| // The "default" embedded rule comes after all other embedded rules. |
| bool embedding_origin1_empty = entry1.embedding_origin.is_empty(); |
| bool embedding_origin2_empty = entry2.embedding_origin.is_empty(); |
| if (embedding_origin1_empty != embedding_origin2_empty) |
| return embedding_origin2_empty ? -1 : 1; |
| |
| origin_comparison = |
| CompareOrigins(entry1.embedding_origin, entry2.embedding_origin); |
| } else if (column_id == IDS_EXCEPTIONS_ACTION_HEADER) { |
| // The rows are in different origins. We need to find out how the top-level |
| // origins will compare. |
| while (entries_[row1].origin != entries_[row1].embedding_origin) |
| --row1; |
| while (entries_[row2].origin != entries_[row2].embedding_origin) |
| --row2; |
| } |
| |
| // The entries are at the same "scope". If we're sorting by action, then do |
| // that now. |
| if (column_id == IDS_EXCEPTIONS_ACTION_HEADER) { |
| int compare_text = l10n_util::CompareString16WithCollator( |
| GetCollator(), GetText(row1, column_id), GetText(row2, column_id)); |
| if (compare_text != 0) |
| return compare_text; |
| } |
| |
| // Sort by the relevant origin. |
| return origin_comparison; |
| } |
| |
| void GeolocationExceptionsTableModel::AddEntriesForOrigin( |
| const GURL& origin, |
| const GeolocationContentSettingsMap::OneOriginSettings& settings) { |
| GeolocationContentSettingsMap::OneOriginSettings::const_iterator parent = |
| settings.find(origin); |
| |
| // Add the "parent" entry for the non-embedded setting. |
| entries_.push_back(Entry(origin, origin, |
| (parent == settings.end()) ? CONTENT_SETTING_DEFAULT : parent->second)); |
| |
| // Add the "children" for any embedded settings. |
| GeolocationContentSettingsMap::OneOriginSettings::const_iterator i; |
| for (i = settings.begin(); i != settings.end(); ++i) { |
| // Skip the non-embedded setting which we already added above. |
| if (i == parent) |
| continue; |
| |
| entries_.push_back(Entry(origin, i->first, i->second)); |
| } |
| } |