Now that TemplateURLs are guaranteed to have a unique, non-empty keyword, replace AutocompleteMatch's TemplateURL* with a std::string |keyword_|.  This has two benefits:

* It's safer in general, because the pointer can't be deleted out from under us.  Users inherently have to recheck the TemplateURL* they get via the keyword each time they re-fetch it.

* It allows the SearchProvider to stop keeping copied TemplateURLs for the default and keyword providers (in an attempt to mitigate the above problem) and instead just keep their keywords.  This will also help in removing the TemplateURL copy constructor in a later patch.

Note that there already was a |keyword_| field on AutocompleteMatch, so to make this change we have to expand its meaning a bit to cover the scenarios where before we would have had a |template_url_| but not a keyword.  Code that cares about this distinction has already been migrated to using functions like GetKeywordUIState() instead of checking the member directly so this isn't terribly invasive.

BUG=none
TEST=none
Review URL: https://chromiumcodereview.appspot.com/10382066

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@135905 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/autocomplete/autocomplete.cc b/chrome/browser/autocomplete/autocomplete.cc
index 1dd1d90..ac6dc0a8 100644
--- a/chrome/browser/autocomplete/autocomplete.cc
+++ b/chrome/browser/autocomplete/autocomplete.cc
@@ -54,14 +54,6 @@
 
 using base::TimeDelta;
 
-static bool AreTemplateURLsEqual(const TemplateURL* a,
-                                 const TemplateURL* b) {
-  // We can't use equality of the pointers because SearchProvider copies the
-  // TemplateURLs. Instead we compare based on ID.
-  // a may be NULL, but never b, so we don't handle the case of a==b==NULL.
-  return a && b && (a->id() == b->id());
-}
-
 // AutocompleteInput ----------------------------------------------------------
 
 AutocompleteInput::AutocompleteInput()
