summaryrefslogtreecommitdiffstats
path: root/chrome/browser/autocomplete
diff options
context:
space:
mode:
authorinitial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98>2008-07-26 23:55:29 +0000
committerinitial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98>2008-07-26 23:55:29 +0000
commit09911bf300f1a419907a9412154760efd0b7abc3 (patch)
treef131325fb4e2ad12c6d3504ab75b16dd92facfed /chrome/browser/autocomplete
parent586acc5fe142f498261f52c66862fa417c3d52d2 (diff)
downloadchromium_src-09911bf300f1a419907a9412154760efd0b7abc3.zip
chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.tar.gz
chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.tar.bz2
Add chrome to the repository.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@15 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/autocomplete')
-rw-r--r--chrome/browser/autocomplete/autocomplete.cc754
-rw-r--r--chrome/browser/autocomplete/autocomplete.h800
-rw-r--r--chrome/browser/autocomplete/autocomplete_edit.cc2333
-rw-r--r--chrome/browser/autocomplete/autocomplete_edit.h813
-rw-r--r--chrome/browser/autocomplete/autocomplete_popup.cc1187
-rw-r--r--chrome/browser/autocomplete/autocomplete_popup.h426
-rw-r--r--chrome/browser/autocomplete/autocomplete_unittest.cc361
-rw-r--r--chrome/browser/autocomplete/edit_drop_target.cc177
-rw-r--r--chrome/browser/autocomplete/edit_drop_target.h83
-rw-r--r--chrome/browser/autocomplete/history_contents_provider.cc264
-rw-r--r--chrome/browser/autocomplete/history_contents_provider.h127
-rw-r--r--chrome/browser/autocomplete/history_contents_provider_unittest.cc179
-rw-r--r--chrome/browser/autocomplete/history_url_provider.cc856
-rw-r--r--chrome/browser/autocomplete/history_url_provider.h419
-rw-r--r--chrome/browser/autocomplete/history_url_provider_unittest.cc382
-rw-r--r--chrome/browser/autocomplete/keyword_provider.cc304
-rw-r--r--chrome/browser/autocomplete/keyword_provider.h132
-rw-r--r--chrome/browser/autocomplete/keyword_provider_unittest.cc214
-rw-r--r--chrome/browser/autocomplete/search_provider.cc622
-rw-r--r--chrome/browser/autocomplete/search_provider.h244
20 files changed, 10677 insertions, 0 deletions
diff --git a/chrome/browser/autocomplete/autocomplete.cc b/chrome/browser/autocomplete/autocomplete.cc
new file mode 100644
index 0000000..ecc3bbd
--- /dev/null
+++ b/chrome/browser/autocomplete/autocomplete.cc
@@ -0,0 +1,754 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <algorithm>
+
+#include "chrome/browser/autocomplete/autocomplete.h"
+
+#include "base/string_util.h"
+#include "chrome/browser/autocomplete/history_url_provider.h"
+#include "chrome/browser/autocomplete/history_contents_provider.h"
+#include "chrome/browser/autocomplete/keyword_provider.h"
+#include "chrome/browser/autocomplete/search_provider.h"
+#include "chrome/browser/external_protocol_handler.h"
+#include "chrome/browser/history_tab_ui.h"
+#include "chrome/browser/url_fixer_upper.h"
+#include "chrome/common/gfx/url_elider.h"
+#include "chrome/common/l10n_util.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/pref_service.h"
+#include "googleurl/src/url_canon_ip.h"
+#include "net/base/net_util.h"
+#include "net/base/registry_controlled_domain.h"
+
+#include "generated_resources.h"
+
+
+// AutocompleteInput ----------------------------------------------------------
+
+AutocompleteInput::AutocompleteInput(const std::wstring& text,
+ const std::wstring& desired_tld,
+ bool prevent_inline_autocomplete)
+ : desired_tld_(desired_tld),
+ prevent_inline_autocomplete_(prevent_inline_autocomplete) {
+ // Trim whitespace from edges of input; don't inline autocomplete if there
+ // was trailing whitespace.
+ if (TrimWhitespace(text, TRIM_ALL, &text_) & TRIM_TRAILING)
+ prevent_inline_autocomplete_ = true;
+
+ url_parse::Parsed parts;
+ type_ = Parse(text_, desired_tld, &parts, &scheme_);
+
+ if (type_ == INVALID)
+ return;
+
+ if (type_ == FORCED_QUERY && text_[0] == L'?')
+ text_.erase(0, 1);
+}
+
+//static
+AutocompleteInput::Type AutocompleteInput::Parse(const std::wstring& text,
+ const std::wstring& desired_tld,
+ url_parse::Parsed* parts,
+ std::wstring* scheme) {
+ DCHECK(parts);
+
+ const size_t first_non_white = text.find_first_not_of(kWhitespaceWide, 0);
+ if (first_non_white == std::wstring::npos)
+ return INVALID; // All whitespace.
+
+ if (text.at(first_non_white) == L'?') {
+ // If the first non-whitespace character is a '?', we magically treat this
+ // as a query.
+ return FORCED_QUERY;
+ }
+
+ // Ask our parsing back-end to help us understand what the user typed. We
+ // use the URLFixerUpper here because we want to be smart about what we
+ // consider a scheme. For example, we shouldn't consider www.google.com:80
+ // to have a scheme.
+ const std::wstring parsed_scheme(URLFixerUpper::SegmentURL(text, parts));
+ if (scheme)
+ *scheme = parsed_scheme;
+
+ if (parsed_scheme == L"file") {
+ // A user might or might not type a scheme when entering a file URL.
+ return URL;
+ }
+
+ // If the user typed a scheme, determine our available actions based on that.
+ if (parts->scheme.is_valid()) {
+ // See if we know how to handle the URL internally.
+ if (URLRequest::IsHandledProtocol(WideToASCII(parsed_scheme)))
+ return URL;
+
+ // There are also some schemes that we convert to other things before they
+ // reach the renderer or else the renderer handles internally without
+ // reaching the URLRequest logic. We thus won't catch these above, but we
+ // should still claim to handle them.
+ if ((parsed_scheme == L"view-source") || (parsed_scheme == L"javascript") ||
+ (parsed_scheme == L"data"))
+ return URL;
+
+ // Finally, check and see if the user has explicitly opened this scheme as
+ // a URL before. We need to do this last because some schemes may be in
+ // here as "blocked" (e.g. "javascript") because we don't want pages to open
+ // them, but users still can.
+ switch (ExternalProtocolHandler::GetBlockState(parsed_scheme)) {
+ case ExternalProtocolHandler::DONT_BLOCK:
+ return URL;
+
+ case ExternalProtocolHandler::BLOCK:
+ // If we don't want the user to open the URL, don't let it be navigated
+ // to at all.
+ return QUERY;
+
+ default:
+ // We don't know about this scheme. It's likely to be a search operator
+ // like "site:" or "link:". We classify it as UNKNOWN so the user has
+ // the option of treating it as a URL if we're wrong.
+ // Note that SegmentURL() is smart so we aren't tricked by "c:\foo" or
+ // "www.example.com:81" in this case.
+ return UNKNOWN;
+ }
+ }
+
+ // The user didn't type a scheme. Assume that this is either an HTTP URL or
+ // not a URL at all; try to determine which.
+
+ // It's not clear that we can reach here with an empty "host" (maybe on some
+ // kinds of garbage input?), but if we did, it couldn't be a URL.
+ if (!parts->host.is_nonempty())
+ return QUERY;
+ // (We use the registry length later below but ask for it here so we can check
+ // the host's validity at this point.)
+ const std::wstring host(text.substr(parts->host.begin, parts->host.len));
+ const size_t registry_length =
+ RegistryControlledDomainService::GetRegistryLength(host, false);
+ if (registry_length == std::wstring::npos)
+ return QUERY; // It's not clear to me that we can reach this...
+
+ // A space in the "host" means this is a query. (Technically, IE and GURL
+ // allow hostnames with spaces for wierd intranet machines, but it's supposed
+ // to be illegal and I'm not worried about users trying to type these in.)
+ if (host.find(' ') != std::wstring::npos)
+ return QUERY;
+
+ // Presence of a password/port mean this is almost certainly a URL. We don't
+ // treat usernames (without passwords) as indicating a URL, because this could
+ // be an email address like "user@mail.com" which is more likely a search than
+ // an HTTP auth login attempt.
+ if (parts->password.is_nonempty() || parts->port.is_nonempty())
+ return URL;
+
+ // See if the host is an IP address.
+ bool is_ip_address;
+ net_util::CanonicalizeHost(host, &is_ip_address);
+ if (is_ip_address) {
+ // If the user originally typed a host that looks like an IP address (a
+ // dotted quad), they probably want to open it. If the original input was
+ // something else (like a single number), they probably wanted to search for
+ // it. This is true even if the URL appears to have a path: "1.2/45" is
+ // more likely a search (for the answer to a math problem) than a URL.
+ url_parse::Component components[4];
+ const bool found_ipv4 =
+ url_canon::FindIPv4Components(text.c_str(), parts->host, components);
+ DCHECK(found_ipv4);
+ for (size_t i = 0; i < arraysize(components); ++i) {
+ if (!components[i].is_nonempty())
+ return UNKNOWN;
+ }
+ return URL;
+ }
+
+ // The host doesn't look like a number, so see if the user's given us a path.
+ if (parts->path.is_nonempty()) {
+ // Most inputs with paths are URLs, even ones without known registries (e.g.
+ // intranet URLs). However, if there's no known registry, and the path has
+ // a space, this is more likely a query with a slash in the first term (e.g.
+ // "ps/2 games") than a URL. We can still open URLs with spaces in the path
+ // by escaping the space, and we will still inline autocomplete them if
+ // users have typed them in the past, but we default to searching since
+ // that's the common case.
+ return ((registry_length == 0) &&
+ (text.substr(parts->path.begin, parts->path.len).find(' ') !=
+ std::wstring::npos)) ? UNKNOWN : URL;
+ }
+
+ // If we reach here with a username, our input looks like "user@host"; this is
+ // the case mentioned above, where we think this is more likely an email
+ // address than an HTTP auth attempt, so search for it.
+ if (parts->username.is_nonempty())
+ return UNKNOWN;
+
+ // We have a bare host string. See if it has a known TLD. If so, it's
+ // probably a URL.
+ if (registry_length != 0)
+ return URL;
+
+ // No TLD that we know about. This could be:
+ // * A string that the user wishes to add a desired_tld to to get a URL. If
+ // we reach this point, we know there's no known TLD on the string, so the
+ // fixup code will be willing to add one; thus this is a URL.
+ // * A single word "foo"; possibly an intranet site, but more likely a search.
+ // This is ideally an UNKNOWN, and we can let the Alternate Nav URL code
+ // catch our mistakes.
+ // * A URL with a valid TLD we don't know about yet. If e.g. a registrar adds
+ // "xxx" as a TLD, then until we add it to our data file, Chrome won't know
+ // "foo.xxx" is a real URL. So ideally this is a URL, but we can't really
+ // distinguish this case from:
+ // * A "URL-like" string that's not really a URL (like
+ // "browser.tabs.closeButtons" or "java.awt.event.*"). This is ideally a
+ // QUERY. Since the above case and this one are indistinguishable, and this
+ // case is likely to be much more common, just say these are both UNKNOWN,
+ // which should default to the right thing and let users correct us on a
+ // case-by-case basis.
+ return desired_tld.empty() ? UNKNOWN : REQUESTED_URL;
+}
+
+bool AutocompleteInput::Equals(const AutocompleteInput& other) const {
+ return (text_ == other.text_) &&
+ (type_ == other.type_) &&
+ (desired_tld_ == other.desired_tld_) &&
+ (scheme_ == other.scheme_) &&
+ (prevent_inline_autocomplete_ == other.prevent_inline_autocomplete_);
+}
+
+void AutocompleteInput::Clear() {
+ text_.clear();
+ type_ = INVALID;
+ scheme_.clear();
+ desired_tld_.clear();
+ prevent_inline_autocomplete_ = false;
+}
+
+// AutocompleteMatch ----------------------------------------------------------
+
+AutocompleteMatch::AutocompleteMatch()
+ : provider(NULL),
+ relevance(0),
+ deletable(false),
+ inline_autocomplete_offset(std::wstring::npos),
+ transition(PageTransition::TYPED),
+ is_history_what_you_typed_match(false),
+ type(URL),
+ template_url(NULL),
+ starred(false) {
+}
+
+AutocompleteMatch::AutocompleteMatch(AutocompleteProvider* provider,
+ int relevance,
+ bool deletable)
+ : provider(provider),
+ relevance(relevance),
+ deletable(deletable),
+ inline_autocomplete_offset(std::wstring::npos),
+ transition(PageTransition::TYPED),
+ is_history_what_you_typed_match(false),
+ type(URL),
+ template_url(NULL),
+ starred(false) {
+}
+
+// static
+bool AutocompleteMatch::MoreRelevant(const AutocompleteMatch& elem1,
+ const AutocompleteMatch& elem2) {
+ // For equal-relevance matches, we sort alphabetically, so that providers
+ // who return multiple elements at the same priority get a "stable" sort
+ // across multiple updates.
+ if (elem1.relevance == elem2.relevance)
+ return elem1.contents > elem2.contents;
+
+ // A negative relevance indicates the real relevance can be determined by
+ // negating the value. If both relevances are negative, negate the result
+ // so that we end up with positive relevances, then negative relevances with
+ // the negative relevances sorted by absolute values.
+ const bool result = elem1.relevance > elem2.relevance;
+ return (elem1.relevance < 0 && elem2.relevance < 0) ? !result : result;
+}
+
+// static
+bool AutocompleteMatch::DestinationSortFunc(const AutocompleteMatch& elem1,
+ const AutocompleteMatch& elem2) {
+ // Sort identical destination_urls together. Place the most relevant matches
+ // first, so that when we call std::unique(), these are the ones that get
+ // preserved.
+ return (elem1.destination_url != elem2.destination_url) ?
+ (elem1.destination_url < elem2.destination_url) :
+ MoreRelevant(elem1, elem2);
+}
+
+// static
+bool AutocompleteMatch::DestinationsEqual(const AutocompleteMatch& elem1,
+ const AutocompleteMatch& elem2) {
+ return elem1.destination_url == elem2.destination_url;
+}
+
+// static
+void AutocompleteMatch::ClassifyMatchInString(
+ const std::wstring& find_text,
+ const std::wstring& text,
+ int style,
+ ACMatchClassifications* classification) {
+ ClassifyLocationInString(text.find(find_text), find_text.length(),
+ text.length(), style, classification);
+}
+
+void AutocompleteMatch::ClassifyLocationInString(
+ size_t match_location,
+ size_t match_length,
+ size_t overall_length,
+ int style,
+ ACMatchClassifications* classification) {
+ // Classifying an empty match makes no sense and will lead to validation
+ // errors later.
+ DCHECK(match_length > 0);
+
+ classification->clear();
+
+ // Don't classify anything about an empty string
+ // (AutocompleteMatch::Validate() checks this).
+ if (overall_length == 0)
+ return;
+
+ // Mark pre-match portion of string (if any).
+ if (match_location != 0) {
+ classification->push_back(ACMatchClassification(0, style));
+ }
+
+ // Mark matching portion of string.
+ if (match_location == std::wstring::npos) {
+ // No match, above classification will suffice for whole string.
+ return;
+ }
+ classification->push_back(ACMatchClassification(match_location,
+ (style | ACMatchClassification::MATCH) & ~ACMatchClassification::DIM));
+
+ // Mark post-match portion of string (if any).
+ const size_t after_match(match_location + match_length);
+ if (after_match < overall_length) {
+ classification->push_back(ACMatchClassification(after_match, style));
+ }
+}
+
+#ifndef NDEBUG
+void AutocompleteMatch::Validate() const {
+ ValidateClassifications(contents, contents_class);
+ ValidateClassifications(description, description_class);
+}
+
+void AutocompleteMatch::ValidateClassifications(
+ const std::wstring& text,
+ const ACMatchClassifications& classifications) const {
+ if (text.empty()) {
+ DCHECK(classifications.size() == 0);
+ return;
+ }
+
+ // The classifications should always cover the whole string.
+ DCHECK(classifications.size() > 0) << "No classification for text";
+ DCHECK(classifications[0].offset == 0) << "Classification misses beginning";
+ if (classifications.size() == 1)
+ return;
+
+ // The classifications should always be sorted.
+ size_t last_offset = classifications[0].offset;
+ for (ACMatchClassifications::const_iterator i(classifications.begin() + 1);
+ i != classifications.end(); ++i) {
+ DCHECK(i->offset > last_offset) << "Classification unsorted";
+ DCHECK(i->offset < text.length()) << "Classification out of bounds";
+ last_offset = i->offset;
+ }
+}
+#endif
+
+// AutocompleteProvider -------------------------------------------------------
+
+// static
+size_t AutocompleteProvider::max_matches_ = 3;
+
+AutocompleteProvider::~AutocompleteProvider() {
+ Stop();
+}
+
+void AutocompleteProvider::SetProfile(Profile* profile) {
+ DCHECK(profile);
+ Stop(); // It makes no sense to continue running a query from an old profile.
+ profile_ = profile;
+}
+
+std::wstring AutocompleteProvider::StringForURLDisplay(
+ const GURL& url,
+ bool check_accept_lang) {
+ return gfx::ElideUrl(url, ChromeFont(), 0, check_accept_lang && profile_ ?
+ profile_->GetPrefs()->GetString(prefs::kAcceptLanguages) :
+ std::wstring());
+}
+
+// AutocompleteResult ---------------------------------------------------------
+
+// static
+size_t AutocompleteResult::max_matches_ = 6;
+
+void AutocompleteResult::Selection::Clear() {
+ destination_url.clear();
+ provider_affinity = NULL;
+ is_history_what_you_typed_match = false;
+}
+
+AutocompleteResult::AutocompleteResult() {
+ // Reserve space for the max number of matches we'll show. The +1 accounts
+ // for the history shortcut match as it isn't included in max_matches.
+ matches_.reserve(max_matches() + 1);
+
+ // It's probably safe to do this in the initializer list, but there's little
+ // penalty to doing it here and it ensures our object is fully constructed
+ // before calling member functions.
+ default_match_ = end();
+}
+
+void AutocompleteResult::CopyFrom(const AutocompleteResult& rhs) {
+ if (this == &rhs)
+ return;
+
+ matches_ = rhs.matches_;
+ // Careful! You can't just copy iterators from another container, you have to
+ // reconstruct them.
+ default_match_ = (rhs.default_match_ == rhs.end()) ?
+ end() : (begin() + (rhs.default_match_ - rhs.begin()));
+}
+
+void AutocompleteResult::AppendMatches(const ACMatches& matches) {
+ default_match_ = end();
+ std::copy(matches.begin(), matches.end(), std::back_inserter(matches_));
+}
+
+void AutocompleteResult::AddMatch(const AutocompleteMatch& match) {
+ default_match_ = end();
+ matches_.insert(std::upper_bound(matches_.begin(), matches_.end(), match,
+ &AutocompleteMatch::MoreRelevant), match);
+}
+
+void AutocompleteResult::SortAndCull() {
+ default_match_ = end();
+
+ // Remove duplicates.
+ std::sort(matches_.begin(), matches_.end(),
+ &AutocompleteMatch::DestinationSortFunc);
+ matches_.erase(std::unique(matches_.begin(), matches_.end(),
+ &AutocompleteMatch::DestinationsEqual),
+ matches_.end());
+
+ // Find the top max_matches.
+ if (matches_.size() > max_matches()) {
+ std::partial_sort(matches_.begin(), matches_.begin() + max_matches(),
+ matches_.end(), &AutocompleteMatch::MoreRelevant);
+ matches_.resize(max_matches());
+ }
+
+ // HistoryContentsProvider use a negative relevance as a way to avoid
+ // starving out other provider results, yet we may end up using the result. To
+ // make sure such results are sorted correctly we search for all
+ // relevances < 0 and negate them. If we change our relevance algorithm to
+ // properly mix different providers results, this can go away.
+ for (ACMatches::iterator i = matches_.begin(); i != matches_.end(); ++i) {
+ if (i->relevance < 0)
+ i->relevance = -i->relevance;
+ }
+
+ // Now put the final result set in order.
+ std::sort(matches_.begin(), matches_.end(), &AutocompleteMatch::MoreRelevant);
+}
+
+bool AutocompleteResult::SetDefaultMatch(const Selection& selection) {
+ default_match_ = end();
+
+ // Look for the best match.
+ for (const_iterator i(begin()); i != end(); ++i) {
+ // If we have an exact match, return immediately.
+ if (selection.is_history_what_you_typed_match ?
+ i->is_history_what_you_typed_match :
+ (!selection.destination_url.empty() &&
+ (i->destination_url == selection.destination_url))) {
+ default_match_ = i;
+ return true;
+ }
+
+ // Otherwise, see if this match is closer to the desired selection than the
+ // existing one.
+ if (default_match_ == end()) {
+ // No match at all yet, pick the first one we see.
+ default_match_ = i;
+ } else if (selection.provider_affinity == NULL) {
+ // No provider desired, choose solely based on relevance.
+ if (AutocompleteMatch::MoreRelevant(*i, *default_match_))
+ default_match_ = i;
+ } else {
+ // Desired provider trumps any undesired provider; otherwise choose based
+ // on relevance.
+ const bool providers_match =
+ (i->provider == selection.provider_affinity);
+ const bool default_provider_doesnt_match =
+ (default_match_->provider != selection.provider_affinity);
+ if ((providers_match && default_provider_doesnt_match) ||
+ ((providers_match || default_provider_doesnt_match) &&
+ AutocompleteMatch::MoreRelevant(*i, *default_match_)))
+ default_match_ = i;
+ }
+ }
+ return false;
+}
+
+std::wstring AutocompleteResult::GetAlternateNavURL(
+ const AutocompleteInput& input,
+ const_iterator match) const {
+ if (((input.type() == AutocompleteInput::UNKNOWN) ||
+ (input.type() == AutocompleteInput::REQUESTED_URL)) &&
+ (match->transition != PageTransition::TYPED)) {
+ for (const_iterator i(begin()); i != end(); ++i) {
+ if (i->is_history_what_you_typed_match) {
+ return (i->destination_url == match->destination_url) ?
+ std::wstring() : i->destination_url;
+ }
+ }
+ }
+ return std::wstring();
+}
+
+#ifndef NDEBUG
+void AutocompleteResult::Validate() const {
+ for (const_iterator i(begin()); i != end(); ++i)
+ i->Validate();
+}
+#endif
+
+// AutocompleteController -----------------------------------------------------
+
+const int AutocompleteController::kNoItemSelected = -1;
+
+AutocompleteController::AutocompleteController(ACControllerListener* listener,
+ Profile* profile)
+ : listener_(listener) {
+ providers_.push_back(new SearchProvider(this, profile));
+ providers_.push_back(new HistoryURLProvider(this, profile));
+ keyword_provider_ = new KeywordProvider(this, profile);
+ providers_.push_back(keyword_provider_);
+ if (listener) {
+ // These providers are async-only, so there's no need to create them when
+ // we'll only be doing synchronous queries.
+ history_contents_provider_ = new HistoryContentsProvider(this, profile);
+ providers_.push_back(history_contents_provider_);
+ } else {
+ history_contents_provider_ = NULL;
+ }
+ for (ACProviders::iterator i(providers_.begin()); i != providers_.end(); ++i)
+ (*i)->AddRef();
+}
+
+AutocompleteController::~AutocompleteController() {
+ for (ACProviders::iterator i(providers_.begin()); i != providers_.end(); ++i)
+ (*i)->Release();
+
+ providers_.clear(); // Not really necessary.
+}
+
+void AutocompleteController::SetProfile(Profile* profile) {
+ for (ACProviders::iterator i(providers_.begin()); i != providers_.end(); ++i)
+ (*i)->SetProfile(profile);
+}
+
+bool AutocompleteController::Start(const AutocompleteInput& input,
+ bool minimal_changes,
+ bool synchronous_only) {
+ input_ = input;
+ for (ACProviders::iterator i(providers_.begin()); i != providers_.end();
+ ++i) {
+ (*i)->Start(input, minimal_changes, synchronous_only);
+ if (synchronous_only)
+ DCHECK((*i)->done());
+ }
+
+ return QueryComplete();
+}
+
+void AutocompleteController::Stop() const {
+ for (ACProviders::const_iterator i(providers_.begin());
+ i != providers_.end(); ++i) {
+ if (!(*i)->done())
+ (*i)->Stop();
+ }
+}
+
+void AutocompleteController::OnProviderUpdate(bool updated_matches) {
+ // Notify listener when something has changed.
+ if (!listener_) {
+ NOTREACHED(); // This should never be called for synchronous queries, and
+ // since |listener_| is NULL, the owner of the controller
+ // should only be running synchronous queries.
+ return; // But, this isn't fatal, so don't crash.
+ }
+ const bool query_complete = QueryComplete();
+ if (updated_matches || query_complete)
+ listener_->OnAutocompleteUpdate(updated_matches, query_complete);
+}
+
+void AutocompleteController::GetResult(AutocompleteResult* result) {
+ // Add all providers' results.
+ result->Reset();
+ for (ACProviders::const_iterator i(providers_.begin());
+ i != providers_.end(); ++i)
+ result->AppendMatches((*i)->matches());
+
+ // Sort the matches and trim to a small number of "best" matches.
+ result->SortAndCull();
+
+ if (history_contents_provider_)
+ AddHistoryContentsShortcut(result);
+
+#ifndef NDEBUG
+ result->Validate();
+#endif
+}
+
+bool AutocompleteController::QueryComplete() const {
+ for (ACProviders::const_iterator i(providers_.begin());
+ i != providers_.end(); ++i) {
+ if (!(*i)->done())
+ return false;
+ }
+
+ return true;
+}
+
+size_t AutocompleteController::CountMatchesNotInResult(
+ const AutocompleteProvider* provider,
+ const AutocompleteResult* result,
+ AutocompleteMatch* first_match) {
+ DCHECK(provider && result && first_match);
+
+ // Determine the set of destination URLs.
+ std::set<std::wstring> destination_urls;
+ for (AutocompleteResult::const_iterator i(result->begin());
+ i != result->end(); ++i)
+ destination_urls.insert(i->destination_url);
+
+ const ACMatches& provider_matches = provider->matches();
+ bool found_first_unique_match = false;
+ size_t showing_count = 0;
+ for (ACMatches::const_iterator i = provider_matches.begin();
+ i != provider_matches.end(); ++i) {
+ if (destination_urls.find(i->destination_url) != destination_urls.end()) {
+ showing_count++;
+ } else if (!found_first_unique_match) {
+ found_first_unique_match = true;
+ *first_match = *i;
+ }
+ }
+ return provider_matches.size() - showing_count;
+}
+
+void AutocompleteController::AddHistoryContentsShortcut(
+ AutocompleteResult* result) {
+ DCHECK(result && history_contents_provider_);
+ // Only check the history contents provider if the history contents provider
+ // is done and has matches.
+ if (!history_contents_provider_->done() ||
+ !history_contents_provider_->db_match_count()) {
+ return;
+ }
+
+ if ((history_contents_provider_->db_match_count() <= result->size() + 1) ||
+ history_contents_provider_->db_match_count() == 1) {
+ // We only want to add a shortcut if we're not already showing the matches.
+ AutocompleteMatch first_unique_match;
+ size_t matches_not_shown = CountMatchesNotInResult(
+ history_contents_provider_, result, &first_unique_match);
+ if (matches_not_shown == 0)
+ return;
+ if (matches_not_shown == 1) {
+ // Only one match not shown, add it. The relevance may be negative,
+ // which means we need to negate it to get the true relevance.
+ if (first_unique_match.relevance < 0)
+ first_unique_match.relevance = -first_unique_match.relevance;
+ result->AddMatch(first_unique_match);
+ return;
+ } // else, fall through and add item.
+ }
+
+ AutocompleteMatch match(NULL, 0, false);
+ match.type = AutocompleteMatch::HISTORY_SEARCH;
+ match.fill_into_edit = input_.text();
+
+ // Mark up the text such that the user input text is bold.
+ size_t keyword_offset = std::wstring::npos; // Offset into match.contents.
+ if (history_contents_provider_->db_match_count() ==
+ history_contents_provider_->kMaxMatchCount) {
+ // History contents searcher has maxed out.
+ match.contents = l10n_util::GetStringF(IDS_OMNIBOX_RECENT_HISTORY_MANY,
+ input_.text(),
+ &keyword_offset);
+ } else {
+ // We can report exact matches when there aren't too many.
+ std::vector<size_t> content_param_offsets;
+ match.contents =
+ l10n_util::GetStringF(IDS_OMNIBOX_RECENT_HISTORY,
+ FormatNumber(history_contents_provider_->
+ db_match_count()),
+ input_.text(),
+ &content_param_offsets);
+
+ // content_param_offsets is ordered based on supplied params, we expect
+ // that the second one contains the query (first is the number).
+ if (content_param_offsets.size() == 2) {
+ keyword_offset = content_param_offsets[1];
+ } else {
+ // See comments on an identical NOTREACHED() in search_provider.cc.
+ NOTREACHED();
+ }
+ }
+
+ // NOTE: This comparison succeeds when keyword_offset == std::wstring::npos.
+ if (keyword_offset > 0) {
+ match.contents_class.push_back(
+ ACMatchClassification(0, ACMatchClassification::NONE));
+ }
+ match.contents_class.push_back(
+ ACMatchClassification(keyword_offset, ACMatchClassification::MATCH));
+ if (keyword_offset + input_.text().size() < match.contents.size()) {
+ match.contents_class.push_back(
+ ACMatchClassification(keyword_offset + input_.text().size(),
+ ACMatchClassification::NONE));
+ }
+ match.destination_url =
+ UTF8ToWide(HistoryTabUI::GetHistoryURLWithSearchText(
+ input_.text()).spec());
+ match.transition = PageTransition::AUTO_BOOKMARK;
+ match.provider = history_contents_provider_;
+ result->AddMatch(match);
+}
diff --git a/chrome/browser/autocomplete/autocomplete.h b/chrome/browser/autocomplete/autocomplete.h
new file mode 100644
index 0000000..cdcc0d1
--- /dev/null
+++ b/chrome/browser/autocomplete/autocomplete.h
@@ -0,0 +1,800 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CHROME_BROWSER_AUTOCOMPLETE_AUTOCOMPLETE_H__
+#define CHROME_BROWSER_AUTOCOMPLETE_AUTOCOMPLETE_H__
+
+#include <map>
+#include <string>
+#include <vector>
+#include "base/logging.h"
+#include "base/ref_counted.h"
+#include "chrome/common/page_transition_types.h"
+#include "googleurl/src/url_parse.h"
+
+// The AutocompleteController is the center of the autocomplete system. A
+// class implementing AutocompleteController::Listener creates an instance of
+// the controller, which in turn creates a set of AutocompleteProviders to
+// serve it. The listener can ask the controller to Start() a query; the
+// controller in turn passes this call down to the providers, each of which
+// keeps track of its own results and whether it has finished processing the
+// query. When a provider gets more results or finishes processing, it
+// notifies the controller, which merges the combined results together and
+// returns them to the listener.
+//
+// The listener may also cancel the current query by calling Stop(), which the
+// controller will in turn communicate to all the providers. No callbacks will
+// happen after a request has been stopped.
+//
+// IMPORTANT: There is NO THREAD SAFETY built into this portion of the
+// autocomplete system. All calls to and from the AutocompleteController should
+// happen on the same thread. AutocompleteProviders are responsible for doing
+// their own thread management when they need to return results asynchronously.
+//
+// The AutocompleteProviders each return one kind of results, such as history
+// results or search results. These results are given "relevance" scores.
+// Historically the relevance for each column added up to 100, then scores
+// were from 1-100. Both have proved a bit painful, and will be changed going
+// forward. The important part is that higher relevance scores are more
+// important than lower relevance scores. The relevance scores and class
+// providing the result are as follows:
+//
+// UNKNOWN input type:
+// --------------------------------------------------------------------|-----
+// Keyword (non-substituting, exact match) | 1500
+// HistoryURL (exact or inline autocomplete match) | 1400
+// Search (what you typed) | 1300
+// HistoryURL (what you typed) | 1200
+// Keyword (substituting, exact match) | 1100
+// Search (past query in history) | 1050--
+// HistoryContents (any match in title of starred page) | 1000++
+// HistoryURL (inexact match) | 900++
+// Search (navigational suggestion) | 800++
+// HistoryContents (any match in title of nonstarred page) | 700++
+// Search (suggestion) | 600++
+// HistoryContents (any match in body of starred page) | 550++
+// HistoryContents (any match in body of nonstarred page) | 500++
+// Keyword (inexact match) | 450
+//
+// REQUESTED_URL input type:
+// --------------------------------------------------------------------|-----
+// Keyword (non-substituting, exact match) | 1500
+// HistoryURL (exact or inline autocomplete match) | 1400
+// HistoryURL (what you typed) | 1300
+// Search (what you typed) | 1200
+// Keyword (substituting, exact match) | 1100
+// Search (past query in history) | 1050--
+// HistoryContents (any match in title of starred page) | 1000++
+// HistoryURL (inexact match) | 900++
+// Search (navigational suggestion) | 800++
+// HistoryContents (any match in title of nonstarred page) | 700++
+// Search (suggestion) | 600++
+// HistoryContents (any match in body of starred page) | 550++
+// HistoryContents (any match in body of nonstarred page) | 500++
+// Keyword (inexact match) | 450
+//
+// URL input type:
+// --------------------------------------------------------------------|-----
+// Keyword (non-substituting, exact match) | 1500
+// HistoryURL (exact or inline autocomplete match) | 1400
+// HistoryURL (what you typed) | 1200
+// Keyword (substituting, exact match) | 1100
+// HistoryURL (inexact match) | 900++
+// Search (what you typed) | 850
+// Search (navigational suggestion) | 800++
+// Search (past query in history) | 750--
+// Keyword (inexact match) | 700
+// Search (suggestion) | 300++
+//
+// QUERY input type:
+// --------------------------------------------------------------------|-----
+// Keyword (non-substituting, exact match) | 1500
+// Keyword (substituting, exact match) | 1400
+// Search (what you typed) | 1300
+// Search (past query in history) | 1250--
+// HistoryContents (any match in title of starred page) | 1200++
+// Search (navigational suggestion) | 1000++
+// HistoryContents (any match in title of nonstarred page) | 900++
+// Search (suggestion) | 800++
+// HistoryContents (any match in body of starred page) | 750++
+// HistoryContents (any match in body of nonstarred page) | 700++
+// Keyword (inexact match) | 650
+//
+// FORCED_QUERY input type:
+// --------------------------------------------------------------------|-----
+// Search (what you typed) | 1500
+// Search (past query in history) | 1250--
+// HistoryContents (any match in title of starred page) | 1200++
+// Search (navigational suggestion) | 1000++
+// HistoryContents (any match in title of nonstarred page) | 900++
+// Search (suggestion) | 800++
+// HistoryContents (any match in body of starred page) | 750++
+// HistoryContents (any match in body of nonstarred page) | 700++
+//
+// (A search keyword is a keyword with a replacement string; a bookmark keyword
+// is a keyword with no replacement string, that is, a shortcut for a URL.)
+//
+// The value column gives the ranking returned from the various providers.
+// ++: a series of results with relevance from n up to (n + max_matches).
+// --: relevance score falls off over time (discounted 50 points @ 15 minutes,
+// 450 points @ two weeks)
+
+class AutocompleteInput;
+struct AutocompleteMatch;
+class AutocompleteProvider;
+class AutocompleteResult;
+class AutocompleteController;
+class GURL;
+class HistoryContentsProvider;
+class KeywordProvider;
+class Profile;
+class TemplateURL;
+
+typedef std::vector<AutocompleteMatch> ACMatches;
+typedef std::vector<AutocompleteProvider*> ACProviders;
+
+// AutocompleteInput ----------------------------------------------------------
+
+// The user input for an autocomplete query. Allows copying.
+class AutocompleteInput {
+ public:
+ enum Type {
+ INVALID, // Empty input
+ UNKNOWN, // Valid input whose type cannot be determined
+ REQUESTED_URL, // Input autodetected as UNKNOWN, which the user wants to
+ // treat as an URL by specifying a desired_tld
+ URL, // Input autodetected as a URL
+ QUERY, // Input autodetected as a query
+ FORCED_QUERY, // Input forced to be a query by an initial '?'
+ };
+
+ AutocompleteInput() : type_(INVALID), prevent_inline_autocomplete_(false) {}
+ AutocompleteInput(const std::wstring& text,
+ const std::wstring& desired_tld,
+ bool prevent_inline_autocomplete);
+
+ // Parses |text| and returns the type of input this will be interpreted as.
+ // The components of the input are stored in the output parameter |parts|.
+ static Type Parse(const std::wstring& text,
+ const std::wstring& desired_tld,
+ url_parse::Parsed* parts,
+ std::wstring* scheme);
+
+ // User-provided text to be completed.
+ const std::wstring& text() const { return text_; }
+
+ // Use of this setter is risky, since no other internal state is updated
+ // besides |text_|. Only callers who know that they're not changing the
+ // type/scheme/etc. should use this.
+ void set_text(const std::wstring& text) { text_ = text; }
+
+ // The type of input supplied.
+ Type type() const { return type_; }
+
+ // The scheme parsed from the provided text; only meaningful when type_ is
+ // URL.
+ const std::wstring& scheme() const { return scheme_; }
+
+ // User's desired TLD, if one is not already present in the text to
+ // autocomplete. When this is non-empty, it also implies that "www." should
+ // be prepended to the domain where possible. This should not have a leading
+ // '.' (use "com" instead of ".com").
+ const std::wstring& desired_tld() const { return desired_tld_; }
+
+ // Returns whether inline autocompletion should be prevented.
+ const bool prevent_inline_autocomplete() const {
+ return prevent_inline_autocomplete_;
+ }
+
+ // operator==() by another name.
+ bool Equals(const AutocompleteInput& other) const;
+
+ // Resets all internal variables to the null-constructed state.
+ void Clear();
+
+ private:
+ std::wstring text_;
+ Type type_;
+ std::wstring scheme_;
+ std::wstring desired_tld_;
+ bool prevent_inline_autocomplete_;
+};
+
+// AutocompleteMatch ----------------------------------------------------------
+
+// A single result line with classified spans. The autocomplete popup displays
+// the 'contents' and the 'description' (the description is optional) in the
+// autocomplete dropdown, and fills in 'fill_into_edit' into the textbox when
+// that line is selected. fill_into_edit may be the same as 'description' for
+// things like URLs, but may be different for searches or other providers. For
+// example, a search result may say "Search for asdf" as the description, but
+// "asdf" should appear in the box.
+struct AutocompleteMatch {
+ // Autocomple results return strings that are classified according to a
+ // separate vector of styles. This vector must be sorted, and associates
+ // flags with portions of the strings. It is required that all text be
+ // inside a classification range. Even if you have no classification, you
+ // should create an entry at offset 0 with no flags.
+ //
+ // Example: The user typed "goog"
+ // http://www.google.com/ Google
+ // ^ ^ ^ ^ ^
+ // 0, | 15, | 4,
+ // 11,match 0,match
+ //
+ // This structure holds the classifiction information for each span.
+ struct ACMatchClassification {
+ // The values in here are not mutually exclusive -- use them like a
+ // bitfield. This also means we use "int" instead of this enum type when
+ // passing the values around, so the compiler doesn't complain.
+ enum Style {
+ NONE = 0,
+ URL = 1 << 0, // A URL
+ MATCH = 1 << 1, // A match for the user's search term
+ DIM = 1 << 2, // "Helper text"
+ };
+
+ ACMatchClassification(size_t offset, int style)
+ : offset(offset),
+ style(style) {
+ }
+
+ // Offset within the string that this classification starts
+ size_t offset;
+
+ int style;
+ };
+
+ typedef std::vector<ACMatchClassification> ACMatchClassifications;
+
+ // The type of this match.
+ // URL: a url, typically one the user previously entered but it may have
+ // also been suggested. This is the default.
+ // KEYWORD: a keyword.
+ // SEARCH: short cut for typing type into the Google homepage. This should
+ // only be used if the full URL is not shown.
+ enum Type {
+ // Something that looks like a URL ("http://foo.com", "internal-server/").
+ // This is the default.
+ URL,
+
+ // A manually created or auto-generated keyword, with or without a query
+ // component. Auto-generated keywords may look similar to urls. See
+ // keyword_autocomplete.cc.
+ KEYWORD,
+
+ // A search term or phrase for the user's default search provider
+ // ("games", "foo"). These visually look similar to keywords. See
+ // google_autocomplete.cc.
+ SEARCH,
+
+ // Shortcut that takes the user to destinations->history.
+ HISTORY_SEARCH
+ };
+
+ AutocompleteMatch();
+ AutocompleteMatch(AutocompleteProvider* provider,
+ int relevance,
+ bool deletable);
+
+ // Comparison function for determining when one match is better than another.
+ static bool MoreRelevant(const AutocompleteMatch& elem1,
+ const AutocompleteMatch& elem2);
+
+ // Comparison functions for removing matches with duplicate destinations.
+ static bool DestinationSortFunc(const AutocompleteMatch& elem1,
+ const AutocompleteMatch& elem2);
+ static bool DestinationsEqual(const AutocompleteMatch& elem1,
+ const AutocompleteMatch& elem2);
+
+ // Helper functions for classes creating matches:
+ // Fills in the classifications for |text|, using |style| as the base style
+ // and marking the first instance of |find_text| as a match. (This match
+ // will also not be dimmed, if |style| has DIM set.)
+ static void ClassifyMatchInString(const std::wstring& find_text,
+ const std::wstring& text,
+ int style,
+ ACMatchClassifications* classifications);
+
+ // Similar to ClassifyMatchInString(), but for cases where the range to mark
+ // as matching is already known (avoids calling find()). This can be helpful
+ // when find() would be misleading (e.g. you want to mark the second match in
+ // a string instead of the first).
+ static void ClassifyLocationInString(size_t match_location,
+ size_t match_length,
+ size_t overall_length,
+ int style,
+ ACMatchClassifications* classifications);
+
+ // The provider of this match, used to remember which provider the user had
+ // selected when the input changes. This may be NULL, in which case there is
+ // no provider (or memory of the user's selection).
+ AutocompleteProvider* provider;
+
+ // The relevance of this match. See table above for scores returned by
+ // various providers. This is used to rank matches among all responding
+ // providers, so different providers must be carefully tuned to supply
+ // matches with appropriate relevance.
+ //
+ // If the relevance is negative, it will only be displayed if there are not
+ // enough non-negative items in all the providers to max out the popup. In
+ // this case, the relevance of the additional items will be inverted so they
+ // can be mixed in with the rest of the relevances. This allows a provider
+ // to group its results, having the added items appear intermixed with its
+ // other results.
+ //
+ // TODO(pkasting): http://b/1111299 This should be calculated algorithmically,
+ // rather than being a fairly fixed value defined by the table above.
+ int relevance;
+
+ // True if the user should be able to delete this match.
+ bool deletable;
+
+ // This string is loaded into the location bar when the item is selected
+ // by pressing the arrow keys. This may be different than a URL, for example,
+ // for search suggestions, this would just be the search terms.
+ std::wstring fill_into_edit;
+
+ // The position within fill_into_edit from which we'll display the inline
+ // autocomplete string. This will be std::wstring::npos if this match should
+ // not be inline autocompleted.
+ size_t inline_autocomplete_offset;
+
+ // The URL to actually load when the autocomplete item is selected. This URL
+ // should be canonical so we can compare URLs with strcmp to avoid dupes.
+ // It may be empty if there is no possible navigation.
+ std::wstring destination_url;
+
+ // The text displayed on the left in the search results
+ std::wstring contents;
+ ACMatchClassifications contents_class;
+
+ // Displayed to the right of the result as the title or other helper info
+ std::wstring description;
+ ACMatchClassifications description_class;
+
+ // The transition type to use when the user opens this match. By default
+ // this is TYPED. Providers whose matches do not look like URLs should set
+ // it to GENERATED.
+ PageTransition::Type transition;
+
+ // True when this match is the "what you typed" match from the history
+ // system.
+ bool is_history_what_you_typed_match;
+
+ // Type of this match.
+ Type type;
+
+ // If this match corresponds to a keyword, this is the TemplateURL the
+ // keyword was obtained from.
+ const TemplateURL* template_url;
+
+ // True if the user has starred the destination URL.
+ bool starred;
+
+#ifndef NDEBUG
+ // Does a data integrity check on this match.
+ void Validate() const;
+
+ // Checks one text/classifications pair for valid values.
+ void ValidateClassifications(
+ const std::wstring& text,
+ const ACMatchClassifications& classifications) const;
+#endif
+};
+
+typedef AutocompleteMatch::ACMatchClassification ACMatchClassification;
+typedef std::vector<ACMatchClassification> ACMatchClassifications;
+
+// AutocompleteProvider -------------------------------------------------------
+
+// A single result provider for the autocomplete system. Given user input, the
+// provider decides what (if any) matches to return, their relevance, and their
+// classifications.
+class AutocompleteProvider
+ : public base::RefCountedThreadSafe<AutocompleteProvider> {
+ public:
+ class ACProviderListener {
+ public:
+ // Called by a provider as a notification that something has changed.
+ // |updated_matches| should be true iff the matches have changed in some
+ // way (they may not have changed if, for example, the provider did an
+ // asynchronous query to get more results, came up with none, and is now
+ // giving up).
+ //
+ // NOTE: Providers MUST only call this method while processing asynchronous
+ // queries. Do not call this for a synchronous query.
+ //
+ // NOTE: There's no parameter to tell the listener _which_ provider is
+ // calling it. Because the AutocompleteController (the typical listener)
+ // doesn't cache the providers' individual results locally, it has to get
+ // them all again when this is called anyway, so such a parameter wouldn't
+ // actually be useful.
+ virtual void OnProviderUpdate(bool updated_matches) = 0;
+ };
+
+ AutocompleteProvider(ACProviderListener* listener,
+ Profile* profile,
+ char* name)
+ : listener_(listener),
+ profile_(profile),
+ done_(true),
+ name_(name) {
+ }
+
+ virtual ~AutocompleteProvider();
+
+ // Invoked when the profile changes.
+ void SetProfile(Profile* profile);
+
+ // Called to start an autocomplete query. The provider is responsible for
+ // tracking its results for this query and whether it is done processing the
+ // query. When new results are available or the provider finishes, it
+ // calls the controller's OnProviderUpdate() method. The controller can then
+ // get the new results using the provider's accessors.
+ // Exception: Results available immediately after starting the query (that
+ // is, synchronously) do not cause any notifications to be sent. The
+ // controller is expected to check for these without prompting (since
+ // otherwise, starting each provider running would result in a flurry of
+ // notifications).
+ //
+ // Once Stop() has been called, no more notifications should be sent.
+ //
+ // |minimal_changes| is an optimization that lets the provider do less work
+ // when the |input|'s text hasn't changed. See the body of
+ // AutocompletePopup::StartAutocomplete().
+ //
+ // If |synchronous_only| is true, no asynchronous work should be scheduled;
+ // the provider should stop after it has returned all the
+ // synchronously-available results. This also means any in-progress
+ // asynchronous work should be canceled, so the provider does not call back at
+ // a later time.
+ virtual void Start(const AutocompleteInput& input,
+ bool minimal_changes,
+ bool synchronous_only) = 0;
+
+ // Called when a provider must not make any more callbacks for the current
+ // query.
+ virtual void Stop() {
+ done_ = true;
+ }
+
+ // Returns the set of matches for the current query.
+ const ACMatches& matches() const { return matches_; }
+
+ // Returns whether the provider is done processing the query.
+ bool done() const { return done_; }
+
+ // Returns the name of this provider.
+ const char* name() const { return name_; }
+
+ // Called to delete a match and the backing data that produced it. This
+ // match should not appear again in this or future queries. This can only be
+ // called for matches the provider marks as deletable.
+ // NOTE: Remember to call OnProviderUpdate() if matches_ is updated.
+ virtual void DeleteMatch(const AutocompleteMatch& match) {}
+
+ static void set_max_matches(size_t max_matches) {
+ max_matches_ = max_matches;
+ }
+
+ static size_t max_matches() { return max_matches_; }
+
+ protected:
+ // The profile associated with the AutocompleteProvider. Reference is not
+ // owned by us.
+ Profile* profile_;
+
+ ACProviderListener* listener_;
+ ACMatches matches_;
+ bool done_;
+
+ // The name of this provider. Used for logging.
+ const char* name_;
+
+ // A convenience function to call gfx::ElideUrl() with the current set of
+ // "Accept Languages" when check_accept_lang is true. Otherwise, it's called
+ // with an empty list.
+ std::wstring StringForURLDisplay(const GURL& url, bool check_accept_lang);
+
+ private:
+ // A suggested upper bound for how many matches a provider should return.
+ // TODO(pkasting): http://b/1111299 , http://b/933133 This should go away once
+ // we have good relevance heuristics; the controller should handle all
+ // culling.
+ static size_t max_matches_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(AutocompleteProvider);
+};
+
+typedef AutocompleteProvider::ACProviderListener ACProviderListener;
+
+// AutocompleteResult ---------------------------------------------------------
+
+// All matches from all providers for a particular query. This also tracks
+// what the default match should be if the user doesn't manually select another
+// match.
+class AutocompleteResult {
+ public:
+ typedef ACMatches::const_iterator const_iterator;
+ typedef ACMatches::iterator iterator;
+
+ // The "Selection" struct is the information we need to select the same match
+ // in one result set that was selected in another.
+ struct Selection {
+ Selection()
+ : provider_affinity(NULL),
+ is_history_what_you_typed_match(false) {
+ }
+
+ // Clear the selection entirely.
+ void Clear();
+
+ // True when the selection is empty.
+ bool empty() const {
+ return destination_url.empty() && !provider_affinity &&
+ !is_history_what_you_typed_match;
+ }
+
+ // The desired destination URL.
+ std::wstring destination_url;
+
+ // The desired provider. If we can't find a match with the specified
+ // |destination_url|, we'll use the best match from this provider.
+ const AutocompleteProvider* provider_affinity;
+
+ // True when this is the HistoryURLProvider's "what you typed" match. This
+ // can't be tracked using |destination_url| because its URL changes on every
+ // keystroke, so if this is set, we'll preserve the selection by simply
+ // choosing the new "what you typed" entry and ignoring |destination_url|.
+ bool is_history_what_you_typed_match;
+ };
+
+ static void set_max_matches(size_t max_matches) {
+ max_matches_ = max_matches;
+ }
+ static size_t max_matches() { return max_matches_; }
+
+ AutocompleteResult();
+
+ // operator=() by another name.
+ void CopyFrom(const AutocompleteResult& rhs);
+
+ // Adds a single match. The match is inserted at the appropriate position
+ // based on relevancy and display order. This is ONLY for use after
+ // SortAndCull has been invoked.
+ void AddMatch(const AutocompleteMatch& match);
+
+ // Adds a new set of matches to the set of results.
+ void AppendMatches(const ACMatches& matches);
+
+ // Removes duplicates, puts the list in sorted order and culls to leave only
+ // the best kMaxMatches results.
+ void SortAndCull();
+
+ // Vector-style accessors/operators.
+ size_t size() const { return matches_.size(); }
+ bool empty() const { return matches_.empty(); }
+ const_iterator begin() const { return matches_.begin(); }
+ iterator begin() { return matches_.begin(); }
+ const_iterator end() const { return matches_.end(); }
+ iterator end() { return matches_.end(); }
+
+ // Returns the match at the given index.
+ const AutocompleteMatch& match_at(size_t index) const {
+ DCHECK(index < matches_.size());
+ return matches_[index];
+ }
+
+ // Get the default match for the query (not necessarily the first). Returns
+ // end() if there is no default match.
+ const_iterator default_match() const { return default_match_; }
+
+ // Sets the default match to the best result. When a particular URL is
+ // desired, we return that if available; otherwise, if a provider affinity is
+ // specified, we pick the most relevant match from that provider; otherwise,
+ // we return the best match overall.
+ // Returns true if the selection specified an exact match and we were able to
+ // find and use it.
+ bool SetDefaultMatch(const Selection& selection);
+
+ // Given some input and a particular match in this result set, returns the
+ // "alternate navigation URL", if any, for that match. This is a URL to try
+ // offering as a navigational option in case the user didn't actually mean to
+ // navigate to the URL of |match|. For example, if the user's local intranet
+ // contains site "foo", and the user types "foo", we default to searching for
+ // "foo" when the user may have meant to navigate there. In cases like this,
+ // |match| will point to the "search for 'foo'" result, and this function will
+ // return "http://foo/".
+ std::wstring GetAlternateNavURL(const AutocompleteInput& input,
+ const_iterator match) const;
+
+ // Releases the resources associated with this object. Some callers may
+ // want to perform several searches without creating new results each time.
+ // They can call this function to re-use the result for another query.
+ void Reset() {
+ matches_.clear();
+ default_match_ = end();
+ }
+
+#ifndef NDEBUG
+ // Does a data integrity check on this result.
+ void Validate() const;
+#endif
+
+ private:
+ // Max number of matches we'll show from the various providers. We may end
+ // up showing an additional shortcut for Destinations->History, see
+ // AddHistoryContentsShortcut.
+ static size_t max_matches_;
+ ACMatches matches_;
+ const_iterator default_match_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(AutocompleteResult);
+};
+
+// AutocompleteController -----------------------------------------------------
+
+// The coordinator for autocomplete queries, responsible for combining the
+// results from a series of providers into one AutocompleteResult and
+// interacting with the Listener that owns it.
+class AutocompleteController : public ACProviderListener {
+ public:
+ class ACControllerListener {
+ public:
+ // Called by the controller when new results are available and/or the query
+ // is complete. The listener can then call GetResult() and provide an
+ // AutocompleteResult* to be filled in.
+ //
+ // Note that this function is never called for synchronous_only queries
+ // (see Start()). If you're only using those, you can create the controller
+ // with a NULL listener.
+ virtual void OnAutocompleteUpdate(bool updated_result,
+ bool query_complete) = 0;
+ };
+
+ // Used to indicate an index that is not selected in a call to Update()
+ // and for merging results.
+ static const int kNoItemSelected;
+
+ // Normally, you will call the first constructor. Unit tests can use the
+ // second to set the providers to some known testing providers. The default
+ // providers will be overridden and the controller will take ownership of the
+ // providers, Release()ing them on destruction.
+ //
+ // It is safe to pass NULL for |listener| iff you only ever use synchronous
+ // queries.
+ AutocompleteController(ACControllerListener* listener, Profile* profile);
+#ifdef UNIT_TEST
+ AutocompleteController(ACControllerListener* listener,
+ const ACProviders& providers)
+ : listener_(listener),
+ providers_(providers),
+ keyword_provider_(NULL),
+ history_contents_provider_(NULL) {
+ }
+#endif
+ ~AutocompleteController();
+
+ // Invoked when the profile changes. This forwards the call down to all
+ // the AutocompleteProviders.
+ void SetProfile(Profile* profile);
+
+ // Starts an autocomplete query, which continues until all providers are
+ // done or the query is Stop()ed. It is safe to Start() a new query without
+ // Stop()ing the previous one.
+ //
+ // If |minimal_changes| is true, |input| is the same as in the previous
+ // query, except for a different desired_tld_ and possibly type_. Most
+ // providers should just be able to recalculate priorities in this case and
+ // return synchronously, or at least faster than otherwise.
+ //
+ // If |synchronous_only| is true, the controller asks the providers to only
+ // return results which are synchronously available, which should mean that
+ // all providers will be done immediately.
+ //
+ // The controller does not notify the listener about any results available
+ // immediately; the caller should call GetResult() manually if it wants
+ // these. The return value is whether the query is complete; if it is
+ // false, then the controller will call OnAutocompleteUpdate() with future
+ // result updates (unless the query is Stop()ed).
+ bool Start(const AutocompleteInput& input,
+ bool minimal_changes,
+ bool synchronous_only);
+
+ // Cancels the current query, ensuring there will be no future callbacks to
+ // OnAutocompleteUpdate() (until Start() is called again).
+ void Stop() const;
+
+ // Called by the listener to get the current results of the query.
+ void GetResult(AutocompleteResult* result);
+
+ // From AutocompleteProvider::Listener
+ virtual void OnProviderUpdate(bool updated_matches);
+
+ KeywordProvider* keyword_provider() const { return keyword_provider_; }
+
+ private:
+ // Returns true if all providers have finished processing the query.
+ bool QueryComplete() const;
+
+ // Returns the number of matches from provider whose destination urls are
+ // not in result. first_match is set to the first match whose destination url
+ // is NOT in result.
+ size_t CountMatchesNotInResult(const AutocompleteProvider* provider,
+ const AutocompleteResult* result,
+ AutocompleteMatch* first_match);
+
+ // If the HistoryContentsAutocomplete provider is done and there are more
+ // matches in the database than currently shown, an entry is added to
+ // result to show all history matches.
+ void AddHistoryContentsShortcut(AutocompleteResult* result);
+
+ ACControllerListener* listener_; // May be NULL.
+
+ // A list of all providers.
+ ACProviders providers_;
+
+ KeywordProvider* keyword_provider_;
+
+ HistoryContentsProvider* history_contents_provider_;
+
+ // Input passed to Start.
+ AutocompleteInput input_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(AutocompleteController);
+};
+
+typedef AutocompleteController::ACControllerListener ACControllerListener;
+
+// AutocompleteLog ------------------------------------------------------------
+
+// The data to log (via the metrics service) when the user selects an item
+// from the omnibox popup.
+struct AutocompleteLog {
+ AutocompleteLog(std::wstring text,
+ size_t selected_index,
+ size_t inline_autocompleted_length,
+ const AutocompleteResult& result)
+ : text(text),
+ selected_index(selected_index),
+ inline_autocompleted_length(inline_autocompleted_length),
+ result(result) {
+ }
+ // The user's input text in the omnibox.
+ std::wstring text;
+ // Selected index (if selected) or -1 (AutocompletePopup::kNoMatch).
+ size_t selected_index;
+ // Inline autocompleted length (if displayed).
+ size_t inline_autocompleted_length;
+ // Result set.
+ const AutocompleteResult& result;
+};
+
+#endif // CHROME_BROWSER_AUTOCOMPLETE_AUTOCOMPLETE_H__
diff --git a/chrome/browser/autocomplete/autocomplete_edit.cc b/chrome/browser/autocomplete/autocomplete_edit.cc
new file mode 100644
index 0000000..5cb875d
--- /dev/null
+++ b/chrome/browser/autocomplete/autocomplete_edit.cc
@@ -0,0 +1,2333 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "chrome/browser/autocomplete/autocomplete_edit.h"
+
+#include <locale>
+
+#include "base/base_drag_source.h"
+#include "base/clipboard_util.h"
+#include "base/gfx/skia_utils.h"
+#include "base/ref_counted.h"
+#include "base/string_util.h"
+#include "chrome/app/chrome_dll_resource.h"
+#include "chrome/browser/autocomplete/edit_drop_target.h"
+#include "chrome/browser/autocomplete/keyword_provider.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/controller.h"
+#include "chrome/browser/drag_utils.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/tab_contents.h"
+#include "chrome/browser/template_url.h"
+#include "chrome/browser/template_url_model.h"
+#include "chrome/browser/url_fixer_upper.h"
+#include "chrome/browser/user_metrics.h"
+#include "chrome/browser/views/location_bar_view.h"
+#include "chrome/common/clipboard_service.h"
+#include "chrome/common/gfx/chrome_canvas.h"
+#include "chrome/common/gfx/utils.h"
+#include "chrome/common/l10n_util.h"
+#include "chrome/common/os_exchange_data.h"
+#include "chrome/common/win_util.h"
+#include "chrome/views/accessibility/autocomplete_accessibility.h"
+#include "googleurl/src/url_util.h"
+
+#include "generated_resources.h"
+
+#pragma comment(lib, "oleacc.lib") // Needed for accessibility support.
+
+// TODO (jcampan): these colors should be derived from the system colors to
+// ensure they show properly. Bug #948807.
+// Colors used to emphasize the scheme in the URL.
+static const COLORREF kSecureSchemeColor = RGB(0, 150, 20);
+static const COLORREF kInsecureSchemeColor = RGB(200, 0, 0);
+
+// Colors used to strike-out the scheme when it is insecure.
+static const SkColor kSchemeStrikeoutColor = SkColorSetRGB(210, 0, 0);
+static const SkColor kSchemeSelectedStrikeoutColor =
+ SkColorSetRGB(255, 255, 255);
+
+///////////////////////////////////////////////////////////////////////////////
+// Helper classes
+
+AutocompleteEdit::ScopedFreeze::ScopedFreeze(AutocompleteEdit* edit,
+ ITextDocument* text_object_model)
+ : edit_(edit),
+ text_object_model_(text_object_model) {
+ // Freeze the screen.
+ if (text_object_model_) {
+ long count;
+ text_object_model_->Freeze(&count);
+ }
+}
+
+AutocompleteEdit::ScopedFreeze::~ScopedFreeze() {
+ // Unfreeze the screen.
+ // NOTE: If this destructor is reached while the edit is being destroyed (for
+ // example, because we double-clicked the edit of a popup and caused it to
+ // transform to an unconstrained window), it will no longer have an HWND, and
+ // text_object_model_ may point to a destroyed object, so do nothing here.
+ if (edit_->IsWindow() && text_object_model_) {
+ long count;
+ text_object_model_->Unfreeze(&count);
+ if (count == 0) {
+ // We need to UpdateWindow() here instead of InvalidateRect() because, as
+ // far as I can tell, the edit likes to synchronously erase its background
+ // when unfreezing, thus requiring us to synchronously redraw if we don't
+ // want flicker.
+ edit_->UpdateWindow();
+ }
+ }
+}
+
+AutocompleteEdit::ScopedSuspendUndo::ScopedSuspendUndo(
+ ITextDocument* text_object_model)
+ : text_object_model_(text_object_model) {
+ // Suspend Undo processing.
+ if (text_object_model_)
+ text_object_model_->Undo(tomSuspend, NULL);
+}
+
+AutocompleteEdit::ScopedSuspendUndo::~ScopedSuspendUndo() {
+ // Resume Undo processing.
+ if (text_object_model_)
+ text_object_model_->Undo(tomResume, NULL);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// AutocompleteEdit
+
+// These are used to hook the CRichEditCtrl's calls to BeginPaint() and
+// EndPaint() and provide a memory DC instead. See OnPaint().
+static HWND edit_hwnd = NULL;
+static PAINTSTRUCT paint_struct;
+
+// A single AutocompleteController used solely for making synchronous calls to
+// determine how to deal with the clipboard contents for Paste And Go
+// functionality. We avoid using the popup's controller here because we don't
+// want to interrupt in-progress queries or modify the popup state just
+// because the user right-clicked the edit. We don't need a controller for
+// every edit because this will always be accessed on the main thread, so we
+// won't have thread-safety problems.
+static AutocompleteController* paste_and_go_controller = NULL;
+static int paste_and_go_controller_refcount = 0;
+
+AutocompleteEdit::AutocompleteEdit(const ChromeFont& font,
+ Controller* controller,
+ ToolbarModel* model,
+ ChromeViews::View* parent_view,
+ HWND hwnd,
+ Profile* profile,
+ CommandController* command_controller,
+ bool popup_window_mode)
+ : controller_(controller),
+ model_(model),
+ popup_(new AutocompletePopup(font, this, profile)),
+ popup_window_mode_(popup_window_mode),
+ has_focus_(false),
+ user_input_in_progress_(false),
+ just_deleted_text_(false),
+ has_temporary_text_(false),
+ paste_state_(NONE),
+ tracking_click_(false),
+ tracking_double_click_(false),
+ double_click_time_(0),
+ can_discard_mousemove_(false),
+ control_key_state_(UP),
+ command_controller_(command_controller),
+ parent_view_(parent_view),
+ font_(font),
+ profile_(profile),
+ possible_drag_(false),
+ in_drag_(false),
+ initiated_drag_(false),
+ drop_highlight_position_(-1),
+ is_keyword_hint_(false),
+ disable_keyword_ui_(false),
+ show_search_hint_(true),
+ background_color_(0),
+ scheme_security_level_(ToolbarModel::NORMAL) {
+ if (!popup_window_mode_ && ++paste_and_go_controller_refcount == 1) {
+ // We don't have a controller yet, so create one. No listener is needed
+ // since we'll only be doing synchronous calls, and no profile is set since
+ // we'll set this before each call to the controller.
+ paste_and_go_controller = new AutocompleteController(NULL, NULL);
+ }
+
+ saved_selection_for_focus_change_.cpMin = -1;
+
+ // Statics used for global patching of riched20.dll.
+ static HMODULE richedit_module = NULL;
+ static iat_patch::IATPatchFunction patch_begin_paint;
+ static iat_patch::IATPatchFunction patch_end_paint;
+
+ if (!richedit_module) {
+ richedit_module = LoadLibrary(L"riched20.dll");
+ if (richedit_module) {
+ DCHECK(!patch_begin_paint.is_patched());
+ patch_begin_paint.Patch(richedit_module, "user32.dll", "BeginPaint",
+ &BeginPaintIntercept);
+ DCHECK(!patch_end_paint.is_patched());
+ patch_end_paint.Patch(richedit_module, "user32.dll", "EndPaint",
+ &EndPaintIntercept);
+ }
+ }
+
+ Create(hwnd, 0, 0, 0, l10n_util::GetExtendedStyles());
+ SetReadOnly(popup_window_mode_);
+ SetFont(font_.hfont());
+
+ // NOTE: Do not use SetWordBreakProcEx() here, that is no longer supported as
+ // of Rich Edit 2.0 onward.
+ SendMessage(m_hWnd, EM_SETWORDBREAKPROC, 0,
+ reinterpret_cast<LPARAM>(&WordBreakProc));
+
+ // Get the metrics for the font.
+ HDC dc = ::GetDC(NULL);
+ SelectObject(dc, font_.hfont());
+ TEXTMETRIC tm = {0};
+ GetTextMetrics(dc, &tm);
+ font_ascent_ = tm.tmAscent;
+ const float kXHeightRatio = 0.7f; // The ratio of a font's x-height to its
+ // cap height. Sadly, Windows doesn't
+ // provide a true value for a font's
+ // x-height in its text metrics, so we
+ // approximate.
+ font_x_height_ = static_cast<int>((static_cast<float>(font_ascent_ -
+ tm.tmInternalLeading) * kXHeightRatio) + 0.5);
+ const int kTextBaseline = 18; // The distance from the top of the field to
+ // the desired baseline of the rendered text.
+ font_y_adjustment_ = kTextBaseline - font_ascent_;
+ font_descent_ = tm.tmDescent;
+
+ // Get the number of twips per pixel, which we need below to offset our text
+ // by the desired number of pixels.
+ const long kTwipsPerPixel = kTwipsPerInch / GetDeviceCaps(dc, LOGPIXELSY);
+ ::ReleaseDC(NULL, dc);
+
+ // Set the default character style -- adjust to our desired baseline and make
+ // text grey.
+ CHARFORMAT cf = {0};
+ cf.dwMask = CFM_OFFSET | CFM_COLOR;
+ cf.yOffset = -font_y_adjustment_ * kTwipsPerPixel;
+ cf.crTextColor = GetSysColor(COLOR_GRAYTEXT);
+ SetDefaultCharFormat(cf);
+
+ // Set up context menu.
+ context_menu_.reset(new Menu(this, Menu::TOPLEFT, m_hWnd));
+ if (popup_window_mode_) {
+ context_menu_->AppendMenuItemWithLabel(IDS_COPY,
+ l10n_util::GetString(IDS_COPY));
+ } else {
+ context_menu_->AppendMenuItemWithLabel(IDS_UNDO,
+ l10n_util::GetString(IDS_UNDO));
+ context_menu_->AppendSeparator();
+ context_menu_->AppendMenuItemWithLabel(IDS_CUT,
+ l10n_util::GetString(IDS_CUT));
+ context_menu_->AppendMenuItemWithLabel(IDS_COPY,
+ l10n_util::GetString(IDS_COPY));
+ context_menu_->AppendMenuItemWithLabel(IDS_PASTE,
+ l10n_util::GetString(IDS_PASTE));
+ // GetContextualLabel() will override this next label with the
+ // IDS_PASTE_AND_SEARCH label as needed.
+ context_menu_->AppendMenuItemWithLabel(
+ IDS_PASTE_AND_GO, l10n_util::GetString(IDS_PASTE_AND_GO));
+ context_menu_->AppendSeparator();
+ context_menu_->AppendMenuItemWithLabel(IDS_SELECTALL,
+ l10n_util::GetString(IDS_SELECTALL));
+ context_menu_->AppendSeparator();
+ context_menu_->AppendMenuItemWithLabel(
+ IDS_EDIT_SEARCH_ENGINES, l10n_util::GetString(IDS_EDIT_SEARCH_ENGINES));
+ }
+
+ // By default RichEdit has a drop target. Revoke it so that we can install our
+ // own. Revoke takes care of deleting the existing one.
+ RevokeDragDrop(m_hWnd);
+
+ // Register our drop target. RichEdit appears to invoke RevokeDropTarget when
+ // done so that we don't have to explicitly.
+ if (!popup_window_mode_) {
+ scoped_refptr<EditDropTarget> drop_target = new EditDropTarget(this);
+ RegisterDragDrop(m_hWnd, drop_target.get());
+ }
+}
+
+AutocompleteEdit::~AutocompleteEdit() {
+ NotificationService::current()->Notify(NOTIFY_AUTOCOMPLETE_EDIT_DESTROYED,
+ Source<AutocompleteEdit>(this), NotificationService::NoDetails());
+
+ if (!popup_window_mode_ && --paste_and_go_controller_refcount == 0)
+ delete paste_and_go_controller;
+}
+
+void AutocompleteEdit::Update(const TabContents* tab_for_state_restoring) {
+ // When there's a new URL, and the user is not editing anything or the edit
+ // doesn't have focus, we want to revert the edit to show the new URL. (The
+ // common case where the edit doesn't have focus is when the user has started
+ // an edit and then abandoned it and clicked a link on the page.)
+ std::wstring permanent_text = model_->GetText();
+ const bool visibly_changed_permanent_text =
+ (permanent_text_ != permanent_text) &&
+ (!user_input_in_progress_ || !has_focus_);
+
+ permanent_text_ = permanent_text;
+
+ COLORREF background_color =
+ LocationBarView::kBackgroundColorByLevel[model_->GetSchemeSecurityLevel()];
+
+ // Bail early when no visible state will actually change (prevents an
+ // unnecessary ScopedFreeze, and thus UpdateWindow()).
+ if ((background_color == background_color_) &&
+ (model_->GetSchemeSecurityLevel() == scheme_security_level_) &&
+ !visibly_changed_permanent_text &&
+ !tab_for_state_restoring)
+ return;
+
+ // Update our local state as desired. We set scheme_security_level_ here so
+ // it will already be correct before we get to any RevertAll()s below and use
+ // it.
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ if (background_color_ != background_color) {
+ background_color_ = background_color;
+ SetBackgroundColor(background_color_);
+ }
+ const bool changed_security_level =
+ (model_->GetSchemeSecurityLevel() != scheme_security_level_);
+ scheme_security_level_ = model_->GetSchemeSecurityLevel();
+
+ // When we're switching to a new tab, restore its state, if any.
+ if (tab_for_state_restoring) {
+ // Make sure we reset our own state first. The new tab may not have any
+ // saved state, or it may not have had input in progress, in which case we
+ // won't overwrite all our local state.
+ RevertAll();
+
+ const AutocompleteEdit::State* const state =
+ tab_for_state_restoring->saved_location_bar_state();
+ if (state) {
+ // Restore any user editing.
+ if (state->user_input_in_progress) {
+ // NOTE: Be sure and set keyword-related state BEFORE invoking
+ // DisplayTextFromUserText(), as its result depends upon this state.
+ keyword_ = state->keyword;
+ is_keyword_hint_ = state->is_keyword_hint;
+ disable_keyword_ui_ = state->disable_keyword_ui;
+ show_search_hint_ = state->show_search_hint;
+ SetUserText(state->user_text, DisplayTextFromUserText(state->user_text),
+ false);
+ popup_->manually_selected_match_ = state->manually_selected_match;
+ }
+
+ // Restore user's selection. We do this after restoring the user_text
+ // above so we're selecting in the correct string.
+ SetSelectionRange(state->selection);
+ saved_selection_for_focus_change_ =
+ state->saved_selection_for_focus_change;
+ }
+ } else if (visibly_changed_permanent_text) {
+ // Not switching tabs, just updating the permanent text. (In the case where
+ // we _were_ switching tabs, the RevertAll() above already drew the new
+ // permanent text.)
+
+ // Tweak: if the edit was previously nonempty and had all the text selected,
+ // select all the new text. This makes one particular case better: the
+ // user clicks in the box to change it right before the permanent URL is
+ // changed. Since the new URL is still fully selected, the user's typing
+ // will replace the edit contents as they'd intended.
+ //
+ // NOTE: The selection can be longer than the text length if the edit is in
+ // in rich text mode and the user has selected the "phantom newline" at the
+ // end, so use ">=" instead of "==" to see if all the text is selected. In
+ // theory we prevent this case from ever occurring, but this is still safe.
+ CHARRANGE sel;
+ GetSelection(sel);
+ const bool was_reversed = (sel.cpMin > sel.cpMax);
+ const bool was_sel_all = (sel.cpMin != sel.cpMax) && IsSelectAll(sel);
+
+ RevertAll();
+
+ if (was_sel_all)
+ SelectAll(was_reversed);
+ } else if (changed_security_level) {
+ // Only the security style changed, nothing else. Redraw our text using it.
+ EmphasizeURLComponents();
+ }
+}
+
+void AutocompleteEdit::SetProfile(Profile* profile) {
+ DCHECK(profile);
+ profile_ = profile;
+ popup_->SetProfile(profile);
+}
+
+void AutocompleteEdit::SaveStateToTab(TabContents* tab) {
+ DCHECK(tab);
+
+ // Like typing, switching tabs "accepts" the temporary text as the user
+ // text, because it makes little sense to have temporary text when the
+ // popup is closed.
+ if (user_input_in_progress_)
+ InternalSetUserText(UserTextFromDisplayText(GetText()));
+
+ CHARRANGE selection;
+ GetSelection(selection);
+ tab->set_saved_location_bar_state(new State(selection,
+ saved_selection_for_focus_change_, user_input_in_progress_, user_text_,
+ popup_->manually_selected_match_, keyword_, is_keyword_hint_,
+ disable_keyword_ui_, show_search_hint_));
+}
+
+std::wstring AutocompleteEdit::GetText() const {
+ const int len = GetTextLength() + 1;
+ std::wstring str;
+ GetWindowText(WriteInto(&str, len), len);
+ return str;
+}
+
+std::wstring AutocompleteEdit::GetURLForCurrentText(
+ PageTransition::Type* transition,
+ bool* is_history_what_you_typed_match,
+ std::wstring* alternate_nav_url) {
+ return (popup_->is_open() || popup_->query_in_progress()) ?
+ popup_->URLsForCurrentSelection(transition,
+ is_history_what_you_typed_match,
+ alternate_nav_url) :
+ popup_->URLsForDefaultMatch(UserTextFromDisplayText(GetText()),
+ GetDesiredTLD(), transition,
+ is_history_what_you_typed_match,
+ alternate_nav_url);
+}
+
+void AutocompleteEdit::SelectAll(bool reversed) {
+ if (reversed)
+ SetSelection(GetTextLength(), 0);
+ else
+ SetSelection(0, GetTextLength());
+}
+
+void AutocompleteEdit::RevertAll() {
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ ClosePopup();
+ popup_->manually_selected_match_.Clear();
+ SetInputInProgress(false);
+ paste_state_ = NONE;
+ InternalSetUserText(std::wstring());
+ SetWindowText(permanent_text_.c_str());
+ keyword_.clear();
+ is_keyword_hint_ = false;
+ disable_keyword_ui_ = false;
+ show_search_hint_ = permanent_text_.empty();
+ PlaceCaretAt(has_focus_ ? permanent_text_.length() : 0);
+ saved_selection_for_focus_change_.cpMin = -1;
+ has_temporary_text_ = false;
+ TextChanged();
+}
+
+void AutocompleteEdit::AcceptInput(WindowOpenDisposition disposition,
+ bool for_drop) {
+ // Get the URL and transition type for the selected entry.
+ PageTransition::Type transition;
+ bool is_history_what_you_typed_match;
+ std::wstring alternate_nav_url;
+ const std::wstring url(GetURLForCurrentText(&transition,
+ &is_history_what_you_typed_match,
+ &alternate_nav_url));
+ if (url.empty())
+ return;
+
+ if (url == permanent_text_) {
+ // When the user hit enter on the existing permanent URL, treat it like a
+ // reload for scoring purposes. We could detect this by just checking
+ // user_input_in_progress_, but it seems better to treat "edits" that end
+ // up leaving the URL unchanged (e.g. deleting the last character and then
+ // retyping it) as reloads too.
+ transition = PageTransition::RELOAD;
+ } else if (for_drop || ((paste_state_ != NONE) &&
+ is_history_what_you_typed_match)) {
+ // When the user pasted in a URL and hit enter, score it like a link click
+ // rather than a normal typed URL, so it doesn't get inline autocompleted
+ // as aggressively later.
+ transition = PageTransition::LINK;
+ }
+
+ OpenURL(url, disposition, transition, alternate_nav_url,
+ AutocompletePopup::kNoMatch,
+ is_keyword_hint_ ? std::wstring() : keyword_);
+}
+
+void AutocompleteEdit::OpenURL(const std::wstring& url,
+ WindowOpenDisposition disposition,
+ PageTransition::Type transition,
+ const std::wstring& alternate_nav_url,
+ size_t selected_line,
+ const std::wstring& keyword) {
+ if (url.empty())
+ return;
+
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ SendOpenNotification(selected_line, keyword);
+
+ if (disposition != NEW_BACKGROUND_TAB)
+ RevertAll(); // Revert the box to its unedited state
+ controller_->OnAutocompleteAccept(url, disposition, transition,
+ alternate_nav_url);
+}
+
+void AutocompleteEdit::ClosePopup() {
+ popup_->StopAutocomplete();
+}
+
+IAccessible* AutocompleteEdit::GetIAccessible() {
+ if (!autocomplete_accessibility_) {
+ CComObject<AutocompleteAccessibility>* accessibility = NULL;
+ if (!SUCCEEDED(CComObject<AutocompleteAccessibility>::CreateInstance(
+ &accessibility)) || !accessibility)
+ return NULL;
+
+ // Wrap the created object in a smart pointer so it won't leak.
+ CComPtr<IAccessible> accessibility_comptr(accessibility);
+ if (!SUCCEEDED(accessibility->Initialize(this)))
+ return NULL;
+
+ // Copy to the class smart pointer, and notify that an instance of
+ // IAccessible was allocated for m_hWnd.
+ autocomplete_accessibility_ = accessibility_comptr;
+ NotifyWinEvent(EVENT_OBJECT_CREATE, m_hWnd, OBJID_CLIENT, CHILDID_SELF);
+ }
+ // Detach to leave ref counting to the caller.
+ return autocomplete_accessibility_.Detach();
+}
+
+void AutocompleteEdit::SetDropHighlightPosition(int position) {
+ if (drop_highlight_position_ != position) {
+ RepaintDropHighlight(drop_highlight_position_);
+ drop_highlight_position_ = position;
+ RepaintDropHighlight(drop_highlight_position_);
+ }
+}
+
+void AutocompleteEdit::MoveSelectedText(int new_position) {
+ const std::wstring selected_text(GetSelectedText());
+ CHARRANGE sel;
+ GetSel(sel);
+ DCHECK((sel.cpMax != sel.cpMin) && (new_position >= 0) &&
+ (new_position <= GetTextLength()));
+
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ OnBeforePossibleChange();
+
+ // Nuke the selected text.
+ ReplaceSel(L"", TRUE);
+
+ // And insert it into the new location.
+ if (new_position >= sel.cpMin)
+ new_position -= (sel.cpMax - sel.cpMin);
+ PlaceCaretAt(new_position);
+ ReplaceSel(selected_text.c_str(), TRUE);
+
+ OnAfterPossibleChange();
+}
+
+void AutocompleteEdit::InsertText(int position, const std::wstring& text) {
+ DCHECK((position >= 0) && (position <= GetTextLength()));
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ OnBeforePossibleChange();
+ SetSelection(position, position);
+ ReplaceSel(text.c_str());
+ OnAfterPossibleChange();
+}
+
+void AutocompleteEdit::PasteAndGo(const std::wstring& text) {
+ if (CanPasteAndGo(text))
+ PasteAndGo();
+}
+
+bool AutocompleteEdit::OverrideAccelerator(
+ const ChromeViews::Accelerator& accelerator) {
+ // Only override <esc>, and only when there is input in progress -- otherwise,
+ // if focus happens to be in the location bar, users can't still hit <esc> to
+ // stop a load.
+ if ((accelerator.GetKeyCode() != VK_ESCAPE) || accelerator.IsAltDown() ||
+ !user_input_in_progress_)
+ return false;
+
+ if (!has_temporary_text_ ||
+ (popup_->URLsForCurrentSelection(NULL, NULL, NULL) == original_url_)) {
+ // The popup isn't open or the selection in it is still the default
+ // selection, so revert the box all the way back to its unedited state.
+ RevertAll();
+ return true;
+ }
+
+ // The user typed something, then selected a different item. Restore the
+ // text they typed and change back to the default item.
+ // NOTE: This purposefully does not reset paste_state_.
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ just_deleted_text_ = false;
+ const std::wstring new_window_text(user_text_ +
+ inline_autocomplete_text_);
+ SetWindowText(new_window_text.c_str());
+ SetSelectionRange(original_selection_);
+ has_temporary_text_ = false;
+ popup_->manually_selected_match_ = original_selected_match_;
+ UpdatePopup();
+ TextChanged();
+ return true;
+}
+
+void AutocompleteEdit::HandleExternalMsg(UINT msg,
+ UINT flags,
+ const CPoint& screen_point) {
+ if (msg == WM_CAPTURECHANGED) {
+ SendMessage(msg, 0, NULL);
+ return;
+ }
+
+ CPoint client_point(screen_point);
+ ::MapWindowPoints(NULL, m_hWnd, &client_point, 1);
+ SendMessage(msg, flags, MAKELPARAM(client_point.x, client_point.y));
+}
+
+void AutocompleteEdit::OnPopupDataChanged(
+ const std::wstring& text,
+ bool is_temporary_text,
+ const AutocompleteResult::Selection& previous_selected_match,
+ const std::wstring& keyword,
+ bool is_keyword_hint,
+ bool can_show_search_hint) {
+ // We don't want to show the search hint if we're showing a keyword hint or
+ // selected keyword, or (subtle!) if we would be showing a selected keyword
+ // but for disable_keyword_ui_.
+ can_show_search_hint &= keyword.empty();
+
+ // Update keyword/hint-related local state.
+ bool keyword_state_changed = (keyword_ != keyword) ||
+ ((is_keyword_hint_ != is_keyword_hint) && !keyword.empty()) ||
+ (show_search_hint_ != can_show_search_hint);
+ if (keyword_state_changed) {
+ keyword_ = keyword;
+ is_keyword_hint_ = is_keyword_hint;
+ show_search_hint_ = can_show_search_hint;
+ }
+
+ // Handle changes to temporary text.
+ if (is_temporary_text) {
+ if (!has_temporary_text_) {
+ // Save the original selection and URL so it can be reverted later.
+ has_temporary_text_ = true;
+ GetSelection(original_selection_);
+ original_url_ = popup_->URLsForCurrentSelection(NULL, NULL, NULL);
+ original_selected_match_ = previous_selected_match;
+ }
+
+ // Set new text and cursor position. Sometimes this does extra work (e.g.
+ // when the new text and the old text are identical), but it's only called
+ // when the user manually changes the selected line in the popup, so that's
+ // not really a problem. Also, even when the text hasn't changed we'd want
+ // to update the caret, because if the user had the cursor in the middle of
+ // the text and then arrowed to another entry with the same text, we'd still
+ // want to move the caret.
+ const std::wstring display_text(DisplayTextFromUserText(text));
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ SetWindowText(display_text.c_str());
+ PlaceCaretAt(display_text.length());
+ TextChanged();
+ return;
+ }
+
+ // Handle changes to inline autocomplete text. Don't make changes if the user
+ // is showing temporary text. Making display changes would be obviously
+ // wrong; making changes to the inline_autocomplete_text_ itself turns out to
+ // be more subtlely wrong, because it means hitting esc will no longer revert
+ // to the original state before arrowing.
+ if (!has_temporary_text_) {
+ inline_autocomplete_text_ = text;
+ // Update the text and selection. Because this can be called repeatedly
+ // while typing, we've careful not to freeze the edit unless we really need
+ // to. Also, unlike in the temporary text case above, here we don't want to
+ // update the caret/selection unless we have to, since this might make the
+ // user's caret position change without warning during typing.
+ const std::wstring display_text(
+ DisplayTextFromUserText(user_text_ + inline_autocomplete_text_));
+ if (display_text != GetText()) {
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ SetWindowText(display_text.c_str());
+ // Set a reversed selection to keep the caret in the same position, which
+ // avoids scrolling the user's text.
+ SetSelection(
+ static_cast<LONG>(display_text.length()),
+ static_cast<LONG>(DisplayTextFromUserText(user_text_).length()));
+ TextChanged();
+ return;
+ }
+ }
+
+ // If the above changes didn't warrant a text update but we did change keyword
+ // state, we have yet to notify the controller about it.
+ if (keyword_state_changed)
+ controller_->OnChanged();
+}
+
+bool AutocompleteEdit::IsCommandEnabled(int id) const {
+ switch (id) {
+ case IDS_UNDO: return !!CanUndo();
+ case IDS_CUT: return !!CanCut();
+ case IDS_COPY: return !!CanCopy();
+ case IDS_PASTE: return !!CanPaste();
+ case IDS_PASTE_AND_GO: return CanPasteAndGo(GetClipboardText());
+ case IDS_SELECTALL: return !!CanSelectAll();
+ case IDS_EDIT_SEARCH_ENGINES:
+ return command_controller_->IsCommandEnabled(IDC_EDIT_SEARCH_ENGINES);
+ default: NOTREACHED(); return false;
+ }
+}
+
+bool AutocompleteEdit::GetContextualLabel(int id, std::wstring* out) const {
+ if ((id != IDS_PASTE_AND_GO) ||
+ // No need to change the default IDS_PASTE_AND_GO label for a typed
+ // destination (this is also the type set when Paste And Go is disabled).
+ (paste_and_go_transition_ == PageTransition::TYPED))
+ return false;
+
+ out->assign(l10n_util::GetString(IDS_PASTE_AND_SEARCH));
+ return true;
+}
+
+void AutocompleteEdit::ExecuteCommand(int id) {
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ if (id == IDS_PASTE_AND_GO) {
+ // This case is separate from the switch() below since we don't want to wrap
+ // it in OnBefore/AfterPossibleChange() calls.
+ PasteAndGo();
+ return;
+ }
+
+ OnBeforePossibleChange();
+ switch (id) {
+ case IDS_UNDO:
+ Undo();
+ break;
+
+ case IDS_CUT:
+ Cut();
+ break;
+
+ case IDS_COPY:
+ Copy();
+ break;
+
+ case IDS_PASTE:
+ Paste();
+ break;
+
+ case IDS_SELECTALL:
+ SelectAll(false);
+ break;
+
+ case IDS_EDIT_SEARCH_ENGINES:
+ command_controller_->ExecuteCommand(IDC_EDIT_SEARCH_ENGINES);
+ break;
+
+ default:
+ NOTREACHED();
+ break;
+ }
+ OnAfterPossibleChange();
+}
+
+// static
+int CALLBACK AutocompleteEdit::WordBreakProc(LPTSTR edit_text,
+ int current_pos,
+ int num_bytes,
+ int action) {
+ // TODO(pkasting): http://b/1111308 We should let other people, like ICU and
+ // GURL, do the work for us here instead of writing all this ourselves.
+
+ // Sadly, even though the MSDN docs claim that the third parameter here is a
+ // number of characters, they lie. It's a number of bytes.
+ const int length = num_bytes / sizeof(wchar_t);
+
+ // With no clear guidance from the MSDN docs on how to handle "not found" in
+ // the "find the nearest xxx..." cases below, I cap the return values at
+ // [0, length]. Since one of these (0) is also a valid position, the return
+ // values are thus ambiguous :(
+ switch (action) {
+ // Find nearest character before current position that begins a word.
+ case WB_LEFT:
+ case WB_MOVEWORDLEFT: {
+ if (current_pos < 2) {
+ // Either current_pos == 0, so we have a "not found" case and return 0,
+ // or current_pos == 1, and the only character before this position is
+ // at 0.
+ return 0;
+ }
+
+ // Look for a delimiter before the previous character; the previous word
+ // starts immediately after. (If we looked for a delimiter before the
+ // current character, we could stop on the immediate prior character,
+ // which would mean we'd return current_pos -- which isn't "before the
+ // current position".)
+ const int prev_delim =
+ WordBreakProc(edit_text, current_pos - 1, num_bytes, WB_LEFTBREAK);
+
+ if ((prev_delim == 0) &&
+ !WordBreakProc(edit_text, 0, num_bytes, WB_ISDELIMITER)) {
+ // Got back 0, but position 0 isn't a delimiter. This was a "not
+ // found" 0, so return one of our own.
+ return 0;
+ }
+
+ return prev_delim + 1;
+ }
+
+ // Find nearest character after current position that begins a word.
+ case WB_RIGHT:
+ case WB_MOVEWORDRIGHT: {
+ if (WordBreakProc(edit_text, current_pos, num_bytes, WB_ISDELIMITER)) {
+ // The current character is a delimiter, so the next character starts
+ // a new word. Done.
+ return current_pos + 1;
+ }
+
+ // Look for a delimiter after the current character; the next word starts
+ // immediately after.
+ const int next_delim =
+ WordBreakProc(edit_text, current_pos, num_bytes, WB_RIGHTBREAK);
+ if (next_delim == length) {
+ // Didn't find a delimiter. Return length to signal "not found".
+ return length;
+ }
+
+ return next_delim + 1;
+ }
+
+ // Determine if the current character delimits words.
+ case WB_ISDELIMITER:
+ return !!(WordBreakProc(edit_text, current_pos, num_bytes, WB_CLASSIFY) &
+ WBF_BREAKLINE);
+
+ // Return the classification of the current character.
+ case WB_CLASSIFY:
+ if (IsWhitespace(edit_text[current_pos])) {
+ // Whitespace normally breaks words, but the MSDN docs say that we must
+ // not break on the CRs in a "CR, LF" or a "CR, CR, LF" sequence. Just
+ // check for an arbitrarily long sequence of CRs followed by LF and
+ // report "not a delimiter" for the current CR in that case.
+ while ((current_pos < (length - 1)) &&
+ (edit_text[current_pos] == 0x13)) {
+ if (edit_text[++current_pos] == 0x10)
+ return WBF_ISWHITE;
+ }
+ return WBF_BREAKLINE | WBF_ISWHITE;
+ }
+
+ // Punctuation normally breaks words, but the first two characters in
+ // "://" (end of scheme) should not be breaks, so that "http://" will be
+ // treated as one word.
+ if (ispunct(edit_text[current_pos], std::locale()) &&
+ !SchemeEnd(edit_text, current_pos, length) &&
+ !SchemeEnd(edit_text, current_pos - 1, length))
+ return WBF_BREAKLINE;
+
+ // Normal character, no flags.
+ return 0;
+
+ // Finds nearest delimiter before current position.
+ case WB_LEFTBREAK:
+ for (int i = current_pos - 1; i >= 0; --i) {
+ if (WordBreakProc(edit_text, i, num_bytes, WB_ISDELIMITER))
+ return i;
+ }
+ return 0;
+
+ // Finds nearest delimiter after current position.
+ case WB_RIGHTBREAK:
+ for (int i = current_pos + 1; i < length; ++i) {
+ if (WordBreakProc(edit_text, i, num_bytes, WB_ISDELIMITER))
+ return i;
+ }
+ return length;
+ }
+
+ NOTREACHED();
+ return 0;
+}
+
+// static
+bool AutocompleteEdit::SchemeEnd(LPTSTR edit_text,
+ int current_pos,
+ int length) {
+ return (current_pos >= 0) &&
+ ((length - current_pos) > 2) &&
+ (edit_text[current_pos] == ':') &&
+ (edit_text[current_pos + 1] == '/') &&
+ (edit_text[current_pos + 2] == '/');
+}
+
+// static
+HDC AutocompleteEdit::BeginPaintIntercept(HWND hWnd, LPPAINTSTRUCT lpPaint) {
+ if (!edit_hwnd || (hWnd != edit_hwnd))
+ return ::BeginPaint(hWnd, lpPaint);
+
+ *lpPaint = paint_struct;
+ return paint_struct.hdc;
+}
+
+// static
+BOOL AutocompleteEdit::EndPaintIntercept(HWND hWnd,
+ CONST PAINTSTRUCT* lpPaint) {
+ return (edit_hwnd && (hWnd == edit_hwnd)) ?
+ true : ::EndPaint(hWnd, lpPaint);
+}
+
+void AutocompleteEdit::OnChar(TCHAR ch, UINT repeat_count, UINT flags) {
+ // Don't let alt-enter beep. Not sure this is necessary, as the standard
+ // alt-enter will hit DiscardWMSysChar() and get thrown away, and
+ // ctrl-alt-enter doesn't seem to reach here for some reason? At least not on
+ // my system... still, this is harmless and maybe necessary in other locales.
+ if (ch == VK_RETURN && (flags & KF_ALTDOWN))
+ return;
+
+ // Escape is processed in OnKeyDown. Don't let any WM_CHAR messages propagate
+ // as we don't want the RichEdit to do anything funky.
+ if (ch == VK_ESCAPE && !(flags & KF_ALTDOWN))
+ return;
+
+ if (ch == VK_TAB) {
+ // Don't add tabs to the input.
+ return;
+ }
+
+ HandleKeystroke(GetCurrentMessage()->message, ch, repeat_count, flags);
+}
+
+void AutocompleteEdit::OnContextMenu(HWND window, const CPoint& point) {
+ if (point.x == -1 || point.y == -1) {
+ POINT p;
+ GetCaretPos(&p);
+ MapWindowPoints(HWND_DESKTOP, &p, 1);
+ context_menu_->RunMenuAt(p.x, p.y);
+ } else {
+ context_menu_->RunMenuAt(point.x, point.y);
+ }
+}
+
+void AutocompleteEdit::OnCopy() {
+ const std::wstring text(GetSelectedText());
+ if (text.empty())
+ return;
+
+ ClipboardService* clipboard = g_browser_process->clipboard_service();
+ clipboard->Clear();
+ clipboard->WriteText(text);
+
+ // Check if the user is copying the whole address bar. If they are, we
+ // assume they are trying to copy a URL and write this to the clipboard as a
+ // hyperlink.
+ if (static_cast<int>(text.length()) < GetTextLength())
+ return;
+
+ // The entire control is selected. Let's see what the user typed. Usually
+ // we'd use GetDesiredTLD() to figure out the TLD, but right now the user is
+ // probably holding down control to cause the copy (which would make us always
+ // think the user wanted ".com" added).
+ url_parse::Parsed parts;
+ const AutocompleteInput::Type type = AutocompleteInput::Parse(
+ UserTextFromDisplayText(text), std::wstring(), &parts, NULL);
+ if (type == AutocompleteInput::URL) {
+ const GURL url(URLFixerUpper::FixupURL(text, std::wstring()));
+ clipboard->WriteHyperlink(text, url.spec());
+ }
+}
+
+void AutocompleteEdit::OnCut() {
+ OnCopy();
+
+ // This replace selection will have no effect (even on the undo stack) if the
+ // current selection is empty.
+ ReplaceSel(L"", true);
+}
+
+LRESULT AutocompleteEdit::OnGetObject(UINT uMsg, WPARAM wparam, LPARAM lparam) {
+ // Accessibility readers will send an OBJID_CLIENT message.
+ if (lparam == OBJID_CLIENT) {
+ // Re-attach for internal re-usage of accessibility pointer.
+ autocomplete_accessibility_.Attach(GetIAccessible());
+
+ if (autocomplete_accessibility_) {
+ return LresultFromObject(IID_IAccessible, wparam,
+ autocomplete_accessibility_.p);
+ }
+ }
+ return 0;
+}
+
+LRESULT AutocompleteEdit::OnImeComposition(UINT message,
+ WPARAM wparam,
+ LPARAM lparam) {
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ OnBeforePossibleChange();
+ LRESULT result = DefWindowProc(message, wparam, lparam);
+ if (!OnAfterPossibleChange() && (lparam & GCS_RESULTSTR)) {
+ // The result string changed, but the text in the popup didn't actually
+ // change. This means the user finalized the composition. Rerun
+ // autocomplete so that we can now trigger inline autocomplete if
+ // applicable.
+ //
+ // Note that if we're in the midst of losing focus, UpdatePopup() won't
+ // actually rerun autocomplete, but will just set local state correctly.
+ UpdatePopup();
+ }
+ return result;
+}
+
+void AutocompleteEdit::OnKeyDown(TCHAR key, UINT repeat_count, UINT flags) {
+ if (OnKeyDownAllModes(key, repeat_count, flags) || popup_window_mode_ ||
+ OnKeyDownOnlyWritable(key, repeat_count, flags))
+ return;
+
+ // CRichEditCtrl changes its text on WM_KEYDOWN instead of WM_CHAR for many
+ // different keys (backspace, ctrl-v, ...), so we call this in both cases.
+ HandleKeystroke(GetCurrentMessage()->message, key, repeat_count, flags);
+}
+
+void AutocompleteEdit::OnKeyUp(TCHAR key, UINT repeat_count, UINT flags) {
+ if ((key == VK_CONTROL) && (control_key_state_ != UP)) {
+ control_key_state_ = UP;
+ if (popup_->is_open()) {
+ // Autocomplete history provider results may change, so refresh the
+ // popup. This will force user_input_in_progress_ to true, but if the
+ // popup is open, that should have already been the case.
+ UpdatePopup();
+ }
+ }
+
+ SetMsgHandled(false);
+}
+
+void AutocompleteEdit::OnKillFocus(HWND focus_wnd) {
+ if (m_hWnd == focus_wnd) {
+ // Focus isn't actually leaving.
+ SetMsgHandled(false);
+ return;
+ }
+
+ has_focus_ = false;
+ control_key_state_ = UP;
+ paste_state_ = NONE;
+
+ // Close the popup.
+ ClosePopup();
+
+ // Save the user's existing selection to restore it later.
+ GetSelection(saved_selection_for_focus_change_);
+
+ // Like typing, killing focus "accepts" the temporary text as the user
+ // text, because it makes little sense to have temporary text when the
+ // popup is closed.
+ InternalSetUserText(UserTextFromDisplayText(GetText()));
+ has_temporary_text_ = false;
+
+ // Let the CRichEditCtrl do its default handling. This will complete any
+ // in-progress IME composition. We must do this after setting has_focus_ to
+ // false so that UpdatePopup() will know not to rerun autocomplete.
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ DefWindowProc(WM_KILLFOCUS, reinterpret_cast<WPARAM>(focus_wnd), 0);
+
+ // Hide the "Type to search" hint if necessary. We do this after calling
+ // DefWindowProc() because processing the resulting IME messages may notify
+ // the controller that input is in progress, which could cause the visible
+ // hints to change. (I don't know if there's a real scenario where they
+ // actually do change, but this is safest.)
+ if (show_search_hint_ || (is_keyword_hint_ && !keyword_.empty()))
+ controller_->OnChanged();
+
+ // Cancel any user selection and scroll the text back to the beginning of the
+ // URL. We have to do this after calling DefWindowProc() because otherwise
+ // an in-progress IME composition will be completed at the new caret position,
+ // resulting in the string jumping unexpectedly to the front of the edit.
+ PlaceCaretAt(0);
+}
+
+void AutocompleteEdit::OnLButtonDblClk(UINT keys, const CPoint& point) {
+ // Save the double click info for later triple-click detection.
+ tracking_double_click_ = true;
+ double_click_point_ = point;
+ double_click_time_ = GetCurrentMessage()->time;
+ possible_drag_ = false;
+
+ // Modifying the selection counts as accepting any inline autocompletion, so
+ // track "changes" made by clicking the mouse button.
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ OnBeforePossibleChange();
+ DefWindowProc(WM_LBUTTONDBLCLK, keys,
+ MAKELPARAM(ClipXCoordToVisibleText(point.x, false), point.y));
+ OnAfterPossibleChange();
+
+ gaining_focus_.reset(); // See NOTE in OnMouseActivate().
+}
+
+void AutocompleteEdit::OnLButtonDown(UINT keys, const CPoint& point) {
+ if (gaining_focus_.get()) {
+ // This click is giving us focus, so we need to track how much the mouse
+ // moves to see if it's a drag or just a click. Clicks should select all
+ // the text.
+ tracking_click_ = true;
+ mouse_down_point_ = point;
+
+ // When Chrome was already the activated app, we haven't reached
+ // OnSetFocus() yet. When we get there, don't restore the saved selection,
+ // since it will just screw up the user's interaction with the edit.
+ saved_selection_for_focus_change_.cpMin = -1;
+
+ // Crazy hack: In this particular case, the CRichEditCtrl seems to have an
+ // internal flag that discards the next WM_LBUTTONDOWN without processing
+ // it, so that clicks on the edit when its owning app is not activated are
+ // eaten rather than processed (despite whatever the return value of
+ // DefWindowProc(WM_MOUSEACTIVATE, ...) may say). This behavior is
+ // confusing and we want the click to be treated normally. So, to reset the
+ // CRichEditCtrl's internal flag, we pass it an extra WM_LBUTTONDOWN here
+ // (as well as a matching WM_LBUTTONUP, just in case we'd be confusing some
+ // kind of state tracking otherwise).
+ DefWindowProc(WM_LBUTTONDOWN, keys, MAKELPARAM(point.x, point.y));
+ DefWindowProc(WM_LBUTTONUP, keys, MAKELPARAM(point.x, point.y));
+ }
+
+ // Check for triple click, then reset tracker. Should be safe to subtract
+ // double_click_time_ from the current message's time even if the timer has
+ // wrapped in between.
+ const bool is_triple_click = tracking_double_click_ &&
+ win_util::IsDoubleClick(double_click_point_, point,
+ GetCurrentMessage()->time - double_click_time_);
+ tracking_double_click_ = false;
+
+ if (!gaining_focus_.get() && !is_triple_click)
+ OnPossibleDrag(point);
+
+
+ // Modifying the selection counts as accepting any inline autocompletion, so
+ // track "changes" made by clicking the mouse button.
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ OnBeforePossibleChange();
+ DefWindowProc(WM_LBUTTONDOWN, keys,
+ MAKELPARAM(ClipXCoordToVisibleText(point.x, is_triple_click),
+ point.y));
+ OnAfterPossibleChange();
+
+ gaining_focus_.reset();
+}
+
+void AutocompleteEdit::OnLButtonUp(UINT keys, const CPoint& point) {
+ // default processing should happen first so we can see the result of the
+ // selection
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ DefWindowProc(WM_LBUTTONUP, keys,
+ MAKELPARAM(ClipXCoordToVisibleText(point.x, false), point.y));
+
+ // When the user has clicked and released to give us focus, select all.
+ if (tracking_click_ && !win_util::IsDrag(mouse_down_point_, point)) {
+ // Select all in the reverse direction so as not to scroll the caret
+ // into view and shift the contents jarringly.
+ SelectAll(true);
+ possible_drag_ = false;
+ }
+
+ tracking_click_ = false;
+
+ UpdateDragDone(keys);
+}
+
+LRESULT AutocompleteEdit::OnMouseActivate(HWND window,
+ UINT hit_test,
+ UINT mouse_message) {
+ // First, give other handlers a chance to handle the message to see if we are
+ // actually going to activate and gain focus.
+ LRESULT result = DefWindowProc(WM_MOUSEACTIVATE,
+ reinterpret_cast<WPARAM>(window),
+ MAKELPARAM(hit_test, mouse_message));
+ // Check if we're getting focus from a left click. We have to do this here
+ // rather than in OnLButtonDown() since in many scenarios OnSetFocus() will be
+ // reached before OnLButtonDown(), preventing us from detecting this properly
+ // there. Also in those cases, we need to already know in OnSetFocus() that
+ // we should not restore the saved selection.
+ if (!has_focus_ && (mouse_message == WM_LBUTTONDOWN) &&
+ (result == MA_ACTIVATE)) {
+ DCHECK(!gaining_focus_.get());
+ gaining_focus_.reset(new ScopedFreeze(this, GetTextObjectModel()));
+ // NOTE: Despite |mouse_message| being WM_LBUTTONDOWN here, we're not
+ // guaranteed to call OnLButtonDown() later! Specifically, if this is the
+ // second click of a double click, we'll reach here but later call
+ // OnLButtonDblClk(). Make sure |gaining_focus_| gets reset both places, or
+ // we'll have visual glitchiness and then DCHECK failures.
+
+ // Don't restore saved selection, it will just screw up our interaction
+ // with this edit.
+ saved_selection_for_focus_change_.cpMin = -1;
+ }
+ return result;
+}
+
+void AutocompleteEdit::OnMouseMove(UINT keys, const CPoint& point) {
+ if (possible_drag_) {
+ StartDragIfNecessary(point);
+ // Don't fall through to default mouse handling, otherwise a second
+ // drag session may start.
+ return;
+ }
+
+ if (tracking_click_ && !win_util::IsDrag(mouse_down_point_, point))
+ return;
+
+ tracking_click_ = false;
+
+ // Return quickly if this can't change the selection/cursor, so we don't
+ // create a ScopedFreeze (and thus do an UpdateWindow()) on every
+ // WM_MOUSEMOVE.
+ if (!(keys & MK_LBUTTON)) {
+ DefWindowProc(WM_MOUSEMOVE, keys, MAKELPARAM(point.x, point.y));
+ return;
+ }
+
+ // Clamp the selection to the visible text so the user can't drag to select
+ // the "phantom newline". In theory we could achieve this by clipping the X
+ // coordinate, but in practice the edit seems to behave nondeterministically
+ // with similar sequences of clipped input coordinates fed to it. Maybe it's
+ // reading the mouse cursor position directly?
+ //
+ // This solution has a minor visual flaw, however: if there's a visible cursor
+ // at the edge of the text (only true when there's no selection), dragging the
+ // mouse around outside that edge repaints the cursor on every WM_MOUSEMOVE
+ // instead of allowing it to blink normally. To fix this, we special-case
+ // this exact case and discard the WM_MOUSEMOVE messages instead of passing
+ // them along.
+ //
+ // But even this solution has a flaw! (Argh.) In the case where the user has
+ // a selection that starts at the edge of the edit, and proceeds to the middle
+ // of the edit, and the user is dragging back past the start edge to remove
+ // the selection, there's a redraw problem where the change between having the
+ // last few bits of text still selected and having nothing selected can be
+ // slow to repaint (which feels noticeably strange). This occurs if you only
+ // let the edit receive a single WM_MOUSEMOVE past the edge of the text. I
+ // think on each WM_MOUSEMOVE the edit is repainting its previous state, then
+ // updating its internal variables to the new state but not repainting. To
+ // fix this, we allow one more WM_MOUSEMOVE through after the selection has
+ // supposedly been shrunk to nothing; this makes the edit redraw the selection
+ // quickly so it feels smooth.
+ CHARRANGE selection;
+ GetSel(selection);
+ const bool possibly_can_discard_mousemove =
+ (selection.cpMin == selection.cpMax) &&
+ (((selection.cpMin == 0) &&
+ (ClipXCoordToVisibleText(point.x, false) > point.x)) ||
+ ((selection.cpMin == GetTextLength()) &&
+ (ClipXCoordToVisibleText(point.x, false) < point.x)));
+ if (!can_discard_mousemove_ || !possibly_can_discard_mousemove) {
+ can_discard_mousemove_ = possibly_can_discard_mousemove;
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ OnBeforePossibleChange();
+ // Force the Y coordinate to the center of the clip rect. The edit
+ // behaves strangely when the cursor is dragged vertically: if the cursor
+ // is in the middle of the text, drags inside the clip rect do nothing,
+ // and drags outside the clip rect act as if the cursor jumped to the
+ // left edge of the text. When the cursor is at the right edge, drags of
+ // just a few pixels vertically end up selecting the "phantom newline"...
+ // sometimes.
+ RECT r;
+ GetRect(&r);
+ DefWindowProc(WM_MOUSEMOVE, keys,
+ MAKELPARAM(point.x, (r.bottom - r.top) / 2));
+ OnAfterPossibleChange();
+ }
+}
+
+void AutocompleteEdit::OnPaint(HDC bogus_hdc) {
+ // We need to paint over the top of the edit. If we simply let the edit do
+ // its default painting, then do ours into the window DC, the screen is
+ // updated in between and we can get flicker. To avoid this, we force the
+ // edit to paint into a memory DC, which we also paint onto, then blit the
+ // whole thing to the screen.
+
+ // Don't paint if not necessary.
+ CRect paint_clip_rect;
+ if (!GetUpdateRect(&paint_clip_rect, true))
+ return;
+
+ // Begin painting, and create a memory DC for the edit to paint into.
+ CPaintDC paint_dc(m_hWnd);
+ CDC memory_dc(CreateCompatibleDC(paint_dc));
+ CRect rect;
+ GetClientRect(&rect);
+ // NOTE: This next call uses |paint_dc| instead of |memory_dc| because
+ // |memory_dc| contains a 1x1 monochrome bitmap by default, which will cause
+ // |memory_bitmap| to be monochrome, which isn't what we want.
+ CBitmap memory_bitmap(CreateCompatibleBitmap(paint_dc, rect.Width(),
+ rect.Height()));
+ HBITMAP old_bitmap = memory_dc.SelectBitmap(memory_bitmap);
+
+ // Tell our intercept functions to supply our memory DC to the edit when it
+ // tries to call BeginPaint().
+ //
+ // The sane way to do this would be to use WM_PRINTCLIENT to ask the edit to
+ // paint into our desired DC. Unfortunately, the Rich Edit 3.0 that ships
+ // with Windows 2000/XP/Vista doesn't handle WM_PRINTCLIENT correctly; it
+ // treats it just like WM_PAINT and calls BeginPaint(), ignoring our provided
+ // DC. The Rich Edit 6.0 that ships with Office 2007 handles this better, but
+ // has other issues, and we can't redistribute that DLL anyway. So instead,
+ // we use this scary hack.
+ //
+ // NOTE: It's possible to get nested paint calls (!) (try setting the
+ // permanent URL to something longer than the edit width, then selecting the
+ // contents of the edit, typing a character, and hitting <esc>), so we can't
+ // DCHECK(!edit_hwnd_) here. Instead, just save off the old HWND, which most
+ // of the time will be NULL.
+ HWND old_edit_hwnd = edit_hwnd;
+ edit_hwnd = m_hWnd;
+ paint_struct = paint_dc.m_ps;
+ paint_struct.hdc = memory_dc;
+ DefWindowProc(WM_PAINT, reinterpret_cast<WPARAM>(bogus_hdc), 0);
+
+ // Make the selection look better.
+ EraseTopOfSelection(&memory_dc, rect, paint_clip_rect);
+
+ // Draw a slash through the scheme if this is insecure.
+ if (insecure_scheme_component_.is_nonempty())
+ DrawSlashForInsecureScheme(memory_dc, rect, paint_clip_rect);
+
+ // Draw the drop highlight.
+ if (drop_highlight_position_ != -1)
+ DrawDropHighlight(memory_dc, rect, paint_clip_rect);
+
+ // Blit the memory DC to the actual paint DC and clean up.
+ BitBlt(paint_dc, rect.left, rect.top, rect.Width(), rect.Height(), memory_dc,
+ rect.left, rect.top, SRCCOPY);
+ memory_dc.SelectBitmap(old_bitmap);
+ edit_hwnd = old_edit_hwnd;
+}
+
+void AutocompleteEdit::OnNonLButtonDown(UINT keys, const CPoint& point) {
+ // Interestingly, the edit doesn't seem to cancel triple clicking when the
+ // x-buttons (which usually means "thumb buttons") are pressed, so we only
+ // call this for M and R down.
+ tracking_double_click_ = false;
+
+ OnPossibleDrag(point);
+
+ SetMsgHandled(false);
+}
+
+void AutocompleteEdit::OnNonLButtonUp(UINT keys, const CPoint& point) {
+ UpdateDragDone(keys);
+
+ // Let default handler have a crack at this.
+ SetMsgHandled(false);
+}
+
+void AutocompleteEdit::OnPaste() {
+ // Replace the selection if we have something to paste.
+ const std::wstring text(GetClipboardText());
+ if (!text.empty()) {
+ // If this paste will be replacing all the text, record that, so we can do
+ // different behaviors in such a case.
+ CHARRANGE sel;
+ GetSel(sel);
+ if (IsSelectAll(sel))
+ paste_state_ = REPLACING_ALL;
+ ReplaceSel(text.c_str(), true);
+ }
+}
+
+void AutocompleteEdit::OnSetFocus(HWND focus_wnd) {
+ has_focus_ = true;
+ control_key_state_ = (GetKeyState(VK_CONTROL) < 0) ? DOWN_WITHOUT_CHANGE : UP;
+
+ // Notify controller if it needs to show hint UI of some kind.
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ if (show_search_hint_ || (is_keyword_hint_ && !keyword_.empty()))
+ controller_->OnChanged();
+
+ // Restore saved selection if available.
+ if (saved_selection_for_focus_change_.cpMin != -1) {
+ SetSelectionRange(saved_selection_for_focus_change_);
+ saved_selection_for_focus_change_.cpMin = -1;
+ }
+
+ SetMsgHandled(false);
+}
+
+void AutocompleteEdit::OnSysChar(TCHAR ch, UINT repeat_count, UINT flags) {
+ // Nearly all alt-<xxx> combos result in beeping rather than doing something
+ // useful, so we discard most. Exceptions:
+ // * ctrl-alt-<xxx>, which is sometimes important, generates WM_CHAR instead
+ // of WM_SYSCHAR, so it doesn't need to be handled here.
+ // * alt-space gets translated by the default WM_SYSCHAR handler to a
+ // WM_SYSCOMMAND to open the application context menu, so we need to allow
+ // it through.
+ if (ch == VK_SPACE)
+ SetMsgHandled(false);
+}
+
+void AutocompleteEdit::HandleKeystroke(UINT message, TCHAR key,
+ UINT repeat_count, UINT flags) {
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ OnBeforePossibleChange();
+ DefWindowProc(message, key, MAKELPARAM(repeat_count, flags));
+ OnAfterPossibleChange();
+}
+
+bool AutocompleteEdit::OnKeyDownOnlyWritable(TCHAR key,
+ UINT repeat_count,
+ UINT flags) {
+ // NOTE: Annoyingly, ctrl-alt-<key> generates WM_KEYDOWN rather than
+ // WM_SYSKEYDOWN, so we need to check (flags & KF_ALTDOWN) in various places
+ // in this function even with a WM_SYSKEYDOWN handler.
+
+ int count = repeat_count;
+ switch (key) {
+ case VK_RETURN:
+ AcceptInput((flags & KF_ALTDOWN) ? NEW_FOREGROUND_TAB : CURRENT_TAB,
+ false);
+ return true;
+
+ case VK_UP:
+ count = -count;
+ // FALL THROUGH
+ case VK_DOWN:
+ if (flags & KF_ALTDOWN)
+ return false;
+
+ // NOTE: VK_DOWN/VK_UP purposefully don't trigger any code that resets
+ // paste_state_.
+ disable_keyword_ui_ = false;
+ if (!popup_->is_open()) {
+ if (!popup_->query_in_progress()) {
+ // The popup is neither open nor working on a query already. So,
+ // start an autocomplete query for the current text. This also sets
+ // user_input_in_progress_ to true, which we want: if the user has
+ // started to interact with the popup, changing the permanent_text_
+ // shouldn't change the displayed text.
+ // Note: This does not force the popup to open immediately.
+ if (!user_input_in_progress_)
+ InternalSetUserText(permanent_text_);
+ DCHECK(user_text_ == UserTextFromDisplayText(GetText()));
+ UpdatePopup();
+ }
+
+ // Now go ahead and force the popup to open, and copy the text of the
+ // default item into the edit. We ignore |count|, since without the
+ // popup open, the user doesn't really know what they're interacting
+ // with. Since the user hit an arrow key to explicitly open the popup,
+ // we assume that they prefer the temporary text of the default item
+ // to their own text, like we do when they arrow around an already-open
+ // popup. In many cases the existing text in the edit and the new text
+ // will be the same, and the only visible effect will be to cancel any
+ // selection and place the cursor at the end of the edit.
+ popup_->Move(0);
+ } else {
+ // The popup is open, so the user should be able to interact with it
+ // normally.
+ popup_->Move(count);
+ }
+ return true;
+
+ // Hijacking Editing Commands
+ //
+ // We hijack the keyboard short-cuts for Cut, Copy, and Paste here so that
+ // they go through our clipboard routines. This allows us to be smarter
+ // about how we interact with the clipboard and avoid bugs in the
+ // CRichEditCtrl. If we didn't hijack here, the edit control would handle
+ // these internally with sending the WM_CUT, WM_COPY, or WM_PASTE messages.
+ //
+ // Cut: Shift-Delete and Ctrl-x are treated as cut. Ctrl-Shift-Delete and
+ // Ctrl-Shift-x are not treated as cut even though the underlying
+ // CRichTextEdit would treat them as such.
+ // Copy: Ctrl-c is treated as copy. Shift-Ctrl-c is not. (This is handled
+ // in OnKeyDownAllModes().)
+ // Paste: Shift-Insert and Ctrl-v are tread as paste. Ctrl-Shift-Insert and
+ // Ctrl-Shift-v are not.
+ //
+ // This behavior matches most, but not all Windows programs, and largely
+ // conforms to what users expect.
+
+ case VK_DELETE:
+ if ((flags & KF_ALTDOWN) || GetKeyState(VK_SHIFT) >= 0)
+ return false;
+ if (control_key_state_ == UP) {
+ // Cut text if possible.
+ CHARRANGE selection;
+ GetSel(selection);
+ if (selection.cpMin != selection.cpMax) {
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ OnBeforePossibleChange();
+ Cut();
+ OnAfterPossibleChange();
+ } else if (popup_->is_open()) {
+ // This is a bit overloaded, but we hijack Shift-Delete in this
+ // case to delete the current item from the pop-up. We prefer cutting
+ // to this when possible since that's the behavior more people expect
+ // from Shift-Delete, and it's more commonly useful.
+ popup_->TryDeletingCurrentItem();
+ }
+ }
+ return true;
+
+ case 'X':
+ if ((flags & KF_ALTDOWN) || (control_key_state_ == UP))
+ return false;
+ if (GetKeyState(VK_SHIFT) >= 0) {
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ OnBeforePossibleChange();
+ Cut();
+ OnAfterPossibleChange();
+ }
+ return true;
+
+ case VK_INSERT:
+ case 'V':
+ if ((flags & KF_ALTDOWN) || ((key == 'V') ?
+ (control_key_state_ == UP) : (GetKeyState(VK_SHIFT) >= 0)))
+ return false;
+ if ((key == 'V') ?
+ (GetKeyState(VK_SHIFT) >= 0) : (control_key_state_ == UP)) {
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ OnBeforePossibleChange();
+ Paste();
+ OnAfterPossibleChange();
+ }
+ return true;
+
+ case VK_BACK: {
+ if ((flags & KF_ALTDOWN) || is_keyword_hint_ || keyword_.empty())
+ return false;
+
+ {
+ CHARRANGE selection;
+ GetSel(selection);
+ if ((selection.cpMin != selection.cpMax) || (selection.cpMin != 0))
+ return false;
+ }
+
+ // We're showing a keyword and the user pressed backspace at the beginning
+ // of the text. Delete the trailing space from the keyword forcing the
+ // selected keyword to become empty.
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ OnBeforePossibleChange();
+ const std::wstring window_text(keyword_ + GetText());
+ SetWindowText(window_text.c_str());
+ PlaceCaretAt(keyword_.length());
+ popup_->manually_selected_match_.Clear();
+ keyword_.clear();
+ OnAfterPossibleChange();
+ just_deleted_text_ = true; // OnAfterPossibleChange() fails to clear this
+ // since the edit contents have actually grown
+ // longer.
+ return true;
+ }
+
+ case VK_TAB: {
+ if (is_keyword_hint_ && !keyword_.empty()) {
+ // Accept the keyword.
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ OnBeforePossibleChange();
+ SetWindowText(L"");
+ popup_->manually_selected_match_.Clear();
+ popup_->manually_selected_match_.provider_affinity =
+ popup_->autocomplete_controller()->keyword_provider();
+ is_keyword_hint_ = false;
+ disable_keyword_ui_ = false;
+ OnAfterPossibleChange();
+ just_deleted_text_ = false; // OnAfterPossibleChange() erroneously sets
+ // this since the edit contents have
+ // disappeared. It doesn't really matter,
+ // but we clear it to be consistent.
+
+ // Send out notification (primarily for logging).
+ UserMetrics::RecordAction(L"AcceptedKeywordHint", profile_);
+ }
+ return true;
+ }
+
+ case 0xbb: // Ctrl-'='. Triggers subscripting (even in plain text mode).
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+bool AutocompleteEdit::OnKeyDownAllModes(TCHAR key,
+ UINT repeat_count,
+ UINT flags) {
+ // See KF_ALTDOWN comment atop OnKeyDownOnlyWriteable().
+
+ switch (key) {
+ case VK_CONTROL:
+ if (control_key_state_ == UP) {
+ control_key_state_ = DOWN_WITHOUT_CHANGE;
+ if (popup_->is_open()) {
+ DCHECK(!popup_window_mode_); // How did the popup get open in read-only mode?
+ // Autocomplete history provider results may change, so refresh the
+ // popup. This will force user_input_in_progress_ to true, but if the
+ // popup is open, that should have already been the case.
+ UpdatePopup();
+ }
+ }
+ return false;
+
+ case 'C':
+ // See more detailed comments in OnKeyDownOnlyWriteable().
+ if ((flags & KF_ALTDOWN) || (control_key_state_ == UP))
+ return false;
+ if (GetKeyState(VK_SHIFT) >= 0)
+ Copy();
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+void AutocompleteEdit::OnBeforePossibleChange() {
+ // Record our state.
+ text_before_change_ = GetText();
+ GetSelection(sel_before_change_);
+ select_all_before_change_ = IsSelectAll(sel_before_change_);
+}
+
+bool AutocompleteEdit::OnAfterPossibleChange() {
+ // Prevent the user from selecting the "phantom newline" at the end of the
+ // edit. If they try, we just silently move the end of the selection back to
+ // the end of the real text.
+ CHARRANGE new_sel;
+ GetSelection(new_sel);
+ const int length = GetTextLength();
+ if ((new_sel.cpMin > length) || (new_sel.cpMax > length)) {
+ if (new_sel.cpMin > length)
+ new_sel.cpMin = length;
+ if (new_sel.cpMax > length)
+ new_sel.cpMax = length;
+ SetSelectionRange(new_sel);
+ }
+ const bool selection_differs = (new_sel.cpMin != sel_before_change_.cpMin) ||
+ (new_sel.cpMax != sel_before_change_.cpMax);
+
+ // See if the text or selection have changed since OnBeforePossibleChange().
+ const std::wstring new_text(GetText());
+ const bool text_differs = (new_text != text_before_change_);
+
+ // Update the paste state as appropriate: if we're just finishing a paste
+ // that replaced all the text, preserve that information; otherwise, if we've
+ // made some other edit, clear paste tracking.
+ if (paste_state_ == REPLACING_ALL)
+ paste_state_ = REPLACED_ALL;
+ else if (text_differs)
+ paste_state_ = NONE;
+
+ // If something has changed while the control key is down, prevent
+ // "ctrl-enter" until the control key is released. When we do this, we need
+ // to update the popup if it's open, since the desired_tld will have changed.
+ if ((text_differs || selection_differs) &&
+ (control_key_state_ == DOWN_WITHOUT_CHANGE)) {
+ control_key_state_ = DOWN_WITH_CHANGE;
+ if (!text_differs && !popup_->is_open())
+ return false; // Don't open the popup for no reason.
+ } else if (!text_differs &&
+ (inline_autocomplete_text_.empty() || !selection_differs)) {
+ return false;
+ }
+
+ const bool had_keyword = !is_keyword_hint_ && !keyword_.empty();
+
+ // Modifying the selection counts as accepting the autocompleted text.
+ InternalSetUserText(UserTextFromDisplayText(new_text));
+ has_temporary_text_ = false;
+
+ if (text_differs) {
+ // When the user has deleted text, don't allow inline autocomplete. Make
+ // sure to not flag cases like selecting part of the text and then pasting
+ // (or typing) the prefix of that selection. (We detect these by making
+ // sure the caret, which should be after any insertion, hasn't moved
+ // forward of the old selection start.)
+ just_deleted_text_ = (text_before_change_.length() > new_text.length()) &&
+ (new_sel.cpMin <= std::min(sel_before_change_.cpMin,
+ sel_before_change_.cpMax));
+
+ // When the user doesn't have a selected keyword, deleting text or replacing
+ // all of it with something else should reset the provider affinity. The
+ // typical use case for deleting is that the user starts typing, sees that
+ // some entry is close to what he wants, arrows to it, and then deletes some
+ // unnecessary bit from the end of the string. In this case the user didn't
+ // actually want "provider X", he wanted the string from that entry for
+ // editing purposes, and he's no longer looking at the popup to notice that,
+ // despite deleting some text, the action we'll take on enter hasn't changed
+ // at all.
+ if (!had_keyword && (just_deleted_text_ || select_all_before_change_)) {
+ popup_->manually_selected_match_.Clear();
+ }
+ }
+
+ // Disable the fancy keyword UI if the user didn't already have a visible
+ // keyword and is not at the end of the edit. This prevents us from showing
+ // the fancy UI (and interrupting the user's editing) if the user happens to
+ // have a keyword for 'a', types 'ab' then puts a space between the 'a' and
+ // the 'b'.
+ disable_keyword_ui_ = (is_keyword_hint_ || keyword_.empty()) &&
+ ((new_sel.cpMax != length) || (new_sel.cpMin != length));
+
+ UpdatePopup();
+
+ if (!had_keyword && !is_keyword_hint_ && !keyword_.empty()) {
+ // Went from no selected keyword to a selected keyword. Set the affinity to
+ // the keyword provider. This forces the selected keyword to persist even
+ // if the user deletes all the text.
+ popup_->manually_selected_match_.Clear();
+ popup_->manually_selected_match_.provider_affinity =
+ popup_->autocomplete_controller()->keyword_provider();
+ }
+
+ if (text_differs)
+ TextChanged();
+
+ return true;
+}
+
+void AutocompleteEdit::SetUserText(const std::wstring& text,
+ const std::wstring& display_text,
+ bool update_popup) {
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ SetInputInProgress(true);
+ paste_state_ = NONE;
+ InternalSetUserText(text);
+ SetWindowText(display_text.c_str());
+ PlaceCaretAt(display_text.length());
+ saved_selection_for_focus_change_.cpMin = -1;
+ has_temporary_text_ = false;
+ popup_->manually_selected_match_.Clear();
+ if (update_popup)
+ UpdatePopup();
+ TextChanged();
+}
+
+void AutocompleteEdit::InternalSetUserText(const std::wstring& text) {
+ user_text_ = text;
+ just_deleted_text_ = false;
+ inline_autocomplete_text_.clear();
+}
+
+void AutocompleteEdit::GetSelection(CHARRANGE& sel) const {
+ GetSel(sel);
+
+ // See if we need to reverse the direction of the selection.
+ CComPtr<ITextSelection> selection;
+ const HRESULT hr = GetTextObjectModel()->GetSelection(&selection);
+ DCHECK(hr == S_OK);
+ long flags;
+ selection->GetFlags(&flags);
+ if (flags & tomSelStartActive)
+ std::swap(sel.cpMin, sel.cpMax);
+}
+
+std::wstring AutocompleteEdit::GetSelectedText() const {
+ // Figure out the length of the selection.
+ CHARRANGE sel;
+ GetSel(sel);
+
+ // Grab the selected text.
+ std::wstring str;
+ GetSelText(WriteInto(&str, sel.cpMax - sel.cpMin + 1));
+ return str;
+}
+
+void AutocompleteEdit::SetSelection(LONG start, LONG end) {
+ SetSel(start, end);
+
+ if (start <= end)
+ return;
+
+ // We need to reverse the direction of the selection.
+ CComPtr<ITextSelection> selection;
+ const HRESULT hr = GetTextObjectModel()->GetSelection(&selection);
+ DCHECK(hr == S_OK);
+ selection->SetFlags(tomSelStartActive);
+}
+
+void AutocompleteEdit::PlaceCaretAt(std::wstring::size_type pos) {
+ SetSelection(static_cast<LONG>(pos), static_cast<LONG>(pos));
+}
+
+bool AutocompleteEdit::IsSelectAll(const CHARRANGE& sel) const {
+ const int text_length = GetTextLength();
+ return ((sel.cpMin == 0) && (sel.cpMax >= text_length)) ||
+ ((sel.cpMax == 0) && (sel.cpMin >= text_length));
+}
+
+LONG AutocompleteEdit::ClipXCoordToVisibleText(LONG x,
+ bool is_triple_click) const {
+ // Clip the X coordinate to the left edge of the text. Careful:
+ // PosFromChar(0) may return a negative X coordinate if the beginning of the
+ // text has scrolled off the edit, so don't go past the clip rect's edge.
+ RECT r;
+ GetRect(&r);
+ const int left_bound = std::max(r.left, PosFromChar(0).x);
+ if (x < left_bound)
+ return left_bound;
+
+ // See if we need to clip to the right edge of the text.
+ const int length = GetTextLength();
+ // Asking for the coordinate of any character past the end of the text gets
+ // the pixel just to the right of the last character.
+ const int right_bound = std::min(r.right, PosFromChar(length).x);
+ if ((length == 0) || (x < right_bound))
+ return x;
+
+ // For trailing characters that are 2 pixels wide of less (like "l" in some
+ // fonts), we have a problem:
+ // * Clicks on any pixel within the character will place the cursor before
+ // the character.
+ // * Clicks on the pixel just after the character will not allow triple-
+ // click to work properly (true for any last character width).
+ // So, we move to the last pixel of the character when this is a
+ // triple-click, and moving to one past the last pixel in all other
+ // scenarios. This way, all clicks that can move the cursor will place it at
+ // the end of the text, but triple-click will still work.
+ return is_triple_click ? (right_bound - 1) : right_bound;
+}
+
+void AutocompleteEdit::EmphasizeURLComponents() {
+ ITextDocument* const text_object_model = GetTextObjectModel();
+ ScopedFreeze freeze(this, text_object_model);
+ ScopedSuspendUndo suspend_undo(text_object_model);
+
+ // Save the selection.
+ CHARRANGE saved_sel;
+ GetSelection(saved_sel);
+
+ // See whether the contents are a URL with a non-empty host portion, which we
+ // should emphasize.
+ url_parse::Parsed parts;
+ AutocompleteInput::Parse(GetText(), GetDesiredTLD(), &parts, NULL);
+ bool emphasize = (parts.host.len > 0);
+ // If !user_input_in_progress_, the permanent text is showing, which should
+ // always be a URL, so no further checking is needed. By avoiding checking in
+ // this case, we avoid calling into the autocomplete providers, and thus
+ // initializing the history system, as long as possible, which speeds startup.
+ if (user_input_in_progress_) {
+ // Rather than using the type returned by Parse(), key off the desired page
+ // transition for this input since that can tell us whether an UNKNOWN input
+ // string is going to be treated as a search or a navigation. This is the
+ // same method the Paste And Go system uses.
+ PageTransition::Type transition = PageTransition::LINK;
+ GetURLForCurrentText(&transition, NULL, NULL);
+ if (transition != PageTransition::TYPED)
+ emphasize = false;
+ }
+
+ // Set the baseline emphasis.
+ CHARFORMAT cf = {0};
+ cf.dwMask = CFM_COLOR;
+ cf.dwEffects = 0;
+ cf.crTextColor = (emphasize ? GetSysColor(COLOR_GRAYTEXT) :
+ GetSysColor(COLOR_WINDOWTEXT));
+ SelectAll(false);
+ SetSelectionCharFormat(cf);
+
+ if (emphasize) {
+ // We've found a host name, give it more emphasis.
+ cf.crTextColor = GetSysColor(COLOR_WINDOWTEXT);
+ SetSelection(parts.host.begin, parts.host.end());
+ SetSelectionCharFormat(cf);
+ }
+
+ // Emphasize the scheme for security UI display purposes (if necessary).
+ insecure_scheme_component_.reset();
+ if (!user_input_in_progress_ && parts.scheme.is_nonempty() &&
+ ((scheme_security_level_ == ToolbarModel::SECURE) ||
+ (scheme_security_level_ == ToolbarModel::INSECURE))) {
+ if (scheme_security_level_ == ToolbarModel::SECURE) {
+ cf.crTextColor = kSecureSchemeColor;
+ } else {
+ insecure_scheme_component_.begin = parts.scheme.begin;
+ insecure_scheme_component_.len = parts.scheme.len;
+ cf.crTextColor = kInsecureSchemeColor;
+ }
+ SetSelection(parts.scheme.begin, parts.scheme.end());
+ SetSelectionCharFormat(cf);
+ }
+
+ // Restore the selection.
+ SetSelectionRange(saved_sel);
+}
+
+void AutocompleteEdit::EraseTopOfSelection(CDC* dc,
+ const CRect& client_rect,
+ const CRect& paint_clip_rect) {
+ // Find the area we care about painting. We could calculate the rect
+ // containing just the selected portion, but there's no harm in simply erasing
+ // the whole top of the client area, and at least once I saw us manage to
+ // select the "phantom newline" briefly, which looks very weird if not clipped
+ // off at the same height.
+ CRect erase_rect(client_rect.left, client_rect.top, client_rect.right,
+ client_rect.top + font_y_adjustment_);
+ erase_rect.IntersectRect(erase_rect, paint_clip_rect);
+
+ // Erase to the background color.
+ if (!erase_rect.IsRectNull())
+ dc->FillSolidRect(&erase_rect, background_color_);
+}
+
+void AutocompleteEdit::DrawSlashForInsecureScheme(
+ HDC hdc,
+ const CRect& client_rect,
+ const CRect& paint_clip_rect) {
+ DCHECK(insecure_scheme_component_.is_nonempty());
+
+ // Calculate the rect, in window coordinates, containing the portion of the
+ // scheme where we'll be drawing the slash. Vertically, we draw across one
+ // x-height of text, plus an additional 3 stroke diameters (the stroke width
+ // plus a half-stroke width of space between the stroke and the text, both
+ // above and below the text).
+ const int font_top = client_rect.top + font_y_adjustment_;
+ const SkScalar kStrokeWidthPixels = SkIntToScalar(2);
+ const int kAdditionalSpaceOutsideFont =
+ static_cast<int>(ceil(kStrokeWidthPixels * 1.5f));
+ const CRect scheme_rect(PosFromChar(insecure_scheme_component_.begin).x,
+ font_top + font_ascent_ - font_x_height_ -
+ kAdditionalSpaceOutsideFont,
+ PosFromChar(insecure_scheme_component_.end()).x,
+ font_top + font_ascent_ +
+ kAdditionalSpaceOutsideFont);
+
+ // Clip to the portion we care about and translate to canvas coordinates
+ // (see the canvas creation below) for use later.
+ CRect canvas_clip_rect, canvas_paint_clip_rect;
+ canvas_clip_rect.IntersectRect(scheme_rect, client_rect);
+ canvas_paint_clip_rect.IntersectRect(canvas_clip_rect, paint_clip_rect);
+ if (canvas_paint_clip_rect.IsRectNull())
+ return; // We don't need to paint any of this region, so just bail early.
+ canvas_clip_rect.OffsetRect(-scheme_rect.left, -scheme_rect.top);
+ canvas_paint_clip_rect.OffsetRect(-scheme_rect.left, -scheme_rect.top);
+
+ // Create a paint context for drawing the antialiased stroke.
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setStrokeWidth(kStrokeWidthPixels);
+ paint.setStrokeCap(SkPaint::kRound_Cap);
+
+ // Create a canvas as large as |scheme_rect| to do our drawing, and initialize
+ // it to fully transparent so any antialiasing will look nice when painted
+ // atop the edit.
+ ChromeCanvas canvas(scheme_rect.Width(), scheme_rect.Height(), false);
+ // TODO (jcampan): This const_cast should not be necessary once the SKIA
+ // API has been changed to return a non-const bitmap.
+ (const_cast<SkBitmap&>(canvas.getDevice()->accessBitmap(true))).
+ eraseARGB(0, 0, 0, 0);
+
+ // Calculate the start and end of the stroke, which are just the lower left
+ // and upper right corners of the canvas, inset by the radius of the endcap
+ // so we don't clip the endcap off.
+ const SkScalar kEndCapRadiusPixels = kStrokeWidthPixels / SkIntToScalar(2);
+ const SkPoint start_point = {
+ kEndCapRadiusPixels,
+ SkIntToScalar(scheme_rect.Height()) - kEndCapRadiusPixels };
+ const SkPoint end_point = {
+ SkIntToScalar(scheme_rect.Width()) - kEndCapRadiusPixels,
+ kEndCapRadiusPixels };
+
+ // Calculate the selection rectangle in canvas coordinates, which we'll use
+ // to clip the stroke so we can draw the unselected and selected portions.
+ CHARRANGE sel;
+ GetSel(sel);
+ const SkRect selection_rect = {
+ SkIntToScalar(PosFromChar(sel.cpMin).x - scheme_rect.left),
+ SkIntToScalar(0),
+ SkIntToScalar(PosFromChar(sel.cpMax).x - scheme_rect.left),
+ SkIntToScalar(scheme_rect.Height()) };
+
+ // Draw the unselected portion of the stroke.
+ canvas.save();
+ if (selection_rect.isEmpty() ||
+ canvas.clipRect(selection_rect, SkRegion::kDifference_Op)) {
+ paint.setColor(kSchemeStrikeoutColor);
+ canvas.drawLine(start_point.fX, start_point.fY,
+ end_point.fX, end_point.fY, paint);
+ }
+ canvas.restore();
+
+ // Draw the selected portion of the stroke.
+ if (!selection_rect.isEmpty() && canvas.clipRect(selection_rect)) {
+ paint.setColor(kSchemeSelectedStrikeoutColor);
+ canvas.drawLine(start_point.fX, start_point.fY,
+ end_point.fX, end_point.fY, paint);
+ }
+
+ // Now copy what we drew to the target HDC.
+ canvas.getTopPlatformDevice().drawToHDC(hdc,
+ scheme_rect.left + canvas_paint_clip_rect.left - canvas_clip_rect.left,
+ std::max(scheme_rect.top, client_rect.top) + canvas_paint_clip_rect.top -
+ canvas_clip_rect.top, &canvas_paint_clip_rect);
+}
+
+void AutocompleteEdit::DrawDropHighlight(HDC hdc,
+ const CRect& client_rect,
+ const CRect& paint_clip_rect) {
+ DCHECK(drop_highlight_position_ != -1);
+
+ const int highlight_y = client_rect.top + font_y_adjustment_;
+ const int highlight_x = PosFromChar(drop_highlight_position_).x - 1;
+ const CRect highlight_rect(highlight_x,
+ highlight_y,
+ highlight_x + 1,
+ highlight_y + font_ascent_ + font_descent_);
+
+ // Clip the highlight to the region being painted.
+ CRect clip_rect;
+ clip_rect.IntersectRect(highlight_rect, paint_clip_rect);
+ if (clip_rect.IsRectNull())
+ return;
+
+ HGDIOBJ last_pen = SelectObject(hdc, CreatePen(PS_SOLID, 1, RGB(0, 0, 0)));
+ MoveToEx(hdc, clip_rect.left, clip_rect.top, NULL);
+ LineTo(hdc, clip_rect.left, clip_rect.bottom);
+ DeleteObject(SelectObject(hdc, last_pen));
+}
+
+std::wstring AutocompleteEdit::GetDesiredTLD() const {
+ return (control_key_state_ == DOWN_WITHOUT_CHANGE) ? L"com" : L"";
+}
+
+void AutocompleteEdit::UpdatePopup() {
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ SetInputInProgress(true);
+
+ if (!has_focus_) {
+ // When we're in the midst of losing focus, don't rerun autocomplete. This
+ // can happen when losing focus causes the IME to cancel/finalize a
+ // composition. We still want to note that user input is in progress, we
+ // just don't want to do anything else.
+ //
+ // Note that in this case the ScopedFreeze above was unnecessary; however,
+ // we're inside the callstack of OnKillFocus(), which has already frozen the
+ // edit, so this will never result in an unnecessary UpdateWindow() call.
+ return;
+ }
+
+ // Figure out whether the user is trying to compose something in an IME.
+ bool ime_composing = false;
+ HIMC context = ImmGetContext(m_hWnd);
+ if (context) {
+ ime_composing = !!ImmGetCompositionString(context, GCS_COMPSTR, NULL, 0);
+ ImmReleaseContext(m_hWnd, context);
+ }
+
+ // Don't inline autocomplete when:
+ // * The user is deleting text
+ // * The caret/selection isn't at the end of the text
+ // * The user has just pasted in something that replaced all the text
+ // * The user is trying to compose something in an IME
+ CHARRANGE sel;
+ GetSel(sel);
+ popup_->StartAutocomplete(user_text_, GetDesiredTLD(),
+ just_deleted_text_ || (sel.cpMax < GetTextLength()) ||
+ (paste_state_ != NONE) || ime_composing);
+}
+
+void AutocompleteEdit::TextChanged() {
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ EmphasizeURLComponents();
+ controller_->OnChanged();
+}
+
+std::wstring AutocompleteEdit::DisplayTextFromUserText(
+ const std::wstring& text) const {
+ return (is_keyword_hint_ || keyword_.empty()) ?
+ text : KeywordProvider::SplitReplacementStringFromInput(text);
+}
+
+std::wstring AutocompleteEdit::UserTextFromDisplayText(
+ const std::wstring& text) const {
+ return (is_keyword_hint_ || keyword_.empty()) ?
+ text : (keyword_ + L" " + text);
+}
+
+std::wstring AutocompleteEdit::GetClipboardText() const {
+ // Try text format.
+ ClipboardService* clipboard = g_browser_process->clipboard_service();
+ if (clipboard->IsFormatAvailable(CF_UNICODETEXT)) {
+ std::wstring text;
+ clipboard->ReadText(&text);
+
+ // Note: Unlike in the find popup and textfield view, here we completely
+ // remove whitespace strings containing newlines. We assume users are
+ // most likely pasting in URLs that may have been split into multiple
+ // lines in terminals, email programs, etc., and so linebreaks indicate
+ // completely bogus whitespace that would just cause the input to be
+ // invalid.
+ return CollapseWhitespace(text, true);
+ }
+
+ // Try bookmark format.
+ //
+ // It is tempting to try bookmark format first, but the URL we get out of a
+ // bookmark has been cannonicalized via GURL. This means if a user copies
+ // and pastes from the URL bar to itself, the text will get fixed up and
+ // cannonicalized, which is not what the user expects. By pasting in this
+ // order, we are sure to paste what the user copied.
+ if (clipboard->IsFormatAvailable(ClipboardUtil::GetUrlWFormat()->cfFormat)) {
+ std::string url_str;
+ clipboard->ReadBookmark(NULL, &url_str);
+ // pass resulting url string through GURL to normalize
+ GURL url(url_str);
+ if (url.is_valid())
+ return UTF8ToWide(url.spec());
+ }
+
+ return std::wstring();
+}
+
+bool AutocompleteEdit::CanPasteAndGo(const std::wstring& text) const {
+ if (popup_window_mode_)
+ return false;
+
+ // Reset local state.
+ paste_and_go_url_.clear();
+ paste_and_go_transition_ = PageTransition::TYPED;
+ paste_and_go_alternate_nav_url_.clear();
+
+ // See if the clipboard text can be parsed.
+ const AutocompleteInput input(text, std::wstring(), true);
+ if (input.type() == AutocompleteInput::INVALID)
+ return false;
+
+ // Ask the controller what do do with this input.
+ paste_and_go_controller->SetProfile(profile_);
+ // This is cheap, and since there's one
+ // paste_and_go_controller for many tabs which
+ // may all have different profiles, it ensures
+ // we're always using the right one.
+ const bool done = paste_and_go_controller->Start(input, false, true);
+ DCHECK(done);
+ AutocompleteResult result;
+ paste_and_go_controller->GetResult(&result);
+ if (result.empty())
+ return false;
+
+ // Set local state based on the default action for this input.
+ result.SetDefaultMatch(AutocompleteResult::Selection());
+ const AutocompleteResult::const_iterator match(result.default_match());
+ DCHECK(match != result.end());
+ paste_and_go_url_ = match->destination_url;
+ paste_and_go_transition_ = match->transition;
+ paste_and_go_alternate_nav_url_ = result.GetAlternateNavURL(input, match);
+
+ return !paste_and_go_url_.empty();
+}
+
+void AutocompleteEdit::PasteAndGo() {
+ // The final parameter to OpenURL, keyword, is not quite correct here: it's
+ // possible to "paste and go" a string that contains a keyword. This is
+ // enough of an edge case that we ignore this possibility.
+ RevertAll();
+ OpenURL(paste_and_go_url_, CURRENT_TAB, paste_and_go_transition_,
+ paste_and_go_alternate_nav_url_, AutocompletePopup::kNoMatch,
+ std::wstring());
+}
+
+void AutocompleteEdit::SetInputInProgress(bool in_progress) {
+ if (user_input_in_progress_ == in_progress)
+ return;
+
+ user_input_in_progress_ = in_progress;
+ controller_->OnInputInProgress(in_progress);
+}
+
+void AutocompleteEdit::SendOpenNotification(size_t selected_line,
+ const std::wstring& keyword) {
+ // We only care about cases where there is a selection (i.e. the popup is
+ // open).
+ if (popup_->is_open()) {
+ scoped_ptr<AutocompleteLog> log(popup_->GetAutocompleteLog());
+ if (selected_line != AutocompletePopup::kNoMatch)
+ log->selected_index = selected_line;
+ else if (!has_temporary_text_)
+ log->inline_autocompleted_length = inline_autocomplete_text_.length();
+ NotificationService::current()->Notify(
+ NOTIFY_OMNIBOX_OPENED_URL, Source<Profile>(profile_),
+ Details<AutocompleteLog>(log.get()));
+ }
+
+ TemplateURLModel* template_url_model = profile_->GetTemplateURLModel();
+ if (keyword.empty() || !template_url_model)
+ return;
+
+ const TemplateURL* const template_url =
+ template_url_model->GetTemplateURLForKeyword(keyword);
+ if (template_url) {
+ UserMetrics::RecordAction(L"AcceptedKeyword", profile_);
+ template_url_model->IncrementUsageCount(template_url);
+ }
+
+ // NOTE: We purposefully don't increment the usage count of the default search
+ // engine, if applicable; see comments in template_url.h.
+}
+
+ITextDocument* AutocompleteEdit::GetTextObjectModel() const {
+ if (!text_object_model_) {
+ // This is lazily initialized, instead of being initialized in the
+ // constructor, in order to avoid hurting startup performance.
+ CComPtr<IRichEditOle> ole_interface;
+ ole_interface.Attach(GetOleInterface());
+ text_object_model_ = ole_interface;
+ }
+ return text_object_model_;
+}
+
+void AutocompleteEdit::StartDragIfNecessary(const CPoint& point) {
+ if (initiated_drag_ || !win_util::IsDrag(mouse_down_point_, point))
+ return;
+
+ scoped_refptr<OSExchangeData> data = new OSExchangeData;
+
+ DWORD supported_modes = DROPEFFECT_COPY;
+
+ CHARRANGE sel;
+ GetSelection(sel);
+
+ // We're about to start a drag session, but the edit is expecting a mouse up
+ // that it uses to reset internal state. If we don't send a mouse up now,
+ // when the mouse moves back into the edit the edit will reset the selection.
+ // So, we send the event now which resets the selection. We then restore the
+ // selection and start the drag. We always send lbuttonup as otherwise we
+ // might trigger a context menu (right up). This seems scary, but doesn't
+ // seem to cause problems.
+ {
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ DefWindowProc(WM_LBUTTONUP, 0,
+ MAKELPARAM(mouse_down_point_.x, mouse_down_point_.y));
+ SetSelectionRange(sel);
+ }
+
+ const std::wstring start_text(GetText());
+ if (IsSelectAll(sel)) {
+ // All the text is selected, export as URL.
+ const std::wstring url(GetURLForCurrentText(NULL, NULL, NULL));
+ std::wstring title;
+ SkBitmap favicon;
+ if (url == permanent_text_) {
+ title = controller_->GetTitle();
+ favicon = controller_->GetFavIcon();
+ }
+ const GURL gurl(url);
+ drag_utils::SetURLAndDragImage(gurl, title, favicon, data.get());
+ data->SetURL(gurl, title);
+ supported_modes |= DROPEFFECT_LINK;
+ UserMetrics::RecordAction(L"Omnibox_DragURL", profile_);
+ } else {
+ supported_modes |= DROPEFFECT_MOVE;
+ UserMetrics::RecordAction(L"Omnibox_DragString", profile_);
+ }
+
+ data->SetString(GetSelectedText());
+
+ scoped_refptr<BaseDragSource> drag_source(new BaseDragSource);
+ DWORD dropped_mode;
+ in_drag_ = true;
+ if (DoDragDrop(data, drag_source, supported_modes, &dropped_mode) ==
+ DRAGDROP_S_DROP) {
+ if ((dropped_mode == DROPEFFECT_MOVE) && (start_text == GetText())) {
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ OnBeforePossibleChange();
+ SetSelectionRange(sel);
+ ReplaceSel(L"", true);
+ OnAfterPossibleChange();
+ }
+ // else case, not a move or it was a move and the drop was on us.
+ // If the drop was on us, EditDropTarget took care of the move so that
+ // we don't have to delete the text.
+ possible_drag_ = false;
+ } else {
+ // Drag was canceled or failed. The mouse may still be down and
+ // over us, in which case we need possible_drag_ to remain true so
+ // that we don't forward mouse move events to the edit which will
+ // start another drag.
+ //
+ // NOTE: we didn't use mouse capture during the mouse down as DoDragDrop
+ // does its own capture.
+ CPoint cursor_location;
+ GetCursorPos(&cursor_location);
+
+ CRect client_rect;
+ GetClientRect(&client_rect);
+
+ CPoint client_origin_on_screen(client_rect.left, client_rect.top);
+ ClientToScreen(&client_origin_on_screen);
+ client_rect.MoveToXY(client_origin_on_screen.x,
+ client_origin_on_screen.y);
+ possible_drag_ = (client_rect.PtInRect(cursor_location) &&
+ ((GetKeyState(VK_LBUTTON) != 0) ||
+ (GetKeyState(VK_MBUTTON) != 0) ||
+ (GetKeyState(VK_RBUTTON) != 0)));
+ }
+
+ in_drag_ = false;
+ initiated_drag_ = true;
+ tracking_click_ = false;
+}
+
+void AutocompleteEdit::OnPossibleDrag(const CPoint& point) {
+ if (possible_drag_)
+ return;
+
+ mouse_down_point_ = point;
+ initiated_drag_ = false;
+
+ CHARRANGE selection;
+ GetSel(selection);
+ if (selection.cpMin != selection.cpMax) {
+ const POINT min_sel_location(PosFromChar(selection.cpMin));
+ const POINT max_sel_location(PosFromChar(selection.cpMax));
+ // NOTE: we don't consider the y location here as we always pass a
+ // y-coordinate in the middle to the default handler which always triggers
+ // a drag regardless of the y-coordinate.
+ possible_drag_ = (point.x >= min_sel_location.x) &&
+ (point.x < max_sel_location.x);
+ }
+}
+
+void AutocompleteEdit::UpdateDragDone(UINT keys) {
+ possible_drag_ = (possible_drag_ &&
+ ((keys & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) != 0));
+}
+
+void AutocompleteEdit::RepaintDropHighlight(int position) {
+ if ((position != -1) && (position <= GetTextLength())) {
+ const POINT min_loc(PosFromChar(position));
+ const RECT highlight_bounds = {min_loc.x - 1, font_y_adjustment_,
+ min_loc.x + 2, font_ascent_ + font_descent_ + font_y_adjustment_};
+ InvalidateRect(&highlight_bounds, false);
+ }
+}
diff --git a/chrome/browser/autocomplete/autocomplete_edit.h b/chrome/browser/autocomplete/autocomplete_edit.h
new file mode 100644
index 0000000..9f3ecca
--- /dev/null
+++ b/chrome/browser/autocomplete/autocomplete_edit.h
@@ -0,0 +1,813 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CHROME_BROWSER_AUTOCOMPLETE_AUTOCOMPLETE_EDIT_H__
+#define CHROME_BROWSER_AUTOCOMPLETE_AUTOCOMPLETE_EDIT_H__
+
+#include <atlbase.h>
+#include <atlapp.h>
+#include <atlcomcli.h>
+#include <atlctrls.h>
+#include <oleacc.h>
+#include <tom.h> // For ITextDocument, a COM interface to CRichEditCtrl
+
+#include "base/iat_patch.h"
+#include "base/scoped_ptr.h"
+#include "chrome/browser/autocomplete/autocomplete_popup.h"
+#include "chrome/browser/security_style.h"
+#include "chrome/browser/toolbar_model.h"
+#include "chrome/common/gfx/chrome_font.h"
+#include "chrome/views/menu.h"
+
+class CommandController;
+class Profile;
+class TabContents;
+
+// Provides the implementation of an edit control with a drop-down
+// autocomplete box. The box itself is implemented in autocomplete_popup.cc
+// This file implements the edit box and management for the popup.
+//
+// This implementation is currently appropriate for the URL bar, where the
+// autocomplete dropdown is always displayed because there is always a
+// default item. For web page autofill and other applications, this is
+// probably not appropriate. We may want to add a flag to determine which
+// of these modes we're in.
+class AutocompleteEdit
+ : public CWindowImpl<AutocompleteEdit,
+ CRichEditCtrl,
+ CWinTraits<WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL |
+ ES_NOHIDESEL> >,
+ public CRichEditCommands<AutocompleteEdit>,
+ public Menu::Delegate {
+ public:
+ DECLARE_WND_CLASS(L"Chrome_AutocompleteEdit");
+
+ // Embedders of this control must implement this class.
+ class Controller {
+ public:
+ // When the user presses enter or selects a line with the mouse, this
+ // function will get called synchronously with the url to open and
+ // disposition and transition to use when opening it.
+ //
+ // |alternate_nav_url|, if non-empty, contains the alternate navigation URL
+ // for |url|, which the controller can check for existence. See comments on
+ // AutocompleteResult::GetAlternateNavURL().
+ virtual void OnAutocompleteAccept(const std::wstring& url,
+ WindowOpenDisposition disposition,
+ PageTransition::Type transition,
+ const std::wstring& alternate_nav_url) = 0;
+
+ // Called when anything has changed that might affect the layout or contents
+ // of the views around the edit, including the text of the edit and the
+ // status of any keyword- or hint-related state.
+ virtual void OnChanged() = 0;
+
+ // Called whenever the user starts or stops an input session (typing,
+ // interacting with the edit, etc.). When user input is not in progress,
+ // the edit is guaranteed to be showing the permanent text.
+ virtual void OnInputInProgress(bool in_progress) = 0;
+
+ // Returns the favicon of the current page.
+ virtual SkBitmap GetFavIcon() const = 0;
+
+ // Returns the title of the current page.
+ virtual std::wstring GetTitle() const = 0;
+ };
+
+ // The State struct contains enough information about the AutocompleteEdit to
+ // save/restore a user's typing, caret position, etc. across tab changes. We
+ // explicitly don't preserve things like whether the popup was open as this
+ // might be weird.
+ struct State {
+ State(const CHARRANGE& selection,
+ const CHARRANGE& saved_selection_for_focus_change,
+ bool user_input_in_progress,
+ const std::wstring& user_text,
+ const AutocompleteResult::Selection& manually_selected_match,
+ const std::wstring& keyword,
+ bool is_keyword_hint,
+ bool disable_keyword_ui,
+ bool show_search_hint)
+ : selection(selection),
+ saved_selection_for_focus_change(saved_selection_for_focus_change),
+ user_input_in_progress(user_input_in_progress),
+ user_text(user_text),
+ manually_selected_match(manually_selected_match),
+ keyword(keyword),
+ is_keyword_hint(is_keyword_hint),
+ disable_keyword_ui(disable_keyword_ui),
+ show_search_hint(show_search_hint) {
+ }
+
+ const CHARRANGE selection;
+ const CHARRANGE saved_selection_for_focus_change;
+ bool user_input_in_progress;
+ const std::wstring user_text;
+ AutocompleteResult::Selection manually_selected_match;
+ const std::wstring keyword;
+ const bool is_keyword_hint;
+ const bool disable_keyword_ui;
+ const bool show_search_hint;
+ };
+
+ // The given observer is notified when the user selects an item
+ AutocompleteEdit(const ChromeFont& font,
+ Controller* controller,
+ ToolbarModel* model,
+ ChromeViews::View* parent_view,
+ HWND hwnd,
+ Profile* profile,
+ CommandController* command_controller,
+ bool popup_window_mode);
+ ~AutocompleteEdit();
+
+ // Called when any LocationBarView state changes. If
+ // |tab_for_state_restoring| is non-NULL, it points to a TabContents whose
+ // state we should restore.
+ void Update(const TabContents* tab_for_state_restoring);
+
+ // Invoked when the profile has changed.
+ void SetProfile(Profile* profile);
+
+ // For use when switching tabs, this saves the current state onto the tab so
+ // that it can be restored during a later call to Update().
+ void SaveStateToTab(TabContents* tab);
+
+ // Returns the current text of the edit control, which could be the
+ // "temporary" text set by the popup, the "permanent" text set by the
+ // browser, or just whatever the user has currently typed.
+ std::wstring GetText() const;
+
+ // Returns the URL. If the user has not edited the text, this returns the
+ // permanent text. If the user has edited the text, this returns the default
+ // match based on the current text, which may be a search URL, or keyword
+ // generated URL.
+ //
+ // See AutocompleteEdit for a description of the args (they may be null if
+ // not needed).
+ std::wstring GetURLForCurrentText(PageTransition::Type* transition,
+ bool* is_history_what_you_typed_match,
+ std::wstring* alternate_nav_url);
+
+ // The user text is the text the user has manually keyed in. When present,
+ // this is shown in preference to the permanent text; hitting escape will
+ // revert to the permanent text.
+ void SetUserText(const std::wstring& text) { SetUserText(text, text, true); }
+
+ // Selects all the text in the edit. Use this in place of SetSelAll() to
+ // avoid selecting the "phantom newline" at the end of the edit.
+ void SelectAll(bool reversed);
+
+ // Reverts the edit and popup back to their unedited state (permanent text
+ // showing, popup closed, no user input in progress).
+ void RevertAll();
+
+ // Asks the browser to load the popup's currently selected item, using the
+ // supplied disposition. This may close the popup. If |for_drop| is true,
+ // it indicates the input is being accepted as part of a drop operation and
+ // the transition should be treated as LINK (so that it won't trigger the
+ // URL to be autocompleted).
+ void AcceptInput(WindowOpenDisposition disposition,
+ bool for_drop);
+
+ // Asks the browser to load the specified URL, which is assumed to be one of
+ // the popup entries, using the supplied disposition and transition type.
+ // |alternate_nav_url|, if non-empty, contains the alternate navigation URL
+ // for |url|. See comments on AutocompleteResult::GetAlternateNavURL().
+ //
+ // |selected_line| is passed to AutocompletePopup::LogOpenedURL(); see
+ // comments there.
+ //
+ // If the URL was expanded from a keyword, |keyword| is that keyword.
+ //
+ // This may close the popup.
+ void OpenURL(const std::wstring& url,
+ WindowOpenDisposition disposition,
+ PageTransition::Type transition,
+ const std::wstring& alternate_nav_url,
+ size_t selected_line,
+ const std::wstring& keyword);
+
+ // Closes the autocomplete popup, if it's open.
+ void ClosePopup();
+
+ // Accessors for keyword-related state (see comments on keyword_ and
+ // is_keyword_hint_).
+ std::wstring keyword() const {
+ return ((is_keyword_hint_ && has_focus_) ||
+ (!is_keyword_hint_ && !disable_keyword_ui_)) ?
+ keyword_ : std::wstring();
+ }
+ bool is_keyword_hint() const { return is_keyword_hint_; }
+
+ // True if we should show the "Type to search" hint (see comments on
+ // show_search_hint_).
+ bool show_search_hint() const { return has_focus_ && show_search_hint_; }
+
+ ChromeViews::View* parent_view() const { return parent_view_; }
+
+ // Returns true if a query to an autocomplete provider is currently
+ // in progress. This logic should in the future live in
+ // AutocompleteController but resides here for now. This method is used by
+ // AutomationProvider::AutocompleteEditIsQueryInProgress.
+ bool query_in_progress() const { return popup_->query_in_progress(); }
+
+ // Returns the lastest autocomplete results. This logic should in the future
+ // live in AutocompleteController but resides here for now. This method is
+ // used by AutomationProvider::AutocompleteEditGetMatches.
+ const AutocompleteResult* latest_result() const {
+ return popup_->latest_result();
+ }
+
+ // Exposes custom IAccessible implementation to the overall MSAA hierarchy.
+ IAccessible* GetIAccessible();
+
+ void SetDropHighlightPosition(int position);
+ int drop_highlight_position() const { return drop_highlight_position_; }
+
+ // Returns true if a drag a drop session was initiated by this edit.
+ bool in_drag() const { return in_drag_; }
+
+ // Moves the selected text to the specified position.
+ void MoveSelectedText(int new_position);
+
+ // Inserts the text at the specified position.
+ void InsertText(int position, const std::wstring& text);
+
+ // Invokes CanPasteAndGo with the specified text, and if successful navigates
+ // to the appropriate URL. The behavior of this is the same as if the user
+ // typed in the specified text and pressed enter.
+ void PasteAndGo(const std::wstring& text);
+
+ // Called before an accelerator is processed to give us a chance to override
+ // it.
+ bool OverrideAccelerator(const ChromeViews::Accelerator& accelerator);
+
+ // Handler for external events passed in to us. The View that owns us may
+ // send us events that we should treat as if they were events on us.
+ void HandleExternalMsg(UINT msg, UINT flags, const CPoint& screen_point);
+
+ // Called back by the AutocompletePopup when any relevant data changes. This
+ // rolls together several separate pieces of data into one call so we can
+ // update all the UI efficiently:
+ // |text| is either the new temporary text (if |is_temporary_text| is true)
+ // from the user manually selecting a different match, or the inline
+ // autocomplete text (if |is_temporary_text| is false).
+ // |previous_selected_match| is only used when changing the temporary text;
+ // it is the match that was (manually or automatically) selected before
+ // the current manual selection, and is saved to be restored later if the
+ // user hits <esc>.
+ // |can_show_search_hint| is true if the current choice is nonexistent or a
+ // search result; in these cases it may be OK to show the "Type to search"
+ // hint (see comments on show_search_hint_).
+ // |keyword| is the keyword to show a hint for if |is_keyword_hint| is true,
+ // or the currently selected keyword if |is_keyword_hint| is false (see
+ // comments on keyword_ and is_keyword_hint_).
+ void OnPopupDataChanged(
+ const std::wstring& text,
+ bool is_temporary_text,
+ const AutocompleteResult::Selection& previous_selected_match,
+ const std::wstring& keyword,
+ bool is_keyword_hint,
+ bool can_show_search_hint);
+
+ // CWindowImpl
+ BEGIN_MSG_MAP(AutocompleteEdit)
+ MSG_WM_CHAR(OnChar)
+ MSG_WM_CONTEXTMENU(OnContextMenu)
+ MSG_WM_COPY(OnCopy)
+ MSG_WM_CUT(OnCut)
+ MESSAGE_HANDLER_EX(WM_GETOBJECT, OnGetObject)
+ MESSAGE_HANDLER_EX(WM_IME_COMPOSITION, OnImeComposition)
+ MSG_WM_KEYDOWN(OnKeyDown)
+ MSG_WM_KEYUP(OnKeyUp)
+ MSG_WM_KILLFOCUS(OnKillFocus)
+ MSG_WM_LBUTTONDBLCLK(OnLButtonDblClk)
+ MSG_WM_LBUTTONDOWN(OnLButtonDown)
+ MSG_WM_LBUTTONUP(OnLButtonUp)
+ MSG_WM_MBUTTONDOWN(OnNonLButtonDown)
+ MSG_WM_MBUTTONUP(OnNonLButtonUp)
+ MSG_WM_MOUSEACTIVATE(OnMouseActivate)
+ MSG_WM_MOUSEMOVE(OnMouseMove)
+ MSG_WM_PAINT(OnPaint)
+ MSG_WM_PASTE(OnPaste)
+ MSG_WM_RBUTTONDOWN(OnNonLButtonDown)
+ MSG_WM_RBUTTONUP(OnNonLButtonUp)
+ MSG_WM_SETFOCUS(OnSetFocus)
+ MSG_WM_SYSCHAR(OnSysChar) // WM_SYSxxx == WM_xxx with ALT down
+ MSG_WM_SYSKEYDOWN(OnKeyDown)
+ MSG_WM_SYSKEYUP(OnKeyUp)
+ DEFAULT_REFLECTION_HANDLER() // avoids black margin area
+ END_MSG_MAP()
+
+ // Menu::Delegate
+ virtual bool IsCommandEnabled(int id) const;
+ virtual bool GetContextualLabel(int id, std::wstring* out) const;
+ virtual void ExecuteCommand(int id);
+
+ private:
+ // This object freezes repainting of the edit until the object is destroyed.
+ // Some methods of the CRichEditCtrl draw synchronously to the screen. If we
+ // don't freeze, the user will see a rapid series of calls to these as
+ // flickers.
+ //
+ // Freezing the control while it is already frozen is permitted; the control
+ // will unfreeze once both freezes are released (the freezes stack).
+ class ScopedFreeze {
+ public:
+ ScopedFreeze(AutocompleteEdit* edit, ITextDocument* text_object_model);
+ ~ScopedFreeze();
+
+ private:
+ AutocompleteEdit* const edit_;
+ ITextDocument* const text_object_model_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(ScopedFreeze);
+ };
+
+ // This object suspends placing any operations on the edit's undo stack until
+ // the object is destroyed. If we don't do this, some of the operations we
+ // perform behind the user's back will be undoable by the user, which feels
+ // bizarre and confusing.
+ class ScopedSuspendUndo {
+ public:
+ explicit ScopedSuspendUndo(ITextDocument* text_object_model);
+ ~ScopedSuspendUndo();
+
+ private:
+ ITextDocument* const text_object_model_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(ScopedSuspendUndo);
+ };
+
+ enum ControlKeyState {
+ UP, // The control key is not depressed.
+ DOWN_WITHOUT_CHANGE, // The control key is depressed, and the edit's
+ // contents/selection have not changed since it was
+ // depressed. This is the only state in which we
+ // do the "ctrl-enter" behavior when the user hits
+ // enter.
+ DOWN_WITH_CHANGE, // The control key is depressed, and the edit's
+ // contents/selection have changed since it was
+ // depressed. If the user now hits enter, we assume
+ // he simply hasn't released the key, rather than that
+ // he intended to hit "ctrl-enter".
+ };
+
+ enum PasteState {
+ NONE, // Most recent edit was not a paste that replaced all text.
+ REPLACED_ALL, // Most recent edit was a paste that replaced all text.
+ REPLACING_ALL, // In the middle of doing a paste that replaces all
+ // text. We need this intermediate state because OnPaste()
+ // does the actual detection of such pastes, but
+ // OnAfterPossibleChange() has to update the paste state
+ // for every edit. If OnPaste() set the state directly to
+ // REPLACED_ALL, OnAfterPossibleChange() wouldn't know
+ // whether that represented the current edit or a past one.
+ };
+
+ // Replacement word-breaking proc for the rich edit control.
+ static int CALLBACK WordBreakProc(LPTSTR edit_text,
+ int current_pos,
+ int num_bytes,
+ int action);
+
+ // Returns true if |edit_text| starting at |current_pos| is "://".
+ static bool SchemeEnd(LPTSTR edit_text, int current_pos, int length);
+
+ // Intercepts. See OnPaint().
+ static HDC WINAPI BeginPaintIntercept(HWND hWnd, LPPAINTSTRUCT lpPaint);
+ static BOOL WINAPI EndPaintIntercept(HWND hWnd, CONST PAINTSTRUCT* lpPaint);
+
+ // Message handlers
+ void OnChar(TCHAR ch, UINT repeat_count, UINT flags);
+ void OnContextMenu(HWND window, const CPoint& point);
+ void OnCopy();
+ void OnCut();
+ LRESULT OnGetObject(UINT uMsg, WPARAM wparam, LPARAM lparam);
+ LRESULT OnImeComposition(UINT message, WPARAM wparam, LPARAM lparam);
+ void OnKeyDown(TCHAR key, UINT repeat_count, UINT flags);
+ void OnKeyUp(TCHAR key, UINT repeat_count, UINT flags);
+ void OnKillFocus(HWND focus_wnd);
+ void OnLButtonDblClk(UINT keys, const CPoint& point);
+ void OnLButtonDown(UINT keys, const CPoint& point);
+ void OnLButtonUp(UINT keys, const CPoint& point);
+ LRESULT OnMouseActivate(HWND window, UINT hit_test, UINT mouse_message);
+ void OnMouseMove(UINT keys, const CPoint& point);
+ void OnNonLButtonDown(UINT keys, const CPoint& point);
+ void OnNonLButtonUp(UINT keys, const CPoint& point);
+ void OnPaint(HDC bogus_hdc);
+ void OnPaste();
+ void OnSetFocus(HWND focus_wnd);
+ void OnSysChar(TCHAR ch, UINT repeat_count, UINT flags);
+
+ // Helper function for OnChar() and OnKeyDown() that handles keystrokes that
+ // could change the text in the edit.
+ void HandleKeystroke(UINT message, TCHAR key, UINT repeat_count, UINT flags);
+
+ // Helper functions for OnKeyDown() that handle accelerators applicable when
+ // we're not read-only and all the time, respectively. These return true if
+ // they handled the key.
+ bool OnKeyDownOnlyWritable(TCHAR key, UINT repeat_count, UINT flags);
+ bool OnKeyDownAllModes(TCHAR key, UINT repeat_count, UINT flags);
+
+ // Every piece of code that can change the edit should call these functions
+ // before and after the change. These functions determine if anything
+ // meaningful changed, and do any necessary updating and notification.
+ void OnBeforePossibleChange();
+ // OnAfterPossibleChange() returns true if there was a change that caused it
+ // to call UpdatePopup().
+ bool OnAfterPossibleChange();
+
+ // Implementation for SetUserText(wstring). SetUserText(wstring) invokes this
+ // with |update_popup| set to false.
+ void SetUserText(const std::wstring& text, const std::wstring& display_text,
+ bool update_popup);
+
+ // The inline autocomplete text is shown, selected, appended to the user's
+ // current input. For example, if the user types in "go", and the
+ // autocomplete system determines that the best match is "google", this
+ // routine might be called with |text| = "ogle".
+ // Returns true if the text in the edit changed.
+ bool SetInlineAutocompleteText(const std::wstring& text);
+
+ // The temporary text is set when the user arrows around the autocomplete
+ // popup. When present, this is shown in preference to the user text;
+ // hitting escape will revert to the user text.
+ // |previous_selected_match| is used to update the member variable
+ // |original_selected_match_| if there wasn't already any temporary text.
+ // Returns true if the text in the edit changed.
+ bool SetTemporaryText(
+ const std::wstring& text,
+ const AutocompleteResult::Selection& previous_selected_match);
+
+ // Called whenever user_text_ should change.
+ void InternalSetUserText(const std::wstring& text);
+
+ // Like GetSel(), but returns a range where |cpMin| will be larger than
+ // |cpMax| if the cursor is at the start rather than the end of the selection
+ // (in other words, tracks selection direction as well as offsets).
+ // Note the non-Google-style "non-const-ref" argument, which matches GetSel().
+ void GetSelection(CHARRANGE& sel) const;
+
+ // Returns the currently selected text of the edit control.
+ std::wstring GetSelectedText() const;
+
+ // Like SetSel(), but respects the selection direction implied by |start| and
+ // |end|: if |end| < |start|, the effective cursor will be placed at the
+ // beginning of the selection.
+ void SetSelection(LONG start, LONG end);
+
+ // Like SetSelection(), but takes a CHARRANGE.
+ void SetSelectionRange(const CHARRANGE& sel) {
+ SetSelection(sel.cpMin, sel.cpMax);
+ }
+
+ // Places the caret at the given position. This clears any selection.
+ void PlaceCaretAt(std::wstring::size_type pos);
+
+ // Returns true if |sel| represents a forward or backward selection of all the
+ // text.
+ bool IsSelectAll(const CHARRANGE& sel) const;
+
+ // Given an X coordinate in client coordinates, returns that coordinate
+ // clipped to be within the horizontal bounds of the visible text.
+ //
+ // This is used in our mouse handlers to work around quirky behaviors of the
+ // underlying CRichEditCtrl like not supporting triple-click when the user
+ // doesn't click on the text itself.
+ //
+ // |is_triple_click| should be true iff this is the third click of a triple
+ // click. Sadly, we need to clip slightly differently in this case.
+ LONG ClipXCoordToVisibleText(LONG x, bool is_triple_click) const;
+
+ // Parses the contents of the control for the scheme and the host name.
+ // Highlights the scheme in green or red depending on it security level.
+ // If a host name is found, it makes it visually stronger.
+ void EmphasizeURLComponents();
+
+ // Erases the portion of the selection in the font's y-adjustment area. For
+ // some reason the edit draws the selection rect here even though it's not
+ // part of the font.
+ void EraseTopOfSelection(CDC* dc,
+ const CRect& client_rect,
+ const CRect& paint_clip_rect);
+
+ // Draws a slash across the scheme if desired.
+ void DrawSlashForInsecureScheme(HDC hdc,
+ const CRect& client_rect,
+ const CRect& paint_clip_rect);
+
+ // Renders the drop highlight.
+ void DrawDropHighlight(HDC hdc,
+ const CRect& client_rect,
+ const CRect& paint_clip_rect);
+
+ // When the user is pressing the control key, we interpret this as requesting
+ // us to add the top-level domain "com" to the contents of the edit control.
+ // Some users expect this behavior because it is present in other browsers.
+ std::wstring GetDesiredTLD() const;
+
+ // Updates the autocomplete popup and other state after the text has been
+ // changed by the user.
+ void UpdatePopup();
+
+ // Internally invoked whenever the text changes in some way.
+ void TextChanged();
+
+ // Conversion between user text and display text. User text is the text the
+ // user has input. Display text is the text being shown in the edit. The
+ // two are different if a keyword is selected.
+ std::wstring DisplayTextFromUserText(const std::wstring& text) const;
+ std::wstring UserTextFromDisplayText(const std::wstring& text) const;
+
+ // Returns the current clipboard contents as a string that can be pasted in.
+ // In addition to just getting CF_UNICODETEXT out, this can also extract URLs
+ // from bookmarks on the clipboard.
+ std::wstring AutocompleteEdit::GetClipboardText() const;
+
+ // Determines whether the user can "paste and go", given the specified text.
+ // This also updates the internal paste-and-go-related state variables as
+ // appropriate so that the controller doesn't need to be repeatedly queried
+ // for the same text in every clipboard-related function.
+ bool CanPasteAndGo(const std::wstring& text) const;
+
+ // Navigates to the destination last supplied to CanPasteAndGo.
+ void PasteAndGo();
+
+ // Sets the state of user_input_in_progress_, and notifies the observer if
+ // that state has changed.
+ void SetInputInProgress(bool in_progress);
+
+ // As necessary, sends out notification that the user is accepting a URL in
+ // the edit. If the accepted URL is from selecting a keyword, |keyword| is
+ // the selected keyword.
+ // If |selected_line| is kNoMatch, the currently selected line is used for the
+ // metrics log record; otherwise, the provided value is used as the selected
+ // line. This is used when the user opens a URL without actually selecting
+ // its entry, such as middle-clicking it.
+ void SendOpenNotification(size_t selected_line, const std::wstring& keyword);
+
+ // Getter for the text_object_model_, used by the ScopedXXX classes. Note
+ // that the pointer returned here is only valid as long as the
+ // AutocompleteEdit is still alive.
+ ITextDocument* GetTextObjectModel() const;
+
+ // Invoked during a mouse move. As necessary starts a drag and drop session.
+ void StartDragIfNecessary(const CPoint& point);
+
+ // Invoked during a mouse down. If the mouse location is over the selection
+ // this sets possible_drag_ to true to indicate a drag should start if the
+ // user moves the mouse far enough to start a drag.
+ void OnPossibleDrag(const CPoint& point);
+
+ // Invoked when a mouse button is released. If none of the buttons are still
+ // down, this sets possible_drag_ to false.
+ void UpdateDragDone(UINT keys);
+
+ // Redraws the necessary region for a drop highlight at the specified
+ // position. This does nothing if position is beyond the bounds of the
+ // text.
+ void RepaintDropHighlight(int position);
+
+ Controller* controller_;
+
+ // The Popup itself.
+ scoped_ptr<AutocompletePopup> popup_;
+
+ // When true, the location bar view is read only and also is has a slightly
+ // different presentation (font size / color). This is used for popups.
+ bool popup_window_mode_;
+
+ // Whether the edit has focus.
+ bool has_focus_;
+
+ // Non-null when the edit is gaining focus from a left click. This is only
+ // needed between when WM_MOUSEACTIVATE and WM_LBUTTONDOWN get processed. It
+ // serves two purposes: first, by communicating to OnLButtonDown() that we're
+ // gaining focus from a left click, it allows us to work even with the
+ // inconsistent order in which various Windows messages get sent (see comments
+ // in OnMouseActivate()). Second, by holding the edit frozen, it ensures that
+ // when we process WM_SETFOCUS the edit won't first redraw itself with the
+ // caret at the beginning, and then have it blink to where the mouse cursor
+ // really is shortly afterward.
+ scoped_ptr<ScopedFreeze> gaining_focus_;
+
+ // The URL of the currently displayed page.
+ std::wstring permanent_text_;
+
+ // This flag is true when the user has modified the contents of the edit, but
+ // not yet accepted them. We use this to determine when we need to save
+ // state (on switching tabs) and whether changes to the page URL should be
+ // immediately displayed.
+ // This flag will be true in a superset of the cases where the popup is open.
+ bool user_input_in_progress_;
+
+ // The text that the user has entered. This does not include inline
+ // autocomplete text that has not yet been accepted.
+ std::wstring user_text_;
+
+ // When the user closes the popup, we need to remember the URL for their
+ // desired choice, so that if they hit enter without reopening the popup we
+ // know where to go. We could simply rerun autocomplete in this case, but
+ // we'd need to either wait for all results to come in (unacceptably slow) or
+ // do the wrong thing when the user had chosen some provider whose results
+ // were not returned instantaneously.
+ //
+ // This variable is only valid when user_input_in_progress_ is true, since
+ // when it is false the user has either never input anything (so there won't
+ // be a value here anyway) or has canceled their input, which should be
+ // treated the same way. Also, since this is for preserving a desired URL
+ // after the popup has been closed, we ignore this if the popup is open, and
+ // simply ask the popup for the desired URL directly. As a result, the
+ // contents of this variable only need to be updated when the popup is closed
+ // but user_input_in_progress_ is not being cleared.
+ std::wstring url_for_remembered_user_selection_;
+
+ // Inline autocomplete is allowed if the user has not just deleted text, and
+ // no temporary text is showing. In this case, inline_autocomplete_text_ is
+ // appended to the user_text_ and displayed selected (at least initially).
+ //
+ // NOTE: When the popup is closed there should never be inline autocomplete
+ // text (actions that close the popup should either accept the text, convert
+ // it to a normal selection, or change the edit entirely).
+ bool just_deleted_text_;
+ std::wstring inline_autocomplete_text_;
+
+ // Used by Set/RevertTemporaryText to keep track of whether there
+ // is currently a temporary text. The original_selection_ is only valid when
+ // there is temporary text.
+ //
+ // Example of use: If the user types "goog", then arrows down in the
+ // autocomplete popup until, say, "google.com" appears in the edit box, then
+ // the user_text_ is still "goog", and "google.com" is "temporary text".
+ // When the user hits <esc>, the edit box reverts to "goog". Hit <esc> again
+ // and the popup is closed and "goog" is replaced by the permanent_text_,
+ // which is the URL of the current page.
+ //
+ // original_url_ is valid in the same cases as original_selection_, and is
+ // used as the unique identifier of the originally selected item. Thus, if
+ // the user arrows to a different item with the same text, we can still
+ // distinguish them and not revert all the way to the permanent_text_.
+ //
+ // original_selected_match_, which is valid in the same cases as the other
+ // two original_* members, is the manually selected match to revert the popup
+ // to, if any. This can be non-empty when the user has selected a keyword
+ // (by hitting <tab> when applicable), or when the user has manually selected
+ // a match and then continued to edit it.
+ bool has_temporary_text_;
+ CHARRANGE original_selection_;
+ std::wstring original_url_;
+ AutocompleteResult::Selection original_selected_match_;
+
+ // When the user's last action was to paste and replace all the text, we
+ // disallow inline autocomplete (on the theory that the user is trying to
+ // paste in a new URL or part of one, and in either case inline autocomplete
+ // would get in the way).
+ PasteState paste_state_;
+
+ // When the user clicks to give us focus, we watch to see if they're clicking
+ // or dragging. When they're clicking, we select nothing until mouseup, then
+ // select all the text in the edit. During this process, tracking_click_ is
+ // true and mouse_down_point_ holds the original click location. At other
+ // times, tracking_click_ is false, and the contents of mouse_down_point_
+ // should be ignored.
+ bool tracking_click_;
+ CPoint mouse_down_point_;
+
+ // We need to know if the user triple-clicks, so track double click points
+ // and times so we can see if subsequent clicks are actually triple clicks.
+ bool tracking_double_click_;
+ CPoint double_click_point_;
+ DWORD double_click_time_;
+
+ // Used to discard unnecessary WM_MOUSEMOVE events after the first such
+ // unnecessary event. See detailed comments in OnMouseMove().
+ bool can_discard_mousemove_;
+
+ // Variables for tracking state before and after a possible change.
+ std::wstring text_before_change_;
+ CHARRANGE sel_before_change_;
+ bool select_all_before_change_;
+
+ // Holds the user's selection across focus changes. cpMin holds -1 when
+ // there is no saved selection.
+ CHARRANGE saved_selection_for_focus_change_;
+
+ // The context menu for the edit.
+ scoped_ptr<Menu> context_menu_;
+
+ // Whether the control key is depressed. We track this to avoid calling
+ // UpdatePopup() repeatedly if the user holds down the key, and to know
+ // whether to trigger "ctrl-enter" behavior.
+ ControlKeyState control_key_state_;
+
+ // The object that handles additional command functionality exposed on the
+ // edit, such as invoking the keyword editor.
+ CommandController* command_controller_;
+
+ // The parent view for the edit, used to align the popup and for
+ // accessibility.
+ ChromeViews::View* parent_view_;
+
+ // Font we're using. We keep a reference to make sure the font supplied to
+ // the constructor doesn't go away before we do.
+ ChromeFont font_;
+
+ // Metrics about the font, which we keep so we don't need to recalculate them
+ // every time we paint. |font_y_adjustment_| is the number of pixels we need
+ // to shift the font vertically in order to make its baseline be at our
+ // desired baseline in the edit.
+ int font_ascent_;
+ int font_descent_;
+ int font_x_height_;
+ int font_y_adjustment_;
+
+ // If true, indicates the mouse is down and if the mouse is moved enough we
+ // should start a drag.
+ bool possible_drag_;
+
+ // If true, we're in a call to DoDragDrop.
+ bool in_drag_;
+
+ // If true indicates we've run a drag and drop session. This is used to
+ // avoid starting two drag and drop sessions if the drag is canceled while
+ // the mouse is still down.
+ bool initiated_drag_;
+
+ // Position of the drop highlight. If this is -1, there is no drop highlight.
+ int drop_highlight_position_;
+
+ // The keyword associated with the current match. The user may have an actual
+ // selected keyword, or just some input text that looks like a keyword (so we
+ // can show a hint to press <tab>). This is the keyword in either case;
+ // is_keyword_hint_ (below) distinguishes the two cases.
+ std::wstring keyword_;
+
+ // True if the keyword associated with this match is merely a hint, i.e. the
+ // user hasn't actually selected a keyword yet. When this is true, we can use
+ // keyword_ to show a "Press <tab> to search" sort of hint.
+ bool is_keyword_hint_;
+
+ // In some cases, such as when the user is editing in the middle of the input
+ // string, the input might look like a keyword, but we don't want to display
+ // the keyword UI, so as not to interfere with the user's editing.
+ bool disable_keyword_ui_;
+
+ // True when it's safe to show a "Type to search" hint to the user (when the
+ // edit is empty, or the user is in the process of searching).
+ bool show_search_hint_;
+
+ // Security UI-related data.
+ COLORREF background_color_;
+ ToolbarModel::SecurityLevel scheme_security_level_;
+
+ // Paste And Go-related state. See CanPasteAndGo().
+ mutable std::wstring paste_and_go_url_;
+ mutable PageTransition::Type paste_and_go_transition_;
+ mutable std::wstring paste_and_go_alternate_nav_url_;
+
+ // This interface is useful for accessing the CRichEditCtrl at a low level.
+ mutable CComQIPtr<ITextDocument> text_object_model_;
+
+ ToolbarModel* model_;
+
+ Profile* profile_;
+
+ // This contains the scheme char start and stop indexes that should be
+ // striken-out when displaying an insecure scheme.
+ url_parse::Component insecure_scheme_component_;
+
+ // Instance of accessibility information and handling.
+ mutable CComPtr<IAccessible> autocomplete_accessibility_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(AutocompleteEdit);
+};
+
+#endif // CHROME_BROWSER_AUTOCOMPLETE_AUTOCOMPLETE_EDIT_H__
diff --git a/chrome/browser/autocomplete/autocomplete_popup.cc b/chrome/browser/autocomplete/autocomplete_popup.cc
new file mode 100644
index 0000000..278bc28
--- /dev/null
+++ b/chrome/browser/autocomplete/autocomplete_popup.cc
@@ -0,0 +1,1187 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "chrome/browser/autocomplete/autocomplete_popup.h"
+
+#include <cmath>
+
+#include "base/scoped_ptr.h"
+#include "base/string_util.h"
+#include "chrome/app/theme/theme_resources.h"
+#include "chrome/browser/autocomplete/autocomplete_edit.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/net/dns_global.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/template_url.h"
+#include "chrome/browser/template_url_model.h"
+#include "chrome/browser/views/location_bar_view.h"
+#include "chrome/common/gfx/chrome_canvas.h"
+#include "chrome/common/resource_bundle.h"
+#include "third_party/icu38/public/common/unicode/ubidi.h"
+
+namespace {
+// The amount of time we'll wait after a provider returns before updating,
+// in order to coalesce results.
+const int kPopupCoalesceMs = 100;
+
+// The maximum time we'll allow the popup to go without updating.
+const int kPopupUpdateMaxDelayMs = 300;
+
+// Padding between text and the star indicator, in pixels.
+const int kStarPadding = 4;
+
+};
+
+// A simple wrapper class for the bidirectional iterator of ICU.
+// This class uses the bidirctional iterator of ICU to split a line of
+// bidirectional texts into visual runs in its display order.
+class BiDiLineIterator {
+ public:
+ BiDiLineIterator();
+ ~BiDiLineIterator();
+
+ // Initialize the bidirectional iterator with the specified text.
+ // Parameters
+ // * text [in] (const std::wstring&)
+ // Represents the text to be iterated with this iterator.
+ // * right_to_left [in] (bool)
+ // Represents whether or not the default text direction is right-to-left.
+ // Possible parameters are listed below:
+ // - true, the default text direction is right-to-left.
+ // - false, the default text direction is left-to-right.
+ // * url [in] (bool)
+ // Represents whether or not this text is a URL.
+ // Return values
+ // * true
+ // The bidirectional iterator is created successfully.
+ // * false
+ // An error occured while creating the bidirectional iterator.
+ UBool Open(const std::wstring& text, bool right_to_left, bool url);
+
+ // Retrieve the number of visual runs in the text.
+ // Return values
+ // * A positive value
+ // Represents the number of visual runs.
+ // * 0
+ // Represents an error.
+ int CountRuns();
+
+ // Get the logical offset, length, and direction of the specified visual run.
+ // Parameters
+ // * index [in] (int)
+ // Represents the index of the visual run. This value must be less than
+ // the return value of the CountRuns() function.
+ // * start [out] (int*)
+ // Represents the index of the specified visual run, in characters.
+ // * length [out] (int*)
+ // Represents the length of the specified visual run, in characters.
+ // Return values
+ // * UBIDI_LTR
+ // Represents this run should be rendered in the left-to-right reading
+ // order.
+ // * UBIDI_RTL
+ // Represents this run should be rendered in the right-to-left reading
+ // order.
+ UBiDiDirection GetVisualRun(int index, int* start, int* length);
+
+ private:
+ UBiDi* bidi_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(BiDiLineIterator);
+};
+
+BiDiLineIterator::BiDiLineIterator()
+ : bidi_(NULL) {
+}
+
+BiDiLineIterator::~BiDiLineIterator() {
+ if (bidi_) {
+ ubidi_close(bidi_);
+ bidi_ = NULL;
+ }
+}
+
+UBool BiDiLineIterator::Open(const std::wstring& text,
+ bool right_to_left,
+ bool url) {
+ DCHECK(bidi_ == NULL);
+ UErrorCode error = U_ZERO_ERROR;
+ bidi_ = ubidi_openSized(static_cast<int>(text.length()), 0, &error);
+ if (U_FAILURE(error))
+ return false;
+ if (right_to_left && url)
+ ubidi_setReorderingMode(bidi_, UBIDI_REORDER_RUNS_ONLY);
+ ubidi_setPara(bidi_, text.data(), static_cast<int>(text.length()),
+ right_to_left ? UBIDI_DEFAULT_RTL : UBIDI_DEFAULT_LTR,
+ NULL, &error);
+ return U_SUCCESS(error);
+}
+
+int BiDiLineIterator::CountRuns() {
+ DCHECK(bidi_ != NULL);
+ UErrorCode error = U_ZERO_ERROR;
+ const int runs = ubidi_countRuns(bidi_, &error);
+ return U_SUCCESS(error) ? runs : 0;
+}
+
+UBiDiDirection BiDiLineIterator::GetVisualRun(int index,
+ int* start,
+ int* length) {
+ DCHECK(bidi_ != NULL);
+ return ubidi_getVisualRun(bidi_, index, start, length);
+}
+
+// This class implements a utility used for mirroring x-coordinates when the
+// application language is a right-to-left one.
+class MirroringContext {
+ public:
+ MirroringContext();
+ ~MirroringContext();
+
+ // Initializes the bounding region used for mirroring coordinates.
+ // This class uses the center of this region as an axis for calculating
+ // mirrored coordinates.
+ void Initialize(int min_x, int max_x, bool enabled);
+
+ // Return the "left" side of the specified region.
+ // When the application language is a right-to-left one, this function
+ // calculates the mirrored coordinates of the input region and returns the
+ // left side of the mirrored region.
+ // The input region must be in the bounding region specified in the
+ // Initialize() function.
+ int GetLeft(int min_x, int max_x) const;
+
+ // Returns whether or not we are mirroring the x coordinate.
+ bool enabled() const {
+ return enabled_;
+ }
+
+ private:
+ int min_x_;
+ int center_x_;
+ int max_x_;
+ bool enabled_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(MirroringContext);
+};
+
+MirroringContext::MirroringContext()
+ : min_x_(0),
+ center_x_(0),
+ max_x_(0),
+ enabled_(false) {
+}
+
+MirroringContext::~MirroringContext() {
+}
+
+void MirroringContext::Initialize(int min_x, int max_x, bool enabled) {
+ min_x_ = min_x;
+ max_x_ = max_x;
+ if (min_x_ > max_x_)
+ std::swap(min_x_, max_x_);
+ center_x_ = min_x + (max_x - min_x) / 2;
+ enabled_ = enabled;
+}
+
+int MirroringContext::GetLeft(int min_x, int max_x) const {
+ if (!enabled_)
+ return min_x;
+ int mirrored_min_x = center_x_ + (center_x_ - min_x);
+ int mirrored_max_x = center_x_ + (center_x_ - max_x);
+ return std::min(mirrored_min_x, mirrored_max_x);
+}
+
+const wchar_t AutocompletePopup::DrawLineInfo::ellipsis_str[] = L"\x2026";
+
+AutocompletePopup::AutocompletePopup(const ChromeFont& font,
+ AutocompleteEdit* editor,
+ Profile* profile)
+ : editor_(editor),
+ controller_(new AutocompleteController(this, profile)),
+ profile_(profile),
+ line_info_(font),
+ query_in_progress_(false),
+ update_pending_(false),
+ // Creating the Timers directly instead of using StartTimer() ensures
+ // they won't actually start running until we use ResetTimer().
+ coalesce_timer_(new Timer(kPopupCoalesceMs, this, false)),
+ max_delay_timer_(new Timer(kPopupUpdateMaxDelayMs, this, true)),
+ hovered_line_(kNoMatch),
+ selected_line_(kNoMatch),
+ mirroring_context_(new MirroringContext()),
+ star_(ResourceBundle::GetSharedInstance().GetBitmapNamed(IDR_CONTENT_STAR_ON)) {
+}
+
+AutocompletePopup::~AutocompletePopup() {
+ StopAutocomplete();
+}
+
+void AutocompletePopup::SetProfile(Profile* profile) {
+ DCHECK(profile);
+ profile_ = profile;
+ controller_->SetProfile(profile);
+}
+
+void AutocompletePopup::StartAutocomplete(
+ const std::wstring& text,
+ const std::wstring& desired_tld,
+ bool prevent_inline_autocomplete) {
+ // The user is interacting with the edit, so stop tracking hover.
+ SetHoveredLine(kNoMatch);
+
+ // See if we can avoid rerunning autocomplete when the query hasn't changed
+ // much. If the popup isn't open, we threw the past results away, so no
+ // shortcuts are possible.
+ const AutocompleteInput input(text, desired_tld, prevent_inline_autocomplete);
+ bool minimal_changes = false;
+ if (is_open()) {
+ // When the user hits escape with a temporary selection, the edit asks us
+ // to update, but the text it supplies hasn't changed since the last query.
+ // Instead of stopping or rerunning the last query, just do an immediate
+ // repaint with the new (probably NULL) provider affinity.
+ if (input_.Equals(input)) {
+ SetDefaultMatchAndUpdate(true);
+ return;
+ }
+
+ // When the user presses or releases the ctrl key, the desired_tld changes,
+ // and when the user finishes an IME composition, inline autocomplete may
+ // no longer be prevented. In both these cases the text itself hasn't
+ // changed since the last query, and some providers can do much less work
+ // (and get results back more quickly). Taking advantage of this reduces
+ // flicker.
+ if (input_.text() == text)
+ minimal_changes = true;
+ }
+ input_ = input;
+
+ // If we're starting a brand new query, stop caring about any old query.
+ TimerManager* const tm = MessageLoop::current()->timer_manager();
+ if (!minimal_changes && query_in_progress_) {
+ update_pending_ = false;
+ tm->StopTimer(coalesce_timer_.get());
+ }
+
+ // Start the new query.
+ query_in_progress_ = !controller_->Start(input_, minimal_changes, false);
+ controller_->GetResult(&latest_result_);
+
+ // If we're not ready to show results and the max update interval timer isn't
+ // already running, start it now.
+ if (query_in_progress_ && !tm->IsTimerRunning(max_delay_timer_.get()))
+ tm->ResetTimer(max_delay_timer_.get());
+
+ SetDefaultMatchAndUpdate(!query_in_progress_);
+}
+
+void AutocompletePopup::StopAutocomplete() {
+ // Close any old query.
+ if (query_in_progress_) {
+ controller_->Stop();
+ query_in_progress_ = false;
+ update_pending_ = false;
+ }
+
+ // Reset results. This will force the popup to close.
+ latest_result_.Reset();
+ CommitLatestResults(true);
+
+ // Clear input_ to make sure we don't try and use any of these results for
+ // the next query we receive. Strictly speaking this isn't necessary, since
+ // the popup isn't open, but it keeps our internal state consistent and
+ // serves as future-proofing in case the code in StartAutocomplete() changes.
+ input_.Clear();
+}
+
+std::wstring AutocompletePopup::URLsForCurrentSelection(
+ PageTransition::Type* transition,
+ bool* is_history_what_you_typed_match,
+ std::wstring* alternate_nav_url) const {
+ // The popup may be out of date, and we always want the latest match. The
+ // most common case of this is when the popup is open, the user changes the
+ // contents of the edit, and then presses enter before any results have been
+ // displayed, but still wants to choose the would-be-default action.
+ //
+ // Can't call CommitLatestResults(), because
+ // latest_result_.default_match_index() may not match selected_line_, and
+ // we want to preserve the user's selection.
+ if (latest_result_.empty())
+ return std::wstring();
+
+ const AutocompleteResult* result;
+ AutocompleteResult::const_iterator match;
+ if (update_pending_) {
+ // The default match on the latest result should be up-to-date. If the user
+ // changed the selection since that result was generated using the arrow
+ // keys, Move() will have force updated the popup.
+ result = &latest_result_;
+ match = result->default_match();
+ } else {
+ result = &result_;
+ DCHECK(selected_line_ < result_.size());
+ match = result->begin() + selected_line_;
+ }
+ if (transition)
+ *transition = match->transition;
+ if (is_history_what_you_typed_match)
+ *is_history_what_you_typed_match = match->is_history_what_you_typed_match;
+ if (alternate_nav_url && manually_selected_match_.empty())
+ *alternate_nav_url = result->GetAlternateNavURL(input_, match);
+ return match->destination_url;
+}
+
+std::wstring AutocompletePopup::URLsForDefaultMatch(
+ const std::wstring& text,
+ const std::wstring& desired_tld,
+ PageTransition::Type* transition,
+ bool* is_history_what_you_typed_match,
+ std::wstring* alternate_nav_url) {
+ // Cancel any existing query.
+ StopAutocomplete();
+
+ // Run the new query and get only the synchronously available results.
+ const AutocompleteInput input(text, desired_tld, true);
+ const bool done = controller_->Start(input, false, true);
+ DCHECK(done);
+ controller_->GetResult(&result_);
+ if (result_.empty())
+ return std::wstring();
+
+ // Get the URLs for the default match.
+ result_.SetDefaultMatch(manually_selected_match_);
+ const AutocompleteResult::const_iterator match = result_.default_match();
+ const std::wstring url(match->destination_url); // Need to copy since we
+ // reset result_ below.
+ if (transition)
+ *transition = match->transition;
+ if (is_history_what_you_typed_match)
+ *is_history_what_you_typed_match = match->is_history_what_you_typed_match;
+ if (alternate_nav_url && manually_selected_match_.empty())
+ *alternate_nav_url = result_.GetAlternateNavURL(input, match);
+ result_.Reset();
+
+ return url;
+}
+
+AutocompleteLog* AutocompletePopup::GetAutocompleteLog() {
+ return new AutocompleteLog(input_.text(), selected_line_, 0, result_);
+}
+
+void AutocompletePopup::Move(int count) {
+ // The user is using the keyboard to change the selection, so stop tracking
+ // hover.
+ SetHoveredLine(kNoMatch);
+
+ // Force the popup to open/update, so the user is interacting with the
+ // latest results.
+ CommitLatestResults(false);
+ if (result_.empty())
+ return;
+
+ // Clamp the new line to [0, result_.count() - 1].
+ const size_t new_line = selected_line_ + count;
+ SetSelectedLine(((count < 0) && (new_line >= selected_line_)) ?
+ 0 : std::min(new_line, result_.size() - 1));
+}
+
+void AutocompletePopup::TryDeletingCurrentItem() {
+ // We could use URLsForCurrentSelection() here, but it seems better to try
+ // and shift-delete the actual selection, rather than any "in progress, not
+ // yet visible" one.
+ if (selected_line_ == kNoMatch)
+ return;
+ const AutocompleteMatch& match = result_.match_at(selected_line_);
+ if (match.deletable) {
+ query_in_progress_ = true;
+ size_t selected_line = selected_line_;
+ match.provider->DeleteMatch(match); // This will synchronously call back
+ // to OnAutocompleteUpdate()
+ CommitLatestResults(false);
+ if (!result_.empty()) {
+ // Move the selection to the next choice after the deleted one, but clear
+ // the manual selection so this choice doesn't act "sticky".
+ //
+ // It might also be correct to only call Clear() here when
+ // manually_selected_match_ didn't already have a provider() (i.e. when
+ // there was no existing manual selection). It's not clear what the user
+ // wants when they shift-delete something they've arrowed to. If they
+ // arrowed down just to shift-delete it, we should probably Clear(); if
+ // they arrowed to do something else, then got a bad match while typing,
+ // we probably shouldn't.
+ SetSelectedLine(std::min(result_.size() - 1, selected_line));
+ manually_selected_match_.Clear();
+ }
+ }
+}
+
+void AutocompletePopup::OnAutocompleteUpdate(bool updated_result,
+ bool query_complete) {
+ DCHECK(query_in_progress_);
+ if (updated_result)
+ controller_->GetResult(&latest_result_);
+ query_in_progress_ = !query_complete;
+
+ SetDefaultMatchAndUpdate(query_complete);
+}
+
+void AutocompletePopup::Run() {
+ CommitLatestResults(false);
+}
+
+void AutocompletePopup::OnLButtonDown(UINT keys, const CPoint& point) {
+ const size_t new_hovered_line = PixelToLine(point.y);
+ SetHoveredLine(new_hovered_line);
+ SetSelectedLine(new_hovered_line);
+}
+
+void AutocompletePopup::OnMButtonDown(UINT keys, const CPoint& point) {
+ SetHoveredLine(PixelToLine(point.y));
+}
+
+void AutocompletePopup::OnLButtonUp(UINT keys, const CPoint& point) {
+ OnButtonUp(point, CURRENT_TAB);
+}
+
+void AutocompletePopup::OnMButtonUp(UINT keys, const CPoint& point) {
+ OnButtonUp(point, NEW_BACKGROUND_TAB);
+}
+
+LRESULT AutocompletePopup::OnMouseActivate(HWND window,
+ UINT hit_test,
+ UINT mouse_message) {
+ return MA_NOACTIVATE;
+}
+
+void AutocompletePopup::OnMouseLeave() {
+ // The mouse has left the window, so no line is hovered.
+ SetHoveredLine(kNoMatch);
+}
+
+void AutocompletePopup::OnMouseMove(UINT keys, const CPoint& point) {
+ // Track hover when
+ // (a) The left or middle button is down (the user is interacting via the
+ // mouse)
+ // (b) The user moves the mouse from where we last stopped tracking hover
+ // (c) We started tracking previously due to (a) or (b) and haven't stopped
+ // yet (user hasn't used the keyboard to interact again)
+ const bool action_button_pressed = !!(keys & (MK_MBUTTON | MK_LBUTTON));
+ CPoint screen_point(point);
+ ClientToScreen(&screen_point);
+ if (action_button_pressed || (last_hover_coordinates_ != screen_point) ||
+ (hovered_line_ != kNoMatch)) {
+ // Determine the hovered line from the y coordinate of the event. We don't
+ // need to check whether the x coordinates are within the window since if
+ // they weren't someone else would have received the WM_MOUSEMOVE.
+ const size_t new_hovered_line = PixelToLine(point.y);
+ SetHoveredLine(new_hovered_line);
+
+ // When the user has the left button down, update their selection
+ // immediately (don't wait for mouseup).
+ if (keys & MK_LBUTTON)
+ SetSelectedLine(new_hovered_line);
+ }
+}
+
+void AutocompletePopup::OnPaint(HDC other_dc) {
+ DCHECK(!result_.empty()); // Shouldn't be drawing an empty popup
+
+ CPaintDC dc(m_hWnd);
+
+ RECT rc;
+ GetClientRect(&rc);
+ mirroring_context_->Initialize(rc.left, rc.right,
+ l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT);
+ DrawBorder(rc, dc);
+
+ bool all_descriptions_empty = true;
+ for (AutocompleteResult::const_iterator i(result_.begin());
+ i != result_.end(); ++i) {
+ if (!i->description.empty()) {
+ all_descriptions_empty = false;
+ break;
+ }
+ }
+
+ // Only repaint the invalid lines.
+ const size_t first_line = PixelToLine(dc.m_ps.rcPaint.top);
+ const size_t last_line = PixelToLine(dc.m_ps.rcPaint.bottom);
+ for (size_t i = first_line; i <= last_line; ++i) {
+ DrawLineInfo::LineStatus status;
+ // Selection should take precedence over hover.
+ if (i == selected_line_)
+ status = DrawLineInfo::SELECTED;
+ else if (i == hovered_line_)
+ status = DrawLineInfo::HOVERED;
+ else
+ status = DrawLineInfo::NORMAL;
+ DrawEntry(dc, rc, i, status, all_descriptions_empty,
+ result_.match_at(i).starred);
+ }
+}
+
+void AutocompletePopup::OnButtonUp(const CPoint& point,
+ WindowOpenDisposition disposition) {
+ const size_t selected_line = PixelToLine(point.y);
+ const AutocompleteMatch& match = result_.match_at(selected_line);
+ // OpenURL() may close the popup, which will clear the result set and, by
+ // extension, |match| and its contents. So copy the relevant strings out to
+ // make sure they stay alive until the call completes.
+ const std::wstring url(match.destination_url);
+ std::wstring keyword;
+ const bool is_keyword_hint = GetKeywordForMatch(match, &keyword);
+ editor_->OpenURL(url, disposition, match.transition, std::wstring(),
+ selected_line, is_keyword_hint ? std::wstring() : keyword);
+}
+
+void AutocompletePopup::SetDefaultMatchAndUpdate(bool immediately) {
+ if (!latest_result_.SetDefaultMatch(manually_selected_match_) &&
+ !query_in_progress_) {
+ // We don't clear the provider affinity because the user didn't do
+ // something to indicate that they want a different provider, we just
+ // couldn't find the specific match they wanted.
+ manually_selected_match_.destination_url.clear();
+ manually_selected_match_.is_history_what_you_typed_match = false;
+ }
+
+ if (immediately) {
+ CommitLatestResults(true);
+ } else if (!update_pending_) {
+ // Coalesce the results for the next kPopupCoalesceMs milliseconds.
+ update_pending_ = true;
+ MessageLoop::current()->timer_manager()->ResetTimer(coalesce_timer_.get());
+ }
+
+ // Update the edit with the possibly new data for this match.
+ // NOTE: This must be done after the code above, so that our internal state
+ // will be consistent when the edit calls back to URLsForCurrentSelection().
+ std::wstring inline_autocomplete_text;
+ std::wstring keyword;
+ bool is_keyword_hint = false;
+ bool can_show_search_hint = true;
+ const AutocompleteResult::const_iterator match(
+ latest_result_.default_match());
+ if (match != latest_result_.end()) {
+ if ((match->inline_autocomplete_offset != std::wstring::npos) &&
+ (match->inline_autocomplete_offset < match->fill_into_edit.length())) {
+ inline_autocomplete_text =
+ match->fill_into_edit.substr(match->inline_autocomplete_offset);
+ }
+ // Warm up DNS Prefetch Cache.
+ chrome_browser_net::DnsPrefetchUrlString(match->destination_url);
+ // We could prefetch the alternate nav URL, if any, but because there can be
+ // many of these as a user types an initial series of characters, the OS DNS
+ // cache could suffer eviction problems for minimal gain.
+
+ is_keyword_hint = GetKeywordForMatch(*match, &keyword);
+ can_show_search_hint = (match->type == AutocompleteMatch::SEARCH);
+ }
+ editor_->OnPopupDataChanged(inline_autocomplete_text, false,
+ manually_selected_match_ /* ignored */, keyword, is_keyword_hint,
+ can_show_search_hint);
+}
+
+void AutocompletePopup::CommitLatestResults(bool force) {
+ if (!force && !update_pending_)
+ return;
+
+ update_pending_ = false;
+
+ // If we're going to trim the window size to no longer include the hovered
+ // line, turn hover off first. We need to do this before changing result_
+ // since SetHover() should be able to trust that the old and new hovered
+ // lines are valid.
+ //
+ // Practically, this only currently happens when we're closing the window by
+ // setting latest_result_ to an empty list.
+ if ((hovered_line_ != kNoMatch) && (latest_result_.size() <= hovered_line_))
+ SetHoveredLine(kNoMatch);
+
+ result_.CopyFrom(latest_result_);
+ selected_line_ = (result_.default_match() == result_.end()) ?
+ kNoMatch : (result_.default_match() - result_.begin());
+
+ UpdatePopupAppearance();
+
+ // The max update interval timer either needs to be reset (if more updates
+ // are to come) or stopped (when we're done with the query). The coalesce
+ // timer should always just be stopped.
+ TimerManager* const tm = MessageLoop::current()->timer_manager();
+ tm->StopTimer(coalesce_timer_.get());
+ if (query_in_progress_)
+ tm->ResetTimer(max_delay_timer_.get());
+ else
+ tm->StopTimer(max_delay_timer_.get());
+}
+
+void AutocompletePopup::UpdatePopupAppearance() {
+ if (result_.empty()) {
+ // No results, close any existing popup.
+ if (m_hWnd) {
+ DestroyWindow();
+ m_hWnd = NULL;
+ }
+ return;
+ }
+
+ // Figure the coordinates of the popup:
+ // Get the coordinates of the location bar view; these are returned relative
+ // to its parent.
+ CRect rc;
+ editor_->parent_view()->GetBounds(&rc);
+ // Subtract the top left corner to make the coordinates relative to the
+ // location bar view itself, and convert to screen coordinates.
+ CPoint top_left(-rc.TopLeft());
+ ChromeViews::View::ConvertPointToScreen(editor_->parent_view(), &top_left);
+ rc.OffsetRect(top_left);
+ // Expand by one pixel on each side since that's the amount the location bar
+ // view is inset from the divider line that edges the adjacent buttons.
+ // Deflate the top and bottom by the height of the extra graphics around the
+ // edit.
+ // TODO(pkasting): http://b/972786 This shouldn't be hardcoded to rely on
+ // LocationBarView constants. Instead we should just make the edit be "at the
+ // right coordinates", or something else generic.
+ rc.InflateRect(1, -LocationBarView::kTextVertMargin);
+ // Now rc is the exact width we want and is positioned like the edit would
+ // be, so shift the top and bottom downwards so the new top is where the old
+ // bottom is and the rect has the height we need for all our entries, plus a
+ // one-pixel border on top and bottom.
+ rc.top = rc.bottom;
+ rc.bottom += static_cast<int>(result_.size()) * line_info_.line_height + 2;
+
+ if (!m_hWnd) {
+ // To prevent this window from being activated, we create an invisible
+ // window and manually show it without activating it.
+ Create(editor_->m_hWnd, rc, AUTOCOMPLETEPOPUP_CLASSNAME, WS_POPUP,
+ WS_EX_TOOLWINDOW);
+ // When an IME is attached to the rich-edit control, retrieve its window
+ // handle and show this popup window under the IME windows.
+ // Otherwise, show this popup window under top-most windows.
+ // TODO(hbono): http://b/1111369 if we exclude this popup window from the
+ // display area of IME windows, this workaround becomes unnecessary.
+ HWND ime_window = ImmGetDefaultIMEWnd(editor_->m_hWnd);
+ SetWindowPos(ime_window ? ime_window : HWND_NOTOPMOST, 0, 0, 0, 0,
+ SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_SHOWWINDOW);
+ } else {
+ // Already open, just resize the window. This is a bit tricky; we want to
+ // repaint the whole window, since the contents may have changed, but
+ // MoveWindow() won't repaint portions that haven't moved or been
+ // added/removed. So we first call InvalidateRect(), so the next repaint
+ // paints the whole window, then tell MoveWindow() to do the actual
+ // repaint, which will also properly repaint Windows formerly under the
+ // popup.
+ InvalidateRect(NULL, false);
+ MoveWindow(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, true);
+ }
+
+ // TODO(pkasting): http://b/1111369 We should call ImmSetCandidateWindow() on
+ // the editor_'s IME context here, and exclude ourselves from its display
+ // area. Not clear what to pass for the lpCandidate->ptCurrentPos member,
+ // though...
+}
+
+int AutocompletePopup::LineTopPixel(size_t line) const {
+ DCHECK(line <= result_.size());
+ // The popup has a 1 px top border.
+ return line_info_.line_height * static_cast<int>(line) + 1;
+}
+
+size_t AutocompletePopup::PixelToLine(int y) const {
+ const size_t line = std::max(y - 1, 0) / line_info_.line_height;
+ return (line < result_.size()) ? line : (result_.size() - 1);
+}
+
+void AutocompletePopup::SetHoveredLine(size_t line) {
+ const bool is_disabling = (line == kNoMatch);
+ DCHECK(is_disabling || (line < result_.size()));
+
+ if (line == hovered_line_)
+ return; // Nothing to do
+
+ const bool is_enabling = (hovered_line_ == kNoMatch);
+ if (is_enabling || is_disabling) {
+ TRACKMOUSEEVENT tme;
+ tme.cbSize = sizeof(TRACKMOUSEEVENT);
+ if (is_disabling) {
+ // Save the current mouse position to check against for re-enabling.
+ GetCursorPos(&last_hover_coordinates_); // Returns screen coordinates
+
+ // Cancel existing registration for WM_MOUSELEAVE notifications.
+ tme.dwFlags = TME_CANCEL | TME_LEAVE;
+ } else {
+ // Register for WM_MOUSELEAVE notifications.
+ tme.dwFlags = TME_LEAVE;
+ }
+ tme.hwndTrack = m_hWnd;
+ tme.dwHoverTime = HOVER_DEFAULT; // Not actually used
+ TrackMouseEvent(&tme);
+ }
+
+ // Make sure the old hovered line is redrawn. No need to redraw the selected
+ // line since selection overrides hover so the appearance won't change.
+ if (!is_enabling && (hovered_line_ != selected_line_))
+ InvalidateLine(hovered_line_);
+
+ // Change the hover to the new line and make sure it's redrawn.
+ hovered_line_ = line;
+ if (!is_disabling && (hovered_line_ != selected_line_))
+ InvalidateLine(hovered_line_);
+}
+
+void AutocompletePopup::SetSelectedLine(size_t line) {
+ DCHECK(line < result_.size());
+ if (result_.empty())
+ return;
+
+ if (line == selected_line_)
+ return; // Nothing to do
+
+ // Update the edit with the new data for this match.
+ const AutocompleteMatch& match = result_.match_at(line);
+ std::wstring keyword;
+ const bool is_keyword_hint = GetKeywordForMatch(match, &keyword);
+ editor_->OnPopupDataChanged(match.fill_into_edit, true,
+ manually_selected_match_, keyword,
+ is_keyword_hint,
+ (match.type == AutocompleteMatch::SEARCH));
+
+ // Track the user's selection until they cancel it.
+ manually_selected_match_.destination_url = match.destination_url;
+ manually_selected_match_.provider_affinity = match.provider;
+ manually_selected_match_.is_history_what_you_typed_match =
+ match.is_history_what_you_typed_match;
+
+ // Repaint old and new selected lines immediately, so that the edit doesn't
+ // appear to update [much] faster than the popup. We must not update
+ // |selected_line_| before calling OnPopupDataChanged() (since the edit may
+ // call us back to get data about the old selection), and we must not call
+ // UpdateWindow() before updating |selected_line_| (since the paint routine
+ // relies on knowing the correct selected line).
+ InvalidateLine(selected_line_);
+ selected_line_ = line;
+ InvalidateLine(selected_line_);
+ UpdateWindow();
+}
+
+void AutocompletePopup::InvalidateLine(size_t line) {
+ DCHECK(line < result_.size());
+
+ RECT rc;
+ GetClientRect(&rc);
+ rc.top = LineTopPixel(line);
+ rc.bottom = rc.top + line_info_.line_height;
+ InvalidateRect(&rc, false);
+}
+
+// Draws a light border around the inside of the window with the given client
+// rectangle and DC.
+void AutocompletePopup::DrawBorder(const RECT& rc, HDC dc) {
+ HPEN hpen = CreatePen(PS_SOLID, 1, RGB(199, 202, 206));
+ HGDIOBJ old_pen = SelectObject(dc, hpen);
+
+ int width = rc.right - rc.left - 1;
+ int height = rc.bottom - rc.top - 1;
+
+ MoveToEx(dc, 0, 0, NULL);
+ LineTo(dc, 0, height);
+ LineTo(dc, width, height);
+ LineTo(dc, width, 0);
+ LineTo(dc, 0, 0);
+
+ SelectObject(dc, old_pen);
+ DeleteObject(hpen);
+}
+
+int AutocompletePopup::DrawString(HDC dc,
+ int x,
+ int y,
+ int max_x,
+ const wchar_t* text,
+ int length,
+ int style,
+ const DrawLineInfo::LineStatus status,
+ const MirroringContext* context,
+ bool text_direction_is_rtl) const {
+ if (length <= 0)
+ return 0;
+
+ // Set up the text decorations.
+ SelectObject(dc, (style & ACMatchClassification::MATCH) ?
+ line_info_.bold_font.hfont() : line_info_.regular_font.hfont());
+ const COLORREF foreground = (style & ACMatchClassification::URL) ?
+ line_info_.url_colors[status] : line_info_.text_colors[status];
+ const COLORREF background = line_info_.background_colors[status];
+ SetTextColor(dc, (style & ACMatchClassification::DIM) ?
+ DrawLineInfo::AlphaBlend(foreground, background, 0xAA) : foreground);
+
+ // Retrieve the width of the decorated text and display it. When we cannot
+ // display this fragment in the given width, we trim the fragment and add an
+ // ellipsis.
+ //
+ // TODO(hbono): http:///b/1222425 We should change the following eliding code
+ // with more aggressive one.
+ int text_x = x;
+ int max_length = 0;
+ SIZE text_size = {0};
+ GetTextExtentExPoint(dc, text, length,
+ max_x - line_info_.ellipsis_width - text_x, &max_length,
+ NULL, &text_size);
+
+ if (max_length < length)
+ GetTextExtentPoint32(dc, text, max_length, &text_size);
+
+ const int mirrored_x = context->GetLeft(text_x, text_x + text_size.cx);
+ RECT text_bounds = {mirrored_x,
+ 0,
+ mirrored_x + text_size.cx,
+ line_info_.line_height};
+
+ int flags = DT_SINGLELINE | DT_NOPREFIX;
+ if (text_direction_is_rtl)
+ // In order to make sure RTL text is displayed correctly (for example, a
+ // trailing space should be displayed on the left and not on the right), we
+ // pass the flag DT_RTLREADING.
+ flags |= DT_RTLREADING;
+
+ DrawText(dc, text, length, &text_bounds, flags);
+ text_x += text_size.cx;
+
+ // Draw the ellipsis. Note that since we use the mirroring context, the
+ // ellipsis are drawn either to the right or to the left of the text.
+ if (max_length < length) {
+ TextOut(dc, context->GetLeft(text_x, text_x + line_info_.ellipsis_width),
+ 0, line_info_.ellipsis_str, arraysize(line_info_.ellipsis_str) - 1);
+ text_x += line_info_.ellipsis_width;
+ }
+
+ return text_x - x;
+}
+
+void AutocompletePopup::DrawMatchFragments(
+ HDC dc,
+ const std::wstring& text,
+ const ACMatchClassifications& classifications,
+ int x,
+ int y,
+ int max_x,
+ DrawLineInfo::LineStatus status) const {
+ if (!text.length())
+ return;
+
+ // Check whether or not this text is a URL string.
+ // A URL string is basically in English with possible included words in
+ // Arabic or Hebrew. For such case, ICU provides a special algorithm and we
+ // should use it.
+ bool url = false;
+ for (ACMatchClassifications::const_iterator i = classifications.begin();
+ i != classifications.end(); ++i) {
+ if (i->style & ACMatchClassification::URL)
+ url = true;
+ }
+
+ // Initialize a bidirectional line iterator of ICU and split the text into
+ // visual runs. (A visual run is consecutive characters which have the same
+ // display direction and should be displayed at once.)
+ BiDiLineIterator bidi_line;
+ if (!bidi_line.Open(text, mirroring_context_->enabled(), url))
+ return;
+ const int runs = bidi_line.CountRuns();
+
+ // Draw the visual runs.
+ // This loop splits each run into text fragments with the given
+ // classifications and draws the text fragments.
+ // When the direction of a run is right-to-left, we have to mirror the
+ // x-coordinate of this run and render the fragments in the right-to-left
+ // reading order. To handle this display order independently from the one of
+ // this popup window, this loop renders a run with the steps below:
+ // 1. Create a local display context for each run;
+ // 2. Render the run into the local display context, and;
+ // 3. Copy the local display context to the one of the popup window.
+ int run_x = x;
+ for (int run = 0; run < runs; ++run) {
+ int run_start = 0;
+ int run_length = 0;
+
+ // The index we pass to GetVisualRun corresponds to the position of the run
+ // in the displayed text. For example, the string "Google in HEBREW" (where
+ // HEBREW is text in the Hebrew language) has two runs: "Google in " which
+ // is an LTR run, and "HEBREW" which is an RTL run. In an LTR context, the
+ // run "Google in " has the index 0 (since it is the leftmost run
+ // displayed). In an RTL context, the same run has the index 1 because it
+ // is the rightmost run. This is why the order in which we traverse the
+ // runs is different depending on the locale direction.
+ //
+ // Note that for URLs we always traverse the runs from lower to higher
+ // indexes because the return order of runs for a URL always matches the
+ // physical order of the context.
+ int current_run =
+ (mirroring_context_->enabled() && !url) ? (runs - run - 1) : run;
+ const UBiDiDirection run_direction = bidi_line.GetVisualRun(current_run,
+ &run_start,
+ &run_length);
+ const int run_end = run_start + run_length;
+
+ // Set up a local display context for rendering this run.
+ int text_x = 0;
+ const int text_max_x = max_x - run_x;
+ MirroringContext run_context;
+ run_context.Initialize(0, text_max_x, run_direction == UBIDI_RTL);
+
+ // In addition to creating a mirroring context for the run, we indicate
+ // whether the run needs to be rendered as RTL text. The mirroring context
+ // alone in not sufficient because there are cases where a mirrored RTL run
+ // needs to be rendered in an LTR context (for example, an RTL run within
+ // an URL).
+ bool run_direction_is_rtl = (run_direction == UBIDI_RTL) && !url;
+ CDC text_dc(CreateCompatibleDC(dc));
+ CBitmap text_bitmap(CreateCompatibleBitmap(dc, text_max_x,
+ line_info_.font_height));
+ SelectObject(text_dc, text_bitmap);
+ const RECT text_rect = {0, 0, text_max_x, line_info_.line_height};
+ FillRect(text_dc, &text_rect, line_info_.brushes[status]);
+ SetBkMode(text_dc, TRANSPARENT);
+
+ // Split this run with the given classifications and draw the fragments
+ // into the local display context.
+ for (ACMatchClassifications::const_iterator i = classifications.begin();
+ i != classifications.end(); ++i) {
+ const int text_start = std::max(run_start, static_cast<int>(i->offset));
+ const int text_end = std::min(run_end, (i != classifications.end() - 1) ?
+ static_cast<int>((i + 1)->offset) : run_end);
+ text_x += DrawString(text_dc, text_x, 0, text_max_x, &text[text_start],
+ text_end - text_start, i->style, status,
+ &run_context, run_direction_is_rtl);
+ }
+
+ // Copy the local display context to the one of the popup window and
+ // delete the local display context.
+ BitBlt(dc, mirroring_context_->GetLeft(run_x, run_x + text_x), y, text_x,
+ line_info_.line_height, text_dc, run_context.GetLeft(0, text_x), 0,
+ SRCCOPY);
+ run_x += text_x;
+ }
+}
+
+void AutocompletePopup::DrawEntry(HDC dc,
+ const RECT& client_rect,
+ size_t line,
+ DrawLineInfo::LineStatus status,
+ bool all_descriptions_empty,
+ bool starred) const {
+ // Calculate outer bounds of entry, and fill background.
+ const int top_pixel = LineTopPixel(line);
+ const RECT rc = {1, top_pixel, client_rect.right - client_rect.left - 1,
+ top_pixel + line_info_.line_height};
+ FillRect(dc, &rc, line_info_.brushes[status]);
+
+ // Calculate and display contents/description sections as follows:
+ // * 2 px top margin, bottom margin is handled by line_height.
+ const int y = rc.top + 2;
+
+ // * 1 char left/right margin.
+ const int side_margin = line_info_.ave_char_width;
+
+ // * 50% of the remaining width is initially allocated to each section, with
+ // a 2 char margin followed by the star column and kStarPadding padding.
+ const int content_min_x = rc.left + side_margin;
+ const int description_max_x = rc.right - side_margin;
+ const int mid_line = (description_max_x - content_min_x) / 2 + content_min_x;
+ const int star_col_width = kStarPadding + star_->width();
+ const int content_right_margin = line_info_.ave_char_width * 2;
+
+ // * If this would make the content section display fewer than 40 characters,
+ // the content section is increased to that minimum at the expense of the
+ // description section.
+ const int content_width =
+ std::max(mid_line - content_min_x - content_right_margin,
+ line_info_.ave_char_width * 40);
+ const int description_width = description_max_x - content_min_x -
+ content_width - star_col_width;
+
+ // * If this would make the description section display fewer than 20
+ // characters, or if there are no descriptions to display or the result is
+ // the HISTORY_SEARCH shortcut, the description section is eliminated, and
+ // all the available width is used for the content section.
+ int star_x;
+ const AutocompleteMatch& match = result_.match_at(line);
+ if ((description_width < (line_info_.ave_char_width * 20)) ||
+ all_descriptions_empty ||
+ (match.type == AutocompleteMatch::HISTORY_SEARCH)) {
+ star_x = description_max_x - star_col_width + kStarPadding;
+ DrawMatchFragments(dc, match.contents, match.contents_class, content_min_x,
+ y, star_x - kStarPadding, status);
+ } else {
+ star_x = description_max_x - description_width - star_col_width;
+ DrawMatchFragments(dc, match.contents, match.contents_class, content_min_x,
+ y, content_min_x + content_width, status);
+ DrawMatchFragments(dc, match.description, match.description_class,
+ description_max_x - description_width, y,
+ description_max_x, status);
+ }
+ if (starred)
+ DrawStar(dc, star_x,
+ (line_info_.line_height - star_->height()) / 2 + top_pixel);
+}
+
+void AutocompletePopup::DrawStar(HDC dc, int x, int y) const {
+ ChromeCanvas canvas(star_->width(), star_->height(), false);
+ // Make the background completely transparent.
+ canvas.drawColor(SK_ColorBLACK, SkPorterDuff::kClear_Mode);
+ canvas.DrawBitmapInt(*star_, 0, 0);
+ canvas.getTopPlatformDevice().drawToHDC(
+ dc, mirroring_context_->GetLeft(x, x + star_->width()), y, NULL);
+}
+
+bool AutocompletePopup::GetKeywordForMatch(const AutocompleteMatch& match,
+ std::wstring* keyword) {
+ // Assume we have no keyword until we find otherwise.
+ keyword->clear();
+
+ // If the current match is a keyword, return that as the selected keyword.
+ if (match.template_url && match.template_url->url() &&
+ match.template_url->url()->SupportsReplacement()) {
+ keyword->assign(match.template_url->keyword());
+ return false;
+ }
+
+ // See if the current match's fill_into_edit corresponds to a keyword.
+ if (!profile_->GetTemplateURLModel())
+ return false;
+ profile_->GetTemplateURLModel()->Load();
+ const std::wstring keyword_hint(
+ TemplateURLModel::CleanUserInputKeyword(match.fill_into_edit));
+ if (keyword_hint.empty())
+ return false;
+
+ // Don't provide a hint if this keyword doesn't support replacement.
+ const TemplateURL* const template_url =
+ profile_->GetTemplateURLModel()->GetTemplateURLForKeyword(keyword_hint);
+ if (!template_url || !template_url->url() ||
+ !template_url->url()->SupportsReplacement())
+ return false;
+
+ keyword->assign(keyword_hint);
+ return true;
+}
+
+AutocompletePopup::DrawLineInfo::DrawLineInfo(const ChromeFont& font) {
+ // Create regular and bold fonts.
+ regular_font = font.DeriveFont(-1);
+ bold_font = regular_font.DeriveFont(0, ChromeFont::BOLD);
+
+ // The total padding added to each line (bottom padding is what is
+ // left over after DrawEntry() specifies its top offset).
+ static const int kTotalLinePadding = 5;
+ font_height = std::max(regular_font.height(), bold_font.height());
+ line_height = font_height + kTotalLinePadding;
+ ave_char_width = regular_font.ave_char_width();
+ ellipsis_width = std::max(regular_font.GetStringWidth(ellipsis_str),
+ bold_font.GetStringWidth(ellipsis_str));
+
+ // Create background colors.
+ background_colors[NORMAL] = GetSysColor(COLOR_WINDOW);
+ background_colors[SELECTED] = GetSysColor(COLOR_HIGHLIGHT);
+ background_colors[HOVERED] =
+ AlphaBlend(background_colors[SELECTED], background_colors[NORMAL], 0x40);
+
+ // Create text colors.
+ text_colors[NORMAL] = GetSysColor(COLOR_WINDOWTEXT);
+ text_colors[HOVERED] = text_colors[NORMAL];
+ text_colors[SELECTED] = GetSysColor(COLOR_HIGHLIGHTTEXT);
+
+ // Create brushes and url colors.
+ const COLORREF dark_url(0x008000);
+ const COLORREF light_url(0xd0ffd0);
+ for (int i = 0; i < MAX_STATUS_ENTRIES; ++i) {
+ // Pick whichever URL color contrasts better.
+ const double dark_contrast =
+ LuminosityContrast(dark_url, background_colors[i]);
+ const double light_contrast =
+ LuminosityContrast(light_url, background_colors[i]);
+ url_colors[i] = (dark_contrast > light_contrast) ? dark_url : light_url;
+
+ brushes[i] = CreateSolidBrush(background_colors[i]);
+ }
+}
+
+AutocompletePopup::DrawLineInfo::~DrawLineInfo() {
+ for (int i = 0; i < MAX_STATUS_ENTRIES; ++i)
+ DeleteObject(brushes[i]);
+}
+
+// static
+double AutocompletePopup::DrawLineInfo::LuminosityContrast(COLORREF color1,
+ COLORREF color2) {
+ // This algorithm was adapted from the following text at
+ // http://juicystudio.com/article/luminositycontrastratioalgorithm.php :
+ //
+ // "[Luminosity contrast can be calculated as] (L1+.05) / (L2+.05) where L is
+ // luminosity and is defined as .2126*R + .7152*G + .0722B using linearised
+ // R, G, and B values. Linearised R (for example) = (R/FS)^2.2 where FS is
+ // full scale value (255 for 8 bit color channels). L1 is the higher value
+ // (of text or background) and L2 is the lower value.
+ //
+ // The Gamma correction and RGB constants are derived from the Standard
+ // Default Color Space for the Internet (sRGB), and the 0.05 offset is
+ // included to compensate for contrast ratios that occur when a value is at
+ // or near zero, and for ambient light effects.
+ const double l1 = Luminosity(color1);
+ const double l2 = Luminosity(color2);
+ return (l1 > l2) ? ((l1 + 0.05) / (l2 + 0.05)) : ((l2 + 0.05) / (l1 + 0.05));
+}
+
+// static
+double AutocompletePopup::DrawLineInfo::Luminosity(COLORREF color) {
+ // See comments in LuminosityContrast().
+ const double linearised_r =
+ pow(static_cast<double>(GetRValue(color)) / 255.0, 2.2);
+ const double linearised_g =
+ pow(static_cast<double>(GetGValue(color)) / 255.0, 2.2);
+ const double linearised_b =
+ pow(static_cast<double>(GetBValue(color)) / 255.0, 2.2);
+ return (0.2126 * linearised_r) + (0.7152 * linearised_g) +
+ (0.0722 * linearised_b);
+}
+
+COLORREF AutocompletePopup::DrawLineInfo::AlphaBlend(COLORREF foreground,
+ COLORREF background,
+ BYTE alpha) {
+ if (alpha == 0)
+ return background;
+ else if (alpha == 0xff)
+ return foreground;
+
+ return RGB(
+ ((GetRValue(foreground) * alpha) +
+ (GetRValue(background) * (0xff - alpha))) / 0xff,
+ ((GetGValue(foreground) * alpha) +
+ (GetGValue(background) * (0xff - alpha))) / 0xff,
+ ((GetBValue(foreground) * alpha) +
+ (GetBValue(background) * (0xff - alpha))) / 0xff);
+}
diff --git a/chrome/browser/autocomplete/autocomplete_popup.h b/chrome/browser/autocomplete/autocomplete_popup.h
new file mode 100644
index 0000000..e96bc31
--- /dev/null
+++ b/chrome/browser/autocomplete/autocomplete_popup.h
@@ -0,0 +1,426 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CHROME_BROWSER_AUTOCOMPLETE_AUTOCOMPLETE_POPUP_H__
+#define CHROME_BROWSER_AUTOCOMPLETE_AUTOCOMPLETE_POPUP_H__
+
+#include <atlbase.h>
+#include <atlapp.h>
+#include <atlcrack.h>
+#include <atlmisc.h>
+
+#include "base/task.h"
+#include "base/timer.h"
+#include "base/win_util.h"
+#include "chrome/browser/autocomplete/autocomplete.h"
+#include "chrome/common/gfx/chrome_font.h"
+#include "chrome/views/view.h"
+
+#define AUTOCOMPLETEPOPUP_CLASSNAME L"Chrome_AutocompletePopup"
+
+class AutocompleteEdit;
+class Profile;
+class SkBitmap;
+class MirroringContext;
+
+// This class implements a popup window used by AutocompleteEdit to display
+// autocomplete results.
+class AutocompletePopup
+ : public CWindowImpl<AutocompletePopup, CWindow, CControlWinTraits>,
+ public ACControllerListener,
+ public Task {
+ public:
+ DECLARE_WND_CLASS_EX(AUTOCOMPLETEPOPUP_CLASSNAME,
+ ((win_util::GetWinVersion() < win_util::WINVERSION_XP) ?
+ 0 : CS_DROPSHADOW), COLOR_WINDOW)
+
+ BEGIN_MSG_MAP(AutocompletePopup)
+ MSG_WM_ERASEBKGND(OnEraseBkgnd)
+ MSG_WM_LBUTTONDOWN(OnLButtonDown)
+ MSG_WM_MBUTTONDOWN(OnMButtonDown)
+ MSG_WM_LBUTTONUP(OnLButtonUp)
+ MSG_WM_MBUTTONUP(OnMButtonUp)
+ MSG_WM_MOUSEACTIVATE(OnMouseActivate)
+ MSG_WM_MOUSELEAVE(OnMouseLeave)
+ MSG_WM_MOUSEMOVE(OnMouseMove)
+ MSG_WM_PAINT(OnPaint)
+ END_MSG_MAP()
+
+ AutocompletePopup(const ChromeFont& font,
+ AutocompleteEdit* editor,
+ Profile* profile);
+ ~AutocompletePopup();
+
+ // Invoked when the profile has changed.
+ void SetProfile(Profile* profile);
+
+ // Gets autocomplete results for the given text. If there are results, opens
+ // the popup if necessary and fills it with the new data. Otherwise, closes
+ // the popup if necessary.
+ //
+ // |prevent_inline_autocomplete| is true if the generated result set should
+ // not require inline autocomplete for the default match. This is difficult
+ // to explain in the abstract; the practical use case is that after the user
+ // deletes text in the edit, the HistoryURLProvider should make sure not to
+ //promote a match requiring inline autocomplete too highly.
+ void StartAutocomplete(const std::wstring& text,
+ const std::wstring& desired_tld,
+ bool prevent_inline_autocomplete);
+
+ // Closes the window and cancels any pending asynchronous queries
+ void StopAutocomplete();
+
+ // Returns true if no autocomplete query is currently running.
+ bool query_in_progress() const { return query_in_progress_; }
+
+ // Returns true if the popup is currently open.
+ bool is_open() const { return m_hWnd != NULL; }
+
+ // Returns the URL for the selected match. If an update is in progress,
+ // "selected" means "default in the latest results". If there are no
+ // results, returns the empty string.
+ //
+ // If |transition_type| is non-NULL, it will be set to the appropriate
+ // transition type for the selected entry (TYPED or GENERATED).
+ //
+ // If |is_history_what_you_typed_match| is non-NULL, it will be set based on
+ // the selected entry's is_history_what_you_typed value.
+ //
+ // If |alternate_nav_url| is non-NULL, it will be set to the alternate
+ // navigation URL for |url| if one exists, or left unchanged otherwise. See
+ // comments on AutocompleteResult::GetAlternateNavURL().
+ std::wstring URLsForCurrentSelection(
+ PageTransition::Type* transition,
+ bool* is_history_what_you_typed_match,
+ std::wstring* alternate_nav_url) const;
+
+ // This is sort of a hybrid between StartAutocomplete() and
+ // URLForCurrentSelection(). When the popup isn't open and the user hits
+ // enter, we want to get the default result for the user's input immediately,
+ // and not open the popup, continue running autocomplete, etc. Therefore,
+ // this does a query for only the synchronously available results for the
+ // provided input parameters, sets |transition|,
+ // |is_history_what_you_typed_match|, and |alternate_nav_url| (if applicable)
+ // based on the default match, and returns its url. |transition|,
+ // |is_history_what_you_typed_match| and/or |alternate_nav_url| may be null,
+ // in which case they are not updated.
+ //
+ // If there are no matches for |text|, leaves the outparams unset and returns
+ // the empty string.
+ std::wstring URLsForDefaultMatch(const std::wstring& text,
+ const std::wstring& desired_tld,
+ PageTransition::Type* transition,
+ bool* is_history_what_you_typed_match,
+ std::wstring* alternate_nav_url);
+
+ // Returns a pointer to a heap-allocated AutocompleteLog containing the
+ // current input text, selected match, and result set. The caller is
+ // responsible for deleting the object.
+ AutocompleteLog* GetAutocompleteLog();
+
+ // Immediately updates and opens the popup if necessary, then moves the
+ // current selection down (|count| > 0) or up (|count| < 0), clamping to the
+ // first or last result if necessary. If |count| == 0, the selection will be
+ // unchanged, but the popup will still redraw and modify the text in the
+ // AutocompleteEdit.
+ void Move(int count);
+
+ // Called when the user hits shift-delete. This should determine if the item
+ // can be removed from history, and if so, remove it and update the popup.
+ void TryDeletingCurrentItem();
+
+ // ACControllerListener - called when more autocomplete data is available or
+ // when the query is complete.
+ //
+ // When the input for the current query has a provider affinity, we try to
+ // keep the current result set's default match as the new default match.
+ virtual void OnAutocompleteUpdate(bool updated_result, bool query_complete);
+
+ // Task - called when either timer fires. Calls CommitLatestResults().
+ virtual void Run();
+
+ // Returns the AutocompleteController used by this popup.
+ AutocompleteController* autocomplete_controller() const {
+ return controller_.get();
+ }
+
+ const AutocompleteResult* latest_result() const {
+ return &latest_result_;
+ }
+
+ // The match the user has manually chosen, if any.
+ AutocompleteResult::Selection manually_selected_match_;
+
+ // The token value for selected_line_, hover_line_ and functions dealing with
+ // a "line number" that indicates "no line".
+ static const size_t kNoMatch = -1;
+
+ private:
+ // Caches GDI objects and information for drawing.
+ struct DrawLineInfo {
+ enum LineStatus {
+ NORMAL = 0,
+ HOVERED,
+ SELECTED,
+ MAX_STATUS_ENTRIES
+ };
+
+ explicit DrawLineInfo(const ChromeFont& font);
+ ~DrawLineInfo();
+
+ static COLORREF AlphaBlend(COLORREF foreground,
+ COLORREF background,
+ BYTE alpha);
+
+ static const wchar_t ellipsis_str[];
+
+ ChromeFont regular_font; // Fonts used for rendering AutocompleteMatches.
+ ChromeFont bold_font;
+ int font_height; // Height (in pixels) of a line of text w/o
+ // padding.
+ int line_height; // Height (in pixels) of a line of text w/padding.
+ int ave_char_width; // Width (in pixels) of an average character of
+ // the regular font.
+ int ellipsis_width; // Width (in pixels) of the ellipsis_str.
+
+ // colors
+ COLORREF background_colors[MAX_STATUS_ENTRIES];
+ COLORREF text_colors[MAX_STATUS_ENTRIES];
+ COLORREF url_colors[MAX_STATUS_ENTRIES];
+
+ // brushes
+ HBRUSH brushes[MAX_STATUS_ENTRIES];
+
+ private:
+ static double LuminosityContrast(COLORREF color1, COLORREF color2);
+ static double Luminosity(COLORREF color);
+ };
+
+ // message handlers
+ LRESULT OnEraseBkgnd(HDC hdc) {
+ // We do all needed erasing ourselves in OnPaint, so the only thing that
+ // WM_ERASEBKGND will do is cause flicker. Disable it by just returning
+ // nonzero here ("erase completed") without doing anything.
+ return 1;
+ }
+ void OnLButtonDown(UINT keys, const CPoint& point);
+ void OnMButtonDown(UINT keys, const CPoint& point);
+ void OnLButtonUp(UINT keys, const CPoint& point);
+ void OnMButtonUp(UINT keys, const CPoint& point);
+ LRESULT OnMouseActivate(HWND window, UINT hit_test, UINT mouse_message);
+ void OnMouseLeave();
+ void OnMouseMove(UINT keys, const CPoint& point);
+ void OnPaint(HDC hdc);
+
+ // Called by On*ButtonUp() to do the actual work of handling a button
+ // release. Opens the item at the given coordinate, using the supplied
+ // disposition.
+ void OnButtonUp(const CPoint& point, WindowOpenDisposition disposition);
+
+ // Sets the correct default match in latest_result_, then updates the popup
+ // appearance to match. If |immediately| is true this update happens
+ // synchronously; otherwise, it's deferred until the next scheduled update.
+ void SetDefaultMatchAndUpdate(bool immediately);
+
+ // If an update is pending or |force| is true, immediately updates result_ to
+ // match latest_result_, and calls UpdatePopup() to reflect those changes
+ // back to the user.
+ void CommitLatestResults(bool force);
+
+ // Redraws the popup window to match any changes in result_; this may mean
+ // opening or closing the window.
+ void UpdatePopupAppearance();
+
+ // Gives the topmost y coordinate within |line|, which should be within the
+ // range of valid lines.
+ int LineTopPixel(size_t line) const;
+
+ // Converts the given y-coordinate to a line. Due to drawing slop (window
+ // borders, etc.), |y| might be within the window but outside the range of
+ // pixels which correspond to lines; in this case the result will be clamped,
+ // i.e., the top and bottom lines will be treated as extending to the top and
+ // bottom edges of the window, respectively.
+ size_t PixelToLine(int y) const;
+
+ // Call to change the hovered line. |line| should be within the range of
+ // valid lines (to enable hover) or kNoMatch (to disable hover).
+ void SetHoveredLine(size_t line);
+
+ // Call to change the selected line. This will update all state and repaint
+ // the necessary parts of the window, as well as updating the edit with the
+ // new temporary text. |line| should be within the range of valid lines.
+ // NOTE: This assumes the popup is open, and thus both old and new values for
+ // the selected line should not be kNoMatch.
+ void SetSelectedLine(size_t line);
+
+ // Invalidates one line of the autocomplete popup.
+ void InvalidateLine(size_t line);
+
+ // Draws a light border around the inside of the window with the given client
+ // rectangle and DC.
+ void DrawBorder(const RECT& rc, HDC dc);
+
+ // Draw a string at the specified location with the specified style.
+ // This function is a wrapper function of the DrawText() function to handle
+ // bidirectional strings.
+ // Parameters
+ // * dc [in] (HDC)
+ // Represents the display context to render the given string.
+ // * x [in] (int)
+ // Specifies the left of the bounding rectangle,
+ // * y [in] (int)
+ // Specifies the top of the bounding rectangle,
+ // * max_x [in] (int)
+ // Specifies the right of the bounding rectangle.
+ // * text [in] (const wchar_t*)
+ // Specifies the pointer to the string to be rendered.
+ // * length [in] (int)
+ // Specifies the number of characters in the string.
+ // * style [in] (int)
+ // Specifies the classifications for this string.
+ // This value is a combination of the following values:
+ // - ACMatchClassifications::NONE
+ // - ACMatchClassifications::URL
+ // - ACMatchClassifications::MATCH
+ // - ACMatchClassifications::DIM
+ // * status [in] (const DrawLineInfo::LineStatus)
+ // Specifies the classifications for this line.
+ // * context [in] (const MirroringContext*)
+ // Specifies the context used for mirroring the x-coordinates.
+ // * text_direction_is_rtl [in] (bool)
+ // Determines whether we need to render the string as an RTL string, which
+ // impacts, for example, which side leading/trailing whitespace and
+ // punctuation appear on.
+ // Return Values
+ // * a positive value
+ // Represents the width of the displayed string, in pixels.
+ int DrawString(HDC dc,
+ int x,
+ int y,
+ int max_x,
+ const wchar_t* text,
+ int length,
+ int style,
+ const DrawLineInfo::LineStatus status,
+ const MirroringContext* context,
+ bool text_direction_is_rtl) const;
+
+ // Draws a string from the autocomplete controller which can have specially
+ // marked "match" portions.
+ void DrawMatchFragments(HDC dc,
+ const std::wstring& text,
+ const ACMatchClassifications& classifications,
+ int x,
+ int y,
+ int max_x,
+ DrawLineInfo::LineStatus status) const;
+
+ // Draws one line of the text in the box.
+ void DrawEntry(HDC dc,
+ const RECT& client_rect,
+ size_t line,
+ DrawLineInfo::LineStatus status,
+ bool all_descriptions_empty,
+ bool starred) const;
+
+ // Draws the star at the specified location
+ void DrawStar(HDC dc, int x, int y) const;
+
+ // Gets the selected keyword or keyword hint for the given match. Returns
+ // true if |keyword| represents a keyword hint, or false if |keyword|
+ // represents a selected keyword. (|keyword| will always be set [though
+ // possibly to the empty string], and you cannot have both a selected keyword
+ // and a keyword hint simultaneously.)
+ bool GetKeywordForMatch(const AutocompleteMatch& match,
+ std::wstring* keyword);
+
+ AutocompleteEdit* editor_;
+ scoped_ptr<AutocompleteController> controller_;
+
+ // Profile for current tab.
+ Profile* profile_;
+
+ // Cached GDI information for drawing.
+ DrawLineInfo line_info_;
+
+ // The input for the current query.
+ AutocompleteInput input_;
+
+ // Data from the autocomplete query.
+ AutocompleteResult result_;
+
+ // True if an autocomplete query is currently running.
+ bool query_in_progress_;
+
+ // The latest result available from the autocomplete service. This may be
+ // different than result_ if we've gotten results from our providers that we
+ // haven't yet shown the user. If more results may be coming, we'll wait to
+ // display these in hopes of minimizing flicker; see coalesce_timer_.
+ AutocompleteResult latest_result_;
+
+ // True when there are newer results in latest_result_ than in result_ and
+ // the popup has not been updated to show them.
+ bool update_pending_;
+
+ // Timer that tracks how long it's been since the last provider update we
+ // received. Instead of displaying each update immediately, we batch updates
+ // into groups, which reduces flicker.
+ //
+ // NOTE: Both coalesce_timer_ and max_delay_timer_ (below) are set up during
+ // the constructor, and are guaranteed non-NULL for the life of the popup.
+ scoped_ptr<Timer> coalesce_timer_;
+
+ // Timer that tracks how long it's been since the last time we updated the
+ // onscreen results. This is used to ensure that the popup is somewhat
+ // responsive even when the user types continuously.
+ scoped_ptr<Timer> max_delay_timer_;
+
+ // The line that's currently hovered. If we're not drawing a hover rect,
+ // this will be kNoMatch, even if the cursor is over the popup contents.
+ size_t hovered_line_;
+
+ // When hover_line_ is kNoMatch, this holds the screen coordinates of the
+ // mouse position when hover tracking was turned off. If the mouse moves to a
+ // point over the popup that has different coordinates, hover tracking will be
+ // re-enabled. When hover_line_ is a valid line, the value here is
+ // out-of-date and should be ignored.
+ CPoint last_hover_coordinates_;
+
+ // The currently selected line. This is kNoMatch when nothing is selected,
+ // which should only be true when the popup is closed.
+ size_t selected_line_;
+
+ // Bitmap for the star. This is owned by the ResourceBundle.
+ SkBitmap* star_;
+
+ // A context used for mirroring regions.
+ scoped_ptr<MirroringContext> mirroring_context_;
+};
+
+#endif // CHROME_BROWSER_AUTOCOMPLETE_AUTOCOMPLETE_POPUP_H__
diff --git a/chrome/browser/autocomplete/autocomplete_unittest.cc b/chrome/browser/autocomplete/autocomplete_unittest.cc
new file mode 100644
index 0000000..e2fc2f5
--- /dev/null
+++ b/chrome/browser/autocomplete/autocomplete_unittest.cc
@@ -0,0 +1,361 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/message_loop.h"
+#include "base/ref_counted.h"
+#include "chrome/browser/autocomplete/autocomplete.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// identifiers for known autocomplete providers
+#define HISTORY_IDENTIFIER L"Chrome:History"
+#define SEARCH_IDENTIFIER L"google.com/websearch/en"
+
+namespace {
+
+const int num_results_per_provider = 3;
+
+// Autocomplete provider that provides known results. Note that this is
+// refcounted so that it can also be a task on the message loop.
+class TestProvider : public AutocompleteProvider {
+ public:
+ TestProvider(int relevance, const std::wstring& prefix)
+ : AutocompleteProvider(NULL, NULL, ""),
+ relevance_(relevance),
+ prefix_(prefix) {
+ }
+
+ virtual void Start(const AutocompleteInput& input,
+ bool minimal_changes,
+ bool synchronous_only);
+
+ void set_listener(ACProviderListener* listener) {
+ listener_ = listener;
+ }
+
+ private:
+ void Run();
+
+ void AddResults(int start_at, int num);
+
+ int relevance_;
+ const std::wstring prefix_;
+};
+
+void TestProvider::Start(const AutocompleteInput& input,
+ bool minimal_changes,
+ bool synchronous_only) {
+ if (minimal_changes)
+ return;
+
+ matches_.clear();
+
+ // Generate one result synchronously, the rest later.
+ AddResults(0, 1);
+
+ if (!synchronous_only) {
+ done_ = false;
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &TestProvider::Run));
+ }
+}
+
+void TestProvider::Run() {
+ DCHECK(num_results_per_provider > 0);
+ AddResults(1, num_results_per_provider);
+ done_ = true;
+ DCHECK(listener_);
+ listener_->OnProviderUpdate(true);
+}
+
+void TestProvider::AddResults(int start_at, int num) {
+ for (int i = start_at; i < num; i++) {
+ AutocompleteMatch match(this, relevance_ - i, false);
+
+ wchar_t str[16];
+ swprintf_s(str, L"%d", i);
+ match.fill_into_edit = prefix_ + str;
+ match.destination_url = match.fill_into_edit;
+
+ match.contents = match.destination_url;
+ match.contents_class.push_back(
+ ACMatchClassification(0, ACMatchClassification::NONE));
+ match.description = match.destination_url;
+ match.description_class.push_back(
+ ACMatchClassification(0, ACMatchClassification::NONE));
+
+ matches_.push_back(match);
+ }
+}
+
+class AutocompleteProviderTest : public testing::Test,
+ public ACControllerListener {
+ public:
+ // ACControllerListener
+ virtual void OnAutocompleteUpdate(bool updated_result, bool query_complete);
+
+ protected:
+ // testing::Test
+ virtual void SetUp();
+
+ void ResetController(bool same_destinations);
+
+ // Runs a query on the input "a", and makes sure both providers' input is
+ // properly collected.
+ void RunTest();
+
+ // These providers are owned by the controller once it's created.
+ ACProviders providers_;
+
+ AutocompleteResult result_;
+
+ private:
+ scoped_ptr<AutocompleteController> controller_;
+};
+
+void AutocompleteProviderTest::SetUp() {
+ ResetController(false);
+}
+
+void AutocompleteProviderTest::ResetController(bool same_destinations) {
+ // Forget about any existing providers. The controller owns them and will
+ // Release() them below, when we delete it during the call to reset().
+ providers_.clear();
+
+ // Construct two new providers, with either the same or different prefixes.
+ TestProvider* providerA = new TestProvider(num_results_per_provider, L"a");
+ providerA->AddRef();
+ providers_.push_back(providerA);
+
+ TestProvider* providerB = new TestProvider(num_results_per_provider * 2,
+ same_destinations ? L"a" : L"b");
+ providerB->AddRef();
+ providers_.push_back(providerB);
+
+ // Reset the controller to contain our new providers.
+ AutocompleteController* controller =
+ new AutocompleteController(this, providers_);
+ controller_.reset(controller);
+ providerA->set_listener(controller);
+ providerB->set_listener(controller);
+}
+
+void AutocompleteProviderTest::OnAutocompleteUpdate(bool updated_result,
+ bool query_complete) {
+ controller_->GetResult(&result_);
+ if (query_complete)
+ MessageLoop::current()->Quit();
+}
+
+void AutocompleteProviderTest::RunTest() {
+ result_.Reset();
+ const AutocompleteInput input(L"a", std::wstring(), true);
+ EXPECT_FALSE(controller_->Start(input, false, false));
+
+ // The message loop will terminate when all autocomplete input has been
+ // collected.
+ MessageLoop::current()->Run();
+}
+
+} // namespace
+
+std::ostream& operator<<(std::ostream& os,
+ const AutocompleteResult::const_iterator& iter) {
+ return os << static_cast<const AutocompleteMatch*>(&(*iter));
+}
+
+// Tests that the default selection is set properly when updating results.
+TEST_F(AutocompleteProviderTest, Query) {
+ RunTest();
+
+ // Make sure the default match gets set to the highest relevance match when
+ // we have no preference. The highest relevance matches should come from
+ // the second provider.
+ AutocompleteResult::Selection selection;
+ result_.SetDefaultMatch(selection);
+ EXPECT_EQ(num_results_per_provider * 2, result_.size()); // two providers
+ ASSERT_NE(result_.end(), result_.default_match());
+ EXPECT_EQ(providers_[1], result_.default_match()->provider);
+
+ // Change provider affinity.
+ selection.provider_affinity = providers_[0];
+ result_.SetDefaultMatch(selection);
+ ASSERT_NE(result_.end(), result_.default_match());
+ EXPECT_EQ(providers_[0], result_.default_match()->provider);
+}
+
+TEST_F(AutocompleteProviderTest, UpdateSelection) {
+ RunTest();
+
+ // An empty selection should simply result in the default match overall.
+ AutocompleteResult::Selection selection;
+ result_.SetDefaultMatch(selection);
+ ASSERT_NE(result_.end(), result_.default_match());
+ EXPECT_EQ(providers_[1], result_.default_match()->provider);
+
+ // ...As should specifying a provider that didn't return results.
+ scoped_refptr<TestProvider> test_provider = new TestProvider(0, L"");
+ selection.provider_affinity = test_provider.get();
+ result_.SetDefaultMatch(selection);
+ ASSERT_NE(result_.end(), result_.default_match());
+ EXPECT_EQ(providers_[1], result_.default_match()->provider);
+ selection.provider_affinity = NULL;
+ test_provider = NULL;
+
+ // ...As should specifying a destination URL that doesn't exist, and no
+ // provider.
+ selection.destination_url = L"garbage";
+ selection.provider_affinity = NULL;
+ result_.SetDefaultMatch(selection);
+ ASSERT_NE(result_.end(), result_.default_match());
+ EXPECT_EQ(providers_[1], result_.default_match()->provider);
+ delete selection.provider_affinity;
+
+ // Specifying a valid provider should result in the default match from that
+ // provider.
+ selection.destination_url.clear();
+ selection.provider_affinity = providers_[0];
+ result_.SetDefaultMatch(selection);
+ ASSERT_NE(result_.end(), result_.default_match());
+ EXPECT_EQ(providers_[0], result_.default_match()->provider);
+
+ // ...And nothing should change if we specify a destination that doesn't
+ // exist.
+ selection.destination_url = L"garbage";
+ result_.SetDefaultMatch(selection);
+ ASSERT_NE(result_.end(), result_.default_match());
+ EXPECT_EQ(providers_[0], result_.default_match()->provider);
+
+ // Specifying a particular URL should match that URL.
+ std::wstring non_default_url_from_provider_0(L"a2");
+ selection.destination_url = non_default_url_from_provider_0;
+ selection.provider_affinity = providers_[0];
+ result_.SetDefaultMatch(selection);
+ ASSERT_NE(result_.end(), result_.default_match());
+ EXPECT_EQ(non_default_url_from_provider_0,
+ result_.default_match()->destination_url);
+
+ // ...Even when we ask for a different provider.
+ selection.provider_affinity = providers_[1];
+ result_.SetDefaultMatch(selection);
+ ASSERT_NE(result_.end(), result_.default_match());
+ EXPECT_EQ(non_default_url_from_provider_0,
+ result_.default_match()->destination_url);
+
+ // ...Or when we don't ask for a provider at all.
+ selection.provider_affinity = NULL;
+ result_.SetDefaultMatch(selection);
+ ASSERT_NE(result_.end(), result_.default_match());
+ EXPECT_EQ(non_default_url_from_provider_0,
+ result_.default_match()->destination_url);
+}
+
+TEST_F(AutocompleteProviderTest, RemoveDuplicates) {
+ // Set up the providers to provide duplicate results.
+ ResetController(true);
+
+ RunTest();
+
+ // Make sure all the first provider's results were eliminated by the second
+ // provider's.
+ EXPECT_EQ(num_results_per_provider, result_.size());
+ for (AutocompleteResult::const_iterator i(result_.begin());
+ i != result_.end(); ++i)
+ EXPECT_EQ(providers_[1], i->provider);
+
+ // Set things back to the default for the benefit of any tests that run after
+ // us.
+ ResetController(false);
+}
+
+TEST(AutocompleteTest, InputType) {
+ struct test_data {
+ const wchar_t* input;
+ const AutocompleteInput::Type type;
+ } input_cases[] = {
+ { L"", AutocompleteInput::INVALID },
+ { L"?", AutocompleteInput::FORCED_QUERY },
+ { L"?foo", AutocompleteInput::FORCED_QUERY },
+ { L"?foo bar", AutocompleteInput::FORCED_QUERY },
+ { L"?http://foo.com/bar", AutocompleteInput::FORCED_QUERY },
+ { L"foo", AutocompleteInput::UNKNOWN },
+ { L"foo.com", AutocompleteInput::URL },
+ { L"foo/bar", AutocompleteInput::URL },
+ { L"foo/bar baz", AutocompleteInput::UNKNOWN },
+ { L"http://foo/bar baz", AutocompleteInput::URL },
+ { L"foo bar", AutocompleteInput::QUERY },
+ { L"link:foo.com", AutocompleteInput::UNKNOWN },
+ { L"www.foo.com:81", AutocompleteInput::URL },
+ { L"localhost:8080", AutocompleteInput::URL },
+ { L"en.wikipedia.org/wiki/James Bond", AutocompleteInput::URL },
+ // In Chrome itself, mailto: will get handled by ShellExecute, but in
+ // unittest mode, we don't have the data loaded in the external protocol
+ // handler to know this.
+ // { L"mailto:abuse@foo.com", AutocompleteInput::URL },
+ { L"view-source:http://www.foo.com/", AutocompleteInput::URL },
+ { L"javascript:alert(\"Hey there!\");", AutocompleteInput::URL },
+ { L"C:\\Program Files", AutocompleteInput::URL },
+ { L"\\\\Server\\Folder\\File", AutocompleteInput::URL },
+ { L"http://foo.com/", AutocompleteInput::URL },
+ { L"127.0.0.1", AutocompleteInput::URL },
+ { L"browser.tabs.closeButtons", AutocompleteInput::UNKNOWN },
+ };
+
+ for (int i = 0; i < arraysize(input_cases); ++i) {
+ AutocompleteInput input(input_cases[i].input, std::wstring(), true);
+ EXPECT_EQ(input_cases[i].type, input.type()) << "Input: " <<
+ input_cases[i].input;
+ }
+}
+
+// Test that we can properly compare matches' relevance when at least one is
+// negative.
+TEST(AutocompleteMatch, MoreRelevant) {
+ struct RelevantCases {
+ int r1;
+ int r2;
+ bool expected_result;
+ } cases[] = {
+ { 10, 0, true },
+ { 10, -5, true },
+ { -5, 10, false },
+ { 0, 10, false },
+ { -10, -5, true },
+ { -5, -10, false },
+ };
+
+ AutocompleteMatch m1;
+ AutocompleteMatch m2;
+
+ for (int i = 0; i < arraysize(cases); ++i) {
+ m1.relevance = cases[i].r1;
+ m2.relevance = cases[i].r2;
+ EXPECT_EQ(cases[i].expected_result,
+ AutocompleteMatch::MoreRelevant(m1, m2));
+ }
+}
diff --git a/chrome/browser/autocomplete/edit_drop_target.cc b/chrome/browser/autocomplete/edit_drop_target.cc
new file mode 100644
index 0000000..7be3259
--- /dev/null
+++ b/chrome/browser/autocomplete/edit_drop_target.cc
@@ -0,0 +1,177 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "chrome/browser/autocomplete/edit_drop_target.h"
+
+#include "base/string_util.h"
+#include "chrome/browser/autocomplete/autocomplete_edit.h"
+#include "chrome/common/os_exchange_data.h"
+#include "googleurl/src/gurl.h"
+
+namespace {
+
+// A helper method for determining a valid DROPEFFECT given the allowed
+// DROPEFFECTS. We prefer copy over link.
+DWORD CopyOrLinkDropEffect(DWORD effect) {
+ if (effect & DROPEFFECT_COPY)
+ return DROPEFFECT_COPY;
+ if (effect & DROPEFFECT_LINK)
+ return DROPEFFECT_LINK;
+ return DROPEFFECT_NONE;
+}
+
+}
+
+EditDropTarget::EditDropTarget(AutocompleteEdit* edit)
+ : BaseDropTarget(edit->m_hWnd),
+ edit_(edit),
+ drag_has_url_(false),
+ drag_has_string_(false) {
+}
+
+DWORD EditDropTarget::OnDragEnter(IDataObject* data_object,
+ DWORD key_state,
+ POINT cursor_position,
+ DWORD effect) {
+ OSExchangeData os_data(data_object);
+ drag_has_url_ = os_data.HasURL();
+ drag_has_string_ = !drag_has_url_ && os_data.HasString();
+ if (drag_has_url_) {
+ if (edit_->in_drag()) {
+ // The edit we're associated with originated the drag. No point in
+ // allowing the user to drop back on us.
+ drag_has_url_ = false;
+ }
+ // NOTE: it would be nice to visually show all the text is going to
+ // be replaced by selecting all, but this caused painting problems. In
+ // particular the flashing caret would appear outside the edit! For now
+ // we stick with no visual indicator other than that shown own the mouse
+ // cursor.
+ }
+ return OnDragOver(data_object, key_state, cursor_position, effect);
+}
+
+DWORD EditDropTarget::OnDragOver(IDataObject* data_object,
+ DWORD key_state,
+ POINT cursor_position,
+ DWORD effect) {
+ if (drag_has_url_)
+ return CopyOrLinkDropEffect(effect);
+
+ if (drag_has_string_) {
+ UpdateDropHighlightPosition(cursor_position);
+ if (edit_->drop_highlight_position() == -1 && edit_->in_drag())
+ return DROPEFFECT_NONE;
+ if (edit_->in_drag()) {
+ // The edit we're associated with originated the drag. Do the normal drag
+ // behavior.
+ DCHECK((effect & DROPEFFECT_COPY) && (effect & DROPEFFECT_MOVE));
+ return (key_state & MK_CONTROL) ? DROPEFFECT_COPY : DROPEFFECT_MOVE;
+ }
+ // Our edit didn't originate the drag, only allow link or copy.
+ return CopyOrLinkDropEffect(effect);
+ }
+
+ return DROPEFFECT_NONE;
+}
+
+void EditDropTarget::OnDragLeave(IDataObject* data_object) {
+ ResetDropHighlights();
+}
+
+DWORD EditDropTarget::OnDrop(IDataObject* data_object,
+ DWORD key_state,
+ POINT cursor_position,
+ DWORD effect) {
+ OSExchangeData os_data(data_object);
+
+ if (drag_has_url_) {
+ GURL url;
+ std::wstring title;
+ if (os_data.GetURLAndTitle(&url, &title)) {
+ edit_->SetUserText(UTF8ToWide(url.spec()));
+ edit_->AcceptInput(CURRENT_TAB, true);
+ return CopyOrLinkDropEffect(effect);
+ }
+ } else if (drag_has_string_) {
+ int string_drop_position = edit_->drop_highlight_position();
+ std::wstring text;
+ if ((string_drop_position != -1 || !edit_->in_drag()) &&
+ os_data.GetString(&text)) {
+ DCHECK(string_drop_position == -1 ||
+ ((string_drop_position >= 0) &&
+ (string_drop_position <= edit_->GetTextLength())));
+ const DWORD drop_operation =
+ OnDragOver(data_object, key_state, cursor_position, effect);
+ if (edit_->in_drag()) {
+ if (drop_operation == DROPEFFECT_MOVE)
+ edit_->MoveSelectedText(string_drop_position);
+ else
+ edit_->InsertText(string_drop_position, text);
+ } else {
+ edit_->PasteAndGo(CollapseWhitespace(text, true));
+ }
+ ResetDropHighlights();
+ return drop_operation;
+ }
+ }
+
+ ResetDropHighlights();
+
+ return DROPEFFECT_NONE;
+}
+
+void EditDropTarget::UpdateDropHighlightPosition(
+ const POINT& cursor_screen_position) {
+ if (drag_has_string_) {
+ POINT client_position = cursor_screen_position;
+ ScreenToClient(edit_->m_hWnd, &client_position);
+ int drop_position = edit_->CharFromPos(client_position);
+ if (edit_->in_drag()) {
+ // Our edit originated the drag, don't allow a drop if over the selected
+ // region.
+ LONG sel_start, sel_end;
+ edit_->GetSel(sel_start, sel_end);
+ if ((sel_start != sel_end) && (drop_position >= sel_start) &&
+ (drop_position <= sel_end))
+ drop_position = -1;
+ } else {
+ // A drop from a source other than the edit replaces all the text, so
+ // we don't show the drop location. See comment in OnDragEnter as to why
+ // we don't try and select all here.
+ drop_position = -1;
+ }
+ edit_->SetDropHighlightPosition(drop_position);
+ }
+}
+
+void EditDropTarget::ResetDropHighlights() {
+ if (drag_has_string_)
+ edit_->SetDropHighlightPosition(-1);
+}
diff --git a/chrome/browser/autocomplete/edit_drop_target.h b/chrome/browser/autocomplete/edit_drop_target.h
new file mode 100644
index 0000000..d350c6a
--- /dev/null
+++ b/chrome/browser/autocomplete/edit_drop_target.h
@@ -0,0 +1,83 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CHROME_BROWSER_AUTOCOMPLETE_EDIT_DROP_TARGET_H__
+#define CHROME_BROWSER_AUTOCOMPLETE_EDIT_DROP_TARGET_H__
+
+#include "base/base_drop_target.h"
+
+class AutocompleteEdit;
+
+// EditDropTarget is the IDropTarget implementation installed on
+// AutocompleteEdit. EditDropTarget prefers URL over plain text. A drop of a URL
+// replaces all the text of the edit and navigates immediately to the URL. A
+// drop of plain text from the same edit either copies or moves the selected
+// text, and a drop of plain text from a source other than the edit does a paste
+// and go.
+class EditDropTarget : public BaseDropTarget {
+ public:
+ explicit EditDropTarget(AutocompleteEdit* edit);
+
+ protected:
+ virtual DWORD OnDragEnter(IDataObject* data_object,
+ DWORD key_state,
+ POINT cursor_position,
+ DWORD effect);
+ virtual DWORD OnDragOver(IDataObject* data_object,
+ DWORD key_state,
+ POINT cursor_position,
+ DWORD effect);
+ virtual void OnDragLeave(IDataObject* data_object);
+ virtual DWORD OnDrop(IDataObject* data_object,
+ DWORD key_state,
+ POINT cursor_position,
+ DWORD effect);
+
+ private:
+ // If dragging a string, the drop highlight position of the edit is reset
+ // based on the mouse position.
+ void UpdateDropHighlightPosition(const POINT& cursor_screen_position);
+
+ // Resets the visual drop indicates we install on the edit.
+ void ResetDropHighlights();
+
+ // The edit we're the drop target for.
+ AutocompleteEdit* edit_;
+
+ // If true, the drag session contains a URL.
+ bool drag_has_url_;
+
+ // If true, the drag session contains a string. If drag_has_url_ is true,
+ // this is false regardless of whether the clipboard has a string.
+ bool drag_has_string_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(EditDropTarget);
+};
+
+#endif // CHROME_BROWSER_AUTOCOMPLETE_EDIT_DROP_TARGET_H__
diff --git a/chrome/browser/autocomplete/history_contents_provider.cc b/chrome/browser/autocomplete/history_contents_provider.cc
new file mode 100644
index 0000000..c68b17c
--- /dev/null
+++ b/chrome/browser/autocomplete/history_contents_provider.cc
@@ -0,0 +1,264 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "chrome/browser/autocomplete/history_contents_provider.h"
+
+#include "base/string_util.h"
+#include "chrome/browser/history/query_parser.h"
+#include "chrome/browser/profile.h"
+#include "net/base/net_util.h"
+
+namespace {
+
+// Number of days to search for full text results. The longer this is, the more
+// time it will take.
+const int kDaysToSearch = 30;
+
+// When processing the results from the history query, this structure points to
+// a single result. It allows the results to be sorted and processed without
+// modifying the larger and slower results structure.
+struct MatchReference {
+ const history::URLResult* result;
+ int relevance; // Score of relevance computed by CalculateRelevance.
+};
+
+// This is a > operator for MatchReference.
+bool CompareMatchRelevance(const MatchReference& a, const MatchReference& b) {
+ if (a.relevance != b.relevance)
+ return a.relevance > b.relevance;
+
+ // Want results in reverse-chronological order all else being equal.
+ return a.result->last_visit() > b.result->last_visit();
+}
+
+} // namespace
+
+using history::HistoryDatabase;
+
+void HistoryContentsProvider::Start(const AutocompleteInput& input,
+ bool minimal_changes,
+ bool synchronous_only) {
+ matches_.clear();
+
+ if (input.text().empty() || (input.type() == AutocompleteInput::INVALID) ||
+ // The history service must exist.
+ (!history_service_ &&
+ (!profile_ || !profile_->GetHistoryService(Profile::EXPLICIT_ACCESS)))) {
+ Stop();
+ return;
+ }
+
+ // TODO(pkasting): http://b/888148 We disallow URL input and "URL-like" input
+ // (REQUESTED_URL or UNKNOWN with dots) because we get poor results for it,
+ // but we could get better results if we did better tokenizing instead.
+ if ((input.type() == AutocompleteInput::URL) ||
+ (((input.type() == AutocompleteInput::REQUESTED_URL) ||
+ (input.type() == AutocompleteInput::UNKNOWN)) &&
+ (input.text().find('.') != std::wstring::npos))) {
+ Stop();
+ return;
+ }
+
+ // Change input type and reset relevance counters, so matches will be marked
+ // up properly.
+ input_type_ = input.type();
+ star_title_count_ = star_contents_count_ = title_count_ = contents_count_ = 0;
+
+ // Decide what to do about any previous query/results.
+ if (!minimal_changes) {
+ // Any in-progress request is irrelevant, cancel it.
+ Stop();
+ } else if (have_results_) {
+ // We finished the previous query and still have its results. Mark them up
+ // again for the new input.
+ ConvertResults();
+ return;
+ } else if (!done_) {
+ // We're still running the previous query. If we're allowed to keep running
+ // it, do so, and when it finishes, its results will get marked up for this
+ // new input. In synchronous_only mode, just cancel.
+ if (synchronous_only)
+ Stop();
+ return;
+ }
+
+ if (!synchronous_only) {
+ HistoryService* history = history_service_ ? history_service_ :
+ profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
+ if (history) {
+ done_ = false;
+
+ history::QueryOptions options;
+ options.SetRecentDayRange(kDaysToSearch);
+ options.most_recent_visit_only = true;
+ options.max_count = kMaxMatchCount;
+ history->QueryHistory(input.text(), options, &request_consumer_,
+ NewCallback(this, &HistoryContentsProvider::QueryComplete));
+ }
+ }
+}
+
+void HistoryContentsProvider::Stop() {
+ done_ = true;
+ request_consumer_.CancelAllRequests();
+
+ // Clear the results. We swap in an empty one as the easy way to clear it.
+ history::QueryResults empty_results;
+ results_.Swap(&empty_results);
+ have_results_ = false;
+
+ db_match_count_ = 0;
+}
+
+void HistoryContentsProvider::QueryComplete(HistoryService::Handle handle,
+ history::QueryResults* results) {
+ results_.Swap(results);
+ have_results_ = true;
+ ConvertResults();
+
+ db_match_count_ = static_cast<int>(results_.size());
+ done_ = true;
+ if (listener_)
+ listener_->OnProviderUpdate(!matches_.empty());
+}
+
+void HistoryContentsProvider::ConvertResults() {
+ // Make the result references and score the results.
+ std::vector<MatchReference> result_refs;
+ result_refs.reserve(results_.size());
+ for (size_t i = 0; i < results_.size(); i++) {
+ MatchReference ref;
+ ref.result = &results_[i];
+ ref.relevance = CalculateRelevance(*ref.result);
+ result_refs.push_back(ref);
+ }
+
+ // Get the top matches and add them. Always do max number of matches the popup
+ // will show plus one. This ensures that if the other providers provide the
+ // exact same set of results, and the db only has max_matches + 1 results
+ // available for this query, we know the last one.
+ //
+ // This is done to avoid having the history search shortcut show
+ // 'See 1 previously viewed ...'.
+ //
+ // Note that AutocompleteResult::max_matches() (maximum size of the popup)
+ // is different than both max_matches (the provider's maximum) and
+ // kMaxMatchCount (the number of items we want from the history).
+ size_t max_for_popup = std::min(AutocompleteResult::max_matches() + 1,
+ result_refs.size());
+ size_t max_for_provider = std::min(max_matches(), result_refs.size());
+ std::partial_sort(result_refs.begin(), result_refs.begin() + max_for_popup,
+ result_refs.end(), &CompareMatchRelevance);
+ matches_.clear();
+ for (size_t i = 0; i < max_for_popup; i++) {
+ matches_.push_back(ResultToMatch(*result_refs[i].result,
+ result_refs[i].relevance));
+ }
+
+ // We made more matches than the autocomplete service requested for this
+ // provider (see previous comment). We invert the weights for the items
+ // we want to get removed, but preserve their magnitude which will be used
+ // to fill them in with our other results.
+ for (size_t i = max_for_provider; i < max_for_popup; i++)
+ matches_[i].relevance = -matches_[i].relevance;
+}
+
+AutocompleteMatch HistoryContentsProvider::ResultToMatch(
+ const history::URLResult& result,
+ int score) {
+ // TODO(sky): if matched title highlight matching words in title.
+ // Also show star in popup.
+ AutocompleteMatch match(this, score, false);
+ match.fill_into_edit = StringForURLDisplay(result.url(), true);
+ match.destination_url = UTF8ToWide(result.url().spec());
+ match.contents = match.fill_into_edit;
+ match.contents_class.push_back(
+ ACMatchClassification(0, ACMatchClassification::URL));
+ match.description = result.title();
+ match.starred = result.starred();
+
+ ClassifyDescription(result, &match);
+ return match;
+}
+
+void HistoryContentsProvider::ClassifyDescription(
+ const history::URLResult& result,
+ AutocompleteMatch* match) const {
+ const Snippet::MatchPositions& title_matches = result.title_match_positions();
+
+ size_t offset = 0;
+ if (!title_matches.empty()) {
+ // Classify matches in the title.
+ for (Snippet::MatchPositions::const_iterator i = title_matches.begin();
+ i != title_matches.end(); ++i) {
+ if (i->first != offset) {
+ match->description_class.push_back(
+ ACMatchClassification(offset, ACMatchClassification::NONE));
+ }
+ match->description_class.push_back(
+ ACMatchClassification(i->first, ACMatchClassification::MATCH));
+ offset = i->second;
+ }
+ }
+ if (offset != result.title().size()) {
+ match->description_class.push_back(
+ ACMatchClassification(offset, ACMatchClassification::NONE));
+ }
+}
+
+int HistoryContentsProvider::CalculateRelevance(
+ const history::URLResult& result) {
+ bool in_title = !!result.title_match_positions().size();
+
+ switch (input_type_) {
+ case AutocompleteInput::UNKNOWN:
+ case AutocompleteInput::REQUESTED_URL:
+ if (result.starred()) {
+ return in_title ? 1000 + star_title_count_++ :
+ 550 + star_contents_count_++;
+ } else {
+ return in_title ? 700 + title_count_++ :
+ 500 + contents_count_++;
+ }
+
+ case AutocompleteInput::QUERY:
+ case AutocompleteInput::FORCED_QUERY:
+ if (result.starred()) {
+ return in_title ? 1200 + star_title_count_++ :
+ 750 + star_contents_count_++;
+ } else {
+ return in_title ? 900 + title_count_++ :
+ 700 + contents_count_++;
+ }
+
+ default:
+ NOTREACHED();
+ return 0;
+ }
+}
diff --git a/chrome/browser/autocomplete/history_contents_provider.h b/chrome/browser/autocomplete/history_contents_provider.h
new file mode 100644
index 0000000..ed1b26c
--- /dev/null
+++ b/chrome/browser/autocomplete/history_contents_provider.h
@@ -0,0 +1,127 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CHROME_BROWSER_AUTOCOMPLETE_HISTORY_CONTENTS_PROVIDER_H__
+#define CHROME_BROWSER_AUTOCOMPLETE_HISTORY_CONTENTS_PROVIDER_H__
+
+#include "chrome/browser/autocomplete/autocomplete.h"
+#include "chrome/browser/history/history.h"
+
+// HistoryContentsProvider is an AutocompleteProvider that provides results from
+// the contents (body and/or title) of previously visited pages. Results are
+// obtained asynchronously from the history service.
+class HistoryContentsProvider : public AutocompleteProvider {
+ public:
+ HistoryContentsProvider(ACProviderListener* listener, Profile* profile)
+ : AutocompleteProvider(listener, profile, "HistoryContents"),
+ history_service_(NULL),
+ have_results_(false) {
+ DCHECK(profile);
+ }
+
+#ifdef UNIT_TEST
+ HistoryContentsProvider(ACProviderListener* listener,
+ HistoryService* history_service)
+ : AutocompleteProvider(listener, NULL, "HistoryContents"),
+ history_service_(history_service),
+ have_results_(false) {
+ }
+#endif
+
+ // As necessary asks the history service for the relevant results. When
+ // done SetResults is invoked.
+ virtual void Start(const AutocompleteInput& input,
+ bool minimal_changes,
+ bool synchronous_only);
+
+ virtual void Stop();
+
+ // Returns the total number of matches available in the database, up to
+ // kMaxMatchCount, whichever is smaller.
+ // Return value is only valid if done() returns true.
+ size_t db_match_count() const { return db_match_count_; }
+
+ // The maximum match count we'll report. If the db_match_count is greater
+ // than this, it will be clamped to this result.
+ static const int kMaxMatchCount = 50;
+
+ private:
+ void QueryComplete(HistoryService::Handle handle,
+ history::QueryResults* results);
+
+ // Converts each MatchingPageResult in results_ to an AutocompleteMatch and
+ // adds it to matches_.
+ void ConvertResults();
+
+ // Creates and returns an AutocompleteMatch from a MatchingPageResult.
+ AutocompleteMatch ResultToMatch(const history::URLResult& result,
+ int score);
+
+ // Adds ACMatchClassifications to match from the offset positions in
+ // page_result.
+ void ClassifyDescription(const history::URLResult& result,
+ AutocompleteMatch* match) const;
+
+ // Calculates and returns the relevance of a particular result. See the
+ // chart in autocomplete.h for the list of values this returns.
+ int CalculateRelevance(const history::URLResult& result);
+
+ CancelableRequestConsumerT<int, 0> request_consumer_;
+
+ // This is only non-null for testing, otherwise the HistoryService from the
+ // Profile is used.
+ HistoryService* history_service_;
+
+ // The number of times we're returned each different type of result. These are
+ // used by CalculateRelevance. Initialized in Start.
+ int star_title_count_;
+ int star_contents_count_;
+ int title_count_;
+ int contents_count_;
+
+ // Current autocomplete input type.
+ AutocompleteInput::Type input_type_;
+
+ // Results from most recent query. These are cached so we don't have to
+ // re-issue queries for "minor changes" (which don't affect this provider).
+ history::QueryResults results_;
+
+ // Whether results_ is valid (so we can tell invalid apart from empty).
+ bool have_results_;
+
+ // Current query string.
+ std::wstring query_;
+
+ // Total number of matches available in the database.
+ int db_match_count_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(HistoryContentsProvider);
+};
+
+#endif // CHROME_BROWSER_AUTOCOMPLETE_HISTORY_CONTENTS_PROVIDER_H__
diff --git a/chrome/browser/autocomplete/history_contents_provider_unittest.cc b/chrome/browser/autocomplete/history_contents_provider_unittest.cc
new file mode 100644
index 0000000..d84ec78
--- /dev/null
+++ b/chrome/browser/autocomplete/history_contents_provider_unittest.cc
@@ -0,0 +1,179 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "base/string_util.h"
+#include "chrome/browser/autocomplete/autocomplete.h"
+#include "chrome/browser/autocomplete/history_contents_provider.h"
+#include "chrome/browser/history/history.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+struct TestEntry {
+ const char* url;
+ const wchar_t* title;
+ const wchar_t* body;
+} test_entries[] = {
+ {"http://www.google.com/1", L"PAGEONE 1", L"FOO some body text"},
+ {"http://www.google.com/2", L"PAGEONE 2", L"FOO some more blah blah"},
+ {"http://www.google.com/3", L"PAGETHREE 3", L"BAR some hello world for you"},
+};
+
+// For comparing TestEntry.url with wide strings generated by the autocomplete
+// code
+bool UrlIs(const char* url, const std::wstring& str) {
+ return WideToUTF8(str) == std::string(url);
+}
+
+class HistoryContentsProviderTest : public testing::Test,
+ public ACProviderListener {
+ public:
+
+ void RunQuery(const AutocompleteInput& input,
+ bool minimal_changes,
+ bool synchronous_only) {
+ provider_->Start(input, minimal_changes, synchronous_only);
+
+ // When we're waiting for asynchronous messages, we have to spin the message
+ // loop. This will be exited in the OnProviderUpdate function when complete.
+ if (!synchronous_only)
+ MessageLoop::current()->Run();
+ }
+
+ const ACMatches& matches() const { return provider_->matches(); }
+
+ private:
+ // testing::Test
+ virtual void SetUp() {
+ PathService::Get(base::DIR_TEMP, &history_dir_);
+ file_util::AppendToPath(&history_dir_, L"HistoryContentProviderTest");
+ file_util::Delete(history_dir_, true); // Normally won't exist.
+ file_util::CreateDirectoryW(history_dir_);
+
+ history_service_ = new HistoryService;
+ history_service_->Init(history_dir_);
+
+ // Populate history.
+ for (int i = 0; i < arraysize(test_entries); i++) {
+ // We need the ID scope and page ID so that the visit tracker can find it.
+ // We just use the index for the page ID below.
+ const void* id_scope = reinterpret_cast<void*>(1);
+ GURL url(test_entries[i].url);
+
+ // Add everything in order of time. We don't want to have a time that
+ // is "right now" or it will nondeterministically appear in the results.
+ Time t = Time::Now() - TimeDelta::FromDays(arraysize(test_entries) + i);
+
+ history_service_->AddPage(url, t, id_scope, i, GURL(),
+ PageTransition::LINK, HistoryService::RedirectList());
+ history_service_->SetPageTitle(url, test_entries[i].title);
+ history_service_->SetPageContents(url, test_entries[i].body);
+ }
+
+ provider_ = new HistoryContentsProvider(this, history_service_);
+ }
+
+ virtual void TearDown() {
+ history_service_->SetOnBackendDestroyTask(new MessageLoop::QuitTask);
+ history_service_->Cleanup();
+ provider_ = NULL;
+ history_service_ = NULL;
+
+ // Wait for history thread to complete (the QuitTask will cause it to exit
+ // on destruction). Note: if this never terminates, somebody is probably
+ // leaking a reference to the history backend, so it never calls our
+ // destroy task.
+ MessageLoop::current()->Run();
+
+ file_util::Delete(history_dir_, true);
+ }
+
+ // ACProviderListener
+ virtual void OnProviderUpdate(bool updated_matches) {
+ // When we quit, the test will get back control.
+ MessageLoop::current()->Quit();
+ }
+
+ std::wstring history_dir_;
+
+ scoped_refptr<HistoryContentsProvider> provider_;
+ scoped_refptr<HistoryService> history_service_;
+};
+
+} // namespace
+
+TEST_F(HistoryContentsProviderTest, Body) {
+ AutocompleteInput input(L"FOO", std::wstring(), true);
+ RunQuery(input, false, false);
+
+ // The results should be the first two pages, in decreasing order.
+ const ACMatches& m = matches();
+ ASSERT_EQ(2, m.size());
+ EXPECT_TRUE(UrlIs(test_entries[1].url, m[0].destination_url));
+ EXPECT_STREQ(test_entries[1].title, m[0].description.c_str());
+ EXPECT_TRUE(UrlIs(test_entries[0].url, m[1].destination_url));
+ EXPECT_STREQ(test_entries[0].title, m[1].description.c_str());
+}
+
+TEST_F(HistoryContentsProviderTest, Title) {
+ AutocompleteInput input(L"PAGEONE", std::wstring(), true);
+ RunQuery(input, false, false);
+
+ // The results should be the first two pages.
+ const ACMatches& m = matches();
+ ASSERT_EQ(2, m.size());
+ EXPECT_TRUE(UrlIs(test_entries[1].url, m[0].destination_url));
+ EXPECT_STREQ(test_entries[1].title, m[0].description.c_str());
+ EXPECT_TRUE(UrlIs(test_entries[0].url, m[1].destination_url));
+ EXPECT_STREQ(test_entries[0].title, m[1].description.c_str());
+}
+
+// The "minimal changes" flag should mean that we don't re-query the DB.
+TEST_F(HistoryContentsProviderTest, MinimalChanges) {
+ AutocompleteInput input(L"PAGEONE", std::wstring(), true);
+
+ // A minimal changes request when there have been no real queries should
+ // give us no results.
+ RunQuery(input, true, true);
+ const ACMatches& m1 = matches();
+ EXPECT_EQ(0, m1.size());
+
+ // Now do a "regular" query to get the results.
+ RunQuery(input, false, false);
+ const ACMatches& m2 = matches();
+ EXPECT_EQ(2, m2.size());
+
+ // Now do a minimal one where we want synchronous results, and the results
+ // should still be there.
+ RunQuery(input, true, true);
+ const ACMatches& m3 = matches();
+ EXPECT_EQ(2, m3.size());
+} \ No newline at end of file
diff --git a/chrome/browser/autocomplete/history_url_provider.cc b/chrome/browser/autocomplete/history_url_provider.cc
new file mode 100644
index 0000000..1db8c00
--- /dev/null
+++ b/chrome/browser/autocomplete/history_url_provider.cc
@@ -0,0 +1,856 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "chrome/browser/autocomplete/history_url_provider.h"
+
+#include <algorithm>
+
+#include "base/histogram.h"
+#include "base/message_loop.h"
+#include "base/string_util.h"
+#include "chrome/browser/history/history.h"
+#include "chrome/browser/history/history_backend.h"
+#include "chrome/browser/history/history_database.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/url_fixer_upper.h"
+#include "chrome/common/gfx/url_elider.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/pref_service.h"
+#include "chrome/common/sqlite_utils.h"
+#include "googleurl/src/gurl.h"
+#include "googleurl/src/url_parse.h"
+#include "googleurl/src/url_util.h"
+#include "net/base/net_util.h"
+
+HistoryURLProviderParams::HistoryURLProviderParams(
+ const AutocompleteInput& input,
+ bool trim_http,
+ const ACMatches& matches,
+ const std::wstring& languages)
+ : message_loop(MessageLoop::current()),
+ input(input),
+ trim_http(trim_http),
+ cancel(false),
+ matches(matches),
+ languages(languages) {
+}
+
+void HistoryURLProvider::Start(const AutocompleteInput& input,
+ bool minimal_changes,
+ bool synchronous_only) {
+ // NOTE: We could try hard to do less work in the |minimal_changes| case
+ // here; some clever caching would let us reuse the raw matches from the
+ // history DB without re-querying. However, we'd still have to go back to
+ // the history thread to mark these up properly, and if pass 2 is currently
+ // running, we'd need to wait for it to return to the main thread before
+ // doing this (we can't just write new data for it to read due to thread
+ // safety issues). At that point it's just as fast, and easier, to simply
+ // re-run the query from scratch and ignore |minimal_changes|.
+
+ // Cancel any in-progress query.
+ Stop();
+
+ RunAutocompletePasses(input, true, !synchronous_only);
+}
+
+void HistoryURLProvider::Stop() {
+ done_ = true;
+
+ if (params_)
+ params_->cancel = true;
+}
+
+void HistoryURLProvider::DeleteMatch(const AutocompleteMatch& match) {
+ // Delete the match from the history DB.
+ HistoryService* history_service =
+ profile_ ? profile_->GetHistoryService(Profile::EXPLICIT_ACCESS) :
+ history_service_;
+ GURL selected_url(match.destination_url);
+ if (!history_service || !selected_url.is_valid()) {
+ NOTREACHED() << "Can't delete requested URL";
+ return;
+ }
+ history_service->DeleteURL(selected_url);
+
+ // Delete the match from the current set of matches.
+ bool found = false;
+ for (ACMatches::iterator i(matches_.begin()); i != matches_.end(); ++i) {
+ if (i->destination_url == match.destination_url) {
+ found = true;
+ if (i->is_history_what_you_typed_match) {
+ // We can't get rid of the What You Typed match, but we can make it
+ // look like it has no backing data.
+ i->deletable = false;
+ i->description.clear();
+ i->description_class.clear();
+ } else {
+ matches_.erase(i);
+ }
+ break;
+ }
+ }
+ DCHECK(found) << "Asked to delete a URL that isn't in our set of matches";
+ listener_->OnProviderUpdate(true);
+
+ // Cancel any current pass 2 and rerun it, so we get correct history data.
+ if (!done_) {
+ // Copy params_->input to avoid a race condition where params_ gets deleted
+ // out from under us on the other thread after we set params_->cancel here.
+ AutocompleteInput input(params_->input);
+ params_->cancel = true;
+ RunAutocompletePasses(input, false, true);
+ }
+}
+
+// Called on the history thread.
+void HistoryURLProvider::ExecuteWithDB(history::HistoryBackend* backend,
+ history::URLDatabase* db,
+ HistoryURLProviderParams* params) {
+ // We may get called with a NULL database if it couldn't be properly
+ // initialized. In this case we just say the query is complete.
+ if (db && !params->cancel) {
+ TimeTicks beginning_time = TimeTicks::Now();
+
+ DoAutocomplete(backend, db, params);
+
+ HISTOGRAM_TIMES(L"Autocomplete.HistoryAsyncQueryTime",
+ TimeTicks::Now() - beginning_time);
+ }
+
+ // Return the results (if any) to the main thread.
+ params->message_loop->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &HistoryURLProvider::QueryComplete, params));
+}
+
+// Used by both autocomplete passes, and therefore called on multiple different
+// threads (though not simultaneously).
+void HistoryURLProvider::DoAutocomplete(history::HistoryBackend* backend,
+ history::URLDatabase* db,
+ HistoryURLProviderParams* params) {
+ // Get the matching URLs from the DB
+ typedef std::vector<history::URLRow> URLRowVector;
+ URLRowVector url_matches;
+ HistoryMatches history_matches;
+ for (Prefixes::const_iterator i(prefixes_.begin()); i != prefixes_.end();
+ ++i) {
+ if (params->cancel)
+ return; // canceled in the middle of a query, give up
+ // We only need max_matches results in the end, but before we get there we
+ // need to promote lower-quality matches that are prefixes of
+ // higher-quality matches, and remove lower-quality redirects. So we ask
+ // for more results than we need, of every prefix type, in hopes this will
+ // give us far more than enough to work with. CullRedirects() will then
+ // reduce the list to the best max_matches results.
+ db->AutocompleteForPrefix(i->prefix + params->input.text(),
+ max_matches() * 2, &url_matches);
+ for (URLRowVector::const_iterator j(url_matches.begin());
+ j != url_matches.end(); ++j) {
+ const Prefix* best_prefix = BestPrefix(UTF8ToWide(j->url().spec()),
+ std::wstring());
+ DCHECK(best_prefix != NULL);
+ history_matches.push_back(HistoryMatch(*j, i->prefix.length(),
+ !i->num_components,
+ i->num_components >= best_prefix->num_components));
+ }
+ }
+
+ // Create sorted list of suggestions.
+ CullPoorMatches(&history_matches);
+ SortMatches(&history_matches);
+ PromoteOrCreateShorterSuggestion(db, *params, &history_matches);
+
+ // Try to promote a match as an exact/inline autocomplete match. This also
+ // moves it to the front of |history_matches|, so skip over it when
+ // converting the rest of the matches. We want to provide up to max_matches
+ // results plus the What You Typed result.
+ size_t first_match = 1;
+ size_t exact_suggestion = 0;
+ if (!params->matches.empty() &&
+ FixupExactSuggestion(db, params, &history_matches))
+ exact_suggestion = 1;
+ else if (params->input.prevent_inline_autocomplete() ||
+ history_matches.empty() ||
+ !PromoteMatchForInlineAutocomplete(params, history_matches.front()))
+ first_match = 0;
+
+ // This is the end of the synchronous pass.
+ if (!backend)
+ return;
+
+ // Remove redirects and trim list to size.
+ CullRedirects(backend, &history_matches, max_matches() + exact_suggestion);
+
+ // Convert the history matches to autocomplete matches.
+ for (size_t i = first_match; i < history_matches.size(); ++i) {
+ const HistoryMatch& match = history_matches[i];
+ DCHECK(!exact_suggestion ||
+ (match.url_info.url() !=
+ GURL(params->matches.front().destination_url)));
+ params->matches.push_back(HistoryMatchToACMatch(params, match, NORMAL,
+ history_matches.size() - 1 - i));
+ }
+}
+
+// Called on the main thread when the query is complete.
+void HistoryURLProvider::QueryComplete(
+ HistoryURLProviderParams* params_gets_deleted) {
+ // Ensure |params_gets_deleted| gets deleted on exit.
+ scoped_ptr<HistoryURLProviderParams> params(params_gets_deleted);
+
+ // If the user hasn't already started another query, clear our member pointer
+ // so we can't write into deleted memory.
+ if (params_ == params_gets_deleted)
+ params_ = NULL;
+
+ // Don't send responses for queries that have been canceled.
+ if (params->cancel)
+ return; // Already set done_ when we canceled, no need to set it again.
+
+ done_ = true;
+ matches_.swap(params->matches);
+ listener_->OnProviderUpdate(true);
+}
+
+void HistoryURLProvider::SuggestExactInput(const AutocompleteInput& input,
+ bool trim_http) {
+ AutocompleteMatch match(this,
+ CalculateRelevance(input.type(), WHAT_YOU_TYPED, 0), false);
+
+ // Try to canonicalize the URL. If this fails, don't create a What You Typed
+ // suggestion, since it can't be navigated to. We also need this so other
+ // history suggestions don't duplicate the same effective URL as this.
+ // TODO(brettw) make autocomplete use GURL!
+ GURL canonicalized_url(URLFixerUpper::FixupURL(input.text(),
+ input.desired_tld()));
+ if (!canonicalized_url.is_valid() ||
+ (canonicalized_url.IsStandard() &&
+ !canonicalized_url.SchemeIsFile() && canonicalized_url.host().empty()))
+ return;
+ match.destination_url = UTF8ToWide(canonicalized_url.spec());
+ match.fill_into_edit = StringForURLDisplay(canonicalized_url, false);
+ // NOTE: Don't set match.input_location (to allow inline autocompletion)
+ // here, it's surprising and annoying.
+ // Trim off "http://" if the user didn't type it.
+ const size_t offset = trim_http ? TrimHttpPrefix(&match.fill_into_edit) : 0;
+
+ // Try to highlight "innermost" match location. If we fix up "w" into
+ // "www.w.com", we want to highlight the fifth character, not the first.
+ // This relies on match.destination_url being the non-prefix-trimmed version
+ // of match.contents.
+ match.contents = match.fill_into_edit;
+ const Prefix* best_prefix = BestPrefix(match.destination_url, input.text());
+ // Because of the vagaries of GURL, it's possible for match.destination_url
+ // to not contain the user's input at all. In this case don't mark anything
+ // as a match.
+ const size_t match_location = (best_prefix == NULL) ?
+ std::wstring::npos : best_prefix->prefix.length() - offset;
+ AutocompleteMatch::ClassifyLocationInString(match_location,
+ input.text().length(),
+ match.contents.length(),
+ ACMatchClassification::URL,
+ &match.contents_class);
+
+ match.is_history_what_you_typed_match = true;
+ matches_.push_back(match);
+}
+
+bool HistoryURLProvider::FixupExactSuggestion(history::URLDatabase* db,
+ HistoryURLProviderParams* params,
+ HistoryMatches* matches) const {
+ DCHECK(!params->matches.empty());
+
+ history::URLRow info;
+ AutocompleteMatch& match = params->matches.front();
+
+ // Tricky corner case: The user has visited intranet site "foo", but not
+ // internet site "www.foo.com". He types in foo (getting an exact match),
+ // then tries to hit ctrl-enter. When pressing ctrl, the what-you-typed
+ // match ("www.foo.com") doesn't show up in history, and thus doesn't get a
+ // promoted relevance, but a different match from the input ("foo") does, and
+ // gets promoted for inline autocomplete. Thus instead of getting
+ // "www.foo.com", the user still gets "foo" (and, before hitting enter,
+ // probably gets an odd-looking inline autocomplete of "/").
+ //
+ // We detect this crazy case as follows:
+ // * If the what-you-typed match is not in the history DB,
+ // * and the user has specified a TLD,
+ // * and the input _without_ the TLD _is_ in the history DB,
+ // * ...then just before pressing "ctrl" the best match we supplied was the
+ // what-you-typed match, so stick with it by promoting this.
+ if (!db->GetRowForURL(GURL(match.destination_url), &info)) {
+ if (params->input.desired_tld().empty())
+ return false;
+ // This code should match what SuggestExactInput() would do with no
+ // desired_tld().
+ // TODO(brettw) make autocomplete use GURL!
+ GURL destination_url(URLFixerUpper::FixupURL(params->input.text(),
+ std::wstring()));
+ if (!db->GetRowForURL(destination_url, &info))
+ return false;
+ } else {
+ // We have data for this match, use it.
+ match.starred = info.starred();
+ match.deletable = true;
+ match.description = info.title();
+ AutocompleteMatch::ClassifyMatchInString(params->input.text(),
+ info.title(),
+ ACMatchClassification::NONE,
+ &match.description_class);
+ }
+
+ // Promote as an exact match.
+ match.relevance = CalculateRelevance(params->input.type(),
+ INLINE_AUTOCOMPLETE, 0);
+
+ // Put it on the front of the HistoryMatches for redirect culling.
+ EnsureMatchPresent(info, std::wstring::npos, false, matches, true);
+ return true;
+}
+
+bool HistoryURLProvider::PromoteMatchForInlineAutocomplete(
+ HistoryURLProviderParams* params,
+ const HistoryMatch& match) {
+ // Promote the first match if it's been typed at least n times, where n == 1
+ // for "simple" (host-only) URLs and n == 2 for others. We set a higher bar
+ // for these long URLs because it's less likely that users will want to visit
+ // them again. Even though we don't increment the typed_count for pasted-in
+ // URLs, if the user manually edits the URL or types some long thing in by
+ // hand, we wouldn't want to immediately start autocompleting it.
+ if (!match.url_info.typed_count() ||
+ ((match.url_info.typed_count() == 1) &&
+ !IsHostOnly(match.url_info.url())))
+ return false;
+
+ params->matches.push_back(HistoryMatchToACMatch(params, match,
+ INLINE_AUTOCOMPLETE, 0));
+ return true;
+}
+
+// static
+std::wstring HistoryURLProvider::FixupUserInput(const std::wstring& input) {
+ // Fixup and canonicalize user input.
+ const GURL canonical_gurl(URLFixerUpper::FixupURL(input, std::wstring()));
+ std::wstring output(UTF8ToWide(canonical_gurl.possibly_invalid_spec()));
+ if (output.empty())
+ return input; // This probably won't happen, but there are no guarantees.
+
+ // Don't prepend a scheme when the user didn't have one. Since the fixer
+ // upper only prepends the "http" scheme, that's all we need to check for.
+ url_parse::Component scheme;
+ if (canonical_gurl.SchemeIs("http") &&
+ !url_util::FindAndCompareScheme(input, "http", &scheme))
+ TrimHttpPrefix(&output);
+
+ // Make the number of trailing slashes on the output exactly match the input.
+ // Examples of why not doing this would matter:
+ // * The user types "a" and has this fixed up to "a/". Now no other sites
+ // beginning with "a" will match.
+ // * The user types "file:" and has this fixed up to "file://". Now inline
+ // autocomplete will append too few slashes, resulting in e.g. "file:/b..."
+ // instead of "file:///b..."
+ // * The user types "http:/" and has this fixed up to "http:". Now inline
+ // autocomplete will append too many slashes, resulting in e.g.
+ // "http:///c..." instead of "http://c...".
+ // NOTE: We do this after calling TrimHttpPrefix() since that can strip
+ // trailing slashes (if the scheme is the only thing in the input). It's not
+ // clear that the result of fixup really matters in this case, but there's no
+ // harm in making sure.
+ const size_t last_input_nonslash = input.find_last_not_of(L"/\\");
+ const size_t num_input_slashes = (last_input_nonslash == std::wstring::npos) ?
+ input.length() : (input.length() - 1 - last_input_nonslash);
+ const size_t last_output_nonslash = output.find_last_not_of(L"/\\");
+ const size_t num_output_slashes =
+ (last_output_nonslash == std::wstring::npos) ?
+ output.length() : (output.length() - 1 - last_output_nonslash);
+ if (num_output_slashes < num_input_slashes)
+ output.append(num_input_slashes - num_output_slashes, '/');
+ else if (num_output_slashes > num_input_slashes)
+ output.erase(output.length() - num_output_slashes + num_input_slashes);
+
+ return output;
+}
+
+// static
+size_t HistoryURLProvider::TrimHttpPrefix(std::wstring* url) {
+ url_parse::Component scheme;
+ if (!url_util::FindAndCompareScheme(*url, "http", &scheme))
+ return 0; // Not "http".
+
+ // Erase scheme plus up to two slashes.
+ size_t prefix_len = scheme.end() + 1; // "http:"
+ const size_t after_slashes = std::min(url->length(),
+ static_cast<size_t>(scheme.end() + 3));
+ while ((prefix_len < after_slashes) && ((*url)[prefix_len] == L'/'))
+ ++prefix_len;
+ if (prefix_len == url->length())
+ url->clear();
+ else
+ url->erase(url->begin(), url->begin() + prefix_len);
+ return prefix_len;
+}
+
+// static
+bool HistoryURLProvider::IsHostOnly(const GURL& url) {
+ DCHECK(url.is_valid());
+ return (!url.has_path() || (url.path() == "/")) && !url.has_query() &&
+ !url.has_ref();
+}
+
+// static
+bool HistoryURLProvider::CompareHistoryMatch(const HistoryMatch& a,
+ const HistoryMatch& b) {
+ // A URL that has been typed at all is better than one that has never been
+ // typed. (Note "!"s on each side)
+ if (!a.url_info.typed_count() != !b.url_info.typed_count())
+ return a.url_info.typed_count() > b.url_info.typed_count();
+
+ // Innermost matches (matches after any scheme or "www.") are better than
+ // non-innermost matches.
+ if (a.innermost_match != b.innermost_match)
+ return a.innermost_match;
+
+ // URLs that have been typed more often are better.
+ if (a.url_info.typed_count() != b.url_info.typed_count())
+ return a.url_info.typed_count() > b.url_info.typed_count();
+
+ // Starred pages are better than unstarred pages.
+ if (a.url_info.starred() != b.url_info.starred())
+ return a.url_info.starred();
+
+ // For URLs that have each been typed once, a host (alone) is better than a
+ // page inside.
+ if (a.url_info.typed_count() == 1) {
+ const bool a_is_host_only = IsHostOnly(a.url_info.url());
+ if (a_is_host_only != IsHostOnly(b.url_info.url()))
+ return a_is_host_only;
+ }
+
+ // URLs that have been visited more often are better.
+ if (a.url_info.visit_count() != b.url_info.visit_count())
+ return a.url_info.visit_count() > b.url_info.visit_count();
+
+ // URLs that have been visited more recently are better.
+ return a.url_info.last_visit() > b.url_info.last_visit();
+}
+
+// static
+HistoryURLProvider::Prefixes HistoryURLProvider::GetPrefixes() {
+ // We'll complete text following these prefixes.
+ // NOTE: There's no requirement that these be in any particular order.
+ Prefixes prefixes;
+ prefixes.push_back(Prefix(L"https://www.", 2));
+ prefixes.push_back(Prefix(L"http://www.", 2));
+ prefixes.push_back(Prefix(L"ftp://ftp.", 2));
+ prefixes.push_back(Prefix(L"ftp://www.", 2));
+ prefixes.push_back(Prefix(L"https://", 1));
+ prefixes.push_back(Prefix(L"http://", 1));
+ prefixes.push_back(Prefix(L"ftp://", 1));
+ prefixes.push_back(Prefix(L"", 0)); // Catches within-scheme matches as well
+ return prefixes;
+}
+
+// static
+int HistoryURLProvider::CalculateRelevance(AutocompleteInput::Type input_type,
+ MatchType match_type,
+ size_t match_number) {
+ switch (match_type) {
+ case INLINE_AUTOCOMPLETE:
+ return 1400;
+
+ case WHAT_YOU_TYPED:
+ return (input_type == AutocompleteInput::REQUESTED_URL) ? 1300 : 1200;
+
+ default:
+ return 900 + static_cast<int>(match_number);
+ }
+}
+
+// static
+GURL HistoryURLProvider::ConvertToHostOnly(const HistoryMatch& match,
+ const std::wstring& input) {
+ // See if we should try to do host-only suggestions for this URL. Nonstandard
+ // schemes means there's no authority section, so suggesting the host name
+ // is useless. File URLs are standard, but host suggestion is not useful for
+ // them either.
+ const GURL& url = match.url_info.url();
+ if (!url.is_valid() || !url.IsStandard() || url.SchemeIsFile())
+ return GURL();
+
+ // Transform to a host-only match. Bail if the host no longer matches the
+ // user input (e.g. because the user typed more than just a host).
+ GURL host = url.GetWithEmptyPath();
+ if ((host.spec().length() < (match.input_location + input.length())))
+ return GURL(); // User typing is longer than this host suggestion.
+
+ const std::wstring spec = UTF8ToWide(host.spec());
+ if (spec.compare(match.input_location, input.length(), input))
+ return GURL(); // User typing is no longer a prefix.
+
+ return host;
+}
+
+// static
+void HistoryURLProvider::PromoteOrCreateShorterSuggestion(
+ history::URLDatabase* db,
+ const HistoryURLProviderParams& params,
+ HistoryMatches* matches) {
+ if (matches->empty())
+ return; // No matches, nothing to do.
+
+ // Determine the base URL from which to search, and whether that URL could
+ // itself be added as a match. We can add the base iff it's not "effectively
+ // the same" as any "what you typed" match.
+ const HistoryMatch& match = matches->front();
+ GURL search_base = ConvertToHostOnly(match, params.input.text());
+ bool can_add_search_base_to_matches = params.matches.empty();
+ if (search_base.is_empty()) {
+ // Search from what the user typed when we couldn't reduce the best match
+ // to a host. Careful: use a substring of |match| here, rather than the
+ // first match in |params|, because they might have different prefixes. If
+ // the user typed "google.com", params.matches will hold
+ // "http://google.com/", but |match| might begin with
+ // "http://www.google.com/".
+ // TODO: this should be cleaned up, and is probably incorrect for IDN.
+ std::string new_match = match.url_info.url().possibly_invalid_spec().
+ substr(0, match.input_location + params.input.text().length());
+ search_base = GURL(new_match);
+
+ } else if (!can_add_search_base_to_matches) {
+ // TODO(brettw) this extra GURL conversion should be unnecessary.
+ can_add_search_base_to_matches =
+ (search_base != GURL(params.matches.front().destination_url));
+ }
+ if (search_base == match.url_info.url())
+ return; // Couldn't shorten |match|, so no range of URLs to search over.
+
+ // Search the DB for short URLs between our base and |match|.
+ history::URLRow info(search_base);
+ bool promote = true;
+ // A short URL is only worth suggesting if it's been visited at least a third
+ // as often as the longer URL.
+ const int min_visit_count = ((match.url_info.visit_count() - 1) / 3) + 1;
+ // For stability between the in-memory and on-disk autocomplete passes, when
+ // the long URL has been typed before, only suggest shorter URLs that have
+ // also been typed. Otherwise, the on-disk pass could suggest a shorter URL
+ // (which hasn't been typed) that the in-memory pass doesn't know about,
+ // thereby making the top match, and thus the behavior of inline
+ // autocomplete, unstable.
+ const int min_typed_count = match.url_info.typed_count() ? 1 : 0;
+ if (!db->FindShortestURLFromBase(search_base.possibly_invalid_spec(),
+ match.url_info.url().possibly_invalid_spec(), min_visit_count,
+ min_typed_count, can_add_search_base_to_matches, &info)) {
+ if (!can_add_search_base_to_matches)
+ return; // Couldn't find anything and can't add the search base, bail.
+
+ // Try to get info on the search base itself. Promote it to the top if the
+ // original best match isn't good enough to autocomplete.
+ db->GetRowForURL(search_base, &info);
+ promote = match.url_info.typed_count() <= 1;
+ }
+
+ // Promote or add the desired URL to the list of matches.
+ EnsureMatchPresent(info, match.input_location, match.match_in_scheme,
+ matches, promote);
+}
+
+// static
+void HistoryURLProvider::EnsureMatchPresent(
+ const history::URLRow& info,
+ std::wstring::size_type input_location,
+ bool match_in_scheme,
+ HistoryMatches* matches,
+ bool promote) {
+ // |matches| may already have an entry for this.
+ for (HistoryMatches::iterator i(matches->begin()); i != matches->end();
+ ++i) {
+ if (i->url_info.url() == info.url()) {
+ // Rotate it to the front if the caller wishes.
+ if (promote)
+ std::rotate(matches->begin(), i, i + 1);
+ return;
+ }
+ }
+
+ // No entry, so create one.
+ HistoryMatch match(info, input_location, match_in_scheme, true);
+ if (promote)
+ matches->push_front(match);
+ else
+ matches->push_back(match);
+}
+
+void HistoryURLProvider::RunAutocompletePasses(const AutocompleteInput& input,
+ bool fixup_input_and_run_pass_1,
+ bool run_pass_2) {
+ matches_.clear();
+
+ if ((input.type() != AutocompleteInput::UNKNOWN) &&
+ (input.type() != AutocompleteInput::REQUESTED_URL) &&
+ (input.type() != AutocompleteInput::URL))
+ return;
+
+ // Create a match for exactly what the user typed. This will always be one
+ // of the top two results we return.
+ const bool trim_http = !url_util::FindAndCompareScheme(input.text(),
+ "http", NULL);
+ SuggestExactInput(input, trim_http);
+
+ // We'll need the history service to run both passes, so try to obtain it.
+ HistoryService* const history_service = profile_ ?
+ profile_->GetHistoryService(Profile::EXPLICIT_ACCESS) : history_service_;
+ if (!history_service)
+ return;
+
+ // Create the data structure for the autocomplete passes. We'll save this off
+ // onto the |params_| member for later deletion below if we need to run pass
+ // 2.
+ const std::wstring& languages = profile_ ?
+ profile_->GetPrefs()->GetString(prefs::kAcceptLanguages) : std::wstring();
+ scoped_ptr<HistoryURLProviderParams> params(
+ new HistoryURLProviderParams(input, trim_http, matches_, languages));
+
+ if (fixup_input_and_run_pass_1) {
+ // Do some fixup on the user input before matching against it, so we provide
+ // good results for local file paths, input with spaces, etc.
+ // NOTE: This purposefully doesn't take input.desired_tld() into account; if
+ // it did, then holding "ctrl" would change all the results from the
+ // HistoryURLProvider provider, not just the What You Typed Result.
+ // However, this means we need to call this _after_ calling
+ // SuggestExactInput(), since that function does need to take
+ // input.desired_tld() into account; if it doesn't, it may convert "56" +
+ // ctrl into "0.0.0.56.com" instead of "56.com" like the user probably
+ // wanted. It's not a problem to call this after SuggestExactInput(),
+ // because that function fixes up the user's input in a way that's a
+ // superset of what FixupUserInput() does.
+ const std::wstring fixed_text(FixupUserInput(input.text()));
+ if (fixed_text.empty()) {
+ // Conceivably fixup could result in an empty string (although I don't
+ // have cases where this happens offhand). We can't do anything with
+ // empty input, so just bail; otherwise we'd crash later.
+ return;
+ }
+ params->input.set_text(fixed_text);
+
+ // Pass 1: Get the in-memory URL database, and use it to find and promote
+ // the inline autocomplete match, if any.
+ history::URLDatabase* url_db = history_service->in_memory_database();
+ // url_db can be NULL if it hasn't finished initializing (or failed to
+ // initialize). In this case all we can do is fall back on the second
+ // pass. Ultimately, we should probably try to ensure the history system
+ // starts properly before we get here, as otherwise this can cause
+ // inconsistent behavior when the user has just started the browser and
+ // tries to type immediately.
+ if (url_db) {
+ DoAutocomplete(NULL, url_db, params.get());
+ // params->matches now has the matches we should expose to the provider.
+ // Since pass 2 expects a "clean slate" set of matches that only contains
+ // the not-yet-fixed-up What You Typed match, which is exactly what
+ // matches_ currently contains, just swap them.
+ matches_.swap(params->matches);
+ }
+ }
+
+ // Pass 2: Ask the history service to call us back on the history thread,
+ // where we can read the full on-disk DB.
+ if (run_pass_2) {
+ done_ = false;
+ params_ = params.release(); // This object will be destroyed in
+ // QueryComplete() once we're done with it.
+ history_service->ScheduleAutocomplete(this, params_);
+ }
+}
+
+const HistoryURLProvider::Prefix* HistoryURLProvider::BestPrefix(
+ const std::wstring& text,
+ const std::wstring& prefix_suffix) const {
+ const Prefix* best_prefix = NULL;
+ for (Prefixes::const_iterator i(prefixes_.begin()); i != prefixes_.end();
+ ++i) {
+ if ((best_prefix == NULL) ||
+ (i->num_components > best_prefix->num_components)) {
+ std::wstring prefix_with_suffix(i->prefix + prefix_suffix);
+ if ((text.length() >= prefix_with_suffix.length()) &&
+ !text.compare(0, prefix_with_suffix.length(), prefix_with_suffix))
+ best_prefix = &(*i);
+ }
+ }
+ return best_prefix;
+}
+
+void HistoryURLProvider::SortMatches(HistoryMatches* matches) const {
+ // Sort by quality, best first.
+ std::sort(matches->begin(), matches->end(), &CompareHistoryMatch);
+
+ // Remove duplicate matches (caused by the search string appearing in one of
+ // the prefixes as well as after it). Consider the following scenario:
+ //
+ // User has visited "http://http.com" once and "http://htaccess.com" twice.
+ // User types "http". The autocomplete search with prefix "http://" returns
+ // the first host, while the search with prefix "" returns both hosts. Now
+ // we sort them into rank order:
+ // http://http.com (innermost_match)
+ // http://htaccess.com (!innermost_match, url_info.visit_count == 2)
+ // http://http.com (!innermost_match, url_info.visit_count == 1)
+ //
+ // The above scenario tells us we can't use std::unique(), since our
+ // duplicates are not always sequential. It also tells us we should remove
+ // the lower-quality duplicate(s), since otherwise the returned results won't
+ // be ordered correctly. This is easy to do: we just always remove the later
+ // element of a duplicate pair.
+ // Be careful! Because the vector contents may change as we remove elements,
+ // we use an index instead of an iterator in the outer loop, and don't
+ // precalculate the ending position.
+ for (size_t i = 0; i < matches->size(); ++i) {
+ HistoryMatches::iterator j(matches->begin() + i + 1);
+ while (j != matches->end()) {
+ if ((*matches)[i].url_info.url() == j->url_info.url())
+ j = matches->erase(j);
+ else
+ ++j;
+ }
+ }
+}
+
+void HistoryURLProvider::CullPoorMatches(HistoryMatches* matches) const {
+ static const int kLowQualityMatchTypedLimit = 1;
+ static const int kLowQualityMatchVisitLimit = 3;
+ static const int kLowQualityMatchAgeLimitInDays = 3;
+ Time recent_threshold =
+ Time::Now() - TimeDelta::FromDays(kLowQualityMatchAgeLimitInDays);
+ for (HistoryMatches::iterator i(matches->begin()); i != matches->end();) {
+ const history::URLRow& url_info = i->url_info;
+ if ((url_info.typed_count() <= kLowQualityMatchTypedLimit) &&
+ (url_info.visit_count() <= kLowQualityMatchVisitLimit) &&
+ (url_info.last_visit() < recent_threshold)) {
+ i = matches->erase(i);
+ } else {
+ ++i;
+ }
+ }
+}
+
+void HistoryURLProvider::CullRedirects(history::HistoryBackend* backend,
+ HistoryMatches* matches,
+ size_t max_results) const {
+ for (size_t source = 0;
+ (source < matches->size()) && (source < max_results); ) {
+ const GURL& url = (*matches)[source].url_info.url();
+ // TODO(brettw) this should go away when everything uses GURL.
+ HistoryService::RedirectList redirects;
+ backend->GetMostRecentRedirectsFrom(url, &redirects);
+ if (!redirects.empty()) {
+ // Remove all but the first occurrence of any of these redirects in the
+ // search results. We also must add the URL we queried for, since it may
+ // not be the first match and we'd want to remove it.
+ //
+ // For example, when A redirects to B and our matches are [A, X, B],
+ // we'll get B as the redirects from, and we want to remove the second
+ // item of that pair, removing B. If A redirects to B and our matches are
+ // [B, X, A], we'll want to remove A instead.
+ redirects.push_back(url);
+ source = RemoveSubsequentMatchesOf(matches, source, redirects);
+ } else {
+ // Advance to next item.
+ source++;
+ }
+ }
+
+ if (matches->size() > max_results)
+ matches->resize(max_results);
+}
+
+size_t HistoryURLProvider::RemoveSubsequentMatchesOf(
+ HistoryMatches* matches,
+ size_t source_index,
+ const std::vector<GURL>& remove) const {
+ size_t next_index = source_index + 1; // return value = item after source
+
+ // Find the first occurrence of any URL in the redirect chain. We want to
+ // keep this one since it is rated the highest.
+ HistoryMatches::iterator first(std::find_first_of(
+ matches->begin(), matches->end(), remove.begin(), remove.end()));
+ DCHECK(first != matches->end()) <<
+ "We should have always found at least the original URL.";
+
+ // Find any following occurrences of any URL in the redirect chain, these
+ // should be deleted.
+ HistoryMatches::iterator next(first);
+ next++; // Start searching immediately after the one we found already.
+ while (next != matches->end() &&
+ (next = std::find_first_of(next, matches->end(), remove.begin(),
+ remove.end())) != matches->end()) {
+ // Remove this item. When we remove an item before the source index, we
+ // need to shift it to the right and remember that so we can return it.
+ next = matches->erase(next);
+ if (static_cast<size_t>(next - matches->begin()) < next_index)
+ next_index--;
+ }
+ return next_index;
+}
+
+AutocompleteMatch HistoryURLProvider::HistoryMatchToACMatch(
+ HistoryURLProviderParams* params,
+ const HistoryMatch& history_match,
+ MatchType match_type,
+ size_t match_number) {
+ const history::URLRow& info = history_match.url_info;
+ AutocompleteMatch match(this,
+ CalculateRelevance(params->input.type(), match_type, match_number),
+ !!info.visit_count());
+ match.destination_url = UTF8ToWide(info.url().possibly_invalid_spec());
+ match.fill_into_edit = gfx::ElideUrl(info.url(), ChromeFont(), 0,
+ match_type == WHAT_YOU_TYPED ? std::wstring() : params->languages);
+ if (!params->input.prevent_inline_autocomplete()) {
+ match.inline_autocomplete_offset =
+ history_match.input_location + params->input.text().length();
+ }
+ size_t offset = 0;
+ if (params->trim_http && !history_match.match_in_scheme) {
+ offset = TrimHttpPrefix(&match.fill_into_edit);
+ if (match.inline_autocomplete_offset != std::wstring::npos) {
+ DCHECK(match.inline_autocomplete_offset >= offset);
+ match.inline_autocomplete_offset -= offset;
+ }
+ }
+ DCHECK((match.inline_autocomplete_offset == std::wstring::npos) ||
+ (match.inline_autocomplete_offset <= match.fill_into_edit.length()));
+
+ match.contents = match.fill_into_edit;
+ AutocompleteMatch::ClassifyLocationInString(
+ history_match.input_location - offset, params->input.text().length(),
+ match.contents.length(), ACMatchClassification::URL,
+ &match.contents_class);
+ match.description = info.title();
+ AutocompleteMatch::ClassifyMatchInString(params->input.text(), info.title(),
+ ACMatchClassification::NONE,
+ &match.description_class);
+
+ match.starred = history_match.url_info.starred();
+ return match;
+}
diff --git a/chrome/browser/autocomplete/history_url_provider.h b/chrome/browser/autocomplete/history_url_provider.h
new file mode 100644
index 0000000..cafdb5b
--- /dev/null
+++ b/chrome/browser/autocomplete/history_url_provider.h
@@ -0,0 +1,419 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CHROME_BROWSER_AUTOCOMPLETE_HISTORY_URL_PROVIDER_H__
+#define CHROME_BROWSER_AUTOCOMPLETE_HISTORY_URL_PROVIDER_H__
+
+#include <map>
+#include <vector>
+#include <deque>
+
+#include "base/ref_counted.h"
+#include "chrome/browser/autocomplete/autocomplete.h"
+#include "chrome/browser/history/history_database.h"
+#include "chrome/browser/profile.h"
+
+class HistoryService;
+class MessageLoop;
+
+namespace history {
+
+class HistoryBackend;
+
+} // namespace history
+
+
+// How history autocomplete works
+// ==============================
+//
+// Read down this diagram for temporal ordering.
+//
+// Main thread History thread
+// ----------- --------------
+// AutocompleteController::Start
+// -> HistoryURLProvider::Start
+// -> RunAutocompletePasses
+// -> SuggestExactInput
+// [params_ allocated]
+// -> DoAutocomplete (for inline autocomplete)
+// -> URLDatabase::AutocompleteForPrefix (on in-memory DB)
+// -> HistoryService::ScheduleAutocomplete
+// (return to controller) \
+// HistoryBackend::ScheduleAutocomplete
+// -> HistoryURLProvider::ExecuteWithDB
+// -> DoAutocomplete
+// -> URLDatabase::AutocompleteForPrefix
+// /
+// HistoryService::QueryComplete
+// [params_ destroyed]
+// -> AutocompleteProvider::Listener::OnProviderUpdate
+//
+// The autocomplete controller calls us, and must be called back, on the main
+// thread. When called, we run two autocomplete passes. The first pass runs
+// synchronously on the main thread and queries the in-memory URL database.
+// This pass promotes matches for inline autocomplete if applicable. We do
+// this synchronously so that users get consistent behavior when they type
+// quickly and hit enter, no matter how loaded the main history database is.
+// Doing this synchronously also prevents inline autocomplete from being
+// "flickery" in the AutocompleteEdit. Because the in-memory DB does not have
+// redirect data, results other than the top match might change between the
+// two passes, so we can't just decide to use this pass' matches as the final
+// results.
+//
+// The second autocomplete pass uses the full history database, which must be
+// queried on the history thread. Start() asks the history service schedule to
+// callback on the history thread with a pointer to the main database. When we
+// are done doing queries, we schedule a task on the main thread that notifies
+// the AutocompleteController that we're done.
+//
+// The communication between these threads is done using a
+// HistoryURLProviderParams object. This is allocated in the main thread, and
+// normally deleted in QueryComplete(). So that both autocomplete passes can
+// use the same code, we also use this to hold results during the first
+// autocomplete pass.
+//
+// While the second pass is running, the AutocompleteController may cancel the
+// request. This can happen frequently when the user is typing quickly. In
+// this case, the main thread sets params_->cancel, which the background thread
+// checks periodically. If it finds the flag set, it stops what it's doing
+// immediately and calls back to the main thread. (We don't delete the params
+// on the history thread, because we should only do that when we can safely
+// NULL out params_, and that must be done on the main thread.)
+
+// Used to communicate autocomplete parameters between threads via the history
+// service.
+struct HistoryURLProviderParams {
+ HistoryURLProviderParams(const AutocompleteInput& input,
+ bool trim_http,
+ const ACMatches& matches,
+ const std::wstring& languages);
+
+ MessageLoop* message_loop;
+
+ // A copy of the autocomplete input. We need the copy since this object will
+ // live beyond the original query while it runs on the history thread.
+ AutocompleteInput input;
+
+ // Set when "http://" should be trimmed from the beginning of the URLs.
+ bool trim_http;
+
+ // Set by the main thread to cancel this request. READ ONLY when running in
+ // ExecuteWithDB() on the history thread to prevent deadlock. If this flag is
+ // set when the query runs, the query will be abandoned. This allows us to
+ // avoid running queries that are no longer needed. Since we don't care if
+ // we run the extra queries, the lack of signaling is not a problem.
+ bool cancel;
+
+ // List of matches written by the history thread. We keep this separate list
+ // to avoid having the main thread read the provider's matches while the
+ // history thread is manipulating them. The provider copies this list back
+ // to matches_ on the main thread in QueryComplete().
+ ACMatches matches;
+
+ // Languages we should pass to gfx::ElideUrl.
+ std::wstring languages;
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(HistoryURLProviderParams);
+};
+
+// This class is an autocomplete provider and is also a pseudo-internal
+// component of the history system. See comments above.
+//
+// Note: This object can get leaked on shutdown if there are pending
+// requests on the database (which hold a reference to us). Normally, these
+// messages get flushed for each thread. We do a round trip from main, to
+// history, back to main while holding a reference. If the main thread
+// completes before the history thread, the message to delegate back to the
+// main thread will not run and the reference will leak. Therefore, don't do
+// anything on destruction.
+class HistoryURLProvider : public AutocompleteProvider {
+ public:
+ HistoryURLProvider(ACProviderListener* listener, Profile* profile)
+ : AutocompleteProvider(listener, profile, "HistoryURL"),
+ history_service_(NULL),
+ prefixes_(GetPrefixes()),
+ params_(NULL) {
+ }
+
+#ifdef UNIT_TEST
+ HistoryURLProvider(ACProviderListener* listener,
+ HistoryService* history_service)
+ : AutocompleteProvider(listener, NULL, "History"),
+ history_service_(history_service),
+ prefixes_(GetPrefixes()),
+ params_(NULL) {
+ }
+#endif
+ // no destructor (see note above)
+
+ // AutocompleteProvider
+ virtual void Start(const AutocompleteInput& input,
+ bool minimal_changes,
+ bool synchronous_only);
+ virtual void Stop();
+ virtual void DeleteMatch(const AutocompleteMatch& match);
+
+ // Runs the history query on the history thread, called by the history
+ // system. The history database MAY BE NULL in which case it is not
+ // available and we should return no data. Also schedules returning the
+ // results to the main thread
+ void ExecuteWithDB(history::HistoryBackend* backend,
+ history::URLDatabase* db,
+ HistoryURLProviderParams* params);
+
+ // Actually runs the autocomplete job on the given database, which is
+ // guaranteed not to be NULL.
+ void DoAutocomplete(history::HistoryBackend* backend,
+ history::URLDatabase* db,
+ HistoryURLProviderParams* params);
+
+ // Dispatches the results to the autocomplete controller. Called on the
+ // main thread by ExecuteWithDB when the results are available.
+ // Frees params_gets_deleted on exit.
+ void QueryComplete(HistoryURLProviderParams* params_gets_deleted);
+
+ private:
+ struct Prefix {
+ Prefix(std::wstring prefix, int num_components)
+ : prefix(prefix),
+ num_components(num_components) { }
+
+ std::wstring prefix;
+
+ // The number of "components" in the prefix. The scheme is a component,
+ // and the initial "www." or "ftp." is a component. So "http://foo.com"
+ // and "www.bar.com" each have one component, "ftp://ftp.ftp.com" has two,
+ // and "mysite.com" has none. This is used to tell whether the user's
+ // input is an innermost match or not. See comments in HistoryMatch.
+ int num_components;
+ };
+ typedef std::vector<Prefix> Prefixes;
+
+ // Used for intermediate history result operations.
+ struct HistoryMatch {
+ // Required for STL, we don't use this directly.
+ HistoryMatch()
+ : url_info(),
+ input_location(std::wstring::npos),
+ match_in_scheme(false),
+ innermost_match(true) {
+ }
+
+ HistoryMatch(const history::URLRow& url_info,
+ size_t input_location,
+ bool match_in_scheme,
+ bool innermost_match)
+ : url_info(url_info),
+ input_location(input_location),
+ match_in_scheme(match_in_scheme),
+ innermost_match(innermost_match) {
+ }
+
+ bool operator==(const GURL& url) const {
+ return url_info.url() == url;
+ }
+
+ history::URLRow url_info;
+
+ // The offset of the user's input within the URL.
+ size_t input_location;
+
+ // Whether this is a match in the scheme. This determines whether we'll go
+ // ahead and show a scheme on the URL even if the user didn't type one.
+ // If our best match was in the scheme, not showing the scheme is both
+ // confusing and, for inline autocomplete of the fill_into_edit, dangerous.
+ // (If the user types "h" and we match "http://foo/", we need to inline
+ // autocomplete that, not "foo/", which won't show anything at all, and
+ // will mislead the user into thinking the What You Typed match is what's
+ // selected.)
+ bool match_in_scheme;
+
+ // A match after any scheme/"www.", if the user input could match at both
+ // locations. If the user types "w", an innermost match ("website.com") is
+ // better than a non-innermost match ("www.google.com"). If the user types
+ // "x", no scheme in our prefix list (or "www.") begins with x, so all
+ // matches are, vacuously, "innermost matches".
+ bool innermost_match;
+ };
+ typedef std::deque<HistoryMatch> HistoryMatches;
+
+ enum MatchType {
+ NORMAL,
+ WHAT_YOU_TYPED,
+ INLINE_AUTOCOMPLETE
+ };
+
+ // Fixes up user URL input to make it more possible to match against. Among
+ // many other things, this takes care of the following:
+ // * Prepending file:// to file URLs
+ // * Converting drive letters in file URLs to uppercase
+ // * Converting case-insensitive parts of URLs (like the scheme and domain)
+ // to lowercase
+ // * Convert spaces to %20s
+ // Note that we don't do this in AutocompleteInput's constructor, because if
+ // e.g. we convert a Unicode hostname to punycode, other providers will show
+ // output that surprises the user ("Search Google for xn--6ca.com").
+ static std::wstring FixupUserInput(const std::wstring& input);
+
+ // Trims "http:" and up to two subsequent slashes from |url|. Returns the
+ // number of characters that were trimmed.
+ static size_t TrimHttpPrefix(std::wstring* url);
+
+ // Returns true if |url| is just a host (e.g. "http://www.google.com/") and
+ // not some other subpage (e.g. "http://www.google.com/foo.html").
+ static bool IsHostOnly(const GURL& url);
+
+ // Acts like the > operator for URLInfo classes.
+ static bool CompareHistoryMatch(const HistoryMatch& a,
+ const HistoryMatch& b);
+
+ // Returns the set of prefixes to use for prefixes_.
+ static Prefixes GetPrefixes();
+
+ // Determines the relevance for some input, given its type and which match it
+ // is. If |match_type| is NORMAL, |match_number| is a number
+ // [0, kMaxSuggestions) indicating the relevance of the match (higher == more
+ // relevant). For other values of |match_type|, |match_number| is ignored.
+ static int CalculateRelevance(AutocompleteInput::Type input_type,
+ MatchType match_type,
+ size_t match_number);
+
+ // Given the user's |input| and a |match| created from it, reduce the
+ // match's URL to just a host. If this host still matches the user input,
+ // return it. Returns the empty string on failure.
+ static GURL ConvertToHostOnly(const HistoryMatch& match,
+ const std::wstring& input);
+
+ // See if a shorter version of the best match should be created, and if so
+ // place it at the front of |matches|. This can suggest history URLs that
+ // are prefixes of the best match (if they've been visited enough, compared
+ // to the best match), or create host-only suggestions even when they haven't
+ // been visited before: if the user visited http://example.com/asdf once,
+ // we'll suggest http://example.com/ even if they've never been to it. See
+ // the function body for the exact heuristics used.
+ static void PromoteOrCreateShorterSuggestion(
+ history::URLDatabase* db,
+ const HistoryURLProviderParams& params,
+ HistoryMatches* matches);
+
+ // Ensures that |matches| contains an entry for |info|, which may mean adding
+ // a new such entry (using |input_location| and |match_in_scheme|).
+ //
+ // If |promote| is true, this also ensures the entry is the first element in
+ // |matches|, moving or adding it to the front as appropriate. When
+ // |promote| is false, existing matches are left in place, and newly added
+ // matches are placed at the back.
+ static void EnsureMatchPresent(const history::URLRow& info,
+ std::wstring::size_type input_location,
+ bool match_in_scheme,
+ HistoryMatches* matches,
+ bool promote);
+
+ // Helper function that actually launches the two autocomplete passes.
+ void RunAutocompletePasses(const AutocompleteInput& input,
+ bool fixup_input_and_run_pass_1,
+ bool run_pass_2);
+
+ // Returns the best prefix that begins |text|. "Best" means "greatest number
+ // of components". This may return NULL if no prefix begins |text|.
+ //
+ // |prefix_suffix| (which may be empty) is appended to every attempted
+ // prefix. This is useful when you need to figure out the innermost match
+ // for some user input in a URL.
+ const Prefix* BestPrefix(const std::wstring& text,
+ const std::wstring& prefix_suffix) const;
+
+ // Adds the exact input for what the user has typed as input. This is
+ // called on the main thread to generate the first match synchronously.
+ void SuggestExactInput(const AutocompleteInput& input, bool trim_http);
+
+ // Assumes |params| contains the "what you typed" suggestion created by
+ // SuggestExactInput(). Looks up its info in the DB. If found, fills in the
+ // title from the DB, promotes the match's priority to that of an inline
+ // autocomplete match (maybe it should be slightly better?), and places it on
+ // the front of |params|->matches (so we pick the right matches to throw away
+ // when culling redirects to/from it). Returns whether a match was promoted.
+ bool FixupExactSuggestion(history::URLDatabase* db,
+ HistoryURLProviderParams* params,
+ HistoryMatches* matches) const;
+
+ // Determines if |match| is suitable for inline autocomplete, and promotes it
+ // if so.
+ bool PromoteMatchForInlineAutocomplete(HistoryURLProviderParams* params,
+ const HistoryMatch& match);
+
+ // Sorts the given list of matches.
+ void SortMatches(HistoryMatches* matches) const;
+
+ // Removes results that have been rarely typed or visited, and not any time
+ // recently. The exact parameters for this heuristic can be found in the
+ // function body.
+ void CullPoorMatches(HistoryMatches* matches) const;
+
+ // Removes results that redirect to each other, leaving at most |max_results|
+ // results.
+ void CullRedirects(history::HistoryBackend* backend,
+ HistoryMatches* matches,
+ size_t max_results) const;
+
+ // Helper function for CullRedirects, this removes all but the first
+ // occurance of [any of the set of strings in |remove|] from the |matches|
+ // list.
+ //
+ // The return value is the index of the item that is after the item in the
+ // input identified by |source_index|. If |source_index| or an item before
+ // is removed, the next item will be shifted, and this allows the caller to
+ // pick up on the next one when this happens.
+ size_t RemoveSubsequentMatchesOf(
+ HistoryMatches* matches,
+ size_t source_index,
+ const std::vector<GURL>& remove) const;
+
+ // Converts a line from the database into an autocomplete match for display.
+ AutocompleteMatch HistoryMatchToACMatch(HistoryURLProviderParams* params,
+ const HistoryMatch& history_match,
+ MatchType match_type,
+ size_t match_number);
+
+ // This is only non-null for testing, otherwise the HistoryService from the
+ // Profile is used.
+ HistoryService* history_service_;
+
+ // Prefixes to try appending to user input when looking for a match.
+ const Prefixes prefixes_;
+
+ // Params for the current query. The provider should not free this directly;
+ // instead, it is passed as a parameter through the history backend, and the
+ // parameter itself is freed once it's no longer needed. The only reason we
+ // keep this member is so we can set the cancel bit on it.
+ HistoryURLProviderParams* params_;
+};
+
+#endif // CHROME_BROWSER_AUTOCOMPLETE_HISTORY_URL_PROVIDER_H__
diff --git a/chrome/browser/autocomplete/history_url_provider_unittest.cc b/chrome/browser/autocomplete/history_url_provider_unittest.cc
new file mode 100644
index 0000000..44486e7
--- /dev/null
+++ b/chrome/browser/autocomplete/history_url_provider_unittest.cc
@@ -0,0 +1,382 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/file_util.h"
+#include "base/message_loop.h"
+#include "base/path_service.h"
+#include "chrome/browser/autocomplete/history_url_provider.h"
+#include "chrome/browser/history/history.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+struct TestURLInfo {
+ std::wstring url;
+ std::wstring title;
+ int visit_count;
+ int typed_count;
+ bool starred;
+};
+
+// Contents of the test database.
+static TestURLInfo test_db[] = {
+ {L"http://www.google.com/", L"Google", 3, 3, false},
+
+ // High-quality pages should get a host synthesized as a lower-quality match.
+ {L"http://slashdot.org/favorite_page.html", L"Favorite page", 200, 100,
+ false},
+
+ // Less popular pages should have hosts synthesized as higher-quality
+ // matches.
+ {L"http://kerneltrap.org/not_very_popular.html", L"Less popular", 4, 0,
+ false},
+
+ // Unpopular pages should not appear in the results at all.
+ {L"http://freshmeat.net/unpopular.html", L"Unpopular", 1, 1, false},
+
+ // If a host has a match, we should pick it up during host synthesis.
+ {L"http://news.google.com/?ned=us&topic=n", L"Google News - U.S.", 2, 2,
+ false},
+ {L"http://news.google.com/", L"Google News", 1, 1, false},
+
+ // Suggested short URLs must be "good enough" and must match user input.
+ {L"http://foo.com/", L"Dir", 5, 5, false},
+ {L"http://foo.com/dir/", L"Dir", 2, 2, false},
+ {L"http://foo.com/dir/another/", L"Dir", 5, 1, false},
+ {L"http://foo.com/dir/another/again/", L"Dir", 10, 0, false},
+ {L"http://foo.com/dir/another/again/myfile.html", L"File", 10, 2, false},
+
+ // Starred state is more important than visit count (but less important than
+ // typed count) when sorting URLs. The order in which the URLs were starred
+ // shouldn't matter.
+ // We throw in a lot of extra URLs here to make sure we're testing the
+ // history database's query, not just the autocomplete provider.
+ {L"http://startest.com/y/a", L"A", 2, 2, true},
+ {L"http://startest.com/y/b", L"B", 5, 2, false},
+ {L"http://startest.com/x/c", L"C", 5, 2, true},
+ {L"http://startest.com/x/d", L"D", 5, 5, false},
+ {L"http://startest.com/y/e", L"E", 4, 2, false},
+ {L"http://startest.com/y/f", L"F", 3, 2, false},
+ {L"http://startest.com/y/g", L"G", 3, 2, false},
+ {L"http://startest.com/y/h", L"H", 3, 2, false},
+ {L"http://startest.com/y/i", L"I", 3, 2, false},
+ {L"http://startest.com/y/j", L"J", 3, 2, false},
+ {L"http://startest.com/y/k", L"K", 3, 2, false},
+ {L"http://startest.com/y/l", L"L", 3, 2, false},
+ {L"http://startest.com/y/m", L"M", 3, 2, false},
+
+ // A file: URL is useful for testing that fixup does the right thing w.r.t.
+ // the number of trailing slashes on the user's input.
+ {L"file:///C:/foo.txt", L"", 2, 2, false},
+
+ // Results with absurdly high typed_counts so that very generic queries like
+ // "http" will give consistent results even if more data is added above.
+ {L"http://bogussite.com/a", L"Bogus A", 10002, 10000, false},
+ {L"http://bogussite.com/b", L"Bogus B", 10001, 10000, false},
+ {L"http://bogussite.com/c", L"Bogus C", 10000, 10000, false},
+};
+
+class HistoryURLProviderTest : public testing::Test,
+ public ACProviderListener {
+ // ACProviderListener
+ virtual void OnProviderUpdate(bool updated_matches);
+
+ protected:
+ // testing::Test
+ virtual void SetUp();
+ virtual void TearDown();
+
+ // Fills test data into the history system
+ void FillData();
+
+ // Runs an autocomplete query on |text| and checks to see that the returned
+ // results' destination URLs match those provided.
+ void RunTest(const std::wstring text,
+ const std::wstring& desired_tld,
+ bool prevent_inline_autocomplete,
+ const std::wstring* expected_urls,
+ int num_results);
+
+ ACMatches matches_;
+ scoped_refptr<HistoryService> history_service_;
+
+ private:
+ std::wstring history_dir_;
+ scoped_refptr<HistoryURLProvider> autocomplete_;
+};
+
+void HistoryURLProviderTest::OnProviderUpdate(bool updated_matches) {
+ if (autocomplete_->done())
+ MessageLoop::current()->Quit();
+}
+
+void HistoryURLProviderTest::SetUp() {
+ PathService::Get(base::DIR_TEMP, &history_dir_);
+ file_util::AppendToPath(&history_dir_, L"HistoryURLProviderTest");
+ file_util::Delete(history_dir_, true); // Normally won't exist.
+ file_util::CreateDirectoryW(history_dir_);
+
+ history_service_ = new HistoryService;
+ history_service_->Init(history_dir_);
+
+ autocomplete_ = new HistoryURLProvider(this, history_service_);
+
+ FillData();
+}
+
+void HistoryURLProviderTest::TearDown() {
+ history_service_->SetOnBackendDestroyTask(new MessageLoop::QuitTask);
+ history_service_->Cleanup();
+ autocomplete_ = NULL;
+ history_service_ = NULL;
+
+ // Wait for history thread to complete (the QuitTask will cause it to exit
+ // on destruction). Note: if this never terminates, somebody is probably
+ // leaking a reference to the history backend, so it never calls our
+ // destroy task.
+ MessageLoop::current()->Run();
+
+ file_util::Delete(history_dir_, true);
+}
+
+void HistoryURLProviderTest::FillData() {
+ // All visits are a long time ago (some tests require this since we do some
+ // special logic for things visited very recently). Note that this time must
+ // be more recent than the "archived history" threshold for the data to go
+ // into the main database.
+ //
+ // TODO(brettw) It would be nice if we could test this behavior, in which
+ // case the time would be specifed in the test_db structure.
+ Time visit_time = Time::Now() - TimeDelta::FromDays(80);
+
+ for (int i = 0; i < arraysize(test_db); ++i) {
+ const TestURLInfo& cur = test_db[i];
+ const GURL current_url(cur.url);
+ history_service_->AddPageWithDetails(current_url, cur.title,
+ cur.visit_count, cur.typed_count,
+ visit_time, false);
+ if (cur.starred) {
+ history::StarredEntry star_entry;
+ star_entry.type = history::StarredEntry::URL;
+ star_entry.parent_group_id = HistoryService::kBookmarkBarID;
+ star_entry.url = current_url;
+ history_service_->CreateStarredEntry(star_entry, NULL, NULL);
+ }
+ }
+}
+
+void HistoryURLProviderTest::RunTest(const std::wstring text,
+ const std::wstring& desired_tld,
+ bool prevent_inline_autocomplete,
+ const std::wstring* expected_urls,
+ int num_results) {
+ AutocompleteInput input(text, desired_tld, prevent_inline_autocomplete);
+ autocomplete_->Start(input, false, false);
+ if (!autocomplete_->done())
+ MessageLoop::current()->Run();
+
+ matches_ = autocomplete_->matches();
+ ASSERT_EQ(num_results, matches_.size());
+ for (int i = 0; i < num_results; ++i)
+ EXPECT_EQ(expected_urls[i], matches_[i].destination_url);
+}
+
+TEST_F(HistoryURLProviderTest, PromoteShorterURLs) {
+ // Test that hosts get synthesized below popular pages.
+ const std::wstring expected_nonsynth[] = {
+ L"http://slash/",
+ L"http://slashdot.org/favorite_page.html",
+ L"http://slashdot.org/",
+ };
+ RunTest(L"slash", std::wstring(), true, expected_nonsynth,
+ arraysize(expected_nonsynth));
+
+ // Test that hosts get synthesized above less popular pages.
+ const std::wstring expected_synth[] = {
+ L"http://kernel/",
+ L"http://kerneltrap.org/",
+ L"http://kerneltrap.org/not_very_popular.html",
+ };
+ RunTest(L"kernel", std::wstring(), true, expected_synth,
+ arraysize(expected_synth));
+
+ // Test that unpopular pages are ignored completely.
+ const std::wstring expected_what_you_typed_only[] = {
+ L"http://fresh/",
+ };
+ RunTest(L"fresh", std::wstring(), true, expected_what_you_typed_only,
+ arraysize(expected_what_you_typed_only));
+
+ // Test that if we have a synthesized host that matches a suggestion, they
+ // get combined into one.
+ const std::wstring expected_combine[] = {
+ L"http://news/",
+ L"http://news.google.com/",
+ L"http://news.google.com/?ned=us&topic=n",
+ };
+ RunTest(L"news", std::wstring(), true, expected_combine,
+ arraysize(expected_combine));
+ // The title should also have gotten set properly on the host for the
+ // synthesized one, since it was also in the results.
+ EXPECT_EQ(std::wstring(L"Google News"), matches_[1].description);
+
+ // Test that short URL matching works correctly as the user types more
+ // (several tests):
+ // The entry for foo.com is the best of all five foo.com* entries.
+ const std::wstring short_1[] = {
+ L"http://foo/",
+ L"http://foo.com/",
+ L"http://foo.com/dir/another/again/myfile.html",
+ L"http://foo.com/dir/",
+ };
+ RunTest(L"foo", std::wstring(), true, short_1, arraysize(short_1));
+
+ // When the user types the whole host, make sure we don't get two results for
+ // it.
+ const std::wstring short_2[] = {
+ L"http://foo.com/",
+ L"http://foo.com/dir/another/again/myfile.html",
+ L"http://foo.com/dir/",
+ L"http://foo.com/dir/another/",
+ };
+ RunTest(L"foo.com", std::wstring(), true, short_2, arraysize(short_2));
+ RunTest(L"foo.com/", std::wstring(), true, short_2, arraysize(short_2));
+
+ // The filename is the second best of the foo.com* entries, but there is a
+ // shorter URL that's "good enough". The host doesn't match the user input
+ // and so should not appear.
+ const std::wstring short_3[] = {
+ L"http://foo.com/d",
+ L"http://foo.com/dir/another/",
+ L"http://foo.com/dir/another/again/myfile.html",
+ L"http://foo.com/dir/",
+ };
+ RunTest(L"foo.com/d", std::wstring(), true, short_3, arraysize(short_3));
+
+ // We shouldn't promote shorter URLs than the best if they're not good
+ // enough.
+ const std::wstring short_4[] = {
+ L"http://foo.com/dir/another/a",
+ L"http://foo.com/dir/another/again/myfile.html",
+ L"http://foo.com/dir/another/again/",
+ };
+ RunTest(L"foo.com/dir/another/a", std::wstring(), true, short_4,
+ arraysize(short_4));
+}
+
+TEST_F(HistoryURLProviderTest, Starred) {
+ // Test that starred pages sort properly.
+ const std::wstring star_1[] = {
+ L"http://startest/",
+ L"http://startest.com/x/d",
+ L"http://startest.com/x/c",
+ L"http://startest.com/y/a",
+ };
+ RunTest(L"startest", std::wstring(), true, star_1, arraysize(star_1));
+ const std::wstring star_2[] = {
+ L"http://startest.com/y",
+ L"http://startest.com/y/a",
+ L"http://startest.com/y/b",
+ L"http://startest.com/y/e",
+ };
+ RunTest(L"startest.com/y", std::wstring(), true, star_2, arraysize(star_2));
+}
+
+TEST_F(HistoryURLProviderTest, CullRedirects) {
+ // URLs we will be using, plus the visit counts they will initially get
+ // (the redirect set below will also increment the visit counts). We want
+ // the results to be in A,B,C order. Note also that our visit counts are
+ // all high enough so that domain synthesizing won't get triggered.
+ struct RedirectCase {
+ const wchar_t* url;
+ int count;
+ };
+ static const RedirectCase redirect[] = {
+ {L"http://redirects/A", 30},
+ {L"http://redirects/B", 20},
+ {L"http://redirects/C", 10}
+ };
+ for (int i = 0; i < arraysize(redirect); i++) {
+ history_service_->AddPageWithDetails(GURL(redirect[i].url), L"Title",
+ redirect[i].count, redirect[i].count,
+ Time::Now(), false);
+ }
+
+ // Create a B->C->A redirect chain, but set the visit counts such that they
+ // will appear in A,B,C order in the results. The autocomplete query will
+ // search for the most recent visit when looking for redirects, so this will
+ // be found even though the previous visits had no redirects.
+ HistoryService::RedirectList redirects_to_a;
+ redirects_to_a.push_back(GURL(redirect[1].url));
+ redirects_to_a.push_back(GURL(redirect[2].url));
+ redirects_to_a.push_back(GURL(redirect[0].url));
+ history_service_->AddPage(GURL(redirect[0].url), NULL, 0, GURL(),
+ PageTransition::TYPED, redirects_to_a);
+
+ // Because all the results are part of a redirect chain with other results,
+ // all but the first one (A) should be culled. We should get the default
+ // "what you typed" result, plus this one.
+ const std::wstring typing(L"http://redirects/");
+ const std::wstring expected_results[] = {
+ typing,
+ redirect[0].url};
+ RunTest(typing, std::wstring(), true, expected_results,
+ arraysize(expected_results));
+}
+
+TEST_F(HistoryURLProviderTest, Fixup) {
+ // Test for various past crashes we've had.
+ RunTest(L"\\", std::wstring(), false, NULL, 0);
+
+ RunTest(L"#", std::wstring(), false, NULL, 0);
+
+ const std::wstring crash_1[] = {L"http://%20/"};
+ RunTest(L"%20", std::wstring(), false, crash_1, arraysize(crash_1));
+
+ // Fixing up "file:" should result in an inline autocomplete offset of just
+ // after "file:", not just after "file://".
+ const std::wstring input_1(L"file:");
+ const std::wstring fixup_1[] = {L"file:///", L"file:///C:/foo.txt"};
+ RunTest(input_1, std::wstring(), false, fixup_1, arraysize(fixup_1));
+ EXPECT_EQ(input_1.length(), matches_[1].inline_autocomplete_offset);
+
+ // Fixing up "http:/" should result in an inline autocomplete offset of just
+ // after "http:/", not just after "http:".
+ const std::wstring input_2(L"http:/");
+ const std::wstring fixup_2[] = {
+ L"http://bogussite.com/a",
+ L"http://bogussite.com/b",
+ L"http://bogussite.com/c",
+ };
+ RunTest(input_2, std::wstring(), false, fixup_2, arraysize(fixup_2));
+ EXPECT_EQ(input_2.length(), matches_[0].inline_autocomplete_offset);
+
+ // Adding a TLD to a small number like "56" should result in "www.56.com"
+ // rather than "0.0.0.56.com".
+ std::wstring fixup_3[] = {L"http://www.56.com/"};
+ RunTest(L"56", L"com", true, fixup_3, arraysize(fixup_3));
+} \ No newline at end of file
diff --git a/chrome/browser/autocomplete/keyword_provider.cc b/chrome/browser/autocomplete/keyword_provider.cc
new file mode 100644
index 0000000..cc56029
--- /dev/null
+++ b/chrome/browser/autocomplete/keyword_provider.cc
@@ -0,0 +1,304 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "chrome/browser/autocomplete/keyword_provider.h"
+
+#include <algorithm>
+
+#include "base/string_util.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/template_url.h"
+#include "chrome/browser/template_url_model.h"
+#include "chrome/common/l10n_util.h"
+#include "net/base/escape.h"
+#include "net/base/net_util.h"
+
+#include "generated_resources.h"
+
+static const wchar_t kSearchDescriptionParameter[](L"%1");
+static const wchar_t kSearchValueParameter[](L"%2");
+
+// static
+std::wstring KeywordProvider::SplitReplacementStringFromInput(
+ const std::wstring& input) {
+ // The input may contain leading whitespace, strip it.
+ std::wstring trimmed_input;
+ TrimWhitespace(input, TRIM_LEADING, &trimmed_input);
+
+ // And extract the replacement string.
+ std::wstring remaining_input;
+ SplitKeywordFromInput(trimmed_input, &remaining_input);
+ return remaining_input;
+}
+
+KeywordProvider::KeywordProvider(ACProviderListener* listener, Profile* profile)
+ : AutocompleteProvider(listener, profile, "Keyword"),
+ model_(NULL) {
+}
+
+KeywordProvider::KeywordProvider(ACProviderListener* listener,
+ TemplateURLModel* model)
+ : AutocompleteProvider(listener, NULL, "Keyword"),
+ model_(model) {
+}
+
+
+class KeywordProvider::CompareQuality {
+ public:
+ // A keyword is of higher quality when a greater fraction of it has been
+ // typed, that is, when it is shorter.
+ //
+ // TODO(pkasting): http://b/740691 Most recent and most frequent keywords are
+ // probably better rankings than the fraction of the keyword typed. We should
+ // always put any exact matches first no matter what, since the code in
+ // Start() assumes this (and it makes sense).
+ bool operator()(const std::wstring& keyword1,
+ const std::wstring& keyword2) const {
+ return keyword1.length() < keyword2.length();
+ }
+};
+
+void KeywordProvider::Start(const AutocompleteInput& input,
+ bool minimal_changes,
+ bool synchronous_only) {
+ matches_.clear();
+
+ if ((input.type() == AutocompleteInput::INVALID) ||
+ (input.type() == AutocompleteInput::FORCED_QUERY))
+ return;
+
+ // Split user input into a keyword and some query input.
+ //
+ // We want to suggest keywords even when users have started typing URLs, on
+ // the assumption that they might not realize they no longer need to go to a
+ // site to be able to search it. So we call CleanUserInputKeyword() to strip
+ // any initial scheme and/or "www.". NOTE: Any heuristics or UI used to
+ // automatically/manually create keywords will need to be in sync with
+ // whatever we do here!
+ //
+ // TODO(pkasting): http://b/1112681 If someday we remember usage frequency for
+ // keywords, we might suggest keywords that haven't even been partially typed,
+ // if the user uses them enough and isn't obviously typing something else. In
+ // this case we'd consider all input here to be query input.
+ std::wstring remaining_input;
+ std::wstring keyword(TemplateURLModel::CleanUserInputKeyword(
+ SplitKeywordFromInput(input.text(), &remaining_input)));
+ if (keyword.empty())
+ return;
+
+ // Make sure the model is loaded. This is cheap and quickly bails out if
+ // the model is already loaded.
+ TemplateURLModel* model = profile_ ? profile_->GetTemplateURLModel() : model_;
+ DCHECK(model);
+ model->Load();
+
+ // Get the best matches for this keyword.
+ //
+ // NOTE: We could cache the previous keywords and reuse them here in the
+ // |minimal_changes| case, but since we'd still have to recalculate their
+ // relevances and we can just recreate the results synchronously anyway, we
+ // don't bother.
+ //
+ // TODO(pkasting): http://b/893701 We should remember the user's use of a
+ // search query both from the autocomplete popup and from web pages
+ // themselves.
+ std::vector<std::wstring> keyword_matches;
+ model->FindMatchingKeywords(keyword, !remaining_input.empty(),
+ &keyword_matches);
+ if (keyword_matches.empty())
+ return;
+ std::sort(keyword_matches.begin(), keyword_matches.end(), CompareQuality());
+
+ // Limit to one exact or three inexact matches, and mark them up for display
+ // in the autocomplete popup.
+ // Any exact match is going to be the highest quality match, and thus at the
+ // front of our vector.
+ if (keyword_matches.front() == keyword) {
+ matches_.push_back(CreateAutocompleteMatch(model, keyword, input,
+ keyword.length(),
+ remaining_input));
+ } else {
+ if (keyword_matches.size() > max_matches()) {
+ keyword_matches.erase(keyword_matches.begin() + max_matches(),
+ keyword_matches.end());
+ }
+ for (std::vector<std::wstring>::const_iterator i(keyword_matches.begin());
+ i != keyword_matches.end(); ++i) {
+ matches_.push_back(CreateAutocompleteMatch(model, *i, input,
+ keyword.length(),
+ remaining_input));
+ }
+ }
+}
+
+// static
+std::wstring KeywordProvider::SplitKeywordFromInput(
+ const std::wstring& input,
+ std::wstring* remaining_input) {
+ // Find end of first token. The AutocompleteController has trimmed leading
+ // whitespace, so we need not skip over that.
+ const size_t first_white(input.find_first_of(kWhitespaceWide));
+ DCHECK(first_white != 0);
+ if (first_white == std::wstring::npos)
+ return input; // Only one token provided.
+
+ // Set |remaining_input| to everything after the first token.
+ DCHECK(remaining_input != NULL);
+ const size_t first_nonwhite(input.find_first_not_of(kWhitespaceWide,
+ first_white));
+ if (first_nonwhite != std::wstring::npos)
+ remaining_input->assign(input.begin() + first_nonwhite, input.end());
+
+ // Return first token as keyword.
+ return input.substr(0, first_white);
+}
+
+// static
+void KeywordProvider::FillInURLAndContents(
+ const std::wstring& remaining_input,
+ const TemplateURL* element,
+ AutocompleteMatch* match) {
+ DCHECK(!element->short_name().empty());
+ DCHECK(element->url());
+ DCHECK(element->url()->IsValid());
+ if (remaining_input.empty()) {
+ if (element->url()->SupportsReplacement()) {
+ // No query input; return a generic, no-destination placeholder.
+ match->contents.assign(l10n_util::GetStringF(IDS_KEYWORD_SEARCH,
+ element->short_name(),
+ l10n_util::GetString(IDS_EMPTY_KEYWORD_VALUE)));
+ match->contents_class.push_back(
+ ACMatchClassification(0, ACMatchClassification::DIM));
+ } else {
+ // Keyword that has no replacement text (aka a shorthand for a URL).
+ match->destination_url.assign(element->url()->url());
+ match->contents.assign(element->short_name());
+ AutocompleteMatch::ClassifyLocationInString(0, match->contents.length(),
+ match->contents.length(), ACMatchClassification::NONE,
+ &match->contents_class);
+ }
+ } else {
+ // Create destination URL by escaping user input and substituting into
+ // keyword template URL. The escaping here handles whitespace in user
+ // input, but we rely on later canonicalization functions to do more
+ // fixup to make the URL valid if necessary.
+ DCHECK(element->url()->SupportsReplacement());
+ match->destination_url.assign(element->url()->ReplaceSearchTerms(
+ *element, remaining_input, TemplateURLRef::NO_SUGGESTIONS_AVAILABLE,
+ std::wstring()));
+ std::vector<size_t> content_param_offsets;
+ match->contents.assign(l10n_util::GetStringF(IDS_KEYWORD_SEARCH,
+ element->short_name(),
+ remaining_input,
+ &content_param_offsets));
+ if (content_param_offsets.size() == 2) {
+ AutocompleteMatch::ClassifyLocationInString(content_param_offsets[1],
+ remaining_input.length(), match->contents.length(),
+ ACMatchClassification::NONE, &match->contents_class);
+ } else {
+ // See comments on an identical NOTREACHED() in search_provider.cc.
+ NOTREACHED();
+ }
+ }
+}
+
+// static
+int KeywordProvider::CalculateRelevance(AutocompleteInput::Type type,
+ bool complete,
+ bool is_bookmark_keyword) {
+ if (complete && is_bookmark_keyword)
+ return 1500;
+
+ switch (type) {
+ case AutocompleteInput::UNKNOWN:
+ case AutocompleteInput::REQUESTED_URL:
+ return complete ? 1100 : 450;
+
+ case AutocompleteInput::URL:
+ return complete ? 1100 : 700;
+
+ case AutocompleteInput::QUERY:
+ return complete ? 1400 : 650;
+
+ default:
+ NOTREACHED();
+ return 0;
+ }
+}
+
+AutocompleteMatch KeywordProvider::CreateAutocompleteMatch(
+ TemplateURLModel *model,
+ const std::wstring keyword,
+ const AutocompleteInput& input,
+ size_t prefix_length,
+ const std::wstring& remaining_input) {
+ DCHECK(model);
+ // Get keyword data from data store.
+ const TemplateURL* element(model->GetTemplateURLForKeyword(keyword));
+ DCHECK(element && element->url());
+ const bool supports_replacement = element->url()->SupportsReplacement();
+
+ // Create an edit entry of "[keyword] [remaining input]". This is helpful
+ // even when [remaining input] is empty, as the user can select the popup
+ // choice and immediately begin typing in query input.
+ const bool keyword_complete = (prefix_length == keyword.length());
+ AutocompleteMatch result(this,
+ CalculateRelevance(input.type(), keyword_complete, !supports_replacement),
+ false);
+ result.type = AutocompleteMatch::KEYWORD;
+ result.fill_into_edit.assign(keyword);
+ if (!remaining_input.empty() || !keyword_complete || supports_replacement)
+ result.fill_into_edit.push_back(L' ');
+ result.fill_into_edit.append(remaining_input);
+ if (!input.prevent_inline_autocomplete() &&
+ (keyword_complete || remaining_input.empty()))
+ result.inline_autocomplete_offset = input.text().length();
+
+ // Create destination URL and popup entry content by substituting user input
+ // into keyword templates.
+ FillInURLAndContents(remaining_input, element, &result);
+
+ // Create popup entry description based on the keyword name.
+ result.description.assign(l10n_util::GetStringF(
+ IDS_AUTOCOMPLETE_KEYWORD_DESCRIPTION, keyword));
+ if (supports_replacement)
+ result.template_url = element;
+ static const std::wstring kKeywordDesc(l10n_util::GetString(
+ IDS_AUTOCOMPLETE_KEYWORD_DESCRIPTION));
+ AutocompleteMatch::ClassifyLocationInString(kKeywordDesc.find(L"%s"),
+ prefix_length,
+ result.description.length(),
+ ACMatchClassification::DIM,
+ &result.description_class);
+
+ // Keyword searches don't look like URLs.
+ result.transition = PageTransition::GENERATED;
+
+ return result;
+}
diff --git a/chrome/browser/autocomplete/keyword_provider.h b/chrome/browser/autocomplete/keyword_provider.h
new file mode 100644
index 0000000..1964006
--- /dev/null
+++ b/chrome/browser/autocomplete/keyword_provider.h
@@ -0,0 +1,132 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// This file contains the keyword autocomplete provider. The keyword provider is
+// responsible for remembering/suggesting user "search keyword queries" (e.g.
+// "imdb Godzilla") and then fixing them up into valid URLs. An instance of it
+// gets created and managed by the autocomplete controller. KeywordProvider
+// uses a TemplateURLModel to find the set of keywords.
+//
+// For more information on the autocomplete system in general, including how
+// the autocomplete controller and autocomplete providers work, see
+// chrome/browser/autocomplete.h.
+
+#ifndef CHROME_BROWSER_AUTOCOMPLETE_KEYWORD_PROVIDER_H__
+#define CHROME_BROWSER_AUTOCOMPLETE_KEYWORD_PROVIDER_H__
+
+#include <map>
+#include <string>
+#include <vector>
+#include "chrome/browser/autocomplete/autocomplete.h"
+
+class Profile;
+class TemplateURL;
+class TemplateURLModel;
+
+/****************************** KeywordProvider ******************************/
+
+// Autocomplete provider for keyword input.
+//
+// After construction, the autocomplete controller repeatedly calls Start()
+// with some user input, each time expecting to receive a small set of the best
+// matches (either synchronously or asynchronously).
+//
+// To construct these matches, the provider treats user input as a series of
+// whitespace-delimited tokens and tries to match the first token as the prefix
+// of a known "keyword". A keyword is some string that maps to a search query
+// URL; the rest of the user's input is taken as the input to the query. For
+// example, the keyword "bug" might map to the URL "http://b/issue?id=%s", so
+// input like "bug 123" would become "http://b/issue?id=123".
+//
+// Because we do prefix matching, user input could match more than one keyword
+// at once. (Example: the input "f jazz" matches all keywords starting with
+// "f".) We return the best matches, up to three.
+//
+// The resulting matches are shown with content specified by the keyword
+// (usually "Search [name] for %s"), description "(Keyword: [keyword])", and
+// action "[keyword] %s". If the user has typed a (possibly partial) keyword
+// but no search terms, the suggested result is shown greyed out, with
+// "<enter term(s)>" as the substituted input, and does nothing when selected.
+class KeywordProvider : public AutocompleteProvider {
+ public:
+ KeywordProvider(ACProviderListener* listener, Profile* profile);
+ // For testing.
+ KeywordProvider(ACProviderListener* listener, TemplateURLModel* model);
+
+ // Returns the replacement string from the user input. The replacement
+ // string is the portion of the input that does not contain the keyword.
+ // For example, the replacement string for "b blah" is blah.
+ static std::wstring SplitReplacementStringFromInput(
+ const std::wstring& input);
+
+ // AutocompleteProvider
+ virtual void Start(const AutocompleteInput& input,
+ bool minimal_changes,
+ bool synchronous_only);
+
+ private:
+ // Helper functor for Start(), for sorting keyword matches by quality.
+ class CompareQuality;
+
+ // Extracts the next whitespace-delimited token from input and returns it.
+ // Sets |remaining_input| to everything after the first token (skipping over
+ // intervening whitespace).
+ static std::wstring SplitKeywordFromInput(const std::wstring& input,
+ std::wstring* remaining_input);
+
+ // Fills in the "destination_url" and "contents" fields of |match| with the
+ // provided user input and keyword data.
+ static void FillInURLAndContents(
+ const std::wstring& remaining_input,
+ const TemplateURL* element,
+ AutocompleteMatch* match);
+
+ // Determines the relevance for some input, given its type, whether the user
+ // typed the complete keyword, and whether the keyword is a bookmark keyword
+ // (i.e. one that does not support replacement).
+ static int CalculateRelevance(AutocompleteInput::Type type,
+ bool complete,
+ bool is_bookmark_keyword);
+
+ // Creates a fully marked-up AutocompleteMatch from the user's input.
+ AutocompleteMatch CreateAutocompleteMatch(
+ TemplateURLModel* model,
+ const std::wstring keyword,
+ const AutocompleteInput& input,
+ size_t prefix_length,
+ const std::wstring& remaining_input);
+
+ // Model for the keywords. This is only non-null when testing, otherwise the
+ // TemplateURLModel from the Profile is used.
+ TemplateURLModel* model_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(KeywordProvider);
+};
+
+#endif // CHROME_BROWSER_AUTOCOMPLETE_KEYWORD_PROVIDER_H__
diff --git a/chrome/browser/autocomplete/keyword_provider_unittest.cc b/chrome/browser/autocomplete/keyword_provider_unittest.cc
new file mode 100644
index 0000000..71cab7d
--- /dev/null
+++ b/chrome/browser/autocomplete/keyword_provider_unittest.cc
@@ -0,0 +1,214 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/message_loop.h"
+#include "chrome/browser/autocomplete/keyword_provider.h"
+#include "chrome/browser/template_url.h"
+#include "chrome/browser/template_url_model.h"
+#include "googleurl/src/gurl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class KeywordProviderTest : public testing::Test {
+ protected:
+ struct test_data {
+ const std::wstring input;
+ const int num_results;
+ const std::wstring output[3];
+ };
+
+ KeywordProviderTest() : kw_provider_(NULL) { }
+ virtual ~KeywordProviderTest() { }
+
+ virtual void SetUp();
+ virtual void TearDown();
+
+ void RunTest(test_data* keyword_cases,
+ int num_cases,
+ std::wstring AutocompleteMatch::* member);
+
+ protected:
+ scoped_refptr<KeywordProvider> kw_provider_;
+ scoped_ptr<TemplateURLModel> model_;
+};
+
+void KeywordProviderTest::SetUp() {
+ static const TemplateURLModel::Initializer kTestKeywordData[] = {
+ { L"aa", L"aa.com?foo=%s", L"aa" },
+ { L"aaaa", L"http://aaaa/?aaaa=1&b=%s&c", L"aaaa" },
+ { L"aaaaa", L"%s", L"aaaaa" },
+ { L"ab", L"bogus URL %s", L"ab" },
+ { L"weasel", L"weasel%sweasel", L"weasel" },
+ { L"www", L" +%2B?=%sfoo ", L"www" },
+ { L"z", L"%s=z", L"z" },
+ };
+
+ model_.reset(new TemplateURLModel(kTestKeywordData, arraysize(kTestKeywordData)));
+ kw_provider_ = new KeywordProvider(NULL, model_.get());
+}
+
+void KeywordProviderTest::TearDown() {
+ model_.reset();
+ kw_provider_ = NULL;
+}
+
+void KeywordProviderTest::RunTest(
+ test_data* keyword_cases,
+ int num_cases,
+ std::wstring AutocompleteMatch::* member) {
+ ACMatches matches;
+ for (int i = 0; i < num_cases; ++i) {
+ AutocompleteInput input(keyword_cases[i].input, std::wstring(), true);
+ kw_provider_->Start(input, false, false);
+ EXPECT_TRUE(kw_provider_->done());
+ matches = kw_provider_->matches();
+ EXPECT_EQ(keyword_cases[i].num_results, matches.size()) <<
+ L"Input was: " + keyword_cases[i].input;
+ if (matches.size() == keyword_cases[i].num_results) {
+ for (int j = 0; j < keyword_cases[i].num_results; ++j) {
+ EXPECT_EQ(keyword_cases[i].output[j], matches[j].*member);
+ }
+ }
+ }
+}
+
+TEST_F(KeywordProviderTest, Edit) {
+ test_data edit_cases[] = {
+ // Searching for a nonexistent prefix should give nothing.
+ {L"Not Found", 0, {}},
+ {L"aaaaaNot Found", 0, {}},
+
+ // Check that tokenization only collapses whitespace between first tokens,
+ // no-query-input cases have a space appended, and action is not escaped.
+ {L"z foo", 1, {L"z foo"}},
+ {L"z", 1, {L"z "}},
+ {L"z \t", 1, {L"z "}},
+ {L"z a b c++", 1, {L"z a b c++"}},
+
+ // Matches should be limited to three, and sorted in quality order, not
+ // alphabetical.
+ {L"aaa", 2, {L"aaaa ", L"aaaaa "}},
+ {L"a 1 2 3", 3, {L"aa 1 2 3", L"ab 1 2 3", L"aaaa 1 2 3"}},
+ {L"www.a", 3, {L"aa ", L"ab ", L"aaaa "}},
+ // Exact matches should prevent returning inexact matches.
+ {L"aaaa foo", 1, {L"aaaa foo"}},
+ {L"www.aaaa foo", 1, {L"aaaa foo"}},
+
+ // Clean up keyword input properly.
+ {L"www", 1, {L"www "}},
+ {L"www.", 0, {}},
+ {L"www.w w", 2, {L"www w", L"weasel w"}},
+ {L"http://www", 1, {L"www "}},
+ {L"http://www.", 0, {}},
+ {L"ftp: blah", 0, {}},
+ {L"mailto:z", 1, {L"z "}},
+ };
+
+ RunTest(edit_cases, arraysize(edit_cases),
+ &AutocompleteMatch::fill_into_edit);
+}
+
+TEST_F(KeywordProviderTest, URL) {
+ test_data url_cases[] = {
+ // No query input -> empty destination URL.
+ {L"z", 1, {L""}},
+ {L"z \t", 1, {L""}},
+
+ // Check that tokenization only collapses whitespace between first tokens
+ // and query input, but not rest of URL, is escaped.
+ {L"z a b c++", 1, {L"a+++b+++c%2B%2B=z"}},
+ {L"www.www www", 1, {L" +%2B?=wwwfoo "}},
+
+ // Substitution should work with various locations of the "%s".
+ {L"aaa 1a2b", 2, {L"http://aaaa/?aaaa=1&b=1a2b&c", L"1a2b"}},
+ {L"a 1 2 3", 3, {L"aa.com?foo=1+2+3", L"bogus URL 1+2+3",
+ L"http://aaaa/?aaaa=1&b=1+2+3&c"}},
+ {L"www.w w", 2, {L" +%2B?=wfoo ", L"weaselwweasel"}},
+ };
+
+ RunTest(url_cases, arraysize(url_cases),
+ &AutocompleteMatch::destination_url);
+}
+
+TEST_F(KeywordProviderTest, Contents) {
+ test_data contents_cases[] = {
+ // No query input -> substitute "<enter query>" into contents.
+ {L"z", 1, {L"Search z for <enter query>"}},
+ {L"z \t", 1, {L"Search z for <enter query>"}},
+
+ // Check that tokenization only collapses whitespace between first tokens
+ // and contents are not escaped or unescaped.
+ {L"z a b c++", 1, {L"Search z for a b c++"}},
+ {L"www.www www", 1, {L"Search www for www"}},
+
+ // Substitution should work with various locations of the "%s".
+ {L"aaa", 2, {L"Search aaaa for <enter query>",
+ L"Search aaaaa for <enter query>"}},
+ {L"a 1 2 3", 3, {L"Search aa for 1 2 3", L"Search ab for 1 2 3",
+ L"Search aaaa for 1 2 3"}},
+ {L"www.w w", 2, {L"Search www for w", L"Search weasel for w"}},
+ };
+
+ RunTest(contents_cases, arraysize(contents_cases),
+ &AutocompleteMatch::contents);
+}
+
+TEST_F(KeywordProviderTest, Description) {
+ test_data description_cases[] = {
+ // Whole keyword should be returned for both exact and inexact matches.
+ {L"z foo", 1, {L"(Keyword: z)"}},
+ {L"a foo", 3, {L"(Keyword: aa)", L"(Keyword: ab)",
+ L"(Keyword: aaaa)"}},
+ {L"ftp://www.www w", 1, {L"(Keyword: www)"}},
+
+ // Keyword should be returned regardless of query input.
+ {L"z", 1, {L"(Keyword: z)"}},
+ {L"z \t", 1, {L"(Keyword: z)"}},
+ {L"z a b c++", 1, {L"(Keyword: z)"}},
+ };
+
+ RunTest(description_cases, arraysize(description_cases),
+ &AutocompleteMatch::description);
+}
+
+TEST_F(KeywordProviderTest, AddKeyword) {
+ TemplateURL* template_url = new TemplateURL();
+ std::wstring keyword(L"foo");
+ std::wstring url(L"http://www.google.com/foo?q={searchTerms}");
+ template_url->SetURL(url, 0, 0);
+ template_url->set_keyword(keyword);
+ template_url->set_short_name(L"Test");
+ model_->Add(template_url);
+ ASSERT_TRUE(template_url == model_->GetTemplateURLForKeyword(keyword));
+}
+
+TEST_F(KeywordProviderTest, RemoveKeyword) {
+ std::wstring url(L"http://aaaa/?aaaa=1&b={searchTerms}&c");
+ model_->Remove(model_->GetTemplateURLForKeyword(L"aaaa"));
+ ASSERT_TRUE(model_->GetTemplateURLForKeyword(L"aaaa") == NULL);
+}
diff --git a/chrome/browser/autocomplete/search_provider.cc b/chrome/browser/autocomplete/search_provider.cc
new file mode 100644
index 0000000..40ba841
--- /dev/null
+++ b/chrome/browser/autocomplete/search_provider.cc
@@ -0,0 +1,622 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "chrome/browser/autocomplete/search_provider.h"
+
+#include "base/message_loop.h"
+#include "base/string_util.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/google_util.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/template_url_model.h"
+#include "chrome/common/json_value_serializer.h"
+#include "chrome/common/l10n_util.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/pref_service.h"
+#include "googleurl/src/url_util.h"
+#include "net/base/escape.h"
+
+#include "generated_resources.h"
+
+const int SearchProvider::kQueryDelayMs = 200;
+
+void SearchProvider::Start(const AutocompleteInput& input,
+ bool minimal_changes,
+ bool synchronous_only) {
+ matches_.clear();
+
+ // Can't return search/suggest results for bogus input or if there is no
+ // profile.
+ if (!profile_ || (input.type() == AutocompleteInput::INVALID)) {
+ Stop();
+ return;
+ }
+
+ // Can't search with no default provider.
+ const TemplateURL* const current_default_provider =
+ profile_->GetTemplateURLModel()->GetDefaultSearchProvider();
+ // TODO(pkasting): http://b/1155786 Eventually we should not need all these
+ // checks.
+ if (!current_default_provider || !current_default_provider->url() ||
+ !current_default_provider->url()->SupportsReplacement()) {
+ Stop();
+ return;
+ }
+
+ // If we're still running an old query but have since changed the query text
+ // or the default provider, abort the query.
+ if (!done_ && (!minimal_changes ||
+ (last_default_provider_ != current_default_provider)))
+ Stop();
+
+ // TODO(pkasting): http://b/1162970 We shouldn't need to structure-copy this.
+ // Nor should we need |last_default_provider_| just to know whether the
+ // provider changed.
+ default_provider_ = *current_default_provider;
+ last_default_provider_ = current_default_provider;
+
+ if (input.text().empty()) {
+ // User typed "?" alone. Give them a placeholder result indicating what
+ // this syntax does.
+ AutocompleteMatch match;
+ static const std::wstring kNoQueryInput(
+ l10n_util::GetString(IDS_AUTOCOMPLETE_NO_QUERY));
+ match.contents.assign(l10n_util::GetStringF(
+ IDS_AUTOCOMPLETE_SEARCH_CONTENTS, default_provider_.short_name(),
+ kNoQueryInput));
+ match.contents_class.push_back(
+ ACMatchClassification(0, ACMatchClassification::DIM));
+ match.type = AutocompleteMatch::SEARCH;
+ matches_.push_back(match);
+ Stop();
+ return;
+ }
+
+ input_ = input;
+
+ StartOrStopHistoryQuery(minimal_changes, synchronous_only);
+ StartOrStopSuggestQuery(minimal_changes, synchronous_only);
+ ConvertResultsToAutocompleteMatches();
+}
+
+void SearchProvider::Run() {
+ // Start a new request with the current input.
+ DCHECK(!done_);
+ const TemplateURLRef* const suggestions_url =
+ default_provider_.suggestions_url();
+ DCHECK(suggestions_url->SupportsReplacement());
+ fetcher_.reset(new URLFetcher(GURL(suggestions_url->ReplaceSearchTerms(
+ default_provider_, input_.text(),
+ TemplateURLRef::NO_SUGGESTIONS_AVAILABLE, std::wstring())),
+ URLFetcher::GET, this));
+ fetcher_->set_request_context(profile_->GetRequestContext());
+ fetcher_->Start();
+}
+
+void SearchProvider::Stop() {
+ StopHistory();
+ StopSuggest();
+ done_ = true;
+}
+
+void SearchProvider::OnURLFetchComplete(const URLFetcher* source,
+ const GURL& url,
+ const URLRequestStatus& status,
+ int response_code,
+ const ResponseCookies& cookie,
+ const std::string& data) {
+ DCHECK(!done_);
+ suggest_results_pending_ = false;
+ suggest_results_.clear();
+ navigation_results_.clear();
+ JSONStringValueSerializer deserializer(data);
+ Value* root_val = NULL;
+ have_suggest_results_ = status.is_success() && (response_code == 200) &&
+ deserializer.Deserialize(&root_val) && ParseSuggestResults(root_val);
+ delete root_val;
+ ConvertResultsToAutocompleteMatches();
+ listener_->OnProviderUpdate(!suggest_results_.empty());
+}
+
+void SearchProvider::StartOrStopHistoryQuery(bool minimal_changes,
+ bool synchronous_only) {
+ // For the minimal_changes case, if we finished the previous query and still
+ // have its results, or are allowed to keep running it, just do that, rather
+ // than starting a new query.
+ if (minimal_changes &&
+ (have_history_results_ || (!done_ && !synchronous_only)))
+ return;
+
+ // We can't keep running any previous query, so halt it.
+ StopHistory();
+
+ // We can't start a new query if we're only allowed synchronous results.
+ if (synchronous_only)
+ return;
+
+ // Start the history query.
+ HistoryService* const history_service =
+ profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
+ history_service->GetMostRecentKeywordSearchTerms(default_provider_.id(),
+ input_.text(), static_cast<int>(max_matches()),
+ &history_request_consumer_,
+ NewCallback(this, &SearchProvider::OnGotMostRecentKeywordSearchTerms));
+ history_request_pending_ = true;
+}
+
+void SearchProvider::StartOrStopSuggestQuery(bool minimal_changes,
+ bool synchronous_only) {
+ // Don't run Suggest when off the record, the engine doesn't support it, or
+ // the user has disabled it. Also don't query the server for URLs that aren't
+ // http/https/ftp. Sending things like file: and data: is both a waste of
+ // time and a disclosure of potentially private, local data.
+ if (profile_->IsOffTheRecord() ||
+ !default_provider_.suggestions_url() ||
+ !profile_->GetPrefs()->GetBoolean(prefs::kSearchSuggestEnabled) ||
+ ((input_.type() == AutocompleteInput::URL) &&
+ (input_.scheme() != L"http") && (input_.scheme() != L"https") &&
+ (input_.scheme() != L"ftp"))) {
+ StopSuggest();
+ return;
+ }
+
+ // For the minimal_changes case, if we finished the previous query and still
+ // have its results, or are allowed to keep running it, just do that, rather
+ // than starting a new query.
+ if (minimal_changes &&
+ (have_suggest_results_ || (!done_ && !synchronous_only)))
+ return;
+
+ // We can't keep running any previous query, so halt it.
+ StopSuggest();
+
+ // We can't start a new query if we're only allowed synchronous results.
+ if (synchronous_only)
+ return;
+
+ // Kick off a timer that will start the URL fetch if it completes before
+ // the user types another character.
+ suggest_results_pending_ = true;
+ MessageLoop::current()->timer_manager()->ResetTimer(timer_.get());
+}
+
+void SearchProvider::StopHistory() {
+ history_request_consumer_.CancelAllRequests();
+ history_request_pending_ = false;
+ history_results_.clear();
+ have_history_results_ = false;
+}
+
+void SearchProvider::StopSuggest() {
+ suggest_results_pending_ = false;
+ MessageLoop::current()->timer_manager()->StopTimer(timer_.get());
+ fetcher_.reset(); // Stop any in-progress URL fetch.
+ suggest_results_.clear();
+ have_suggest_results_ = false;
+ star_request_consumer_.CancelAllRequests();
+ star_requests_pending_ = false;
+}
+
+void SearchProvider::OnGotMostRecentKeywordSearchTerms(
+ CancelableRequestProvider::Handle handle,
+ HistoryResults* results) {
+ history_request_pending_ = false;
+ have_history_results_ = true;
+ history_results_ = *results;
+ ConvertResultsToAutocompleteMatches();
+ listener_->OnProviderUpdate(!history_results_.empty());
+}
+
+void SearchProvider::OnQueryURLComplete(HistoryService::Handle handle,
+ bool success,
+ const history::URLRow* url_row,
+ history::VisitVector* unused) {
+ bool is_starred = success ? url_row->starred() : false;
+ star_requests_pending_ = false;
+ // We can't just use star_request_consumer_.HasPendingRequests() here;
+ // see comment in ConvertResultsToAutocompleteMatches().
+ for (NavigationResults::iterator i(navigation_results_.begin());
+ i != navigation_results_.end(); ++i) {
+ if (i->star_request_handle == handle) {
+ i->star_request_handle = 0;
+ i->starred = is_starred;
+ } else if (i->star_request_handle) {
+ star_requests_pending_ = true;
+ }
+ }
+ if (!star_requests_pending_) {
+ // No more requests. Notify the observer.
+ ConvertResultsToAutocompleteMatches();
+ listener_->OnProviderUpdate(true);
+ }
+}
+
+bool SearchProvider::ParseSuggestResults(Value* root_val) {
+ if (!root_val->IsType(Value::TYPE_LIST))
+ return false;
+ ListValue* root_list = static_cast<ListValue*>(root_val);
+
+ Value* query_val;
+ std::wstring query_str;
+ Value* result_val;
+ if ((root_list->GetSize() < 2) || !root_list->Get(0, &query_val) ||
+ !query_val->GetAsString(&query_str) || (query_str != input_.text()) ||
+ !root_list->Get(1, &result_val) || !result_val->IsType(Value::TYPE_LIST))
+ return false;
+
+ ListValue* description_list = NULL;
+ if (root_list->GetSize() > 2) {
+ // 3rd element: Description list.
+ Value* description_val;
+ if (root_list->Get(2, &description_val) &&
+ description_val->IsType(Value::TYPE_LIST))
+ description_list = static_cast<ListValue*>(description_val);
+ }
+
+ // We don't care about the query URL list (the fourth element in the
+ // response) for now.
+
+ // Parse optional data in the results from the Suggest server if any.
+ ListValue* type_list = NULL;
+ // 5th argument: Optional key-value pairs.
+ // TODO: We may iterate the 5th+ arguments of the root_list if any other
+ // optional data are defined.
+ if (root_list->GetSize() > 4) {
+ Value* optional_val;
+ if (root_list->Get(4, &optional_val) &&
+ optional_val->IsType(Value::TYPE_DICTIONARY)) {
+ DictionaryValue* dict_val = static_cast<DictionaryValue*>(optional_val);
+
+ // Parse Google Suggest specific type extension.
+ static const std::wstring kGoogleSuggestType(L"google:suggesttype");
+ if (dict_val->HasKey(kGoogleSuggestType))
+ dict_val->GetList(kGoogleSuggestType, &type_list);
+ }
+ }
+
+ ListValue* result_list = static_cast<ListValue*>(result_val);
+ for (size_t i = 0; i < result_list->GetSize(); ++i) {
+ Value* suggestion_val;
+ std::wstring suggestion_str;
+ if (!result_list->Get(i, &suggestion_val) ||
+ !suggestion_val->GetAsString(&suggestion_str))
+ return false;
+
+ Value* type_val;
+ std::wstring type_str;
+ if (type_list && type_list->Get(i, &type_val) &&
+ type_val->GetAsString(&type_str) && (type_str == L"NAVIGATION")) {
+ Value* site_val;
+ std::wstring site_name;
+ if (navigation_results_.size() < max_matches() &&
+ description_list && description_list->Get(i, &site_val) &&
+ site_val->IsType(Value::TYPE_STRING) &&
+ site_val->GetAsString(&site_name)) {
+ navigation_results_.push_back(NavigationResult(suggestion_str,
+ site_name));
+ }
+ } else {
+ // TODO(kochi): Currently we treat a calculator result as a query, but it
+ // is better to have better presentation for caluculator results.
+ if (suggest_results_.size() < max_matches())
+ suggest_results_.push_back(suggestion_str);
+ }
+ }
+
+ // Request the star state for all URLs from the history service.
+ HistoryService* hs = profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
+ if (!hs)
+ return true;
+
+ for (NavigationResults::iterator i(navigation_results_.begin());
+ i != navigation_results_.end(); ++i) {
+ i->star_request_handle = hs->QueryURL(GURL(i->url), false,
+ &star_request_consumer_,
+ NewCallback(this, &SearchProvider::OnQueryURLComplete));
+ }
+ star_requests_pending_ = !navigation_results_.empty();
+
+ return true;
+}
+
+void SearchProvider::ConvertResultsToAutocompleteMatches() {
+ // Convert all the results to matches and add them to a map, so we can keep
+ // the most relevant match for each result.
+ MatchMap map;
+ const int did_not_accept_suggestion = suggest_results_.empty() ?
+ TemplateURLRef::NO_SUGGESTIONS_AVAILABLE :
+ TemplateURLRef::NO_SUGGESTION_CHOSEN;
+ const Time no_time;
+ AddMatchToMap(input_.text(), CalculateRelevanceForWhatYouTyped(),
+ did_not_accept_suggestion, &map);
+
+ for (HistoryResults::const_iterator i(history_results_.begin());
+ i != history_results_.end(); ++i) {
+ AddMatchToMap(i->term, CalculateRelevanceForHistory(i->time),
+ did_not_accept_suggestion, &map);
+ }
+
+ for (size_t i = 0; i < suggest_results_.size(); ++i) {
+ AddMatchToMap(suggest_results_[i], CalculateRelevanceForSuggestion(i),
+ static_cast<int>(i), &map);
+ }
+
+ // Now add the most relevant matches from the map to |matches_|.
+ matches_.clear();
+ for (MatchMap::const_iterator i(map.begin()); i != map.end(); ++i)
+ matches_.push_back(i->second);
+
+ if (navigation_results_.size()) {
+ // TODO(kochi): http://b/1170574 We add only one results for navigational
+ // suggestions. If we can get more useful information about the score,
+ // consider adding more results.
+ matches_.push_back(NavigationToMatch(navigation_results_[0],
+ CalculateRelevanceForNavigation(0),
+ navigation_results_[0].starred));
+ }
+
+ const size_t max_total_matches = max_matches() + 1; // 1 for "what you typed"
+ std::partial_sort(matches_.begin(),
+ matches_.begin() + std::min(max_total_matches, matches_.size()),
+ matches_.end(), &AutocompleteMatch::MoreRelevant);
+ if (matches_.size() > max_total_matches)
+ matches_.resize(max_total_matches);
+
+ // We're done when both asynchronous subcomponents have finished.
+ // We can't use CancelableRequestConsumer.HasPendingRequests() for
+ // history and star requests here. A pending request is not cleared
+ // until after the completion callback has returned, but we've
+ // reached here from inside that callback. HasPendingRequests()
+ // would therefore return true, and if this is the last thing left
+ // to calculate for this query, we'll never mark the query "done".
+ done_ = !history_request_pending_ &&
+ !suggest_results_pending_ &&
+ !star_requests_pending_;
+}
+
+int SearchProvider::CalculateRelevanceForWhatYouTyped() const {
+ switch (input_.type()) {
+ case AutocompleteInput::UNKNOWN:
+ return 1300;
+
+ case AutocompleteInput::REQUESTED_URL:
+ return 1200;
+
+ case AutocompleteInput::URL:
+ return 850;
+
+ case AutocompleteInput::QUERY:
+ return 1300;
+
+ case AutocompleteInput::FORCED_QUERY:
+ return 1500;
+
+ default:
+ NOTREACHED();
+ return 0;
+ }
+}
+
+int SearchProvider::CalculateRelevanceForHistory(const Time& time) const {
+ // The relevance of past searches falls off over time. This curve is chosen
+ // so that the relevance of a search 15 minutes ago is discounted about 50
+ // points, while the relevance of a search two weeks ago is discounted about
+ // 450 points.
+ const double elapsed_time = std::max((Time::Now() - time).InSecondsF(), 0.);
+ const int score_discount = static_cast<int>(6.5 * pow(elapsed_time, 0.3));
+
+ // Don't let scores go below 0. Negative relevance scores are meaningful in a
+ // different way.
+ int base_score;
+ switch (input_.type()) {
+ case AutocompleteInput::UNKNOWN:
+ case AutocompleteInput::REQUESTED_URL:
+ base_score = 1050;
+ break;
+
+ case AutocompleteInput::URL:
+ base_score = 750;
+ break;
+
+ case AutocompleteInput::QUERY:
+ case AutocompleteInput::FORCED_QUERY:
+ base_score = 1250;
+ break;
+
+ default:
+ NOTREACHED();
+ base_score = 0;
+ break;
+ }
+ return std::max(0, base_score - score_discount);
+}
+
+int SearchProvider::CalculateRelevanceForSuggestion(
+ size_t suggestion_number) const {
+ DCHECK(suggestion_number < suggest_results_.size());
+ const int suggestion_value =
+ static_cast<int>(suggest_results_.size() - 1 - suggestion_number);
+ switch (input_.type()) {
+ case AutocompleteInput::UNKNOWN:
+ case AutocompleteInput::REQUESTED_URL:
+ return 600 + suggestion_value;
+
+ case AutocompleteInput::URL:
+ return 300 + suggestion_value;
+
+ case AutocompleteInput::QUERY:
+ case AutocompleteInput::FORCED_QUERY:
+ return 800 + suggestion_value;
+
+ default:
+ NOTREACHED();
+ return 0;
+ }
+}
+
+int SearchProvider::CalculateRelevanceForNavigation(
+ size_t suggestion_number) const {
+ DCHECK(suggestion_number < navigation_results_.size());
+ // TODO(kochi): http://b/784900 Use relevance score from the NavSuggest
+ // server if possible.
+ switch (input_.type()) {
+ case AutocompleteInput::QUERY:
+ case AutocompleteInput::FORCED_QUERY:
+ return 1000 + static_cast<int>(suggestion_number);
+
+ default:
+ return 800 + static_cast<int>(suggestion_number);
+ }
+}
+
+void SearchProvider::AddMatchToMap(const std::wstring& query_string,
+ int relevance,
+ int accepted_suggestion,
+ MatchMap* map) {
+ AutocompleteMatch match(this, relevance, false);
+ match.type = AutocompleteMatch::SEARCH;
+ std::vector<size_t> content_param_offsets;
+ match.contents.assign(l10n_util::GetStringF(IDS_AUTOCOMPLETE_SEARCH_CONTENTS,
+ default_provider_.short_name(),
+ query_string,
+ &content_param_offsets));
+ if (content_param_offsets.size() == 2) {
+ AutocompleteMatch::ClassifyLocationInString(content_param_offsets[1],
+ query_string.length(),
+ match.contents.length(),
+ ACMatchClassification::NONE,
+ &match.contents_class);
+ } else {
+ // |content_param_offsets| should only not be 2 if:
+ // (a) A translator screws up
+ // (b) The strings have been changed and we haven't been rebuilt properly
+ // (c) Some sort of crazy installer error/DLL version mismatch problem that
+ // gets the wrong data out of the locale DLL?
+ // While none of these are supposed to happen, we've seen this get hit in
+ // the wild, so avoid the vector access in the conditional arm above, which
+ // will crash.
+ NOTREACHED();
+ }
+
+ // When the user forced a query, we need to make sure all the fill_into_edit
+ // values preserve that property. Otherwise, if the user starts editing a
+ // suggestion, non-Search results will suddenly appear.
+ size_t search_start = 0;
+ if (input_.type() == AutocompleteInput::FORCED_QUERY) {
+ match.fill_into_edit.assign(L"?");
+ ++search_start;
+ }
+ match.fill_into_edit.append(query_string);
+ // NOTE: All Google suggestions currently start with the original input, but
+ // not all Yahoo! suggestions do.
+ if (!input_.prevent_inline_autocomplete() &&
+ !match.fill_into_edit.compare(search_start, input_.text().length(),
+ input_.text()))
+ match.inline_autocomplete_offset = search_start + input_.text().length();
+
+ const TemplateURLRef* const search_url = default_provider_.url();
+ DCHECK(search_url->SupportsReplacement());
+ match.destination_url = search_url->ReplaceSearchTerms(default_provider_,
+ query_string,
+ accepted_suggestion,
+ input_.text());
+
+ // Search results don't look like URLs.
+ match.transition = PageTransition::GENERATED;
+
+ // Try to add |match| to |map|. If a match for |query_string| is already in
+ // |map|, replace it if |match| is more relevant.
+ // NOTE: Keep this ToLower() call in sync with url_database.cc.
+ const std::pair<MatchMap::iterator, bool> i = map->insert(
+ std::pair<std::wstring, AutocompleteMatch>(
+ l10n_util::ToLower(query_string), match));
+ // NOTE: We purposefully do a direct relevance comparison here instead of
+ // using AutocompleteMatch::MoreRelevant(), so that we'll prefer "items added
+ // first" rather than "items alphabetically first" when the scores are equal.
+ // The only case this matters is when a user has results with the same score
+ // that differ only by capitalization; because the history system returns
+ // results sorted by recency, this means we'll pick the most recent such
+ // result even if the precision of our relevance score is too low to
+ // distinguish the two.
+ if (!i.second && (match.relevance > i.first->second.relevance))
+ i.first->second = match;
+}
+
+AutocompleteMatch SearchProvider::NavigationToMatch(
+ const NavigationResult& navigation,
+ int relevance,
+ bool starred) {
+ AutocompleteMatch match(this, relevance, false);
+ match.destination_url = navigation.url;
+ match.contents = StringForURLDisplay(GURL(navigation.url), true);
+ // TODO(kochi): Consider moving HistoryURLProvider::TrimHttpPrefix() to some
+ // public utility function.
+ if (!url_util::FindAndCompareScheme(input_.text(), "http", NULL))
+ TrimHttpPrefix(&match.contents);
+ AutocompleteMatch::ClassifyMatchInString(input_.text(), match.contents,
+ ACMatchClassification::URL,
+ &match.contents_class);
+
+ match.description = navigation.site_name;
+ AutocompleteMatch::ClassifyMatchInString(input_.text(), navigation.site_name,
+ ACMatchClassification::NONE,
+ &match.description_class);
+
+ match.starred = starred;
+ // When the user forced a query, we need to make sure all the fill_into_edit
+ // values preserve that property. Otherwise, if the user starts editing a
+ // suggestion, non-Search results will suddenly appear.
+ if (input_.type() == AutocompleteInput::FORCED_QUERY)
+ match.fill_into_edit.assign(L"?");
+ match.fill_into_edit.append(match.contents);
+ // TODO(pkasting): http://b/1112879 These should perhaps be
+ // inline-autocompletable?
+
+ return match;
+}
+
+// TODO(kochi): This is duplicate from HistoryURLProvider.
+// static
+size_t SearchProvider::TrimHttpPrefix(std::wstring* url) {
+ url_parse::Component scheme;
+ if (!url_util::FindAndCompareScheme(*url, "http", &scheme))
+ return 0; // Not "http".
+
+ // Erase scheme plus up to two slashes.
+ size_t prefix_len = scheme.end() + 1; // "http:"
+ const size_t after_slashes = std::min(url->length(),
+ static_cast<size_t>(scheme.end() + 3));
+ while ((prefix_len < after_slashes) && ((*url)[prefix_len] == L'/'))
+ ++prefix_len;
+ if (prefix_len == url->length())
+ url->clear();
+ else
+ url->erase(url->begin(), url->begin() + prefix_len);
+ return prefix_len;
+}
diff --git a/chrome/browser/autocomplete/search_provider.h b/chrome/browser/autocomplete/search_provider.h
new file mode 100644
index 0000000..ab4b2ba
--- /dev/null
+++ b/chrome/browser/autocomplete/search_provider.h
@@ -0,0 +1,244 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// This file contains the Search autocomplete provider. This provider is
+// responsible for all non-keyword autocomplete entries that start with
+// "Search <engine> for ...", including searching for the current input string,
+// search history, and search suggestions. An instance of it gets created and
+// managed by the autocomplete controller.
+//
+// For more information on the autocomplete system in general, including how
+// the autocomplete controller and autocomplete providers work, see
+// chrome/browser/autocomplete.h.
+
+#ifndef CHROME_BROWSER_AUTOCOMPLETE_SEARCH_PROVIDER_H__
+#define CHROME_BROWSER_AUTOCOMPLETE_SEARCH_PROVIDER_H__
+
+#include "chrome/browser/autocomplete/autocomplete.h"
+#include "chrome/browser/history/history.h"
+#include "chrome/browser/template_url.h"
+#include "chrome/browser/url_fetcher.h"
+
+class Profile;
+class Value;
+
+// Autocomplete provider for searches and suggestions from a search engine.
+//
+// After construction, the autocomplete controller repeatedly calls Start()
+// with some user input, each time expecting to receive a small set of the best
+// matches (either synchronously or asynchronously).
+//
+// Initially the provider creates a match that searches for the current input
+// text. It also starts a task to query the Suggest servers. When that data
+// comes back, the provider creates and returns matches for the best
+// suggestions.
+//
+// TODO(pkasting): http://b/893701 This should eventually remember the user's
+// search history and use that to create/rank suggestions as well.
+class SearchProvider : public AutocompleteProvider,
+ public URLFetcher::Delegate,
+ public Task {
+ public:
+ SearchProvider(ACProviderListener* listener, Profile* profile)
+ : AutocompleteProvider(listener, profile, "Search"),
+ last_default_provider_(NULL),
+#pragma warning(suppress: 4355) // Okay to pass "this" here.
+ timer_(new Timer(kQueryDelayMs, this, false)),
+ fetcher_(NULL),
+ star_requests_pending_(false),
+ history_request_pending_(false),
+ have_history_results_(false),
+ suggest_results_pending_(false),
+ have_suggest_results_(false) {
+ }
+
+ // AutocompleteProvider
+ virtual void Start(const AutocompleteInput& input,
+ bool minimal_changes,
+ bool synchronous_only);
+ virtual void Stop();
+
+ // URLFetcher::Delegate
+ virtual void OnURLFetchComplete(const URLFetcher* source,
+ const GURL& url,
+ const URLRequestStatus& status,
+ int response_code,
+ const ResponseCookies& cookies,
+ const std::string& data);
+
+ // Task
+ void Run();
+
+ private:
+ struct NavigationResult {
+ NavigationResult(const std::wstring& url, const std::wstring& site_name)
+ : url(url),
+ site_name(site_name),
+ star_request_handle(0),
+ starred(false) {
+ }
+
+ // The URL.
+ std::wstring url;
+
+ // Name for the site.
+ std::wstring site_name;
+
+ // If non-zero, there is a pending request to the history service to
+ // obtain the starred state.
+ HistoryService::Handle star_request_handle;
+
+ // Whether the URL has been starred.
+ bool starred;
+ };
+
+ typedef std::vector<std::wstring> SuggestResults;
+ typedef std::vector<NavigationResult> NavigationResults;
+ typedef std::vector<history::KeywordSearchTermVisit> HistoryResults;
+ typedef std::map<std::wstring, AutocompleteMatch> MatchMap;
+
+ // Determines whether an asynchronous subcomponent query should run for the
+ // current input. If so, starts it if necessary; otherwise stops it.
+ // NOTE: These functions do not update |done_|. Callers must do so.
+ void StartOrStopHistoryQuery(bool minimal_changes, bool synchronous_only);
+ void StartOrStopSuggestQuery(bool minimal_changes, bool synchronous_only);
+
+ // Functions to stop the separate asynchronous subcomponents.
+ // NOTE: These functions do not update |done_|. Callers must do so.
+ void StopHistory();
+ void StopSuggest();
+
+ // Called back by the history system to return searches that begin with the
+ // input text.
+ void OnGotMostRecentKeywordSearchTerms(
+ CancelableRequestProvider::Handle handle,
+ HistoryResults* results);
+
+ // Notification from the history service that the star state for the URL
+ // is available. If this is the last url's star state that is being requested
+ // the listener is notified.
+ void OnQueryURLComplete(HistoryService::Handle handle,
+ bool success,
+ const history::URLRow* url_row,
+ history::VisitVector* unused);
+
+ // Parses the results from the Suggest server and stores up to kMaxMatches of
+ // them in server_results_. Returns whether parsing succeeded.
+ bool ParseSuggestResults(Value* root_val);
+
+ // Converts the parsed server results in server_results_ to a set of
+ // AutocompleteMatches and adds them to |matches_|. This also sets |done_|
+ // correctly.
+ void ConvertResultsToAutocompleteMatches();
+
+ // Determines the relevance for a particular match. We use different scoring
+ // algorithms for the different types of matches.
+ int CalculateRelevanceForWhatYouTyped() const;
+ // |time| is the time at which this query was last seen.
+ int CalculateRelevanceForHistory(const Time& time) const;
+ // |suggestion_value| is which suggestion this is in the list returned from
+ // the server; the best suggestion is suggestion number 0.
+ int CalculateRelevanceForSuggestion(size_t suggestion_value) const;
+ // |suggestion_value| is same as above.
+ int CalculateRelevanceForNavigation(size_t suggestion_value) const;
+
+ // Creates an AutocompleteMatch for "Search <engine> for |query_string|" with
+ // the supplied relevance. Adds this match to |map|; if such a match already
+ // exists, whichever one has lower relevance is eliminated.
+ void AddMatchToMap(const std::wstring& query_string,
+ int relevance,
+ int accepted_suggestion,
+ MatchMap* map);
+ // Returns an AutocompleteMatch for a navigational suggestion.
+ AutocompleteMatch NavigationToMatch(const NavigationResult& query_string,
+ int relevance,
+ bool starred);
+
+ // Trims "http:" and up to two subsequent slashes from |url|. Returns the
+ // number of characters that were trimmed.
+ // TODO(kochi): this is duplicate from history_autocomplete
+ static size_t TrimHttpPrefix(std::wstring* url);
+
+ // Don't send any queries to the server until some time has elapsed after
+ // the last keypress, to avoid flooding the server with requests we are
+ // likely to end up throwing away anyway.
+ static const int kQueryDelayMs;
+
+ // The user's input.
+ AutocompleteInput input_;
+
+ TemplateURL default_provider_; // Cached across the life of a query so we
+ // behave consistently even if the user
+ // changes their default while the query is
+ // running.
+ const TemplateURL* last_default_provider_;
+ // TODO(pkasting): http://b/1162970 We
+ // shouldn't need this.
+
+ // An object we can use to cancel history and star requests.
+ CancelableRequestConsumer history_request_consumer_;
+ CancelableRequestConsumerT<int, 0> star_request_consumer_;
+
+ // Whether we are waiting for star requests to finish.
+ bool star_requests_pending_;
+
+ // Searches in the user's history that begin with the input text.
+ HistoryResults history_results_;
+
+ // Whether history_results_ is valid (so we can tell invalid apart from
+ // empty).
+ bool have_history_results_;
+
+ // Whether we are waiting for a history request to finish.
+ bool history_request_pending_;
+
+ // True if we're expecting suggest results that haven't yet arrived. This
+ // could be because either |timer_| or |fetcher| is still running (see below).
+ bool suggest_results_pending_;
+
+ // A timer to start a query to the suggest server after the user has stopped
+ // typing for long enough.
+ scoped_ptr<Timer> timer_;
+
+ // The fetcher that retrieves suggest results from the server.
+ scoped_ptr<URLFetcher> fetcher_;
+
+ // Suggestions returned by the Suggest server for the input text.
+ SuggestResults suggest_results_;
+
+ // Navigational suggestions returned by the server.
+ NavigationResults navigation_results_;
+
+ // Whether suggest_results_ is valid.
+ bool have_suggest_results_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(SearchProvider);
+};
+
+#endif // CHROME_BROWSER_AUTOCOMPLETE_SEARCH_PROVIDER_H__