1 // Copyright (c) 2012 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.
5 #include "content/browser/media/media_internals.h"
7 #include "base/metrics/histogram.h"
8 #include "base/strings/string16.h"
9 #include "base/strings/string_number_conversions.h"
10 #include "base/strings/stringprintf.h"
11 #include "content/public/browser/browser_thread.h"
12 #include "content/public/browser/notification_service.h"
13 #include "content/public/browser/notification_types.h"
14 #include "content/public/browser/render_frame_host.h"
15 #include "content/public/browser/render_process_host.h"
16 #include "content/public/browser/web_contents.h"
17 #include "content/public/browser/web_ui.h"
18 #include "media/audio/audio_parameters.h"
19 #include "media/base/media_log_event.h"
20 #include "media/filters/decrypting_video_decoder.h"
21 #include "media/filters/gpu_video_decoder.h"
25 static base::LazyInstance
<content::MediaInternals
>::Leaky g_media_internals
=
26 LAZY_INSTANCE_INITIALIZER
;
28 base::string16
SerializeUpdate(const std::string
& function
,
29 const base::Value
* value
) {
30 return content::WebUI::GetJavascriptCall(
31 function
, std::vector
<const base::Value
*>(1, value
));
34 std::string
EffectsToString(int effects
) {
35 if (effects
== media::AudioParameters::NO_EFFECTS
)
42 { media::AudioParameters::ECHO_CANCELLER
, "ECHO_CANCELLER" },
43 { media::AudioParameters::DUCKING
, "DUCKING" },
44 { media::AudioParameters::KEYBOARD_MIC
, "KEYBOARD_MIC" },
45 { media::AudioParameters::HOTWORD
, "HOTWORD" },
49 for (size_t i
= 0; i
< arraysize(flags
); ++i
) {
50 if (effects
& flags
[i
].flag
) {
54 effects
&= ~flags
[i
].flag
;
61 ret
+= base::IntToString(effects
);
67 std::string
FormatToString(media::AudioParameters::Format format
) {
69 case media::AudioParameters::AUDIO_PCM_LINEAR
:
71 case media::AudioParameters::AUDIO_PCM_LOW_LATENCY
:
72 return "pcm_low_latency";
73 case media::AudioParameters::AUDIO_FAKE
:
81 const char kAudioLogStatusKey
[] = "status";
82 const char kAudioLogUpdateFunction
[] = "media.updateAudioComponent";
88 class AudioLogImpl
: public media::AudioLog
{
90 AudioLogImpl(int owner_id
,
91 media::AudioLogFactory::AudioComponent component
,
92 content::MediaInternals
* media_internals
);
93 ~AudioLogImpl() override
;
95 void OnCreated(int component_id
,
96 const media::AudioParameters
& params
,
97 const std::string
& device_id
) override
;
98 void OnStarted(int component_id
) override
;
99 void OnStopped(int component_id
) override
;
100 void OnClosed(int component_id
) override
;
101 void OnError(int component_id
) override
;
102 void OnSetVolume(int component_id
, double volume
) override
;
103 void OnSwitchOutputDevice(int component_id
,
104 const std::string
& device_id
) override
;
106 // Called by MediaInternals to update the WebContents title for a stream.
107 void SendWebContentsTitle(int component_id
,
108 int render_process_id
,
109 int render_frame_id
);
112 void SendSingleStringUpdate(int component_id
,
113 const std::string
& key
,
114 const std::string
& value
);
115 void StoreComponentMetadata(int component_id
, base::DictionaryValue
* dict
);
116 std::string
FormatCacheKey(int component_id
);
118 static void SendWebContentsTitleHelper(const std::string
& cache_key
,
119 scoped_ptr
<base::DictionaryValue
> dict
,
120 int render_process_id
,
121 int render_frame_id
);
124 const media::AudioLogFactory::AudioComponent component_
;
125 content::MediaInternals
* const media_internals_
;
127 DISALLOW_COPY_AND_ASSIGN(AudioLogImpl
);
130 AudioLogImpl::AudioLogImpl(int owner_id
,
131 media::AudioLogFactory::AudioComponent component
,
132 content::MediaInternals
* media_internals
)
133 : owner_id_(owner_id
),
134 component_(component
),
135 media_internals_(media_internals
) {}
137 AudioLogImpl::~AudioLogImpl() {}
139 void AudioLogImpl::OnCreated(int component_id
,
140 const media::AudioParameters
& params
,
141 const std::string
& device_id
) {
142 base::DictionaryValue dict
;
143 StoreComponentMetadata(component_id
, &dict
);
145 dict
.SetString(kAudioLogStatusKey
, "created");
146 dict
.SetString("device_id", device_id
);
147 dict
.SetString("device_type", FormatToString(params
.format()));
148 dict
.SetInteger("frames_per_buffer", params
.frames_per_buffer());
149 dict
.SetInteger("sample_rate", params
.sample_rate());
150 dict
.SetInteger("channels", params
.channels());
151 dict
.SetString("channel_layout",
152 ChannelLayoutToString(params
.channel_layout()));
153 dict
.SetString("effects", EffectsToString(params
.effects()));
155 media_internals_
->UpdateAudioLog(MediaInternals::CREATE
,
156 FormatCacheKey(component_id
),
157 kAudioLogUpdateFunction
, &dict
);
160 void AudioLogImpl::OnStarted(int component_id
) {
161 SendSingleStringUpdate(component_id
, kAudioLogStatusKey
, "started");
164 void AudioLogImpl::OnStopped(int component_id
) {
165 SendSingleStringUpdate(component_id
, kAudioLogStatusKey
, "stopped");
168 void AudioLogImpl::OnClosed(int component_id
) {
169 base::DictionaryValue dict
;
170 StoreComponentMetadata(component_id
, &dict
);
171 dict
.SetString(kAudioLogStatusKey
, "closed");
172 media_internals_
->UpdateAudioLog(MediaInternals::UPDATE_AND_DELETE
,
173 FormatCacheKey(component_id
),
174 kAudioLogUpdateFunction
, &dict
);
177 void AudioLogImpl::OnError(int component_id
) {
178 SendSingleStringUpdate(component_id
, "error_occurred", "true");
181 void AudioLogImpl::OnSetVolume(int component_id
, double volume
) {
182 base::DictionaryValue dict
;
183 StoreComponentMetadata(component_id
, &dict
);
184 dict
.SetDouble("volume", volume
);
185 media_internals_
->UpdateAudioLog(MediaInternals::UPDATE_IF_EXISTS
,
186 FormatCacheKey(component_id
),
187 kAudioLogUpdateFunction
, &dict
);
190 void AudioLogImpl::OnSwitchOutputDevice(int component_id
,
191 const std::string
& device_id
) {
192 base::DictionaryValue dict
;
193 StoreComponentMetadata(component_id
, &dict
);
194 dict
.SetString("device_id", device_id
);
195 media_internals_
->UpdateAudioLog(MediaInternals::UPDATE_IF_EXISTS
,
196 FormatCacheKey(component_id
),
197 kAudioLogUpdateFunction
, &dict
);
200 void AudioLogImpl::SendWebContentsTitle(int component_id
,
201 int render_process_id
,
202 int render_frame_id
) {
203 scoped_ptr
<base::DictionaryValue
> dict(new base::DictionaryValue());
204 StoreComponentMetadata(component_id
, dict
.get());
205 SendWebContentsTitleHelper(FormatCacheKey(component_id
), dict
.Pass(),
206 render_process_id
, render_frame_id
);
209 std::string
AudioLogImpl::FormatCacheKey(int component_id
) {
210 return base::StringPrintf("%d:%d:%d", owner_id_
, component_
, component_id
);
214 void AudioLogImpl::SendWebContentsTitleHelper(
215 const std::string
& cache_key
,
216 scoped_ptr
<base::DictionaryValue
> dict
,
217 int render_process_id
,
218 int render_frame_id
) {
219 // Page title information can only be retrieved from the UI thread.
220 if (!BrowserThread::CurrentlyOn(BrowserThread::UI
)) {
221 BrowserThread::PostTask(
222 BrowserThread::UI
, FROM_HERE
,
223 base::Bind(&SendWebContentsTitleHelper
, cache_key
, base::Passed(&dict
),
224 render_process_id
, render_frame_id
));
228 const WebContents
* web_contents
= WebContents::FromRenderFrameHost(
229 RenderFrameHost::FromID(render_process_id
, render_frame_id
));
233 // Note: by this point the given audio log entry could have been destroyed, so
234 // we use UPDATE_IF_EXISTS to discard such instances.
235 dict
->SetInteger("render_process_id", render_process_id
);
236 dict
->SetString("web_contents_title", web_contents
->GetTitle());
237 MediaInternals::GetInstance()->UpdateAudioLog(
238 MediaInternals::UPDATE_IF_EXISTS
, cache_key
, kAudioLogUpdateFunction
,
242 void AudioLogImpl::SendSingleStringUpdate(int component_id
,
243 const std::string
& key
,
244 const std::string
& value
) {
245 base::DictionaryValue dict
;
246 StoreComponentMetadata(component_id
, &dict
);
247 dict
.SetString(key
, value
);
248 media_internals_
->UpdateAudioLog(MediaInternals::UPDATE_IF_EXISTS
,
249 FormatCacheKey(component_id
),
250 kAudioLogUpdateFunction
, &dict
);
253 void AudioLogImpl::StoreComponentMetadata(int component_id
,
254 base::DictionaryValue
* dict
) {
255 dict
->SetInteger("owner_id", owner_id_
);
256 dict
->SetInteger("component_id", component_id
);
257 dict
->SetInteger("component_type", component_
);
260 // This class lives on the browser UI thread.
261 class MediaInternals::MediaInternalsUMAHandler
{
263 MediaInternalsUMAHandler();
265 // Called when a render process is terminated. Reports the pipeline status to
266 // UMA for every player associated with the renderer process and then deletes
268 void OnProcessTerminated(int render_process_id
);
270 // Helper function to save the event payload to RendererPlayerMap.
271 void SavePlayerState(int render_process_id
,
272 const media::MediaLogEvent
& event
);
275 struct PipelineInfo
{
276 media::PipelineStatus last_pipeline_status
;
280 bool video_decoder_changed
;
281 std::string audio_codec_name
;
282 std::string video_codec_name
;
283 std::string video_decoder
;
285 : last_pipeline_status(media::PIPELINE_OK
),
289 video_decoder_changed(false) {}
292 // Helper function to report PipelineStatus associated with a player to UMA.
293 void ReportUMAForPipelineStatus(const PipelineInfo
& player_info
);
295 // Helper to generate PipelineStatus UMA name for AudioVideo streams.
296 std::string
GetUMANameForAVStream(const PipelineInfo
& player_info
);
299 typedef std::map
<int, PipelineInfo
> PlayerInfoMap
;
301 // Key is renderer id.
302 typedef std::map
<int, PlayerInfoMap
> RendererPlayerMap
;
304 // Stores player information per renderer.
305 RendererPlayerMap renderer_info_
;
307 DISALLOW_COPY_AND_ASSIGN(MediaInternalsUMAHandler
);
310 MediaInternals::MediaInternalsUMAHandler::MediaInternalsUMAHandler() {
313 void MediaInternals::MediaInternalsUMAHandler::SavePlayerState(
314 int render_process_id
,
315 const media::MediaLogEvent
& event
) {
316 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
317 PlayerInfoMap
& player_info
= renderer_info_
[render_process_id
];
318 switch (event
.type
) {
319 case media::MediaLogEvent::PIPELINE_ERROR
: {
321 event
.params
.GetInteger("pipeline_error", &status
);
322 player_info
[event
.id
].last_pipeline_status
=
323 static_cast<media::PipelineStatus
>(status
);
326 case media::MediaLogEvent::PROPERTY_CHANGE
:
327 if (event
.params
.HasKey("found_audio_stream")) {
328 event
.params
.GetBoolean("found_audio_stream",
329 &player_info
[event
.id
].has_audio
);
331 if (event
.params
.HasKey("found_video_stream")) {
332 event
.params
.GetBoolean("found_video_stream",
333 &player_info
[event
.id
].has_video
);
335 if (event
.params
.HasKey("audio_codec_name")) {
336 event
.params
.GetString("audio_codec_name",
337 &player_info
[event
.id
].audio_codec_name
);
339 if (event
.params
.HasKey("video_codec_name")) {
340 event
.params
.GetString("video_codec_name",
341 &player_info
[event
.id
].video_codec_name
);
343 if (event
.params
.HasKey("video_decoder")) {
344 std::string
previous_video_decoder(player_info
[event
.id
].video_decoder
);
345 event
.params
.GetString("video_decoder",
346 &player_info
[event
.id
].video_decoder
);
347 if (!previous_video_decoder
.empty() &&
348 previous_video_decoder
!= player_info
[event
.id
].video_decoder
) {
349 player_info
[event
.id
].video_decoder_changed
= true;
352 if (event
.params
.HasKey("video_dds")) {
353 event
.params
.GetBoolean("video_dds", &player_info
[event
.id
].video_dds
);
362 std::string
MediaInternals::MediaInternalsUMAHandler::GetUMANameForAVStream(
363 const PipelineInfo
& player_info
) {
364 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
365 static const char kPipelineUmaPrefix
[] = "Media.PipelineStatus.AudioVideo.";
366 std::string uma_name
= kPipelineUmaPrefix
;
367 if (player_info
.video_codec_name
== "vp8") {
369 } else if (player_info
.video_codec_name
== "vp9") {
371 } else if (player_info
.video_codec_name
== "h264") {
374 return uma_name
+ "Other";
377 if (player_info
.video_decoder
==
378 media::DecryptingVideoDecoder::kDecoderName
) {
379 return uma_name
+ "DVD";
382 if (player_info
.video_dds
) {
386 if (player_info
.video_decoder
== media::GpuVideoDecoder::kDecoderName
) {
394 void MediaInternals::MediaInternalsUMAHandler::ReportUMAForPipelineStatus(
395 const PipelineInfo
& player_info
) {
396 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
397 if (player_info
.has_video
&& player_info
.has_audio
) {
398 base::LinearHistogram::FactoryGet(
399 GetUMANameForAVStream(player_info
), 1, media::PIPELINE_STATUS_MAX
,
400 media::PIPELINE_STATUS_MAX
+ 1,
401 base::HistogramBase::kUmaTargetedHistogramFlag
)
402 ->Add(player_info
.last_pipeline_status
);
403 } else if (player_info
.has_audio
) {
404 UMA_HISTOGRAM_ENUMERATION("Media.PipelineStatus.AudioOnly",
405 player_info
.last_pipeline_status
,
406 media::PIPELINE_STATUS_MAX
+ 1);
407 } else if (player_info
.has_video
) {
408 UMA_HISTOGRAM_ENUMERATION("Media.PipelineStatus.VideoOnly",
409 player_info
.last_pipeline_status
,
410 media::PIPELINE_STATUS_MAX
+ 1);
412 UMA_HISTOGRAM_ENUMERATION("Media.PipelineStatus.Unsupported",
413 player_info
.last_pipeline_status
,
414 media::PIPELINE_STATUS_MAX
+ 1);
416 // Report whether video decoder fallback happened, but only if a video decoder
418 if (!player_info
.video_decoder
.empty()) {
419 UMA_HISTOGRAM_BOOLEAN("Media.VideoDecoderFallback",
420 player_info
.video_decoder_changed
);
424 void MediaInternals::MediaInternalsUMAHandler::OnProcessTerminated(
425 int render_process_id
) {
426 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
428 auto players_it
= renderer_info_
.find(render_process_id
);
429 if (players_it
== renderer_info_
.end())
431 auto it
= players_it
->second
.begin();
432 while (it
!= players_it
->second
.end()) {
433 ReportUMAForPipelineStatus(it
->second
);
434 players_it
->second
.erase(it
++);
436 renderer_info_
.erase(players_it
);
439 MediaInternals
* MediaInternals::GetInstance() {
440 return g_media_internals
.Pointer();
443 MediaInternals::MediaInternals()
444 : can_update_(false),
446 uma_handler_(new MediaInternalsUMAHandler()) {
447 registrar_
.Add(this, NOTIFICATION_RENDERER_PROCESS_TERMINATED
,
448 NotificationService::AllBrowserContextsAndSources());
451 MediaInternals::~MediaInternals() {}
453 void MediaInternals::Observe(int type
,
454 const NotificationSource
& source
,
455 const NotificationDetails
& details
) {
456 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
457 DCHECK_EQ(type
, NOTIFICATION_RENDERER_PROCESS_TERMINATED
);
458 RenderProcessHost
* process
= Source
<RenderProcessHost
>(source
).ptr();
460 uma_handler_
->OnProcessTerminated(process
->GetID());
461 pending_events_map_
.erase(process
->GetID());
464 // Converts the |event| to a |update|. Returns whether the conversion succeeded.
465 static bool ConvertEventToUpdate(int render_process_id
,
466 const media::MediaLogEvent
& event
,
467 base::string16
* update
) {
470 base::DictionaryValue dict
;
471 dict
.SetInteger("renderer", render_process_id
);
472 dict
.SetInteger("player", event
.id
);
473 dict
.SetString("type", media::MediaLog::EventTypeToString(event
.type
));
475 // TODO(dalecurtis): This is technically not correct. TimeTicks "can't" be
476 // converted to to a human readable time format. See base/time/time.h.
477 const double ticks
= event
.time
.ToInternalValue();
478 const double ticks_millis
= ticks
/ base::Time::kMicrosecondsPerMillisecond
;
479 dict
.SetDouble("ticksMillis", ticks_millis
);
481 // Convert PipelineStatus to human readable string
482 if (event
.type
== media::MediaLogEvent::PIPELINE_ERROR
) {
484 if (!event
.params
.GetInteger("pipeline_error", &status
) ||
485 status
< static_cast<int>(media::PIPELINE_OK
) ||
486 status
> static_cast<int>(media::PIPELINE_STATUS_MAX
)) {
489 media::PipelineStatus error
= static_cast<media::PipelineStatus
>(status
);
490 dict
.SetString("params.pipeline_error",
491 media::MediaLog::PipelineStatusToString(error
));
493 dict
.Set("params", event
.params
.DeepCopy());
496 *update
= SerializeUpdate("media.onMediaEvent", &dict
);
500 void MediaInternals::OnMediaEvents(
501 int render_process_id
, const std::vector
<media::MediaLogEvent
>& events
) {
502 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
503 // Notify observers that |event| has occurred.
504 for (const auto& event
: events
) {
506 base::string16 update
;
507 if (ConvertEventToUpdate(render_process_id
, event
, &update
))
511 SaveEvent(render_process_id
, event
);
512 uma_handler_
->SavePlayerState(render_process_id
, event
);
516 void MediaInternals::AddUpdateCallback(const UpdateCallback
& callback
) {
517 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
518 update_callbacks_
.push_back(callback
);
520 base::AutoLock
auto_lock(lock_
);
524 void MediaInternals::RemoveUpdateCallback(const UpdateCallback
& callback
) {
525 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
526 for (size_t i
= 0; i
< update_callbacks_
.size(); ++i
) {
527 if (update_callbacks_
[i
].Equals(callback
)) {
528 update_callbacks_
.erase(update_callbacks_
.begin() + i
);
533 base::AutoLock
auto_lock(lock_
);
534 can_update_
= !update_callbacks_
.empty();
537 bool MediaInternals::CanUpdate() {
538 base::AutoLock
auto_lock(lock_
);
542 void MediaInternals::SendHistoricalMediaEvents() {
543 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
544 for (const auto& pending_events
: pending_events_map_
) {
545 for (const auto& event
: pending_events
.second
) {
546 base::string16 update
;
547 if (ConvertEventToUpdate(pending_events
.first
, event
, &update
))
551 // Do not clear the map/list here so that refreshing the UI or opening a
552 // second UI still works nicely!
555 void MediaInternals::SendAudioStreamData() {
556 base::string16 audio_stream_update
;
558 base::AutoLock
auto_lock(lock_
);
559 audio_stream_update
= SerializeUpdate(
560 "media.onReceiveAudioStreamData", &audio_streams_cached_data_
);
562 SendUpdate(audio_stream_update
);
565 void MediaInternals::SendVideoCaptureDeviceCapabilities() {
566 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
571 SendUpdate(SerializeUpdate("media.onReceiveVideoCaptureCapabilities",
572 &video_capture_capabilities_cached_data_
));
575 void MediaInternals::UpdateVideoCaptureDeviceCapabilities(
576 const media::VideoCaptureDeviceInfos
& video_capture_device_infos
) {
577 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
578 video_capture_capabilities_cached_data_
.Clear();
580 for (const auto& video_capture_device_info
: video_capture_device_infos
) {
581 base::ListValue
* format_list
= new base::ListValue();
582 for (const auto& format
: video_capture_device_info
.supported_formats
)
583 format_list
->AppendString(media::VideoCaptureFormat::ToString(format
));
585 base::DictionaryValue
* device_dict
= new base::DictionaryValue();
586 device_dict
->SetString("id", video_capture_device_info
.name
.id());
587 device_dict
->SetString(
588 "name", video_capture_device_info
.name
.GetNameAndModel());
589 device_dict
->Set("formats", format_list
);
590 #if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX) || \
592 device_dict
->SetString(
593 "captureApi", video_capture_device_info
.name
.GetCaptureApiTypeString());
595 video_capture_capabilities_cached_data_
.Append(device_dict
);
598 SendVideoCaptureDeviceCapabilities();
601 scoped_ptr
<media::AudioLog
> MediaInternals::CreateAudioLog(
602 AudioComponent component
) {
603 base::AutoLock
auto_lock(lock_
);
604 return scoped_ptr
<media::AudioLog
>(new AudioLogImpl(
605 owner_ids_
[component
]++, component
, this));
608 void MediaInternals::SetWebContentsTitleForAudioLogEntry(
610 int render_process_id
,
612 media::AudioLog
* audio_log
) {
613 static_cast<AudioLogImpl
*>(audio_log
)
614 ->SendWebContentsTitle(component_id
, render_process_id
, render_frame_id
);
617 void MediaInternals::SendUpdate(const base::string16
& update
) {
618 // SendUpdate() may be called from any thread, but must run on the UI thread.
619 if (!BrowserThread::CurrentlyOn(BrowserThread::UI
)) {
620 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
, base::Bind(
621 &MediaInternals::SendUpdate
, base::Unretained(this), update
));
625 for (size_t i
= 0; i
< update_callbacks_
.size(); i
++)
626 update_callbacks_
[i
].Run(update
);
629 void MediaInternals::SaveEvent(int process_id
,
630 const media::MediaLogEvent
& event
) {
631 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
633 // Max number of saved updates allowed for one process.
634 const size_t kMaxNumEvents
= 128;
636 // Do not save instantaneous events that happen frequently and have little
637 // value in the future.
638 if (event
.type
== media::MediaLogEvent::NETWORK_ACTIVITY_SET
||
639 event
.type
== media::MediaLogEvent::BUFFERED_EXTENTS_CHANGED
) {
643 auto& pending_events
= pending_events_map_
[process_id
];
644 // TODO(xhwang): Notify user that some old logs could have been truncated.
645 // See http://crbug.com/498520
646 if (pending_events
.size() >= kMaxNumEvents
)
647 pending_events
.pop_front();
648 pending_events
.push_back(event
);
651 void MediaInternals::UpdateAudioLog(AudioLogUpdateType type
,
652 const std::string
& cache_key
,
653 const std::string
& function
,
654 const base::DictionaryValue
* value
) {
656 base::AutoLock
auto_lock(lock_
);
657 const bool has_entry
= audio_streams_cached_data_
.HasKey(cache_key
);
658 if ((type
== UPDATE_IF_EXISTS
|| type
== UPDATE_AND_DELETE
) && !has_entry
) {
660 } else if (!has_entry
) {
661 DCHECK_EQ(type
, CREATE
);
662 audio_streams_cached_data_
.Set(cache_key
, value
->DeepCopy());
663 } else if (type
== UPDATE_AND_DELETE
) {
664 scoped_ptr
<base::Value
> out_value
;
665 CHECK(audio_streams_cached_data_
.Remove(cache_key
, &out_value
));
667 base::DictionaryValue
* existing_dict
= NULL
;
669 audio_streams_cached_data_
.GetDictionary(cache_key
, &existing_dict
));
670 existing_dict
->MergeDictionary(value
);
675 SendUpdate(SerializeUpdate(function
, value
));
678 } // namespace content