// 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)); } }