| Index: chrome/browser/geolocation/geolocation_content_settings_table_model.cc | 
| =================================================================== | 
| --- chrome/browser/geolocation/geolocation_content_settings_table_model.cc	(revision 43621) | 
| +++ chrome/browser/geolocation/geolocation_content_settings_table_model.cc	(working copy) | 
| @@ -5,10 +5,47 @@ | 
| #include "chrome/browser/geolocation/geolocation_content_settings_table_model.h" | 
|  | 
| #include "app/l10n_util.h" | 
| +#include "app/l10n_util_collator.h" | 
| #include "app/table_model_observer.h" | 
| #include "base/utf_string_conversions.h" | 
| +#include "chrome/common/url_constants.h" | 
| #include "grit/generated_resources.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 | 
| + | 
| GeolocationContentSettingsTableModel::GeolocationContentSettingsTableModel( | 
| GeolocationContentSettingsMap* map) | 
| : map_(map), | 
| @@ -20,39 +57,55 @@ | 
| AddEntriesForOrigin(i->first, i->second); | 
| } | 
|  | 
| -bool GeolocationContentSettingsTableModel::CanRemoveException(int row) const { | 
| -  const Entry& entry = entries_[row]; | 
| -  return !(entry.origin == entry.embedding_origin && | 
| -           static_cast<size_t>(row + 1) < entries_.size() && | 
| -           entries_[row + 1].origin == entry.origin && | 
| -           entry.setting == CONTENT_SETTING_DEFAULT); | 
| +bool GeolocationContentSettingsTableModel::CanRemoveExceptions( | 
| +    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 true; | 
| } | 
|  | 
| -void GeolocationContentSettingsTableModel::RemoveException(int row) { | 
| -  Entry& entry = entries_[row]; | 
| -  bool next_has_same_origin = static_cast<size_t>(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); | 
| -  } else if (!next_has_same_origin && | 
| -        row > 0 && | 
| -        entries_[row - 1].origin == entry.origin && | 
| -        entries_[row - 1].setting == CONTENT_SETTING_DEFAULT) { | 
| -    // If we remove the last non-default child of a default parent, we should | 
| -    // remove the parent too. | 
| -    entries_.erase(entries_.begin() + row - 1, entries_.begin() + row + 1); | 
| -    if (observer_) | 
| -      observer_->OnItemsRemoved(row - 1, 2); | 
| -  } else { | 
| -    entries_.erase(entries_.begin() + row); | 
| -    if (observer_) | 
| -      observer_->OnItemsRemoved(row, 1); | 
| +void GeolocationContentSettingsTableModel::RemoveExceptions(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 CanRemoveExceptions(), 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)); | 
| } | 
| } | 
|  | 
| @@ -72,17 +125,27 @@ | 
| int column_id) { | 
| const Entry& entry = entries_[row]; | 
| if (column_id == IDS_EXCEPTIONS_HOSTNAME_HEADER) { | 
| -    if (entry.origin == entry.embedding_origin) | 
| -      return UTF8ToWide( | 
| -          GeolocationContentSettingsMap::OriginToString(entry.origin)); | 
| -    if (entry.embedding_origin.is_empty()) | 
| -      return ASCIIToWide("  ") + | 
| +    if (entry.origin == entry.embedding_origin) { | 
| +      return UTF8ToWide(GeolocationContentSettingsMap::OriginToString( | 
| +          entry.origin)); | 
| +    } | 
| +    std::wstring indent(L"    "); | 
| +    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::GetString(IDS_EXCEPTIONS_GEOLOCATION_EMBEDDED_ANY_OTHER); | 
| -    return ASCIIToWide("  ") + | 
| -        l10n_util::GetStringF(IDS_EXCEPTIONS_GEOLOCATION_EMBEDDED_ON_HOST, | 
| -            UTF8ToWide(GeolocationContentSettingsMap::OriginToString( | 
| -                entry.embedding_origin))); | 
| -  } else if (column_id == IDS_EXCEPTIONS_ACTION_HEADER) { | 
| +    } | 
| +    return indent + l10n_util::GetStringF( | 
| +        IDS_EXCEPTIONS_GEOLOCATION_EMBEDDED_ON_HOST, | 
| +        UTF8ToWide(GeolocationContentSettingsMap::OriginToString( | 
| +            entry.embedding_origin))); | 
| +  } | 
| + | 
| +  if (column_id == IDS_EXCEPTIONS_ACTION_HEADER) { | 
| switch (entry.setting) { | 
| case CONTENT_SETTING_ALLOW: | 
| return l10n_util::GetString(IDS_EXCEPTIONS_ALLOW_BUTTON); | 
| @@ -93,11 +156,11 @@ | 
| case CONTENT_SETTING_DEFAULT: | 
| return l10n_util::GetString(IDS_EXCEPTIONS_NOT_SET_BUTTON); | 
| default: | 
| -        NOTREACHED(); | 
| +        break; | 
| } | 
| -  } else { | 
| -    NOTREACHED(); | 
| } | 
| + | 
| +  NOTREACHED(); | 
| return std::wstring(); | 
| } | 
|  | 
| @@ -106,6 +169,55 @@ | 
| observer_ = observer; | 
| } | 
|  | 
| +int GeolocationContentSettingsTableModel::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::CompareStringWithCollator( | 
| +        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 GeolocationContentSettingsTableModel::AddEntriesForOrigin( | 
| const GURL& origin, | 
| const GeolocationContentSettingsMap::OneOriginSettings& settings) { | 
|  |