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