@@ -1046,7 +1038,7 @@
   std::set<string16> keywords;
   for (ACMatches::iterator match(result->begin()); match != result->end();
        ++match) {
-    string16 keyword(match->GetSubstitutingExplicitlyInvokedKeyword());
+    string16 keyword(match->GetSubstitutingExplicitlyInvokedKeyword(profile_));
     if (!keyword.empty()) {
       keywords.insert(keyword);
     } else {
@@ -1072,27 +1064,30 @@
 
 void AutocompleteController::UpdateKeywordDescriptions(
     AutocompleteResult* result) {
-  const TemplateURL* last_template_url = NULL;
+  string16 last_keyword;
   for (AutocompleteResult::iterator i = result->begin(); i != result->end();
        ++i) {
-    if (((i->provider == keyword_provider_) && i->template_url) ||
+    if (((i->provider == keyword_provider_) && !i->keyword.empty()) ||
         ((i->provider == search_provider_) &&
          (i->type == AutocompleteMatch::SEARCH_WHAT_YOU_TYPED ||
           i->type == AutocompleteMatch::SEARCH_HISTORY ||
           i->type == AutocompleteMatch::SEARCH_SUGGEST))) {
       i->description.clear();
       i->description_class.clear();
-      DCHECK(i->template_url);
-      if (!AreTemplateURLsEqual(last_template_url, i->template_url)) {
-        i->description = l10n_util::GetStringFUTF16(
-            IDS_AUTOCOMPLETE_SEARCH_DESCRIPTION,
-            i->template_url->AdjustedShortNameForLocaleDirection());
-        i->description_class.push_back(
-            ACMatchClassification(0, ACMatchClassification::DIM));
+      DCHECK(!i->keyword.empty());
+      if (i->keyword != last_keyword) {
+        const TemplateURL* template_url = i->GetTemplateURL(profile_);
+        if (template_url) {
+          i->description = l10n_util::GetStringFUTF16(
+              IDS_AUTOCOMPLETE_SEARCH_DESCRIPTION,
+              template_url->AdjustedShortNameForLocaleDirection());
+          i->description_class.push_back(
+              ACMatchClassification(0, ACMatchClassification::DIM));
+        }
+        last_keyword = i->keyword;
       }
-      last_template_url = i->template_url;
     } else {
-      last_template_url = NULL;
+      last_keyword.clear();
     }
   }
 }
diff --git a/chrome/browser/autocomplete/autocomplete_edit.cc b/chrome/browser/autocomplete/autocomplete_edit.cc
index 24604b1..9882fb75 100644
--- a/chrome/browser/autocomplete/autocomplete_edit.cc
+++ b/chrome/browser/autocomplete/autocomplete_edit.cc
@@ -496,7 +496,7 @@
     match.transition = content::PAGE_TRANSITION_LINK;
   }
 
-  const TemplateURL* template_url = match.GetTemplateURL();
+  const TemplateURL* template_url = match.GetTemplateURL(profile_);
   if (template_url && template_url->url_ref().HasGoogleBaseURLs())
     GoogleURLTracker::GoogleURLSearchCommitted();
 
@@ -542,7 +542,7 @@
         content::Details<AutocompleteLog>(&log));
   }
 
-  TemplateURL* template_url = match.GetTemplateURL();
+  TemplateURL* template_url = match.GetTemplateURL(profile_);
   if (template_url) {
     if (match.transition == content::PAGE_TRANSITION_KEYWORD) {
       // The user is using a non-substituting keyword or is explicitly in
@@ -559,7 +559,7 @@
                 current_match : result().match_at(index);
 
         // Strip the keyword + leading space off the input.
-        size_t prefix_length = match.template_url->keyword().length() + 1;
+        size_t prefix_length = match.keyword.length() + 1;
         extensions::ExtensionOmniboxEventRouter::OnInputEntered(profile_,
             template_url->GetExtensionId(),
             UTF16ToUTF8(match.fill_into_edit.substr(prefix_length)));
@@ -923,7 +923,7 @@
       // 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.
 
-      match->GetKeywordUIState(&keyword, &is_keyword_hint);
+      match->GetKeywordUIState(profile_, &keyword, &is_keyword_hint);
     }
 
     popup_->OnResultChanged();
diff --git a/chrome/browser/autocomplete/autocomplete_edit_unittest.cc b/chrome/browser/autocomplete/autocomplete_edit_unittest.cc
index 2eb231f..dd80bd1 100644
--- a/chrome/browser/autocomplete/autocomplete_edit_unittest.cc
+++ b/chrome/browser/autocomplete/autocomplete_edit_unittest.cc
@@ -156,6 +156,9 @@
   TestingOmniboxView view;
   TestingAutocompleteEditController controller;
   TestingProfile profile;
+  // NOTE: The TemplateURLService must be created before the
+  // AutocompleteClassifier so that the SearchProvider gets a non-NULL
+  // TemplateURLService at construction time.
   profile.CreateTemplateURLService();
   profile.CreateAutocompleteClassifier();
   AutocompleteEditModel model(&view, &controller, &profile);
diff --git a/chrome/browser/autocomplete/autocomplete_match.cc b/chrome/browser/autocomplete/autocomplete_match.cc
index 63cd210..d5cbebc 100644
--- a/chrome/browser/autocomplete/autocomplete_match.cc
+++ b/chrome/browser/autocomplete/autocomplete_match.cc
@@ -7,6 +7,8 @@
 #include "base/string_util.h"
 #include "chrome/browser/autocomplete/autocomplete_match.h"
 #include "chrome/browser/search_engines/template_url.h"
+#include "chrome/browser/search_engines/template_url_service.h"
+#include "chrome/browser/search_engines/template_url_service_factory.h"
 #include "grit/theme_resources.h"
 
 // AutocompleteMatch ----------------------------------------------------------
@@ -27,7 +29,6 @@
       transition(content::PAGE_TRANSITION_GENERATED),
       is_history_what_you_typed_match(false),
       type(SEARCH_WHAT_YOU_TYPED),
-      template_url(NULL),
       starred(false),
       from_previous(false) {
 }
@@ -43,7 +44,6 @@
       transition(content::PAGE_TRANSITION_TYPED),
       is_history_what_you_typed_match(false),
       type(type),
-      template_url(NULL),
       starred(false),
       from_previous(false) {
 }
@@ -64,7 +64,6 @@
       is_history_what_you_typed_match(match.is_history_what_you_typed_match),
       type(match.type),
       keyword(match.keyword),
-      template_url(match.template_url),
       starred(match.starred),
       from_previous(match.from_previous) {
   if (match.associated_keyword.get())
@@ -96,7 +95,6 @@
   associated_keyword.reset(match.associated_keyword.get() ?
       new AutocompleteMatch(*match.associated_keyword) : NULL);
   keyword = match.keyword;
-  template_url = match.template_url;
   starred = match.starred;
   from_previous = match.from_previous;
 
@@ -288,22 +286,27 @@
   }
 }
 
-void AutocompleteMatch::GetKeywordUIState(string16* keyword,
+void AutocompleteMatch::GetKeywordUIState(Profile* profile,
+                                          string16* keyword,
                                           bool* is_keyword_hint) const {
   *is_keyword_hint = associated_keyword.get() != NULL;
   keyword->assign(*is_keyword_hint ? associated_keyword->keyword :
-      GetSubstitutingExplicitlyInvokedKeyword());
+      GetSubstitutingExplicitlyInvokedKeyword(profile));
 }
 
-string16 AutocompleteMatch::GetSubstitutingExplicitlyInvokedKeyword() const {
+string16 AutocompleteMatch::GetSubstitutingExplicitlyInvokedKeyword(
+    Profile* profile) const {
   if (transition != content::PAGE_TRANSITION_KEYWORD)
     return string16();
-  const TemplateURL* t_url = GetTemplateURL();
+  const TemplateURL* t_url = GetTemplateURL(profile);
   return (t_url && t_url->SupportsReplacement()) ? keyword : string16();
 }
 
-TemplateURL* AutocompleteMatch::GetTemplateURL() const {
-  return template_url;
+TemplateURL* AutocompleteMatch::GetTemplateURL(Profile* profile) const {
+  DCHECK(profile);
+  return keyword.empty() ? NULL :
+      TemplateURLServiceFactory::GetForProfile(profile)->
+          GetTemplateURLForKeyword(keyword);
 }
 
 #ifndef NDEBUG
diff --git a/chrome/browser/autocomplete/autocomplete_match.h b/chrome/browser/autocomplete/autocomplete_match.h
index dd53932..cef1892 100644
--- a/chrome/browser/autocomplete/autocomplete_match.h
+++ b/chrome/browser/autocomplete/autocomplete_match.h
@@ -14,6 +14,7 @@
 #include "googleurl/src/gurl.h"
 
 class AutocompleteProvider;
+class Profile;
 class TemplateURL;
 
 // AutocompleteMatch ----------------------------------------------------------
@@ -168,8 +169,11 @@
   // to the associated keyword and |is_keyword_hint| will be set to true.  Note
   // that only one of these states can be in effect at once.  In all other
   // cases, |keyword| will be cleared, even when our member variable |keyword|
-  // is non-empty.  See also GetSubstitutingExplicitlyInvokedKeyword().
-  void GetKeywordUIState(string16* keyword,
+  // is non-empty -- such as with non-substituting keywords or matches that
+  // represent searches using the default search engine.  See also
+  // GetSubstitutingExplicitlyInvokedKeyword().
+  void GetKeywordUIState(Profile* profile,
+                         string16* keyword,
                          bool* is_keyword_hint) const;
 
   // Returns |keyword|, but only if it represents a substituting keyword that
@@ -178,10 +182,12 @@
   // invoke its keyword), this returns the empty string.  The result is that
   // this function returns a non-empty string in the same cases as when the UI
   // should show up as being "in keyword mode".
-  string16 GetSubstitutingExplicitlyInvokedKeyword() const;
+  string16 GetSubstitutingExplicitlyInvokedKeyword(Profile* profile) const;
 
-  // Returns the TemplateURL associated with this match.
-  TemplateURL* GetTemplateURL() const;
+  // Returns the TemplateURL associated with this match.  This may be NULL if
+  // the match has no keyword OR if the keyword no longer corresponds to a valid
+  // TemplateURL.  See comments on |keyword| below.
+  TemplateURL* GetTemplateURL(Profile* profile) const;
 
   // 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
@@ -243,21 +249,19 @@
   // |associated_keyword| could be a KeywordProvider match for "amazon.com".
   scoped_ptr<AutocompleteMatch> associated_keyword;
 
-  // For matches that correspond to valid substituting keywords ("search
-  // engines" that aren't the default engine, or extension keywords), this
-  // is the keyword.  If this is set, then when displaying this match, the
-  // edit will use the "keyword mode" UI that shows a blue
-  // "Search <engine name>" chit before the user's typing.  This should be
-  // set for any match that's an |associated_keyword| of a match in the main
-  // result list, as well as any other matches in the main result list that
-  // are direct keyword matches (e.g. if the user types in a keyword name and
-  // some search terms directly).
+  // The keyword of the TemplateURL the match originated from.  This is nonempty
+  // for both explicit "keyword mode" matches as well as matches for the default
+  // search provider (so, any match for which we're doing substitution); it
+  // doesn't imply (alone) that the UI is going to show a keyword hint or
+  // keyword mode.  For that, see GetKeywordUIState() or
+  // GetSubstitutingExplicitlyInvokedKeyword().
+  //
+  // CAUTION: The TemplateURL associated with this keyword may be deleted or
+  // modified while the AutocompleteMatch is alive.  This means anyone who
+  // accesses it must perform any necessary sanity checks before blindly using
+  // it!
   string16 keyword;
 
-  // Indicates the TemplateURL the match originated from. This is set for
-  // keywords as well as matches for the default search provider.
-  TemplateURL* template_url;
-
   // True if the user has starred the destination URL.
   bool starred;
 
diff --git a/chrome/browser/autocomplete/autocomplete_popup_model.cc b/chrome/browser/autocomplete/autocomplete_popup_model.cc
index c5a05b6..3e93a16 100644
--- a/chrome/browser/autocomplete/autocomplete_popup_model.cc
+++ b/chrome/browser/autocomplete/autocomplete_popup_model.cc
@@ -108,7 +108,7 @@
   // eliminated and just become a call to the observer on the edit.
   string16 keyword;
   bool is_keyword_hint;
-  match.GetKeywordUIState(&keyword, &is_keyword_hint);
+  match.GetKeywordUIState(edit_model_->profile(), &keyword, &is_keyword_hint);
 
   if (reset_to_default) {
     string16 inline_autocomplete_text;
@@ -197,7 +197,7 @@
 const SkBitmap* AutocompletePopupModel::GetIconIfExtensionMatch(
     const AutocompleteMatch& match) const {
   Profile* profile = edit_model_->profile();
-  const TemplateURL* template_url = match.GetTemplateURL();
+  const TemplateURL* template_url = match.GetTemplateURL(profile);
   return (template_url && template_url->IsExtensionKeyword()) ?
       &profile->GetExtensionService()->GetOmniboxPopupIcon(
           template_url->GetExtensionId()) : NULL;
diff --git a/chrome/browser/autocomplete/autocomplete_unittest.cc b/chrome/browser/autocomplete/autocomplete_unittest.cc
index b0082948..c6f0820 100644
--- a/chrome/browser/autocomplete/autocomplete_unittest.cc
+++ b/chrome/browser/autocomplete/autocomplete_unittest.cc
@@ -264,15 +264,11 @@
     const KeywordTestData* match_data,
     size_t size) {
   ACMatches matches;
-  TemplateURLService* turl_model =
-      TemplateURLServiceFactory::GetForProfile(&profile_);
   for (size_t i = 0; i < size; ++i) {
     AutocompleteMatch match;
     match.fill_into_edit = match_data[i].fill_into_edit;
     match.transition = content::PAGE_TRANSITION_KEYWORD;
     match.keyword = match_data[i].keyword;
-    if (!match.keyword.empty())
-      match.template_url = turl_model->GetTemplateURLForKeyword(match.keyword);
     matches.push_back(match);
   }
 
diff --git a/chrome/browser/autocomplete/keyword_provider.cc b/chrome/browser/autocomplete/keyword_provider.cc
index 4033c8b5..1f70105 100644
--- a/chrome/browser/autocomplete/keyword_provider.cc
+++ b/chrome/browser/autocomplete/keyword_provider.cc
@@ -472,10 +472,7 @@
   // into keyword templates.
   FillInURLAndContents(profile_, remaining_input, element, &match);
 
-  if (supports_replacement) {
-    match.template_url = element;
-    match.keyword = keyword;
-  }
+  match.keyword = keyword;
   match.transition = content::PAGE_TRANSITION_KEYWORD;
 
   return match;
diff --git a/chrome/browser/autocomplete/search_provider.cc b/chrome/browser/autocomplete/search_provider.cc
index c8beef2..0a1af9f 100644
--- a/chrome/browser/autocomplete/search_provider.cc
+++ b/chrome/browser/autocomplete/search_provider.cc
@@ -65,17 +65,18 @@
 
 // SearchProvider::Providers --------------------------------------------------
 
-void SearchProvider::Providers::Set(const TemplateURL* default_provider,
-                                    const TemplateURL* keyword_provider) {
-  // TODO(pkasting): http://b/1162970  We shouldn't need to structure-copy
-  // this. Nor should we need |default_provider_| and |keyword_provider_|
-  // just to know whether the provider changed.
-  default_provider_ = default_provider;
-  if (default_provider)
-    cached_default_provider_ = *default_provider;
-  keyword_provider_ = keyword_provider;
-  if (keyword_provider)
-    cached_keyword_provider_ = *keyword_provider;
+SearchProvider::Providers::Providers(TemplateURLService* template_url_service)
+    : template_url_service_(template_url_service) {
+}
+
+const TemplateURL* SearchProvider::Providers::GetDefaultProviderURL() const {
+  return default_provider_.empty() ? NULL :
+      template_url_service_->GetTemplateURLForKeyword(default_provider_);
+}
+
+const TemplateURL* SearchProvider::Providers::GetKeywordProviderURL() const {
+  return keyword_provider_.empty() ? NULL :
+      template_url_service_->GetTemplateURLForKeyword(keyword_provider_);
 }
 
 
@@ -90,7 +91,7 @@
 
 SearchProvider::SearchProvider(ACProviderListener* listener, Profile* profile)
     : AutocompleteProvider(listener, profile, "Search"),
-      providers_(profile),
+      providers_(TemplateURLServiceFactory::GetForProfile(profile)),
       suggest_results_pending_(0),
       have_suggest_results_(false),
       instant_finalized_(false) {
@@ -185,9 +186,10 @@
   if (keyword_input_text_.empty())
     keyword_provider = NULL;
 
-  const TemplateURL* default_provider =
-      TemplateURLServiceFactory::GetForProfile(profile_)->
-      GetDefaultSearchProvider();
+  TemplateURLService* model = providers_.template_url_service();
+  DCHECK(model);
+  model->Load();
+  const TemplateURL* default_provider = model->GetDefaultSearchProvider();
   if (default_provider && !default_provider->SupportsReplacement())
     default_provider = NULL;
 
@@ -202,15 +204,19 @@
 
   // If we're still running an old query but have since changed the query text
   // or the providers, abort the query.
+  string16 default_provider_keyword(default_provider ?
+      default_provider->keyword() : string16());
+  string16 keyword_provider_keyword(keyword_provider ?
+      keyword_provider->keyword() : string16());
   if (!minimal_changes ||
-      !providers_.equals(default_provider, keyword_provider)) {
+      !providers_.equal(default_provider_keyword, keyword_provider_keyword)) {
     if (done_)
       default_provider_suggest_text_.clear();
     else
       Stop();
   }
 
-  providers_.Set(default_provider, keyword_provider);
+  providers_.set(default_provider_keyword, keyword_provider_keyword);
 
   if (input.text().empty()) {
     // User typed "?" alone.  Give them a placeholder result indicating what
@@ -221,7 +227,7 @@
       match.contents.assign(l10n_util::GetStringUTF16(IDS_EMPTY_KEYWORD_VALUE));
       match.contents_class.push_back(
           ACMatchClassification(0, ACMatchClassification::NONE));
-      match.template_url = providers_.default_provider();
+      match.keyword = providers_.default_provider();
       matches_.push_back(match);
     }
     Stop();
@@ -248,20 +254,27 @@
   DCHECK(!done_);
   suggest_results_pending_ = 0;
   time_suggest_request_sent_ = base::TimeTicks::Now();
-  if (providers_.valid_suggest_for_default_provider()) {
+  const TemplateURL* default_url = providers_.GetDefaultProviderURL();
+  if (default_url && !default_url->suggestions_url().empty()) {
     suggest_results_pending_++;
     default_fetcher_.reset(CreateSuggestFetcher(kDefaultProviderURLFetcherID,
-        providers_.default_provider()->suggestions_url_ref(), input_.text()));
+        default_url->suggestions_url_ref(), input_.text()));
   }
-  if (providers_.valid_suggest_for_keyword_provider()) {
+  const TemplateURL* keyword_url = providers_.GetKeywordProviderURL();
+  if (keyword_url && !keyword_url->suggestions_url().empty()) {
     suggest_results_pending_++;
     keyword_fetcher_.reset(CreateSuggestFetcher(kKeywordProviderURLFetcherID,
-        providers_.keyword_provider()->suggestions_url_ref(),
-        keyword_input_text_));
+        keyword_url->suggestions_url_ref(), keyword_input_text_));
   }
-  // We should only get here if we have a suggest url for the keyword or default
-  // providers.
-  DCHECK_GT(suggest_results_pending_, 0);
+
+  // Both the above can fail if the providers have been modified or deleted
+  // since the query began.
+  if (suggest_results_pending_ == 0) {
+    UpdateDone();
+    // We only need to update the listener if we're actually done.
+    if (done_)
+      listener_->OnProviderUpdate(false);
+  }
 }
 
 void SearchProvider::Stop() {
@@ -315,9 +328,9 @@
   // Record response time for suggest requests sent to Google.  We care
   // only about the common case: the Google default provider used in
   // non-keyword mode.
-  if (!is_keyword_results && providers_.valid_default_provider() &&
-      (providers_.default_provider()->prepopulate_id() ==
-       SEARCH_ENGINE_GOOGLE)) {
+  const TemplateURL* default_url = providers_.GetDefaultProviderURL();
+  if (!is_keyword_results && default_url &&
+      (default_url->prepopulate_id() == SEARCH_ENGINE_GOOGLE)) {
     UMA_HISTOGRAM_TIMES(histogram_name,
                         base::TimeTicks::Now() - time_suggest_request_sent_);
   }
@@ -355,12 +368,14 @@
   // require multiple searches and tracking of "single- vs. multi-word" in the
   // database.
   int num_matches = kMaxMatches * 5;
-  if (providers_.valid_default_provider()) {
-    url_db->GetMostRecentKeywordSearchTerms(providers_.default_provider()->id(),
-        input_.text(), num_matches, &default_history_results_);
+  const TemplateURL* default_url = providers_.GetDefaultProviderURL();
+  if (default_url) {
+    url_db->GetMostRecentKeywordSearchTerms(default_url->id(), input_.text(),
+        num_matches, &default_history_results_);
   }
-  if (providers_.valid_keyword_provider()) {
-    url_db->GetMostRecentKeywordSearchTerms(providers_.keyword_provider()->id(),
+  const TemplateURL* keyword_url = providers_.GetKeywordProviderURL();
+  if (keyword_url) {
+    url_db->GetMostRecentKeywordSearchTerms(keyword_url->id(),
         keyword_input_text_, num_matches, &keyword_history_results_);
   }
 }
@@ -408,9 +423,11 @@
 bool SearchProvider::IsQuerySuitableForSuggest() const {
   // Don't run Suggest in incognito mode, if the engine doesn't support it, or
   // if the user has disabled it.
+  const TemplateURL* default_url = providers_.GetDefaultProviderURL();
+  const TemplateURL* keyword_url = providers_.GetKeywordProviderURL();
   if (profile_->IsOffTheRecord() ||
-      (!providers_.valid_suggest_for_default_provider() &&
-       !providers_.valid_suggest_for_keyword_provider()) ||
+      ((!default_url || default_url->suggestions_url().empty()) &&
+       (!keyword_url || keyword_url->suggestions_url().empty())) ||
       !profile_->GetPrefs()->GetBoolean(prefs::kSearchSuggestEnabled))
     return false;
 
@@ -591,19 +608,17 @@
   int did_not_accept_default_suggestion = default_suggest_results_.empty() ?
         TemplateURLRef::NO_SUGGESTIONS_AVAILABLE :
         TemplateURLRef::NO_SUGGESTION_CHOSEN;
-  if (providers_.valid_default_provider()) {
-    AddMatchToMap(input_.text(), input_.text(),
-                  CalculateRelevanceForWhatYouTyped(),
-                  AutocompleteMatch::SEARCH_WHAT_YOU_TYPED,
+  AddMatchToMap(input_.text(), input_.text(),
+                CalculateRelevanceForWhatYouTyped(),
+                AutocompleteMatch::SEARCH_WHAT_YOU_TYPED,
+                did_not_accept_default_suggestion, false,
+                input_.prevent_inline_autocomplete(), &map);
+  if (!default_provider_suggest_text_.empty()) {
+    AddMatchToMap(input_.text() + default_provider_suggest_text_,
+                  input_.text(), CalculateRelevanceForWhatYouTyped() + 1,
+                  AutocompleteMatch::SEARCH_SUGGEST,
                   did_not_accept_default_suggestion, false,
                   input_.prevent_inline_autocomplete(), &map);
-    if (!default_provider_suggest_text_.empty()) {
-      AddMatchToMap(input_.text() + default_provider_suggest_text_,
-                    input_.text(), CalculateRelevanceForWhatYouTyped() + 1,
-                    AutocompleteMatch::SEARCH_SUGGEST,
-                    did_not_accept_default_suggestion, false,
-                    input_.prevent_inline_autocomplete(), &map);
-    }
   }
 
   AddHistoryResultsToMap(keyword_history_results_, true,
@@ -767,7 +782,7 @@
 }
 
 int SearchProvider::CalculateRelevanceForWhatYouTyped() const {
-  if (providers_.valid_keyword_provider())
+  if (!providers_.keyword_provider().empty())
     return 250;
 
   switch (input_.type()) {
@@ -856,9 +871,13 @@
                                    MatchMap* map) {
   AutocompleteMatch match(this, relevance, false, type);
   std::vector<size_t> content_param_offsets;
-  TemplateURL* provider = is_keyword ?
+  // Bail out now if we don't actually have a valid provider.
+  match.keyword = is_keyword ?
       providers_.keyword_provider() : providers_.default_provider();
-  match.template_url = provider;
+  const TemplateURL* provider_url = match.GetTemplateURL(profile_);
+  if (provider_url == NULL)
+    return;
+
   match.contents.assign(query_string);
   // We do intra-string highlighting for suggestions - the suggested segment
   // will be highlighted, e.g. for input_text = "you" the suggestion may be
@@ -908,7 +927,6 @@
     ++search_start;
   }
   if (is_keyword) {
-    match.keyword = providers_.keyword_provider()->keyword();
     match.fill_into_edit.append(match.keyword + char16(' '));
     search_start += match.keyword.length() + 1;
   }
@@ -919,7 +937,7 @@
                                    input_text))
     match.inline_autocomplete_offset = search_start + input_text.length();
 
-  const TemplateURLRef& search_url = provider->url_ref();
+  const TemplateURLRef& search_url = provider_url->url_ref();
   DCHECK(search_url.SupportsReplacement());
   match.destination_url = GURL(search_url.ReplaceSearchTerms(query_string,
       accepted_suggestion, input_text));
diff --git a/chrome/browser/autocomplete/search_provider.h b/chrome/browser/autocomplete/search_provider.h
index e6a8ca84..5444a8b1 100644
--- a/chrome/browser/autocomplete/search_provider.h
+++ b/chrome/browser/autocomplete/search_provider.h
@@ -30,6 +30,7 @@
 #include "content/public/common/url_fetcher_delegate.h"
 
 class Profile;
+class TemplateURLService;
 
 namespace base {
 class Value;
@@ -91,70 +92,44 @@
   // . The keyword provider. This is used if the user has typed in a keyword.
   class Providers {
    public:
-    explicit Providers(Profile* profile)
-        : cached_default_provider_(profile, TemplateURLData()),
-          cached_keyword_provider_(profile, TemplateURLData()),
-          default_provider_(NULL),
-          keyword_provider_(NULL) {
-    }
+    explicit Providers(TemplateURLService* template_url_service);
 
-    // Returns true if the specified providers match the two providers managed
+    // Returns true if the specified providers match the two providers cached
     // by this class.
-    bool equals(const TemplateURL* default_provider,
-                const TemplateURL* keyword_provider) {
-      return (default_provider == default_provider_ &&
-              keyword_provider == keyword_provider_);
+    bool equal(const string16& default_provider,
+               const string16& keyword_provider) const {
+      return (default_provider == default_provider_) &&
+          (keyword_provider == keyword_provider_);
     }
 
-    // Resets the providers.
-    void Set(const TemplateURL* default_provider,
-             const TemplateURL* keyword_provider);
-
-    TemplateURL* default_provider() {
-      DCHECK(valid_default_provider());
-      return &cached_default_provider_;
+    // Resets the cached providers.
+    void set(const string16& default_provider,
+             const string16& keyword_provider) {
+      default_provider_ = default_provider;
+      keyword_provider_ = keyword_provider;
     }
 
-    TemplateURL* keyword_provider() {
-      DCHECK(valid_keyword_provider());
-      return &cached_keyword_provider_;
-    }
+    TemplateURLService* template_url_service() { return template_url_service_; }
+    const string16& default_provider() const { return default_provider_; }
+    const string16& keyword_provider() const { return keyword_provider_; }
 
-    // Returns true if the default provider is valid.
-    bool valid_default_provider() const { return !!default_provider_; }
+    // NOTE: These may return NULL even if the provider members are nonempty!
+    const TemplateURL* GetDefaultProviderURL() const;
+    const TemplateURL* GetKeywordProviderURL() const;
 
-    // Returns true if the default provider is valid and has a valid suggest
-    // url.
-    bool valid_suggest_for_default_provider() const {
-      return default_provider_ &&
-          !cached_default_provider_.suggestions_url().empty();
-    }
-
-    // Returns true if the keyword provider is valid.
-    bool valid_keyword_provider() const { return !!keyword_provider_; }
-
-    // Returns true if the keyword provider is valid and has a valid suggest
-    // url.
-    bool valid_suggest_for_keyword_provider() const {
-      return keyword_provider_ &&
-          !cached_keyword_provider_.suggestions_url().empty();
-    }
-
-    // Returns true if |from_keyword_provider| is true, or
-    // the keyword provider is not valid.
+    // Returns true if |from_keyword_provider| is true, or the keyword provider
+    // is not valid.
     bool is_primary_provider(bool from_keyword_provider) const {
-      return from_keyword_provider || !valid_keyword_provider();
+      return from_keyword_provider || keyword_provider_.empty();
     }
 
    private:
+    TemplateURLService* template_url_service_;
+
     // Cached across the life of a query so we behave consistently even if the
     // user changes their default while the query is running.
-    TemplateURL cached_default_provider_;
-    TemplateURL cached_keyword_provider_;
-
-    // TODO(pkasting): http://b/1162970  We shouldn't need these.
-    const TemplateURL* default_provider_;
-    const TemplateURL* keyword_provider_;
+    string16 default_provider_;
+    string16 keyword_provider_;
 
     DISALLOW_COPY_AND_ASSIGN(Providers);
   };
diff --git a/chrome/browser/autocomplete/search_provider_unittest.cc b/chrome/browser/autocomplete/search_provider_unittest.cc
index 976e3cd1..f69854ae 100644
--- a/chrome/browser/autocomplete/search_provider_unittest.cc
+++ b/chrome/browser/autocomplete/search_provider_unittest.cc
@@ -340,8 +340,8 @@
   AutocompleteMatch match;
   EXPECT_TRUE(FindMatchWithDestination(keyword_url_, &match));
 
-  // The match should have a TemplateURL.
-  EXPECT_TRUE(match.template_url);
+  // The match should have an associated keyword.
+  EXPECT_FALSE(match.keyword.empty());
 
   // The fill into edit should contain the keyword.
   EXPECT_EQ(keyword_t_url_->keyword() + char16(' ') + keyword_term_,
@@ -632,10 +632,9 @@
   // There should be two matches, one for the keyword one for what you typed.
   ASSERT_EQ(2u, result.size());
 
-  EXPECT_TRUE(result.match_at(0).template_url != NULL);
-  EXPECT_TRUE(result.match_at(1).template_url != NULL);
-  EXPECT_NE(result.match_at(0).template_url,
-            result.match_at(1).template_url);
+  EXPECT_FALSE(result.match_at(0).keyword.empty());
+  EXPECT_FALSE(result.match_at(1).keyword.empty());
+  EXPECT_NE(result.match_at(0).keyword, result.match_at(1).keyword);
 
   EXPECT_FALSE(result.match_at(0).description.empty());
   EXPECT_FALSE(result.match_at(1).description.empty());
@@ -666,5 +665,5 @@
   // Make sure there is a match for 'a.com' and it doesn't have a template_url.
   AutocompleteMatch nav_match;
   EXPECT_TRUE(FindMatchWithDestination(GURL("http://a.com"), &nav_match));
-  EXPECT_FALSE(nav_match.template_url);
+  EXPECT_TRUE(nav_match.keyword.empty());
 }
diff --git a/chrome/browser/extensions/api/omnibox/omnibox_apitest.cc b/chrome/browser/extensions/api/omnibox/omnibox_apitest.cc
index af4a096..8ca81b0 100644
--- a/chrome/browser/extensions/api/omnibox/omnibox_apitest.cc
+++ b/chrome/browser/extensions/api/omnibox/omnibox_apitest.cc
@@ -108,9 +108,7 @@
     EXPECT_FALSE(match.deletable);
 
     match = result.match_at(1);
-    ASSERT_TRUE(match.template_url);
-    EXPECT_TRUE(match.template_url->IsExtensionKeyword());
-    EXPECT_EQ(ASCIIToUTF16("keyword"), match.template_url->keyword());
+    EXPECT_EQ(ASCIIToUTF16("keyword"), match.keyword);
   }
 
   // Test that our extension can send suggestions back to us.
@@ -128,7 +126,7 @@
     const AutocompleteResult& result = autocomplete_controller->result();
     ASSERT_EQ(5U, result.size()) << AutocompleteResultAsString(result);
 
-    ASSERT_TRUE(result.match_at(0).template_url);
+    ASSERT_FALSE(result.match_at(0).keyword.empty());
     EXPECT_EQ(ASCIIToUTF16("keyword suggestio"),
               result.match_at(0).fill_into_edit);
     EXPECT_EQ(ASCIIToUTF16("keyword suggestion1"),
@@ -272,7 +270,7 @@
     // should be to search for what we typed.
     const AutocompleteResult& result = autocomplete_controller->result();
     ASSERT_EQ(5U, result.size()) << AutocompleteResultAsString(result);
-    ASSERT_TRUE(result.match_at(0).template_url);
+    ASSERT_FALSE(result.match_at(0).keyword.empty());
     EXPECT_EQ(ASCIIToUTF16("keyword suggestion3 incognito"),
               result.match_at(3).fill_into_edit);
   }
diff --git a/chrome/browser/instant/instant_controller.cc b/chrome/browser/instant/instant_controller.cc
index fdc8688..b6de819c 100644
--- a/chrome/browser/instant/instant_controller.cc
+++ b/chrome/browser/instant/instant_controller.cc
@@ -133,7 +133,8 @@
   last_url_ = match.destination_url;
   last_user_text_ = user_text;
 
-  const TemplateURL* template_url = match.GetTemplateURL();
+  const TemplateURL* template_url =
+      match.GetTemplateURL(tab_contents_->profile());
   const TemplateURL* default_t_url =
       template_url_service_->GetDefaultSearchProvider();
   if (!IsValidInstantTemplateURL(template_url) || !default_t_url ||
diff --git a/chrome/browser/ui/webui/omnibox/omnibox_ui_handler.cc b/chrome/browser/ui/webui/omnibox/omnibox_ui_handler.cc
index 12f34c57..06dfb94 100644
--- a/chrome/browser/ui/webui/omnibox/omnibox_ui_handler.cc
+++ b/chrome/browser/ui/webui/omnibox/omnibox_ui_handler.cc
@@ -116,8 +116,7 @@
                        it->is_history_what_you_typed_match);
     output->SetString(item_prefix + ".type",
                       AutocompleteMatch::TypeToString(it->type));
-    if (it->template_url != NULL)
-      output->SetString(item_prefix + ".template_url", it->template_url->url());
+    output->SetString(item_prefix + ".keyword", it->keyword);
     output->SetBoolean(item_prefix + ".starred", it->starred);
     output->SetBoolean(item_prefix + ".from_previous", it->from_previous);
   }