| OLD | NEW |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. | 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/ui/app_list/search/answer_card/answer_card_search_provi
der.h" | 5 #include "chrome/browser/ui/app_list/search/answer_card/answer_card_search_provi
der.h" |
| 6 | 6 |
| 7 #include <utility> | 7 #include <utility> |
| 8 | 8 |
| 9 #include "base/command_line.h" | 9 #include "base/command_line.h" |
| 10 #include "base/metrics/histogram_macros.h" | 10 #include "base/metrics/histogram_macros.h" |
| 11 #include "base/metrics/user_metrics.h" | 11 #include "base/metrics/user_metrics.h" |
| 12 #include "base/strings/utf_string_conversions.h" | 12 #include "base/strings/utf_string_conversions.h" |
| 13 #include "chrome/browser/search_engines/template_url_service_factory.h" | 13 #include "chrome/browser/search_engines/template_url_service_factory.h" |
| 14 #include "chrome/browser/ui/app_list/search/answer_card/answer_card_result.h" | 14 #include "chrome/browser/ui/app_list/search/answer_card/answer_card_result.h" |
| 15 #include "chrome/browser/ui/browser_navigator.h" | 15 #include "chrome/browser/ui/browser_navigator.h" |
| 16 #include "chrome/browser/ui/browser_navigator_params.h" | 16 #include "chrome/browser/ui/browser_navigator_params.h" |
| 17 #include "components/omnibox/browser/autocomplete_input.h" | 17 #include "components/omnibox/browser/autocomplete_input.h" |
| 18 #include "components/omnibox/browser/autocomplete_match.h" | 18 #include "components/omnibox/browser/autocomplete_match.h" |
| 19 #include "components/search_engines/template_url_service.h" | 19 #include "components/search_engines/template_url_service.h" |
| 20 #include "content/public/browser/navigation_handle.h" | |
| 21 #include "content/public/browser/page_navigator.h" | |
| 22 #include "net/http/http_response_headers.h" | |
| 23 #include "net/http/http_status_code.h" | |
| 24 #include "ui/app_list/app_list_features.h" | 20 #include "ui/app_list/app_list_features.h" |
| 25 #include "ui/app_list/app_list_model.h" | 21 #include "ui/app_list/app_list_model.h" |
| 26 #include "ui/app_list/search_box_model.h" | 22 #include "ui/app_list/search_box_model.h" |
| 27 | 23 |
| 28 namespace app_list { | 24 namespace app_list { |
| 29 | 25 |
| 30 namespace { | 26 namespace { |
| 31 | 27 |
| 32 enum class SearchAnswerRequestResult { | 28 enum class SearchAnswerRequestResult { |
| 33 REQUEST_RESULT_ANOTHER_REQUEST_STARTED = 0, | 29 REQUEST_RESULT_ANOTHER_REQUEST_STARTED = 0, |
| 34 REQUEST_RESULT_REQUEST_FAILED = 1, | 30 REQUEST_RESULT_REQUEST_FAILED = 1, |
| 35 REQUEST_RESULT_NO_ANSWER = 2, | 31 REQUEST_RESULT_NO_ANSWER = 2, |
| 36 REQUEST_RESULT_RECEIVED_ANSWER = 3, | 32 REQUEST_RESULT_RECEIVED_ANSWER = 3, |
| 37 REQUEST_RESULT_RECEIVED_ANSWER_TOO_LARGE = 4, | 33 REQUEST_RESULT_RECEIVED_ANSWER_TOO_LARGE = 4, |
| 38 REQUEST_RESULT_MAX = 5 | 34 REQUEST_RESULT_MAX = 5 |
| 39 }; | 35 }; |
| 40 | 36 |
| 41 constexpr char kSearchAnswerHasResult[] = "SearchAnswer-HasResult"; | |
| 42 constexpr char kSearchAnswerIssuedQuery[] = "SearchAnswer-IssuedQuery"; | |
| 43 constexpr char kSearchAnswerTitle[] = "SearchAnswer-Title"; | |
| 44 | |
| 45 void RecordRequestResult(SearchAnswerRequestResult request_result) { | 37 void RecordRequestResult(SearchAnswerRequestResult request_result) { |
| 46 UMA_HISTOGRAM_ENUMERATION("SearchAnswer.RequestResult", request_result, | 38 UMA_HISTOGRAM_ENUMERATION("SearchAnswer.RequestResult", request_result, |
| 47 SearchAnswerRequestResult::REQUEST_RESULT_MAX); | 39 SearchAnswerRequestResult::REQUEST_RESULT_MAX); |
| 48 } | 40 } |
| 49 | 41 |
| 50 } // namespace | 42 } // namespace |
| 51 | 43 |
| 52 AnswerCardSearchProvider::AnswerCardSearchProvider( | 44 AnswerCardSearchProvider::AnswerCardSearchProvider( |
| 53 Profile* profile, | 45 Profile* profile, |
| 54 app_list::AppListModel* model, | 46 app_list::AppListModel* model, |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 109 void AnswerCardSearchProvider::UpdatePreferredSize(const gfx::Size& pref_size) { | 101 void AnswerCardSearchProvider::UpdatePreferredSize(const gfx::Size& pref_size) { |
| 110 preferred_size_ = pref_size; | 102 preferred_size_ = pref_size; |
| 111 OnResultAvailable(received_answer_ && IsCardSizeOk() && | 103 OnResultAvailable(received_answer_ && IsCardSizeOk() && |
| 112 !contents_->IsLoading()); | 104 !contents_->IsLoading()); |
| 113 if (!answer_loaded_time_.is_null()) { | 105 if (!answer_loaded_time_.is_null()) { |
| 114 UMA_HISTOGRAM_TIMES("SearchAnswer.ResizeAfterLoadTime", | 106 UMA_HISTOGRAM_TIMES("SearchAnswer.ResizeAfterLoadTime", |
| 115 base::TimeTicks::Now() - answer_loaded_time_); | 107 base::TimeTicks::Now() - answer_loaded_time_); |
| 116 } | 108 } |
| 117 } | 109 } |
| 118 | 110 |
| 119 content::WebContents* AnswerCardSearchProvider::OpenURLFromTab( | |
| 120 const content::OpenURLParams& params) { | |
| 121 // Open the user-clicked link in the browser taking into account the requested | |
| 122 // disposition. | |
| 123 chrome::NavigateParams new_tab_params(profile_, params.url, | |
| 124 params.transition); | |
| 125 | |
| 126 new_tab_params.disposition = params.disposition; | |
| 127 | |
| 128 if (params.disposition == WindowOpenDisposition::NEW_BACKGROUND_TAB) { | |
| 129 // When the user asks to open a link as a background tab, we show an | |
| 130 // activated window with the new activated tab after the user closes the | |
| 131 // launcher. So it's "background" relative to the launcher itself. | |
| 132 new_tab_params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB; | |
| 133 new_tab_params.window_action = chrome::NavigateParams::SHOW_WINDOW_INACTIVE; | |
| 134 } | |
| 135 | |
| 136 chrome::Navigate(&new_tab_params); | |
| 137 | |
| 138 base::RecordAction(base::UserMetricsAction("SearchAnswer_OpenedUrl")); | |
| 139 | |
| 140 return new_tab_params.target_contents; | |
| 141 } | |
| 142 | |
| 143 void AnswerCardSearchProvider::DidFinishNavigation( | 111 void AnswerCardSearchProvider::DidFinishNavigation( |
| 144 content::NavigationHandle* navigation_handle) { | 112 const GURL& url, |
| 145 if (navigation_handle->GetURL() != current_request_url_) { | 113 bool has_error, |
| 114 bool has_answer_card, |
| 115 const std::string& result_title, |
| 116 const std::string& issued_query) { |
| 117 if (url != current_request_url_) { |
| 146 // TODO(vadimt): Remove this and similar logging once testing is complete if | 118 // TODO(vadimt): Remove this and similar logging once testing is complete if |
| 147 // we think this is not useful after release or happens too frequently. | 119 // we think this is not useful after release or happens too frequently. |
| 148 VLOG(1) << "DidFinishNavigation: Another request started"; | 120 VLOG(1) << "DidFinishNavigation: Another request started"; |
| 149 RecordRequestResult( | 121 RecordRequestResult( |
| 150 SearchAnswerRequestResult::REQUEST_RESULT_ANOTHER_REQUEST_STARTED); | 122 SearchAnswerRequestResult::REQUEST_RESULT_ANOTHER_REQUEST_STARTED); |
| 151 return; | 123 return; |
| 152 } | 124 } |
| 153 | 125 |
| 154 VLOG(1) << "DidFinishNavigation: Latest request completed"; | 126 VLOG(1) << "DidFinishNavigation: Latest request completed"; |
| 155 if (!navigation_handle->HasCommitted() || navigation_handle->IsErrorPage() || | 127 if (has_error) { |
| 156 !navigation_handle->IsInMainFrame()) { | |
| 157 LOG(ERROR) << "Failed to navigate: HasCommitted=" | |
| 158 << navigation_handle->HasCommitted() | |
| 159 << ", IsErrorPage=" << navigation_handle->IsErrorPage() | |
| 160 << ", IsInMainFrame=" << navigation_handle->IsInMainFrame(); | |
| 161 RecordRequestResult( | 128 RecordRequestResult( |
| 162 SearchAnswerRequestResult::REQUEST_RESULT_REQUEST_FAILED); | 129 SearchAnswerRequestResult::REQUEST_RESULT_REQUEST_FAILED); |
| 163 return; | 130 return; |
| 164 } | 131 } |
| 165 | 132 |
| 166 if (!features::IsAnswerCardDarkRunEnabled()) { | 133 if (!features::IsAnswerCardDarkRunEnabled()) { |
| 167 if (!ParseResponseHeaders(navigation_handle->GetResponseHeaders())) { | 134 if (!has_answer_card || result_title.empty()) { |
| 168 RecordRequestResult(SearchAnswerRequestResult::REQUEST_RESULT_NO_ANSWER); | 135 RecordRequestResult(SearchAnswerRequestResult::REQUEST_RESULT_NO_ANSWER); |
| 169 return; | 136 return; |
| 170 } | 137 } |
| 138 result_title_ = result_title; |
| 139 if (!issued_query.empty()) |
| 140 result_url_ = GetResultUrl(base::UTF8ToUTF16(issued_query)); |
| 171 } else { | 141 } else { |
| 172 // In the dark run mode, every other "server response" contains a card. | 142 // In the dark run mode, every other "server response" contains a card. |
| 173 dark_run_received_answer_ = !dark_run_received_answer_; | 143 dark_run_received_answer_ = !dark_run_received_answer_; |
| 174 if (!dark_run_received_answer_) | 144 if (!dark_run_received_answer_) |
| 175 return; | 145 return; |
| 176 // SearchResult requires a non-empty id. This "url" will never be opened. | 146 // SearchResult requires a non-empty id. This "url" will never be opened. |
| 177 result_url_ = "some string"; | 147 result_url_ = "https://www.google.com/?q=something"; |
| 178 } | 148 } |
| 179 | 149 |
| 180 received_answer_ = true; | 150 received_answer_ = true; |
| 181 UMA_HISTOGRAM_TIMES("SearchAnswer.NavigationTime", | 151 UMA_HISTOGRAM_TIMES("SearchAnswer.NavigationTime", |
| 182 base::TimeTicks::Now() - server_request_start_time_); | 152 base::TimeTicks::Now() - server_request_start_time_); |
| 183 } | 153 } |
| 184 | 154 |
| 185 void AnswerCardSearchProvider::DidStopLoading() { | 155 void AnswerCardSearchProvider::DidStopLoading() { |
| 186 if (!received_answer_) | 156 if (!received_answer_) |
| 187 return; | 157 return; |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 231 GURL(result_url_), AutocompleteInput(), template_url_service_, | 201 GURL(result_url_), AutocompleteInput(), template_url_service_, |
| 232 base::string16() /* keyword */); | 202 base::string16() /* keyword */); |
| 233 | 203 |
| 234 results.emplace_back(base::MakeUnique<AnswerCardResult>( | 204 results.emplace_back(base::MakeUnique<AnswerCardResult>( |
| 235 profile_, list_controller_, result_url_, stripped_result_url.spec(), | 205 profile_, list_controller_, result_url_, stripped_result_url.spec(), |
| 236 base::UTF8ToUTF16(result_title_), contents_.get())); | 206 base::UTF8ToUTF16(result_title_), contents_.get())); |
| 237 } | 207 } |
| 238 SwapResults(&results); | 208 SwapResults(&results); |
| 239 } | 209 } |
| 240 | 210 |
| 241 bool AnswerCardSearchProvider::ParseResponseHeaders( | |
| 242 const net::HttpResponseHeaders* headers) { | |
| 243 if (!headers) { | |
| 244 LOG(ERROR) << "Failed to parse response headers: no headers"; | |
| 245 return false; | |
| 246 } | |
| 247 if (headers->response_code() != net::HTTP_OK) { | |
| 248 LOG(ERROR) << "Failed to parse response headers: response code=" | |
| 249 << headers->response_code(); | |
| 250 return false; | |
| 251 } | |
| 252 if (!headers->HasHeaderValue(kSearchAnswerHasResult, "true")) { | |
| 253 LOG(ERROR) << "Failed to parse response headers: " << kSearchAnswerHasResult | |
| 254 << " header != true"; | |
| 255 return false; | |
| 256 } | |
| 257 if (!headers->GetNormalizedHeader(kSearchAnswerTitle, &result_title_)) { | |
| 258 LOG(ERROR) << "Failed to parse response headers: " << kSearchAnswerTitle | |
| 259 << " header is not present"; | |
| 260 return false; | |
| 261 } | |
| 262 | |
| 263 std::string issued_query; | |
| 264 // TODO(749320): Make the header mandatory once the production server starts | |
| 265 // serving it. | |
| 266 if (headers->GetNormalizedHeader(kSearchAnswerIssuedQuery, &issued_query)) { | |
| 267 // The header contains the autocompleted query that corresponds to the card | |
| 268 // contents. Use it for the open-URL, and for deduplication with the omnibox | |
| 269 // search results. | |
| 270 result_url_ = GetResultUrl(base::UTF8ToUTF16(issued_query)); | |
| 271 } else { | |
| 272 VLOG(1) << "Warning: " << kSearchAnswerIssuedQuery | |
| 273 << " header is not present"; | |
| 274 } | |
| 275 | |
| 276 return true; | |
| 277 } | |
| 278 | |
| 279 std::string AnswerCardSearchProvider::GetResultUrl( | 211 std::string AnswerCardSearchProvider::GetResultUrl( |
| 280 const base::string16& query) const { | 212 const base::string16& query) const { |
| 281 return template_url_service_->GetDefaultSearchProvider() | 213 return template_url_service_->GetDefaultSearchProvider() |
| 282 ->url_ref() | 214 ->url_ref() |
| 283 .ReplaceSearchTerms(TemplateURLRef::SearchTermsArgs(query), | 215 .ReplaceSearchTerms(TemplateURLRef::SearchTermsArgs(query), |
| 284 template_url_service_->search_terms_data()); | 216 template_url_service_->search_terms_data()); |
| 285 } | 217 } |
| 286 | 218 |
| 287 } // namespace app_list | 219 } // namespace app_list |
| OLD | NEW |