Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 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 | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #import "ios/chrome/browser/voice/text_to_speech_parser.h" | |
| 6 | |
| 7 #include "base/logging.h" | |
| 8 #import "ios/web/public/web_state/js/crw_js_injection_receiver.h" | |
| 9 #include "ios/web/public/web_state/web_state.h" | |
| 10 #import "third_party/google_toolbox_for_mac/src/Foundation/GTMStringEncoding.h" | |
| 11 | |
| 12 namespace { | |
| 13 | |
| 14 // The start and end tags that delimit TTS audio data. | |
| 15 NSString* const kTTSStartTag = @"function(){var _a_tts='"; | |
| 16 NSString* const kTTSEndTag = @"'"; | |
| 17 | |
| 18 // When |kTTSAudioDataExtractorScriptFormat| is evaluated on a Google voice | |
| 19 // search results page, this script will extract the innerHTML from the script | |
| 20 // element containing TTS data. The format takes one parameter, which is the | |
| 21 // start tag from the TTS config singleton. | |
| 22 NSString* const kTTSAudioDataExtractorScriptFormat = | |
| 23 @"(function(){" | |
| 24 " var start_tag = \"%@\";" | |
| 25 " var script_elements = document.getElementsByTagName(\"script\");" | |
| 26 " for (var i = 0; i < script_elements.length; ++i) {" | |
| 27 " var script_html = script_elements[i].innerHTML;" | |
| 28 " if (script_html.indexOf(start_tag) > 0)" | |
| 29 " return script_html;" | |
| 30 " }" | |
| 31 " return \"\";" | |
| 32 "})()"; | |
| 33 // Escaped encoding for GWS Voice Search serice's trailing '='. | |
|
sdefresne
2016/10/25 13:20:30
s/serice/service/?
rohitrao (ping after 24h)
2016/10/25 15:23:12
Done.
| |
| 34 NSString* const kTrailingEqualEncoding = @"\\x3d"; | |
| 35 // The maximum number of trailing '=' characters in a Voice Search SRP. | |
| 36 const NSUInteger kMaxTrailingEqualsCount = 2; | |
| 37 } // namespace | |
|
sdefresne
2016/10/25 13:20:30
nit: blank line before closing the namespace or re
rohitrao (ping after 24h)
2016/10/25 15:23:12
Done.
| |
| 38 | |
| 39 NSData* ExtractVoiceSearchAudioDataFromPageHTML(NSString* page_html) { | |
| 40 if (!page_html.length) | |
| 41 return nil; | |
| 42 | |
| 43 // The data should be near the end of the page, so search backwards. | |
| 44 NSRange data_start_tag_range = | |
| 45 [page_html rangeOfString:kTTSStartTag options:NSBackwardsSearch]; | |
| 46 if (data_start_tag_range.location == NSNotFound) { | |
| 47 DLOG(ERROR) << "Did not find base tts tag in search output. " | |
| 48 << page_html.length; | |
| 49 return nil; | |
| 50 } | |
| 51 | |
| 52 // The base64-encoded data will be everything between | |
| 53 // |audioDataStartTag| and |audioDataEndTag|. | |
| 54 NSUInteger start_position = | |
| 55 data_start_tag_range.location + data_start_tag_range.length; | |
| 56 NSRange data_range = | |
| 57 NSMakeRange(start_position, page_html.length - start_position); | |
| 58 NSRange data_end_tag_range = | |
| 59 [page_html rangeOfString:kTTSEndTag options:0 range:data_range]; | |
| 60 if (data_end_tag_range.location == NSNotFound || | |
| 61 data_end_tag_range.location == start_position) { | |
| 62 DLOG(ERROR) << "Could not find encoded data before tts closing tag."; | |
| 63 return nil; | |
| 64 } | |
| 65 | |
| 66 // Extract the data between the tags. | |
| 67 NSRange audio_data_range = | |
| 68 NSMakeRange(start_position, data_end_tag_range.location - start_position); | |
| 69 NSString* raw_base64_encoded_audio_string = | |
| 70 [page_html substringWithRange:audio_data_range]; | |
| 71 if (!raw_base64_encoded_audio_string) { | |
| 72 DLOG(ERROR) << "Could not find encoded data between tags."; | |
| 73 return nil; | |
| 74 } | |
| 75 | |
| 76 // GWS is escaping the trailing '=' characters to \x3d. | |
| 77 // Clean these up before passing the string to the base64 decoder. | |
| 78 // Note: there are at most 2 encoded trailing '=' characters, so limit the | |
| 79 // string replacement to the last characters of the string. | |
| 80 NSUInteger search_range_length = | |
| 81 std::min(kMaxTrailingEqualsCount * kTrailingEqualEncoding.length, | |
| 82 raw_base64_encoded_audio_string.length); | |
| 83 NSRange search_range = | |
| 84 NSMakeRange(raw_base64_encoded_audio_string.length - search_range_length, | |
| 85 search_range_length); | |
| 86 NSString* base64_encoded_audio_string = [raw_base64_encoded_audio_string | |
| 87 stringByReplacingOccurrencesOfString:kTrailingEqualEncoding | |
| 88 withString:@"=" | |
| 89 options:0 | |
| 90 range:search_range]; | |
| 91 | |
| 92 GTMStringEncoding* base64 = [GTMStringEncoding rfc4648Base64StringEncoding]; | |
| 93 return [base64 decode:base64_encoded_audio_string]; | |
| 94 } | |
| 95 | |
| 96 void ExtractVoiceSearchAudioDataFromWebState( | |
| 97 web::WebState* webState, | |
| 98 TextToSpeechCompletion completion) { | |
| 99 DCHECK(webState); | |
| 100 DCHECK(completion); | |
| 101 NSString* tts_extraction_script = [NSString | |
| 102 stringWithFormat:kTTSAudioDataExtractorScriptFormat, kTTSStartTag]; | |
| 103 [webState->GetJSInjectionReceiver() | |
| 104 executeJavaScript:tts_extraction_script | |
| 105 completionHandler:^(id result, NSError* error) { | |
| 106 completion(ExtractVoiceSearchAudioDataFromPageHTML(result)); | |
| 107 }]; | |
| 108 } | |
| OLD | NEW |