1 // Copyright (c) 2012 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/ui/webui/ntp/suggestions_combiner.h" 6 7 #include <algorithm> 8 9 #include "base/values.h" 10 #include "chrome/browser/profiles/profile.h" 11 #include "chrome/browser/ui/browser.h" 12 #include "chrome/browser/ui/browser_iterator.h" 13 #include "chrome/browser/ui/tabs/tab_strip_model.h" 14 #include "chrome/browser/ui/webui/ntp/suggestions_page_handler.h" 15 #include "chrome/browser/ui/webui/ntp/suggestions_source.h" 16 #include "content/public/browser/web_contents.h" 17 18 namespace { 19 20 static const size_t kSuggestionsCount = 8; 21 22 } // namespace 23 24 SuggestionsCombiner::SuggestionsCombiner( 25 SuggestionsCombiner::Delegate* delegate, 26 Profile* profile) 27 : sources_fetching_count_(0), 28 delegate_(delegate), 29 suggestions_count_(kSuggestionsCount), 30 page_values_(new base::ListValue()), 31 debug_enabled_(false), 32 profile_(profile) { 33 } 34 35 SuggestionsCombiner::~SuggestionsCombiner() { 36 } 37 38 void SuggestionsCombiner::AddSource(SuggestionsSource* source) { 39 source->SetCombiner(this); 40 source->SetDebug(debug_enabled_); 41 sources_.push_back(source); 42 } 43 44 void SuggestionsCombiner::EnableDebug(bool enable) { 45 debug_enabled_ = enable; 46 for (size_t i = 0; i < sources_.size(); ++i) { 47 sources_[i]->SetDebug(enable); 48 } 49 } 50 51 void SuggestionsCombiner::FetchItems(Profile* profile) { 52 sources_fetching_count_ = sources_.size(); 53 for (size_t i = 0; i < sources_.size(); ++i) { 54 sources_[i]->FetchItems(profile); 55 } 56 } 57 58 base::ListValue* SuggestionsCombiner::GetPageValues() { 59 return page_values_.get(); 60 } 61 62 void SuggestionsCombiner::OnItemsReady() { 63 DCHECK_GT(sources_fetching_count_, 0); 64 sources_fetching_count_--; 65 if (sources_fetching_count_ == 0) { 66 FillPageValues(); 67 delegate_->OnSuggestionsReady(); 68 } 69 } 70 71 void SuggestionsCombiner::SetSuggestionsCount(size_t suggestions_count) { 72 suggestions_count_ = suggestions_count; 73 } 74 75 void SuggestionsCombiner::FillPageValues() { 76 int total_weight = 0; 77 for (size_t i = 0; i < sources_.size(); ++i) 78 total_weight += sources_[i]->GetWeight(); 79 DCHECK_GT(total_weight, 0); 80 81 page_values_.reset(new base::ListValue()); 82 83 // Evaluate how many items to obtain from each source. We use error diffusion 84 // to ensure that we get the total desired number of items. 85 int error = 0; 86 87 // Holds the index at which the next item should be added for each source. 88 std::vector<size_t> next_item_index_for_source; 89 next_item_index_for_source.reserve(sources_.size()); 90 for (size_t i = 0; i < sources_.size(); ++i) { 91 int numerator = sources_[i]->GetWeight() * suggestions_count_ + error; 92 error = numerator % total_weight; 93 int item_count = std::min(numerator / total_weight, 94 sources_[i]->GetItemCount()); 95 96 for (int j = 0; j < item_count; ++j) 97 page_values_->Append(sources_[i]->PopItem()); 98 next_item_index_for_source.push_back(page_values_->GetSize()); 99 } 100 101 // Fill in extra items, prioritizing the first source. 102 // Rather than updating |next_item_index_for_source| we keep track of the 103 // number of extra items that were added and offset indices by that much. 104 size_t extra_items_added = 0; 105 for (size_t i = 0; i < sources_.size() && 106 page_values_->GetSize() < suggestions_count_; ++i) { 107 108 size_t index = next_item_index_for_source[i] + extra_items_added; 109 while (page_values_->GetSize() < suggestions_count_) { 110 base::DictionaryValue* item = sources_[i]->PopItem(); 111 if (!item) 112 break; 113 page_values_->Insert(index++, item); 114 extra_items_added++; 115 } 116 } 117 118 // Add page value information common to all sources. 119 for (size_t i = 0; i < page_values_->GetSize(); i++) { 120 base::DictionaryValue* page_value; 121 if (page_values_->GetDictionary(i, &page_value)) 122 AddExtendedInformation(page_value); 123 } 124 } 125 126 void SuggestionsCombiner::AddExtendedInformation( 127 base::DictionaryValue* page_value) { 128 if (debug_enabled_) { 129 std::string url_string; 130 if (page_value->GetString("url", &url_string)) { 131 GURL url(url_string); 132 page_value->SetBoolean("already_open", IsUrlAlreadyOpen(url)); 133 } 134 } 135 } 136 137 bool SuggestionsCombiner::IsUrlAlreadyOpen(const GURL &url) { 138 for (chrome::BrowserIterator it; !it.done(); it.Next()) { 139 const Browser* browser = *it; 140 if (browser->profile()->IsOffTheRecord() || 141 !browser->profile()->IsSameProfile(profile_)) 142 continue; 143 144 for (int i = 0; i < browser->tab_strip_model()->count(); i++) { 145 const content::WebContents* tab = 146 browser->tab_strip_model()->GetWebContentsAt(i); 147 if (tab->GetURL() == url) 148 return true; 149 } 150 } 151 return false; 152 } 153