| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 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/android/contextualsearch/contextual_search_delegate.h" | 5 #include "chrome/browser/android/contextualsearch/contextual_search_delegate.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <utility> | 8 #include <utility> |
| 9 | 9 |
| 10 #include "base/base64.h" | 10 #include "base/base64.h" |
| (...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 91 template_url_service_(template_url_service), | 91 template_url_service_(template_url_service), |
| 92 search_term_callback_(search_term_callback), | 92 search_term_callback_(search_term_callback), |
| 93 surrounding_callback_(surrounding_callback), | 93 surrounding_callback_(surrounding_callback), |
| 94 icing_callback_(icing_callback) { | 94 icing_callback_(icing_callback) { |
| 95 field_trial_.reset(new ContextualSearchFieldTrial()); | 95 field_trial_.reset(new ContextualSearchFieldTrial()); |
| 96 } | 96 } |
| 97 | 97 |
| 98 ContextualSearchDelegate::~ContextualSearchDelegate() { | 98 ContextualSearchDelegate::~ContextualSearchDelegate() { |
| 99 } | 99 } |
| 100 | 100 |
| 101 void ContextualSearchDelegate::StartSearchTermResolutionRequest( | 101 void ContextualSearchDelegate::GatherAndSaveSurroundingText( |
| 102 const std::string& selection, | 102 ContextualSearchContext* contextual_search_context, |
| 103 const std::string& home_country, | 103 content::WebContents* web_contents) { |
| 104 content::WebContents* web_contents, | 104 DCHECK(web_contents); |
| 105 bool may_send_base_page_url) { | 105 RenderFrameHost::TextSurroundingSelectionCallback callback = |
| 106 GatherSurroundingTextWithCallback( | 106 base::Bind(&ContextualSearchDelegate::OnTextSurroundingSelectionAvailable, |
| 107 selection, home_country, web_contents, may_send_base_page_url, | 107 AsWeakPtr()); |
| 108 base::Bind(&ContextualSearchDelegate::StartSearchTermRequestFromSelection, | 108 context_ = contextual_search_context; |
| 109 AsWeakPtr())); | 109 context_->SetBasePageEncoding(web_contents->GetEncoding()); |
| 110 int surroundingTextSize = context_->CanResolve() |
| 111 ? field_trial_->GetSurroundingSize() |
| 112 : field_trial_->GetIcingSurroundingSize(); |
| 113 RenderFrameHost* focused_frame = web_contents->GetFocusedFrame(); |
| 114 if (focused_frame) { |
| 115 focused_frame->RequestTextSurroundingSelection(callback, |
| 116 surroundingTextSize); |
| 117 } else { |
| 118 callback.Run(base::string16(), 0, 0); |
| 119 } |
| 110 } | 120 } |
| 111 | 121 |
| 112 void ContextualSearchDelegate::GatherAndSaveSurroundingText( | 122 void ContextualSearchDelegate::StartSearchTermResolutionRequest( |
| 113 const std::string& selection, | 123 ContextualSearchContext* contextual_search_context, |
| 114 const std::string& home_country, | 124 content::WebContents* web_contents) { |
| 115 content::WebContents* web_contents, | 125 DCHECK(web_contents); |
| 116 bool may_send_base_page_url) { | 126 DCHECK(context_ == contextual_search_context); |
| 117 GatherSurroundingTextWithCallback( | 127 DCHECK(context_->CanResolve()); |
| 118 selection, home_country, web_contents, may_send_base_page_url, | 128 |
| 119 base::Bind(&ContextualSearchDelegate::SaveSurroundingText, AsWeakPtr())); | 129 // Immediately cancel any request that's in flight, since we're building a new |
| 120 // TODO(donnd): clear the context here, since we're done with it (but risky). | 130 // context (and the response disposes of any existing context). |
| 131 search_term_fetcher_.reset(); |
| 132 |
| 133 // Decide if the URL should be sent with the context. |
| 134 GURL page_url(web_contents->GetURL()); |
| 135 if (context_->CanSendBasePageUrl() && |
| 136 CanSendPageURL(page_url, ProfileManager::GetActiveUserProfile(), |
| 137 template_url_service_)) { |
| 138 context_->SetBasePageUrl(page_url); |
| 139 } |
| 140 ResolveSearchTermFromContext(); |
| 121 } | 141 } |
| 122 | 142 |
| 123 void ContextualSearchDelegate::ContinueSearchTermResolutionRequest() { | 143 void ContextualSearchDelegate::ResolveSearchTermFromContext() { |
| 124 DCHECK(context_.get()); | 144 DCHECK(context_); |
| 125 if (!context_.get()) | 145 if (!context_) |
| 126 return; | 146 return; |
| 127 GURL request_url(BuildRequestUrl(context_->home_country)); | 147 GURL request_url(BuildRequestUrl(context_->GetHomeCountry())); |
| 128 DCHECK(request_url.is_valid()); | 148 DCHECK(request_url.is_valid()); |
| 129 | 149 |
| 130 // Reset will delete any previous fetcher, and we won't get any callback. | 150 // Reset will delete any previous fetcher, and we won't get any callback. |
| 131 search_term_fetcher_.reset( | 151 search_term_fetcher_.reset( |
| 132 net::URLFetcher::Create(kContextualSearchURLFetcherID, request_url, | 152 net::URLFetcher::Create(kContextualSearchURLFetcherID, request_url, |
| 133 net::URLFetcher::GET, this).release()); | 153 net::URLFetcher::GET, this).release()); |
| 134 search_term_fetcher_->SetRequestContext(url_request_context_); | 154 search_term_fetcher_->SetRequestContext(url_request_context_); |
| 135 | 155 |
| 136 // Add Chrome experiment state to the request headers. | 156 // Add Chrome experiment state to the request headers. |
| 137 net::HttpRequestHeaders headers; | 157 net::HttpRequestHeaders headers; |
| (...skipping 21 matching lines...) Expand all Loading... |
| 159 if (source->GetStatus().is_success() && response_code == net::HTTP_OK) { | 179 if (source->GetStatus().is_success() && response_code == net::HTTP_OK) { |
| 160 std::string response; | 180 std::string response; |
| 161 bool has_string_response = source->GetResponseAsString(&response); | 181 bool has_string_response = source->GetResponseAsString(&response); |
| 162 DCHECK(has_string_response); | 182 DCHECK(has_string_response); |
| 163 if (has_string_response) { | 183 if (has_string_response) { |
| 164 resolved_search_term = | 184 resolved_search_term = |
| 165 GetResolvedSearchTermFromJson(response_code, response); | 185 GetResolvedSearchTermFromJson(response_code, response); |
| 166 } | 186 } |
| 167 } | 187 } |
| 168 search_term_callback_.Run(*resolved_search_term); | 188 search_term_callback_.Run(*resolved_search_term); |
| 169 | |
| 170 // The ContextualSearchContext is consumed once the request has completed. | |
| 171 context_.reset(); | |
| 172 } | 189 } |
| 173 | 190 |
| 174 std::unique_ptr<ResolvedSearchTerm> | 191 std::unique_ptr<ResolvedSearchTerm> |
| 175 ContextualSearchDelegate::GetResolvedSearchTermFromJson( | 192 ContextualSearchDelegate::GetResolvedSearchTermFromJson( |
| 176 int response_code, | 193 int response_code, |
| 177 const std::string& json_string) { | 194 const std::string& json_string) { |
| 178 std::string search_term; | 195 std::string search_term; |
| 179 std::string display_text; | 196 std::string display_text; |
| 180 std::string alternate_term; | 197 std::string alternate_term; |
| 181 std::string mid; | 198 std::string mid; |
| (...skipping 12 matching lines...) Expand all Loading... |
| 194 json_string, &search_term, &display_text, &alternate_term, &mid, | 211 json_string, &search_term, &display_text, &alternate_term, &mid, |
| 195 &prevent_preload, &mention_start, &mention_end, &context_language, | 212 &prevent_preload, &mention_start, &mention_end, &context_language, |
| 196 &thumbnail_url, &caption, &quick_action_uri, &quick_action_category); | 213 &thumbnail_url, &caption, &quick_action_uri, &quick_action_category); |
| 197 if (mention_start != 0 || mention_end != 0) { | 214 if (mention_start != 0 || mention_end != 0) { |
| 198 // Sanity check that our selection is non-zero and it is less than | 215 // Sanity check that our selection is non-zero and it is less than |
| 199 // 100 characters as that would make contextual search bar hide. | 216 // 100 characters as that would make contextual search bar hide. |
| 200 // We also check that there is at least one character overlap between | 217 // We also check that there is at least one character overlap between |
| 201 // the new and old selection. | 218 // the new and old selection. |
| 202 if (mention_start >= mention_end || | 219 if (mention_start >= mention_end || |
| 203 (mention_end - mention_start) > kContextualSearchMaxSelection || | 220 (mention_end - mention_start) > kContextualSearchMaxSelection || |
| 204 mention_end <= context_->start_offset || | 221 mention_end <= context_->GetStartOffset() || |
| 205 mention_start >= context_->end_offset) { | 222 mention_start >= context_->GetEndOffset()) { |
| 206 start_adjust = 0; | 223 start_adjust = 0; |
| 207 end_adjust = 0; | 224 end_adjust = 0; |
| 208 } else { | 225 } else { |
| 209 start_adjust = mention_start - context_->start_offset; | 226 start_adjust = mention_start - context_->GetStartOffset(); |
| 210 end_adjust = mention_end - context_->end_offset; | 227 end_adjust = mention_end - context_->GetEndOffset(); |
| 211 } | 228 } |
| 212 } | 229 } |
| 213 bool is_invalid = response_code == net::URLFetcher::RESPONSE_CODE_INVALID; | 230 bool is_invalid = response_code == net::URLFetcher::RESPONSE_CODE_INVALID; |
| 214 return std::unique_ptr<ResolvedSearchTerm>(new ResolvedSearchTerm( | 231 return std::unique_ptr<ResolvedSearchTerm>(new ResolvedSearchTerm( |
| 215 is_invalid, response_code, search_term, display_text, alternate_term, mid, | 232 is_invalid, response_code, search_term, display_text, alternate_term, mid, |
| 216 prevent_preload == kDoPreventPreloadValue, start_adjust, end_adjust, | 233 prevent_preload == kDoPreventPreloadValue, start_adjust, end_adjust, |
| 217 context_language, thumbnail_url, caption, quick_action_uri, | 234 context_language, thumbnail_url, caption, quick_action_uri, |
| 218 quick_action_category)); | 235 quick_action_category)); |
| 219 } | 236 } |
| 220 | 237 |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 263 if (!replacement_url.empty()) { | 280 if (!replacement_url.empty()) { |
| 264 size_t pos = request.find(kContextualSearchServerEndpoint); | 281 size_t pos = request.find(kContextualSearchServerEndpoint); |
| 265 if (pos != std::string::npos) { | 282 if (pos != std::string::npos) { |
| 266 request.replace(0, pos + strlen(kContextualSearchServerEndpoint), | 283 request.replace(0, pos + strlen(kContextualSearchServerEndpoint), |
| 267 replacement_url); | 284 replacement_url); |
| 268 } | 285 } |
| 269 } | 286 } |
| 270 return request; | 287 return request; |
| 271 } | 288 } |
| 272 | 289 |
| 273 void ContextualSearchDelegate::GatherSurroundingTextWithCallback( | 290 void ContextualSearchDelegate::OnTextSurroundingSelectionAvailable( |
| 274 const std::string& selection, | |
| 275 const std::string& home_country, | |
| 276 content::WebContents* web_contents, | |
| 277 bool may_send_base_page_url, | |
| 278 HandleSurroundingsCallback callback) { | |
| 279 // Immediately cancel any request that's in flight, since we're building a new | |
| 280 // context (and the response disposes of any existing context). | |
| 281 search_term_fetcher_.reset(); | |
| 282 BuildContext(selection, home_country, web_contents, may_send_base_page_url); | |
| 283 DCHECK(web_contents); | |
| 284 RenderFrameHost* focused_frame = web_contents->GetFocusedFrame(); | |
| 285 if (focused_frame) { | |
| 286 focused_frame->RequestTextSurroundingSelection( | |
| 287 callback, field_trial_->GetSurroundingSize()); | |
| 288 } else { | |
| 289 callback.Run(base::string16(), 0, 0); | |
| 290 } | |
| 291 } | |
| 292 | |
| 293 void ContextualSearchDelegate::BuildContext(const std::string& selection, | |
| 294 const std::string& home_country, | |
| 295 content::WebContents* web_contents, | |
| 296 bool may_send_base_page_url) { | |
| 297 // Decide if the URL should be sent with the context. | |
| 298 GURL page_url(web_contents->GetURL()); | |
| 299 GURL url_to_send; | |
| 300 if (may_send_base_page_url && | |
| 301 CanSendPageURL(page_url, ProfileManager::GetActiveUserProfile(), | |
| 302 template_url_service_)) { | |
| 303 url_to_send = page_url; | |
| 304 } | |
| 305 std::string encoding(web_contents->GetEncoding()); | |
| 306 context_.reset(new ContextualSearchContext(selection, home_country, | |
| 307 url_to_send, encoding)); | |
| 308 } | |
| 309 | |
| 310 void ContextualSearchDelegate::StartSearchTermRequestFromSelection( | |
| 311 const base::string16& surrounding_text, | 291 const base::string16& surrounding_text, |
| 312 int start_offset, | 292 int start_offset, |
| 313 int end_offset) { | 293 int end_offset) { |
| 314 // TODO(donnd): figure out how to gather text surrounding the selection | 294 SaveSurroundingText(surrounding_text, start_offset, end_offset); |
| 315 // for other purposes too: e.g. to determine if we should select the | 295 if (context_->CanResolve()) |
| 316 // word where the user tapped. | |
| 317 if (context_.get()) { | |
| 318 SaveSurroundingText(surrounding_text, start_offset, end_offset); | |
| 319 SendSurroundingText(kSurroundingSizeForUI); | 296 SendSurroundingText(kSurroundingSizeForUI); |
| 320 ContinueSearchTermResolutionRequest(); | |
| 321 } else { | |
| 322 DVLOG(1) << "ctxs: Null context, ignored!"; | |
| 323 } | |
| 324 } | 297 } |
| 325 | 298 |
| 326 void ContextualSearchDelegate::SaveSurroundingText( | 299 void ContextualSearchDelegate::SaveSurroundingText( |
| 327 const base::string16& surrounding_text, | 300 const base::string16& surrounding_text, |
| 328 int start_offset, | 301 int start_offset, |
| 329 int end_offset) { | 302 int end_offset) { |
| 330 DCHECK(context_.get()); | 303 DCHECK(context_); |
| 331 // Sometimes the surroundings are 0, 0, '', so fall back on the selection. | 304 // Sometimes the surroundings are 0, 0, '', so fall back on the selection. |
| 332 // See crbug.com/393100. | 305 // See crbug.com/393100. |
| 306 bool use_selection = false; |
| 333 if (start_offset == 0 && end_offset == 0 && surrounding_text.length() == 0) { | 307 if (start_offset == 0 && end_offset == 0 && surrounding_text.length() == 0) { |
| 334 context_->surrounding_text = base::UTF8ToUTF16(context_->selected_text); | 308 use_selection = true; |
| 335 context_->start_offset = 0; | 309 end_offset = context_->GetOriginalSelectedText().length(); |
| 336 context_->end_offset = context_->selected_text.length(); | |
| 337 } else { | |
| 338 context_->surrounding_text = surrounding_text; | |
| 339 context_->start_offset = start_offset; | |
| 340 context_->end_offset = end_offset; | |
| 341 } | 310 } |
| 311 const base::string16& surrounding_text_or_selection( |
| 312 use_selection ? base::UTF8ToUTF16(context_->GetOriginalSelectedText()) |
| 313 : surrounding_text); |
| 342 | 314 |
| 343 // Pin the start and end offsets to ensure they point within the string. | 315 // Pin the start and end offsets to ensure they point within the string. |
| 344 int surrounding_length = context_->surrounding_text.length(); | 316 int surrounding_length = surrounding_text_or_selection.length(); |
| 345 context_->start_offset = | 317 start_offset = std::min(surrounding_length, std::max(0, start_offset)); |
| 346 std::min(surrounding_length, std::max(0, context_->start_offset)); | 318 end_offset = std::min(surrounding_length, std::max(0, end_offset)); |
| 347 context_->end_offset = | 319 |
| 348 std::min(surrounding_length, std::max(0, context_->end_offset)); | 320 context_->SetSelectionSurroundings(start_offset, end_offset, |
| 321 surrounding_text_or_selection); |
| 349 | 322 |
| 350 // Call the Icing callback with a shortened copy of the surroundings. | 323 // Call the Icing callback with a shortened copy of the surroundings. |
| 351 int icing_surrounding_size = field_trial_->GetIcingSurroundingSize(); | 324 int icing_surrounding_size = field_trial_->GetIcingSurroundingSize(); |
| 352 size_t selection_start = context_->start_offset; | 325 size_t selection_start = start_offset; |
| 353 size_t selection_end = context_->end_offset; | 326 size_t selection_end = end_offset; |
| 354 if (icing_surrounding_size >= 0 && selection_start < selection_end) { | 327 if (icing_surrounding_size >= 0 && selection_start <= selection_end) { |
| 355 int icing_padding_each_side = icing_surrounding_size / 2; | 328 int icing_padding_each_side = icing_surrounding_size / 2; |
| 356 base::string16 icing_surrounding_text = SurroundingTextForIcing( | 329 base::string16 icing_surrounding_text = SurroundingTextForIcing( |
| 357 context_->surrounding_text, icing_padding_each_side, &selection_start, | 330 surrounding_text_or_selection, icing_padding_each_side, |
| 358 &selection_end); | 331 &selection_start, &selection_end); |
| 359 if (selection_start < selection_end) | 332 if (selection_start <= selection_end) |
| 360 icing_callback_.Run(context_->encoding, icing_surrounding_text, | 333 icing_callback_.Run(context_->GetBasePageEncoding(), |
| 361 selection_start, selection_end); | 334 icing_surrounding_text, selection_start, |
| 335 selection_end); |
| 362 } | 336 } |
| 363 } | 337 } |
| 364 | 338 |
| 365 void ContextualSearchDelegate::SendSurroundingText(int max_surrounding_chars) { | 339 void ContextualSearchDelegate::SendSurroundingText(int max_surrounding_chars) { |
| 366 const base::string16& surrounding = context_->surrounding_text; | 340 const base::string16& surrounding = context_->GetSurroundingText(); |
| 367 | 341 |
| 368 // Determine the text after the selection. | 342 // Determine the text after the selection. |
| 369 int surrounding_length = surrounding.length(); // Cast to int. | 343 int surrounding_length = surrounding.length(); // Cast to int. |
| 370 int num_after_characters = std::min( | 344 int num_after_characters = std::min( |
| 371 surrounding_length - context_->end_offset, max_surrounding_chars); | 345 surrounding_length - context_->GetEndOffset(), max_surrounding_chars); |
| 372 base::string16 after_text = surrounding.substr( | 346 base::string16 after_text = |
| 373 context_->end_offset, num_after_characters); | 347 surrounding.substr(context_->GetEndOffset(), num_after_characters); |
| 374 | 348 |
| 375 base::TrimWhitespace(after_text, base::TRIM_ALL, &after_text); | 349 base::TrimWhitespace(after_text, base::TRIM_ALL, &after_text); |
| 376 surrounding_callback_.Run(UTF16ToUTF8(after_text)); | 350 surrounding_callback_.Run(UTF16ToUTF8(after_text)); |
| 377 } | 351 } |
| 378 | 352 |
| 379 void ContextualSearchDelegate::SetDiscourseContextAndAddToHeader( | 353 void ContextualSearchDelegate::SetDiscourseContextAndAddToHeader( |
| 380 const ContextualSearchContext& context) { | 354 const ContextualSearchContext& context) { |
| 381 search_term_fetcher_->AddExtraRequestHeader(GetDiscourseContext(context)); | 355 search_term_fetcher_->AddExtraRequestHeader(GetDiscourseContext(context)); |
| 382 } | 356 } |
| 383 | 357 |
| 384 std::string ContextualSearchDelegate::GetDiscourseContext( | 358 std::string ContextualSearchDelegate::GetDiscourseContext( |
| 385 const ContextualSearchContext& context) { | 359 const ContextualSearchContext& context) { |
| 386 discourse_context::ClientDiscourseContext proto; | 360 discourse_context::ClientDiscourseContext proto; |
| 387 discourse_context::Display* display = proto.add_display(); | 361 discourse_context::Display* display = proto.add_display(); |
| 388 display->set_uri(context.page_url.spec()); | 362 display->set_uri(context.GetBasePageUrl().spec()); |
| 389 | 363 |
| 390 discourse_context::Media* media = display->mutable_media(); | 364 discourse_context::Media* media = display->mutable_media(); |
| 391 media->set_mime_type(context.encoding); | 365 media->set_mime_type(context.GetBasePageEncoding()); |
| 392 | 366 |
| 393 discourse_context::Selection* selection = display->mutable_selection(); | 367 discourse_context::Selection* selection = display->mutable_selection(); |
| 394 selection->set_content(UTF16ToUTF8(context.surrounding_text)); | 368 selection->set_content(UTF16ToUTF8(context.GetSurroundingText())); |
| 395 selection->set_start(context.start_offset); | 369 selection->set_start(context.GetStartOffset()); |
| 396 selection->set_end(context.end_offset); | 370 selection->set_end(context.GetEndOffset()); |
| 397 selection->set_is_uri_encoded(false); | 371 selection->set_is_uri_encoded(false); |
| 398 | 372 |
| 399 std::string serialized; | 373 std::string serialized; |
| 400 proto.SerializeToString(&serialized); | 374 proto.SerializeToString(&serialized); |
| 401 | 375 |
| 402 std::string encoded_context; | 376 std::string encoded_context; |
| 403 base::Base64Encode(serialized, &encoded_context); | 377 base::Base64Encode(serialized, &encoded_context); |
| 404 // The server memoizer expects a web-safe encoding. | 378 // The server memoizer expects a web-safe encoding. |
| 405 std::replace(encoded_context.begin(), encoded_context.end(), '+', '-'); | 379 std::replace(encoded_context.begin(), encoded_context.end(), '+', '-'); |
| 406 std::replace(encoded_context.begin(), encoded_context.end(), '/', '_'); | 380 std::replace(encoded_context.begin(), encoded_context.end(), '/', '_'); |
| (...skipping 192 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 599 end_offset -= trim; | 573 end_offset -= trim; |
| 600 } | 574 } |
| 601 if (result_text.length() > end_offset + padding_each_side_pinned) { | 575 if (result_text.length() > end_offset + padding_each_side_pinned) { |
| 602 // Trim the end. | 576 // Trim the end. |
| 603 result_text = result_text.substr(0, end_offset + padding_each_side_pinned); | 577 result_text = result_text.substr(0, end_offset + padding_each_side_pinned); |
| 604 } | 578 } |
| 605 *start = start_offset; | 579 *start = start_offset; |
| 606 *end = end_offset; | 580 *end = end_offset; |
| 607 return result_text; | 581 return result_text; |
| 608 } | 582 } |
| OLD | NEW |