1 // Copyright 2018 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 #include "content/browser/speech/tts_controller_impl.h"
6 
7 #include <stddef.h>
8 
9 #include <string>
10 #include <vector>
11 
12 #include "base/bind.h"
13 #include "base/containers/queue.h"
14 #include "base/json/json_reader.h"
15 #include "base/metrics/histogram_macros.h"
16 #include "base/metrics/user_metrics.h"
17 #include "base/values.h"
18 #include "build/build_config.h"
19 #include "content/public/browser/content_browser_client.h"
20 #include "content/public/common/content_client.h"
21 #include "services/data_decoder/public/cpp/safe_xml_parser.h"
22 #include "services/data_decoder/public/mojom/xml_parser.mojom.h"
23 #include "third_party/blink/public/mojom/speech/speech_synthesis.mojom.h"
24 
25 namespace content {
26 
27 // A value to be used to indicate that there is no char index available.
28 const int kInvalidCharIndex = -1;
29 
30 // A value to be used to indicate that there is no length available.
31 const int kInvalidLength = -1;
32 
33 //
34 // VoiceData
35 //
36 
VoiceData()37 VoiceData::VoiceData() : remote(false), native(false) {}
38 
39 VoiceData::VoiceData(const VoiceData& other) = default;
40 
~VoiceData()41 VoiceData::~VoiceData() {}
42 
43 //
44 // TtsController
45 //
46 
GetInstance()47 TtsController* TtsController::GetInstance() {
48   return TtsControllerImpl::GetInstance();
49 }
50 
51 // IMPORTANT!
52 // These values are written to logs.  Do not renumber or delete
53 // existing items; add new entries to the end of the list.
54 enum class UMATextToSpeechEvent {
55   START = 0,
56   END = 1,
57   WORD = 2,
58   SENTENCE = 3,
59   MARKER = 4,
60   INTERRUPTED = 5,
61   CANCELLED = 6,
62   SPEECH_ERROR = 7,
63   PAUSE = 8,
64   RESUME = 9,
65 
66   // This must always be the last enum. It's okay for its value to
67   // increase, but none of the other enum values may change.
68   COUNT
69 };
70 
71 //
72 // TtsControllerImpl
73 //
74 
75 // static
GetInstance()76 TtsControllerImpl* TtsControllerImpl::GetInstance() {
77   return base::Singleton<TtsControllerImpl>::get();
78 }
79 
TtsControllerImpl()80 TtsControllerImpl::TtsControllerImpl()
81     : delegate_(nullptr),
82       current_utterance_(nullptr),
83       paused_(false),
84       tts_platform_(nullptr) {}
85 
~TtsControllerImpl()86 TtsControllerImpl::~TtsControllerImpl() {
87   if (current_utterance_) {
88     current_utterance_->Finish();
89     current_utterance_.reset();
90   }
91 
92   // Clear any queued utterances too.
93   ClearUtteranceQueue(false);  // Don't sent events.
94 }
95 
SpeakOrEnqueue(std::unique_ptr<TtsUtterance> utterance)96 void TtsControllerImpl::SpeakOrEnqueue(
97     std::unique_ptr<TtsUtterance> utterance) {
98   // If we're paused and we get an utterance that can't be queued,
99   // flush the queue but stay in the paused state.
100   if (paused_ && !utterance->GetCanEnqueue()) {
101     utterance_deque_.emplace_back(std::move(utterance));
102     Stop();
103     paused_ = true;
104     return;
105   }
106 
107   if (paused_ || (IsSpeaking() && utterance->GetCanEnqueue())) {
108     utterance_deque_.emplace_back(std::move(utterance));
109   } else {
110     Stop();
111     SpeakNow(std::move(utterance));
112   }
113 }
114 
Stop()115 void TtsControllerImpl::Stop() {
116   StopInternal(GURL());
117 }
118 
Stop(const GURL & source_url)119 void TtsControllerImpl::Stop(const GURL& source_url) {
120   StopInternal(source_url);
121 }
122 
StopInternal(const GURL & source_url)123 void TtsControllerImpl::StopInternal(const GURL& source_url) {
124   base::RecordAction(base::UserMetricsAction("TextToSpeech.Stop"));
125 
126   paused_ = false;
127 
128   if (!source_url.is_empty() && current_utterance_ &&
129       current_utterance_->GetSrcUrl().GetOrigin() != source_url.GetOrigin())
130     return;
131 
132   if (current_utterance_ && !current_utterance_->GetEngineId().empty()) {
133     if (GetTtsControllerDelegate()->GetTtsEngineDelegate())
134       GetTtsControllerDelegate()->GetTtsEngineDelegate()->Stop(
135           current_utterance_.get());
136   } else {
137     GetTtsPlatform()->ClearError();
138     GetTtsPlatform()->StopSpeaking();
139   }
140 
141   if (current_utterance_)
142     current_utterance_->OnTtsEvent(TTS_EVENT_INTERRUPTED, kInvalidCharIndex,
143                                    kInvalidLength, std::string());
144   FinishCurrentUtterance();
145   ClearUtteranceQueue(true);  // Send events.
146 }
147 
Pause()148 void TtsControllerImpl::Pause() {
149   base::RecordAction(base::UserMetricsAction("TextToSpeech.Pause"));
150 
151   paused_ = true;
152   if (current_utterance_ && !current_utterance_->GetEngineId().empty()) {
153     if (GetTtsControllerDelegate()->GetTtsEngineDelegate())
154       GetTtsControllerDelegate()->GetTtsEngineDelegate()->Pause(
155           current_utterance_.get());
156   } else if (current_utterance_) {
157     GetTtsPlatform()->ClearError();
158     GetTtsPlatform()->Pause();
159   }
160 }
161 
Resume()162 void TtsControllerImpl::Resume() {
163   base::RecordAction(base::UserMetricsAction("TextToSpeech.Resume"));
164 
165   paused_ = false;
166   if (current_utterance_ && !current_utterance_->GetEngineId().empty()) {
167     if (GetTtsControllerDelegate()->GetTtsEngineDelegate())
168       GetTtsControllerDelegate()->GetTtsEngineDelegate()->Resume(
169           current_utterance_.get());
170   } else if (current_utterance_) {
171     GetTtsPlatform()->ClearError();
172     GetTtsPlatform()->Resume();
173   } else {
174     SpeakNextUtterance();
175   }
176 }
177 
OnTtsEvent(int utterance_id,TtsEventType event_type,int char_index,int length,const std::string & error_message)178 void TtsControllerImpl::OnTtsEvent(int utterance_id,
179                                    TtsEventType event_type,
180                                    int char_index,
181                                    int length,
182                                    const std::string& error_message) {
183   // We may sometimes receive completion callbacks "late", after we've
184   // already finished the utterance (for example because another utterance
185   // interrupted or we got a call to Stop). This is normal and we can
186   // safely just ignore these events.
187   if (!current_utterance_ || utterance_id != current_utterance_->GetId()) {
188     return;
189   }
190 
191   UMATextToSpeechEvent metric;
192   switch (event_type) {
193     case TTS_EVENT_START:
194       metric = UMATextToSpeechEvent::START;
195       break;
196     case TTS_EVENT_END:
197       metric = UMATextToSpeechEvent::END;
198       break;
199     case TTS_EVENT_WORD:
200       metric = UMATextToSpeechEvent::WORD;
201       break;
202     case TTS_EVENT_SENTENCE:
203       metric = UMATextToSpeechEvent::SENTENCE;
204       break;
205     case TTS_EVENT_MARKER:
206       metric = UMATextToSpeechEvent::MARKER;
207       break;
208     case TTS_EVENT_INTERRUPTED:
209       metric = UMATextToSpeechEvent::INTERRUPTED;
210       break;
211     case TTS_EVENT_CANCELLED:
212       metric = UMATextToSpeechEvent::CANCELLED;
213       break;
214     case TTS_EVENT_ERROR:
215       metric = UMATextToSpeechEvent::SPEECH_ERROR;
216       break;
217     case TTS_EVENT_PAUSE:
218       metric = UMATextToSpeechEvent::PAUSE;
219       break;
220     case TTS_EVENT_RESUME:
221       metric = UMATextToSpeechEvent::RESUME;
222       break;
223     default:
224       NOTREACHED();
225       return;
226   }
227   UMA_HISTOGRAM_ENUMERATION("TextToSpeech.Event", metric,
228                             UMATextToSpeechEvent::COUNT);
229 
230   current_utterance_->OnTtsEvent(event_type, char_index, length, error_message);
231   if (current_utterance_->IsFinished()) {
232     FinishCurrentUtterance();
233     SpeakNextUtterance();
234   }
235 }
236 
GetVoices(BrowserContext * browser_context,std::vector<VoiceData> * out_voices)237 void TtsControllerImpl::GetVoices(BrowserContext* browser_context,
238                                   std::vector<VoiceData>* out_voices) {
239   TtsPlatform* tts_platform = GetTtsPlatform();
240   if (tts_platform) {
241     // Ensure we have all built-in voices loaded. This is a no-op if already
242     // loaded.
243     tts_platform->LoadBuiltInTtsEngine(browser_context);
244     if (tts_platform->PlatformImplAvailable())
245       tts_platform->GetVoices(out_voices);
246   }
247 
248   if (browser_context) {
249     TtsControllerDelegate* delegate = GetTtsControllerDelegate();
250     if (delegate && delegate->GetTtsEngineDelegate())
251       delegate->GetTtsEngineDelegate()->GetVoices(browser_context, out_voices);
252   }
253 }
254 
IsSpeaking()255 bool TtsControllerImpl::IsSpeaking() {
256   return current_utterance_ != nullptr || GetTtsPlatform()->IsSpeaking();
257 }
258 
VoicesChanged()259 void TtsControllerImpl::VoicesChanged() {
260   // Existence of platform tts indicates explicit requests to tts. Since
261   // |VoicesChanged| can occur implicitly, only send if needed.
262   for (auto& delegate : voices_changed_delegates_)
263     delegate.OnVoicesChanged();
264 }
265 
AddVoicesChangedDelegate(VoicesChangedDelegate * delegate)266 void TtsControllerImpl::AddVoicesChangedDelegate(
267     VoicesChangedDelegate* delegate) {
268   voices_changed_delegates_.AddObserver(delegate);
269 }
270 
RemoveVoicesChangedDelegate(VoicesChangedDelegate * delegate)271 void TtsControllerImpl::RemoveVoicesChangedDelegate(
272     VoicesChangedDelegate* delegate) {
273   voices_changed_delegates_.RemoveObserver(delegate);
274 }
275 
RemoveUtteranceEventDelegate(UtteranceEventDelegate * delegate)276 void TtsControllerImpl::RemoveUtteranceEventDelegate(
277     UtteranceEventDelegate* delegate) {
278   // First clear any pending utterances with this delegate.
279   std::deque<std::unique_ptr<TtsUtterance>> old_deque;
280   utterance_deque_.swap(old_deque);
281   while (!old_deque.empty()) {
282     std::unique_ptr<TtsUtterance> utterance = std::move(old_deque.front());
283     old_deque.pop_front();
284     if (utterance->GetEventDelegate() != delegate)
285       utterance_deque_.emplace_back(std::move(utterance));
286   }
287 
288   if (current_utterance_ &&
289       current_utterance_->GetEventDelegate() == delegate) {
290     current_utterance_->SetEventDelegate(nullptr);
291     if (!current_utterance_->GetEngineId().empty()) {
292       if (GetTtsControllerDelegate()->GetTtsEngineDelegate())
293         GetTtsControllerDelegate()->GetTtsEngineDelegate()->Stop(
294             current_utterance_.get());
295     } else {
296       GetTtsPlatform()->ClearError();
297       GetTtsPlatform()->StopSpeaking();
298     }
299 
300     FinishCurrentUtterance();
301     if (!paused_)
302       SpeakNextUtterance();
303   }
304 }
305 
SetTtsEngineDelegate(TtsEngineDelegate * delegate)306 void TtsControllerImpl::SetTtsEngineDelegate(TtsEngineDelegate* delegate) {
307   if (!GetTtsControllerDelegate())
308     return;
309 
310   GetTtsControllerDelegate()->SetTtsEngineDelegate(delegate);
311 }
312 
GetTtsEngineDelegate()313 TtsEngineDelegate* TtsControllerImpl::GetTtsEngineDelegate() {
314   if (!GetTtsControllerDelegate())
315     return nullptr;
316 
317   return GetTtsControllerDelegate()->GetTtsEngineDelegate();
318 }
319 
OnBrowserContextDestroyed(BrowserContext * browser_context)320 void TtsControllerImpl::OnBrowserContextDestroyed(
321     BrowserContext* browser_context) {
322   bool did_clear_utterances = false;
323 
324   // First clear the BrowserContext from any utterances.
325   for (std::unique_ptr<TtsUtterance>& utterance : utterance_deque_) {
326     if (utterance->GetBrowserContext() == browser_context) {
327       utterance->ClearBrowserContext();
328       did_clear_utterances = true;
329     }
330   }
331 
332   if (current_utterance_ &&
333       current_utterance_->GetBrowserContext() == browser_context) {
334     current_utterance_->ClearBrowserContext();
335     did_clear_utterances = true;
336   }
337 
338   // If we cleared the BrowserContext from any utterances, stop speech
339   // just to be safe. Do this using PostTask because calling Stop might
340   // try to send notifications and that can trigger code paths that try
341   // to access the BrowserContext that's being deleted. Note that it's
342   // safe to use base::Unretained because this is a singleton.
343   if (did_clear_utterances) {
344     base::ThreadTaskRunnerHandle::Get()->PostTask(
345         FROM_HERE, base::BindOnce(&TtsControllerImpl::StopInternal,
346                                   base::Unretained(this), GURL()));
347   }
348 }
349 
SetTtsPlatform(TtsPlatform * tts_platform)350 void TtsControllerImpl::SetTtsPlatform(TtsPlatform* tts_platform) {
351   tts_platform_ = tts_platform;
352 }
353 
QueueSize()354 int TtsControllerImpl::QueueSize() {
355   return static_cast<int>(utterance_deque_.size());
356 }
357 
GetTtsPlatform()358 TtsPlatform* TtsControllerImpl::GetTtsPlatform() {
359   if (!tts_platform_)
360     tts_platform_ = TtsPlatform::GetInstance();
361   return tts_platform_;
362 }
363 
SpeakNow(std::unique_ptr<TtsUtterance> utterance)364 void TtsControllerImpl::SpeakNow(std::unique_ptr<TtsUtterance> utterance) {
365   // Note: this would only happen if a content embedder failed to provide
366   // their own TtsControllerDelegate. Chrome provides one, and Content Shell
367   // provides a mock one for web tests.
368   if (!GetTtsControllerDelegate()) {
369     utterance->OnTtsEvent(TTS_EVENT_CANCELLED, kInvalidCharIndex,
370                           kInvalidLength, std::string());
371     return;
372   }
373 
374   // Get all available voices and try to find a matching voice.
375   std::vector<VoiceData> voices;
376   GetVoices(utterance->GetBrowserContext(), &voices);
377 
378   // Get the best matching voice. If nothing matches, just set "native"
379   // to true because that might trigger deferred loading of native voices.
380   // TODO(katie): Move most of the GetMatchingVoice logic into content/ and
381   // use the TTS controller delegate to get chrome-specific info as needed.
382   int index =
383       GetTtsControllerDelegate()->GetMatchingVoice(utterance.get(), voices);
384   VoiceData voice;
385   if (index >= 0)
386     voice = voices[index];
387   else
388     voice.native = true;
389 
390   UpdateUtteranceDefaults(utterance.get());
391 
392   GetTtsPlatform()->WillSpeakUtteranceWithVoice(utterance.get(), voice);
393 
394   base::RecordAction(base::UserMetricsAction("TextToSpeech.Speak"));
395   UMA_HISTOGRAM_COUNTS_100000("TextToSpeech.Utterance.TextLength",
396                               utterance->GetText().size());
397   UMA_HISTOGRAM_BOOLEAN("TextToSpeech.Utterance.FromExtensionAPI",
398                         !utterance->GetSrcUrl().is_empty());
399   UMA_HISTOGRAM_BOOLEAN("TextToSpeech.Utterance.HasVoiceName",
400                         !utterance->GetVoiceName().empty());
401   UMA_HISTOGRAM_BOOLEAN("TextToSpeech.Utterance.HasLang",
402                         !utterance->GetLang().empty());
403   UMA_HISTOGRAM_BOOLEAN("TextToSpeech.Utterance.HasRate",
404                         utterance->GetContinuousParameters().rate != 1.0);
405   UMA_HISTOGRAM_BOOLEAN("TextToSpeech.Utterance.HasPitch",
406                         utterance->GetContinuousParameters().pitch != 1.0);
407   UMA_HISTOGRAM_BOOLEAN("TextToSpeech.Utterance.HasVolume",
408                         utterance->GetContinuousParameters().volume != 1.0);
409   UMA_HISTOGRAM_BOOLEAN("TextToSpeech.Utterance.Native", voice.native);
410 
411   if (!voice.native) {
412 #if !defined(OS_ANDROID)
413     DCHECK(!voice.engine_id.empty());
414     current_utterance_ = std::move(utterance);
415     current_utterance_->SetEngineId(voice.engine_id);
416     if (GetTtsControllerDelegate()->GetTtsEngineDelegate())
417       GetTtsControllerDelegate()->GetTtsEngineDelegate()->Speak(
418           current_utterance_.get(), voice);
419     bool sends_end_event =
420         voice.events.find(TTS_EVENT_END) != voice.events.end();
421     if (!sends_end_event) {
422       current_utterance_->Finish();
423       current_utterance_.reset();
424       SpeakNextUtterance();
425     }
426 #endif
427   } else {
428     // It's possible for certain platforms to send start events immediately
429     // during |speak|.
430     current_utterance_ = std::move(utterance);
431     GetTtsPlatform()->ClearError();
432     GetTtsPlatform()->Speak(
433         current_utterance_->GetId(), current_utterance_->GetText(),
434         current_utterance_->GetLang(), voice,
435         current_utterance_->GetContinuousParameters(),
436         base::BindOnce(&TtsControllerImpl::OnSpeakFinished,
437                        base::Unretained(this), current_utterance_->GetId()));
438   }
439 }
440 
OnSpeakFinished(int utterance_id,bool success)441 void TtsControllerImpl::OnSpeakFinished(int utterance_id, bool success) {
442   if (success)
443     return;
444 
445   // Since OnSpeakFinished could run asynchronously, it is possible that the
446   // current utterance has changed. Ignore any such spurious callbacks.
447   if (!current_utterance_ || current_utterance_->GetId() != utterance_id)
448     return;
449 
450   // If the native voice wasn't able to process this speech, see if
451   // the browser has built-in TTS that isn't loaded yet.
452   if (GetTtsPlatform()->LoadBuiltInTtsEngine(
453           current_utterance_->GetBrowserContext())) {
454     utterance_deque_.emplace_back(std::move(current_utterance_));
455     return;
456   }
457 
458   current_utterance_->OnTtsEvent(TTS_EVENT_ERROR, kInvalidCharIndex,
459                                  kInvalidLength, GetTtsPlatform()->GetError());
460   current_utterance_.reset();
461 }
462 
ClearUtteranceQueue(bool send_events)463 void TtsControllerImpl::ClearUtteranceQueue(bool send_events) {
464   while (!utterance_deque_.empty()) {
465     std::unique_ptr<TtsUtterance> utterance =
466         std::move(utterance_deque_.front());
467     utterance_deque_.pop_front();
468     if (send_events) {
469       utterance->OnTtsEvent(TTS_EVENT_CANCELLED, kInvalidCharIndex,
470                             kInvalidLength, std::string());
471     } else {
472       utterance->Finish();
473     }
474   }
475 }
476 
FinishCurrentUtterance()477 void TtsControllerImpl::FinishCurrentUtterance() {
478   if (current_utterance_) {
479     if (!current_utterance_->IsFinished())
480       current_utterance_->OnTtsEvent(TTS_EVENT_INTERRUPTED, kInvalidCharIndex,
481                                      kInvalidLength, std::string());
482     current_utterance_.reset();
483   }
484 }
485 
SpeakNextUtterance()486 void TtsControllerImpl::SpeakNextUtterance() {
487   if (paused_)
488     return;
489 
490   // Start speaking the next utterance in the queue.  Keep trying in case
491   // one fails but there are still more in the queue to try.
492   while (!utterance_deque_.empty() && !current_utterance_) {
493     std::unique_ptr<TtsUtterance> utterance =
494         std::move(utterance_deque_.front());
495     utterance_deque_.pop_front();
496     SpeakNow(std::move(utterance));
497   }
498 }
499 
UpdateUtteranceDefaults(TtsUtterance * utterance)500 void TtsControllerImpl::UpdateUtteranceDefaults(TtsUtterance* utterance) {
501   double rate = utterance->GetContinuousParameters().rate;
502   double pitch = utterance->GetContinuousParameters().pitch;
503   double volume = utterance->GetContinuousParameters().volume;
504 #if defined(OS_CHROMEOS)
505   GetTtsControllerDelegate()->UpdateUtteranceDefaultsFromPrefs(utterance, &rate,
506                                                                &pitch, &volume);
507 #else
508   // Update pitch, rate and volume to defaults if not explicity set on
509   // this utterance.
510   if (rate == blink::mojom::kSpeechSynthesisDoublePrefNotSet)
511     rate = blink::mojom::kSpeechSynthesisDefaultRate;
512   if (pitch == blink::mojom::kSpeechSynthesisDoublePrefNotSet)
513     pitch = blink::mojom::kSpeechSynthesisDefaultPitch;
514   if (volume == blink::mojom::kSpeechSynthesisDoublePrefNotSet)
515     volume = blink::mojom::kSpeechSynthesisDefaultVolume;
516 #endif  // defined(OS_CHROMEOS)
517   utterance->SetContinuousParameters(rate, pitch, volume);
518 }
519 
GetTtsControllerDelegate()520 TtsControllerDelegate* TtsControllerImpl::GetTtsControllerDelegate() {
521   if (delegate_)
522     return delegate_;
523   if (GetContentClient() && GetContentClient()->browser()) {
524     delegate_ = GetContentClient()->browser()->GetTtsControllerDelegate();
525     return delegate_;
526   }
527   return nullptr;
528 }
529 
StripSSML(const std::string & utterance,base::OnceCallback<void (const std::string &)> on_ssml_parsed)530 void TtsControllerImpl::StripSSML(
531     const std::string& utterance,
532     base::OnceCallback<void(const std::string&)> on_ssml_parsed) {
533   // Skip parsing and return if not xml.
534   if (utterance.find("<?xml") == std::string::npos) {
535     std::move(on_ssml_parsed).Run(utterance);
536     return;
537   }
538 
539   // Parse using safe, out-of-process Xml Parser.
540   data_decoder::DataDecoder::ParseXmlIsolated(
541       utterance, base::BindOnce(&TtsControllerImpl::StripSSMLHelper, utterance,
542                                 std::move(on_ssml_parsed)));
543 }
544 
545 // Called when ParseXml finishes.
546 // Uses parsed xml to build parsed utterance text.
StripSSMLHelper(const std::string & utterance,base::OnceCallback<void (const std::string &)> on_ssml_parsed,data_decoder::DataDecoder::ValueOrError result)547 void TtsControllerImpl::StripSSMLHelper(
548     const std::string& utterance,
549     base::OnceCallback<void(const std::string&)> on_ssml_parsed,
550     data_decoder::DataDecoder::ValueOrError result) {
551   // Error checks.
552   // If invalid xml, return original utterance text.
553   if (!result.value) {
554     std::move(on_ssml_parsed).Run(utterance);
555     return;
556   }
557 
558   std::string root_tag_name;
559   data_decoder::GetXmlElementTagName(*result.value, &root_tag_name);
560   // Root element must be <speak>.
561   if (root_tag_name.compare("speak") != 0) {
562     std::move(on_ssml_parsed).Run(utterance);
563     return;
564   }
565 
566   std::string parsed_text = "";
567   // Change from unique_ptr to base::Value* so recursion will work.
568   PopulateParsedText(&parsed_text, &(*result.value));
569 
570   // Run with parsed_text.
571   std::move(on_ssml_parsed).Run(parsed_text);
572 }
573 
PopulateParsedText(std::string * parsed_text,const base::Value * element)574 void TtsControllerImpl::PopulateParsedText(std::string* parsed_text,
575                                            const base::Value* element) {
576   DCHECK(parsed_text);
577   if (!element)
578     return;
579   // Add element's text if present.
580   // Note: We don't use data_decoder::GetXmlElementText because it gets the text
581   // of element's first child, not text of current element.
582   const base::Value* text_value = element->FindKeyOfType(
583       data_decoder::mojom::XmlParser::kTextKey, base::Value::Type::STRING);
584   if (text_value)
585     *parsed_text += text_value->GetString();
586 
587   const base::Value* children = data_decoder::GetXmlElementChildren(*element);
588   if (!children || !children->is_list())
589     return;
590 
591   for (size_t i = 0; i < children->GetList().size(); ++i) {
592     // We need to iterate over all children because some text elements are
593     // nested within other types of elements, such as <emphasis> tags.
594     PopulateParsedText(parsed_text, &children->GetList()[i]);
595   }
596 }
597 
598 }  // namespace content
599