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/possible_url_model.h" 6 7 #include "base/callback.h" 8 #include "base/i18n/rtl.h" 9 #include "base/string_util.h" 10 #include "base/utf_string_conversions.h" 11 #include "chrome/browser/favicon_service.h" 12 #include "chrome/browser/prefs/pref_service.h" 13 #include "chrome/browser/profiles/profile.h" 14 #include "chrome/common/pref_names.h" 15 #include "content/browser/cancelable_request.h" 16 #include "grit/app_resources.h" 17 #include "grit/generated_resources.h" 18 #include "third_party/skia/include/core/SkBitmap.h" 19 #include "ui/base/models/table_model_observer.h" 20 #include "ui/base/resource/resource_bundle.h" 21 #include "ui/base/text/text_elider.h" 22 #include "ui/gfx/codec/png_codec.h" 23 24 using base::Time; 25 using base::TimeDelta; 26 27 namespace { 28 29 // The default favicon. 30 SkBitmap* default_favicon = NULL; 31 32 // How long we query entry points for. 33 const int kPossibleURLTimeScope = 30; 34 35 } // anonymous namespace 36 37 // Contains the data needed to show a result. 38 struct PossibleURLModel::Result { 39 Result() : index(0) {} 40 41 GURL url; 42 // Index of this Result in results_. This is used as the key into 43 // favicon_map_ to lookup the favicon for the url, as well as the index 44 // into results_ when the favicon is received. 45 size_t index; 46 ui::SortedDisplayURL display_url; 47 std::wstring title; 48 }; 49 50 51 PossibleURLModel::PossibleURLModel() 52 : profile_(NULL), 53 observer_(NULL) { 54 if (!default_favicon) { 55 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 56 default_favicon = rb.GetBitmapNamed(IDR_DEFAULT_FAVICON); 57 } 58 } 59 60 PossibleURLModel::~PossibleURLModel() { 61 } 62 63 void PossibleURLModel::Reload(Profile *profile) { 64 profile_ = profile; 65 consumer_.CancelAllRequests(); 66 HistoryService* hs = 67 profile->GetHistoryService(Profile::EXPLICIT_ACCESS); 68 if (hs) { 69 history::QueryOptions options; 70 options.end_time = Time::Now(); 71 options.begin_time = 72 options.end_time - TimeDelta::FromDays(kPossibleURLTimeScope); 73 options.max_count = 50; 74 75 hs->QueryHistory(string16(), options, &consumer_, 76 NewCallback(this, &PossibleURLModel::OnHistoryQueryComplete)); 77 } 78 } 79 80 void PossibleURLModel::OnHistoryQueryComplete(HistoryService::Handle h, 81 history::QueryResults* result) { 82 results_.resize(result->size()); 83 std::string languages = profile_ ? 84 profile_->GetPrefs()->GetString(prefs::kAcceptLanguages) : std::string(); 85 for (size_t i = 0; i < result->size(); ++i) { 86 results_[i].url = (*result)[i].url(); 87 results_[i].index = i; 88 results_[i].display_url = 89 ui::SortedDisplayURL((*result)[i].url(), languages); 90 results_[i].title = UTF16ToWide((*result)[i].title()); 91 } 92 93 // The old version of this code would filter out all but the most recent 94 // visit to each host, plus all typed URLs and AUTO_BOOKMARK transitions. I 95 // think this dialog has a lot of work, and I'm not sure those old 96 // conditions are correct (the results look about equal quality for my 97 // history with and without those conditions), so I'm not spending time 98 // re-implementing them here. They used to be implemented in the history 99 // service, but I think they should be implemented here because that was 100 // pretty specific behavior that shouldn't be generally exposed. 101 102 favicon_map_.clear(); 103 if (observer_) 104 observer_->OnModelChanged(); 105 } 106 107 int PossibleURLModel::RowCount() { 108 return static_cast<int>(results_.size()); 109 } 110 111 const GURL& PossibleURLModel::GetURL(int row) { 112 if (row < 0 || row >= RowCount()) { 113 NOTREACHED(); 114 return GURL::EmptyGURL(); 115 } 116 return results_[row].url; 117 } 118 119 const std::wstring& PossibleURLModel::GetTitle(int row) { 120 if (row < 0 || row >= RowCount()) { 121 NOTREACHED(); 122 return EmptyWString(); 123 } 124 return results_[row].title; 125 } 126 127 string16 PossibleURLModel::GetText(int row, int col_id) { 128 if (row < 0 || row >= RowCount()) { 129 NOTREACHED(); 130 return string16(); 131 } 132 133 if (col_id == IDS_ASI_PAGE_COLUMN) { 134 string16 title = WideToUTF16Hack(GetTitle(row)); 135 // TODO(xji): Consider adding a special case if the title text is a URL, 136 // since those should always have LTR directionality. Please refer to 137 // http://crbug.com/6726 for more information. 138 base::i18n::AdjustStringForLocaleDirection(&title); 139 return title; 140 } 141 142 // TODO(brettw): this should probably pass the GURL up so the URL elider 143 // can be used at a higher level when we know the width. 144 string16 url = results_[row].display_url.display_url(); 145 return base::i18n::GetDisplayStringInLTRDirectionality(url); 146 } 147 148 SkBitmap PossibleURLModel::GetIcon(int row) { 149 if (row < 0 || row >= RowCount()) { 150 NOTREACHED(); 151 return *default_favicon; 152 } 153 154 Result& result = results_[row]; 155 FaviconMap::iterator i = favicon_map_.find(result.index); 156 if (i != favicon_map_.end()) { 157 // We already requested the favicon, return it. 158 if (!i->second.isNull()) 159 return i->second; 160 } else if (profile_) { 161 FaviconService* favicon_service = 162 profile_->GetFaviconService(Profile::EXPLICIT_ACCESS); 163 if (favicon_service) { 164 CancelableRequestProvider::Handle h = 165 favicon_service->GetFaviconForURL( 166 result.url, history::FAVICON, &consumer_, 167 NewCallback(this, &PossibleURLModel::OnFaviconAvailable)); 168 consumer_.SetClientData(favicon_service, h, result.index); 169 // Add an entry to the map so that we don't attempt to request the 170 // favicon again. 171 favicon_map_[result.index] = SkBitmap(); 172 } 173 } 174 return *default_favicon; 175 } 176 177 int PossibleURLModel::CompareValues(int row1, int row2, int column_id) { 178 if (column_id == IDS_ASI_URL_COLUMN) { 179 return results_[row1].display_url.Compare( 180 results_[row2].display_url, GetCollator()); 181 } 182 return ui::TableModel::CompareValues(row1, row2, column_id); 183 } 184 185 void PossibleURLModel::OnFaviconAvailable( 186 FaviconService::Handle h, 187 history::FaviconData favicon) { 188 if (profile_) { 189 FaviconService* favicon_service = 190 profile_->GetFaviconService(Profile::EXPLICIT_ACCESS); 191 size_t index = consumer_.GetClientData(favicon_service, h); 192 if (favicon.is_valid()) { 193 // The decoder will leave our bitmap empty on error. 194 gfx::PNGCodec::Decode(favicon.image_data->front(), 195 favicon.image_data->size(), 196 &(favicon_map_[index])); 197 198 // Notify the observer. 199 if (!favicon_map_[index].isNull() && observer_) 200 observer_->OnItemsChanged(static_cast<int>(index), 1); 201 } 202 } 203 } 204 205 void PossibleURLModel::SetObserver(ui::TableModelObserver* observer) { 206 observer_ = observer; 207 } 208