Home | History | Annotate | Download | only in geolocation
      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