| // Copyright (c) 2016 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 "base/memory/ptr_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/values.h" |
| #include "components/omnibox/browser/autocomplete_provider_client.h" |
| #include "components/omnibox/browser/autocomplete_provider_listener.h" |
| #include "components/omnibox/browser/history_url_provider.h" |
| #include "components/omnibox/browser/physical_web_provider.h" |
| #include "components/omnibox/browser/verbatim_match.h" |
| #include "components/physical_web/data_source/physical_web_data_source.h" |
| #include "components/url_formatter/url_formatter.h" |
| #include "grit/components_strings.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/gfx/text_elider.h" |
| #include "url/gurl.h" |
| |
| namespace { |
| |
| // Relevance score of the first Physical Web URL autocomplete match. This score |
| // is intended to be between ClipboardURLProvider and ZeroSuggestProvider. |
| // Subsequent matches should decrease in relevance to preserve the ordering |
| // in the metadata list. |
| static const int kPhysicalWebUrlBaseRelevance = 700; |
| |
| // The maximum length of the page title's part of the overflow item's |
| // description. Longer titles will be truncated to this length. In a normal |
| // Physical Web match item (non-overflow item) we allow the omnibox display to |
| // truncate the title instead. |
| static const size_t kMaxTitleLengthInOverflow = 15; |
| } |
| |
| // static |
| const size_t PhysicalWebProvider::kPhysicalWebMaxMatches = 1; |
| |
| // static |
| PhysicalWebProvider* PhysicalWebProvider::Create( |
| AutocompleteProviderClient* client, |
| HistoryURLProvider* history_url_provider) { |
| return new PhysicalWebProvider(client, history_url_provider); |
| } |
| |
| void PhysicalWebProvider::Start(const AutocompleteInput& input, |
| bool minimal_changes) { |
| DCHECK(kPhysicalWebMaxMatches < kMaxMatches); |
| |
| Stop(false, false); |
| |
| done_ = false; |
| matches_.clear(); |
| |
| // Stop providing suggestions in incognito mode or when the user enters text |
| // into the omnibox. |
| if (client_->IsOffTheRecord() || !input.from_omnibox_focus()) { |
| done_ = true; |
| return; |
| } |
| |
| PhysicalWebDataSource* data_source = client_->GetPhysicalWebDataSource(); |
| if (!data_source) { |
| done_ = true; |
| return; |
| } |
| |
| ConstructMatches(data_source->GetMetadata().get()); |
| |
| // Physical Web matches should never be default. If the omnibox input is |
| // non-empty and we have at least one Physical Web match, add the current URL |
| // as the default so that hitting enter after focusing the omnibox causes the |
| // current page to reload. If the input field is empty, no default match is |
| // required. |
| if (!matches_.empty() && !input.text().empty()) { |
| matches_.push_back(VerbatimMatchForURL(client_, input, input.current_url(), |
| history_url_provider_, -1)); |
| } |
| |
| done_ = true; |
| } |
| |
| void PhysicalWebProvider::Stop(bool clear_cached_results, |
| bool due_to_user_inactivity) { |
| done_ = true; |
| } |
| |
| PhysicalWebProvider::PhysicalWebProvider( |
| AutocompleteProviderClient* client, |
| HistoryURLProvider* history_url_provider) |
| : AutocompleteProvider(AutocompleteProvider::TYPE_PHYSICAL_WEB), |
| client_(client), |
| history_url_provider_(history_url_provider) {} |
| |
| PhysicalWebProvider::~PhysicalWebProvider() { |
| } |
| |
| void PhysicalWebProvider::ConstructMatches(base::ListValue* metadata_list) { |
| const size_t metadata_count = metadata_list->GetSize(); |
| size_t used_slots = 0; |
| |
| for (size_t i = 0; i < metadata_count; ++i) { |
| base::DictionaryValue* metadata_item = NULL; |
| if (!metadata_list->GetDictionary(i, &metadata_item)) { |
| continue; |
| } |
| |
| std::string url_string; |
| std::string title_string; |
| if (!metadata_item->GetString(kPhysicalWebResolvedUrlKey, &url_string) || |
| !metadata_item->GetString(kPhysicalWebTitleKey, &title_string)) { |
| continue; |
| } |
| base::string16 title = |
| AutocompleteMatch::SanitizeString(base::UTF8ToUTF16(title_string)); |
| |
| // Add match items with decreasing relevance to preserve the ordering in |
| // the metadata list. |
| int relevance = kPhysicalWebUrlBaseRelevance - used_slots; |
| |
| // Append an overflow item if creating a match for each metadata item would |
| // exceed the match limit. |
| const size_t remaining_slots = kPhysicalWebMaxMatches - used_slots; |
| const size_t remaining_metadata = metadata_count - i; |
| if ((remaining_slots == 1) && (remaining_metadata > remaining_slots)) { |
| AppendOverflowItem(remaining_metadata, relevance, title); |
| return; |
| } |
| |
| GURL url(url_string); |
| |
| AutocompleteMatch match(this, relevance, false, |
| AutocompleteMatchType::PHYSICAL_WEB); |
| match.destination_url = url; |
| |
| // Physical Web results should omit http:// (but not https://) and never |
| // appear bold. |
| match.contents = url_formatter::FormatUrl(url, |
| url_formatter::kFormatUrlOmitHTTP, net::UnescapeRule::SPACES, |
| nullptr, nullptr, nullptr); |
| match.contents_class.push_back( |
| ACMatchClassification(0, ACMatchClassification::URL)); |
| |
| match.fill_into_edit = |
| AutocompleteInput::FormattedStringWithEquivalentMeaning( |
| url, match.contents, client_->GetSchemeClassifier()); |
| |
| match.description = title; |
| match.description_class.push_back( |
| ACMatchClassification(0, ACMatchClassification::NONE)); |
| |
| matches_.push_back(match); |
| ++used_slots; |
| } |
| } |
| |
| void PhysicalWebProvider::AppendOverflowItem(int additional_url_count, |
| int relevance, |
| const base::string16& title) { |
| std::string url_string = "chrome://physical-web"; |
| GURL url(url_string); |
| |
| AutocompleteMatch match(this, relevance, false, |
| AutocompleteMatchType::PHYSICAL_WEB_OVERFLOW); |
| match.destination_url = url; |
| |
| base::string16 contents_title = gfx::TruncateString( |
| title, kMaxTitleLengthInOverflow, gfx::CHARACTER_BREAK); |
| if (contents_title.empty()) { |
| match.contents = l10n_util::GetPluralStringFUTF16( |
| IDS_PHYSICAL_WEB_OVERFLOW_EMPTY_TITLE, additional_url_count); |
| } else { |
| base::string16 contents_suffix = l10n_util::GetPluralStringFUTF16( |
| IDS_PHYSICAL_WEB_OVERFLOW, additional_url_count - 1); |
| match.contents = contents_title + base::UTF8ToUTF16(" ") + contents_suffix; |
| } |
| match.contents_class.push_back( |
| ACMatchClassification(0, ACMatchClassification::DIM)); |
| |
| match.fill_into_edit = |
| AutocompleteInput::FormattedStringWithEquivalentMeaning( |
| url, match.contents, client_->GetSchemeClassifier()); |
| |
| match.description = |
| l10n_util::GetStringUTF16(IDS_PHYSICAL_WEB_OVERFLOW_DESCRIPTION); |
| match.description_class.push_back( |
| ACMatchClassification(0, ACMatchClassification::NONE)); |
| |
| matches_.push_back(match); |
| } |