Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / content / renderer / media / peer_connection_tracker.cc
blob753706b0dffdf65d2de9755d0ab646521c888382
1 // Copyright (c) 2013 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 #include "content/renderer/media/peer_connection_tracker.h"
6 #include "base/strings/string_number_conversions.h"
7 #include "base/strings/utf_string_conversions.h"
8 #include "base/thread_task_runner_handle.h"
9 #include "content/common/media/peer_connection_tracker_messages.h"
10 #include "content/renderer/media/rtc_media_constraints.h"
11 #include "content/renderer/media/rtc_peer_connection_handler.h"
12 #include "content/renderer/render_thread_impl.h"
13 #include "third_party/WebKit/public/platform/WebMediaConstraints.h"
14 #include "third_party/WebKit/public/platform/WebMediaStream.h"
15 #include "third_party/WebKit/public/platform/WebMediaStreamSource.h"
16 #include "third_party/WebKit/public/platform/WebMediaStreamTrack.h"
17 #include "third_party/WebKit/public/platform/WebRTCICECandidate.h"
18 #include "third_party/WebKit/public/platform/WebRTCPeerConnectionHandlerClient.h"
19 #include "third_party/WebKit/public/web/WebDocument.h"
20 #include "third_party/WebKit/public/web/WebFrame.h"
21 #include "third_party/WebKit/public/web/WebUserMediaRequest.h"
23 using std::string;
24 using webrtc::MediaConstraintsInterface;
25 using webrtc::StatsReport;
26 using webrtc::StatsReports;
27 using blink::WebRTCPeerConnectionHandlerClient;
29 namespace content {
31 static string SerializeServers(
32 const std::vector<webrtc::PeerConnectionInterface::IceServer>& servers) {
33 string result = "[";
34 for (size_t i = 0; i < servers.size(); ++i) {
35 result += servers[i].uri;
36 if (i != servers.size() - 1)
37 result += ", ";
39 result += "]";
40 return result;
43 static RTCMediaConstraints GetNativeMediaConstraints(
44 const blink::WebMediaConstraints& constraints) {
45 RTCMediaConstraints native_constraints;
47 if (constraints.isNull())
48 return native_constraints;
50 blink::WebVector<blink::WebMediaConstraint> mandatory;
51 constraints.getMandatoryConstraints(mandatory);
52 for (size_t i = 0; i < mandatory.size(); ++i) {
53 native_constraints.AddMandatory(
54 mandatory[i].m_name.utf8(), mandatory[i].m_value.utf8(), false);
57 blink::WebVector<blink::WebMediaConstraint> optional;
58 constraints.getOptionalConstraints(optional);
59 for (size_t i = 0; i < optional.size(); ++i) {
60 native_constraints.AddOptional(
61 optional[i].m_name.utf8(), optional[i].m_value.utf8(), false);
63 return native_constraints;
66 static string SerializeMediaConstraints(
67 const RTCMediaConstraints& constraints) {
68 string result;
69 MediaConstraintsInterface::Constraints mandatory = constraints.GetMandatory();
70 if (!mandatory.empty()) {
71 result += "mandatory: {";
72 for (size_t i = 0; i < mandatory.size(); ++i) {
73 result += mandatory[i].key + ":" + mandatory[i].value;
74 if (i != mandatory.size() - 1)
75 result += ", ";
77 result += "}";
79 MediaConstraintsInterface::Constraints optional = constraints.GetOptional();
80 if (!optional.empty()) {
81 if (!result.empty())
82 result += ", ";
83 result += "optional: {";
84 for (size_t i = 0; i < optional.size(); ++i) {
85 result += optional[i].key + ":" + optional[i].value;
86 if (i != optional.size() - 1)
87 result += ", ";
89 result += "}";
91 return result;
94 static string SerializeMediaStreamComponent(
95 const blink::WebMediaStreamTrack component) {
96 string id = base::UTF16ToUTF8(base::StringPiece16(component.source().id()));
97 return id;
100 static string SerializeMediaDescriptor(
101 const blink::WebMediaStream& stream) {
102 string label = base::UTF16ToUTF8(base::StringPiece16(stream.id()));
103 string result = "label: " + label;
104 blink::WebVector<blink::WebMediaStreamTrack> tracks;
105 stream.audioTracks(tracks);
106 if (!tracks.isEmpty()) {
107 result += ", audio: [";
108 for (size_t i = 0; i < tracks.size(); ++i) {
109 result += SerializeMediaStreamComponent(tracks[i]);
110 if (i != tracks.size() - 1)
111 result += ", ";
113 result += "]";
115 stream.videoTracks(tracks);
116 if (!tracks.isEmpty()) {
117 result += ", video: [";
118 for (size_t i = 0; i < tracks.size(); ++i) {
119 result += SerializeMediaStreamComponent(tracks[i]);
120 if (i != tracks.size() - 1)
121 result += ", ";
123 result += "]";
125 return result;
128 static std::string SerializeIceTransportType(
129 webrtc::PeerConnectionInterface::IceTransportsType type) {
130 string transport_type;
131 switch (type) {
132 case webrtc::PeerConnectionInterface::kNone:
133 transport_type = "none";
134 break;
135 case webrtc::PeerConnectionInterface::kRelay:
136 transport_type = "relay";
137 break;
138 case webrtc::PeerConnectionInterface::kAll:
139 transport_type = "all";
140 break;
141 case webrtc::PeerConnectionInterface::kNoHost:
142 transport_type = "noHost";
143 break;
144 default:
145 NOTREACHED();
147 return transport_type;
150 static std::string SerializeBundlePolicy(
151 webrtc::PeerConnectionInterface::BundlePolicy policy) {
152 string policy_str;
153 switch (policy) {
154 case webrtc::PeerConnectionInterface::kBundlePolicyBalanced:
155 policy_str = "balanced";
156 break;
157 case webrtc::PeerConnectionInterface::kBundlePolicyMaxBundle:
158 policy_str = "max-bundle";
159 break;
160 case webrtc::PeerConnectionInterface::kBundlePolicyMaxCompat:
161 policy_str = "max-compat";
162 break;
163 default:
164 NOTREACHED();
166 return policy_str;
169 static std::string SerializeRtcpMuxPolicy(
170 webrtc::PeerConnectionInterface::RtcpMuxPolicy policy) {
171 string policy_str;
172 switch (policy) {
173 case webrtc::PeerConnectionInterface::kRtcpMuxPolicyNegotiate:
174 policy_str = "negotiate";
175 break;
176 case webrtc::PeerConnectionInterface::kRtcpMuxPolicyRequire:
177 policy_str = "require";
178 break;
179 default:
180 NOTREACHED();
182 return policy_str;
185 #define GET_STRING_OF_STATE(state) \
186 case WebRTCPeerConnectionHandlerClient::state: \
187 result = #state; \
188 break;
190 static string GetSignalingStateString(
191 WebRTCPeerConnectionHandlerClient::SignalingState state) {
192 string result;
193 switch (state) {
194 GET_STRING_OF_STATE(SignalingStateStable)
195 GET_STRING_OF_STATE(SignalingStateHaveLocalOffer)
196 GET_STRING_OF_STATE(SignalingStateHaveRemoteOffer)
197 GET_STRING_OF_STATE(SignalingStateHaveLocalPrAnswer)
198 GET_STRING_OF_STATE(SignalingStateHaveRemotePrAnswer)
199 GET_STRING_OF_STATE(SignalingStateClosed)
200 default:
201 NOTREACHED();
202 break;
204 return result;
207 static string GetIceConnectionStateString(
208 WebRTCPeerConnectionHandlerClient::ICEConnectionState state) {
209 string result;
210 switch (state) {
211 GET_STRING_OF_STATE(ICEConnectionStateStarting)
212 GET_STRING_OF_STATE(ICEConnectionStateChecking)
213 GET_STRING_OF_STATE(ICEConnectionStateConnected)
214 GET_STRING_OF_STATE(ICEConnectionStateCompleted)
215 GET_STRING_OF_STATE(ICEConnectionStateFailed)
216 GET_STRING_OF_STATE(ICEConnectionStateDisconnected)
217 GET_STRING_OF_STATE(ICEConnectionStateClosed)
218 default:
219 NOTREACHED();
220 break;
222 return result;
225 static string GetIceGatheringStateString(
226 WebRTCPeerConnectionHandlerClient::ICEGatheringState state) {
227 string result;
228 switch (state) {
229 GET_STRING_OF_STATE(ICEGatheringStateNew)
230 GET_STRING_OF_STATE(ICEGatheringStateGathering)
231 GET_STRING_OF_STATE(ICEGatheringStateComplete)
232 default:
233 NOTREACHED();
234 break;
236 return result;
239 // Builds a DictionaryValue from the StatsReport.
240 // The caller takes the ownership of the returned value.
241 // Note:
242 // The format must be consistent with what webrtc_internals.js expects.
243 // If you change it here, you must change webrtc_internals.js as well.
244 static base::DictionaryValue* GetDictValueStats(const StatsReport& report) {
245 if (report.values().empty())
246 return NULL;
248 base::DictionaryValue* dict = new base::DictionaryValue();
249 dict->SetDouble("timestamp", report.timestamp());
251 base::ListValue* values = new base::ListValue();
252 dict->Set("values", values);
254 for (const auto& v : report.values()) {
255 const StatsReport::ValuePtr& value = v.second;
256 values->AppendString(value->display_name());
257 switch (value->type()) {
258 case StatsReport::Value::kInt:
259 values->AppendInteger(value->int_val());
260 break;
261 case StatsReport::Value::kFloat:
262 values->AppendDouble(value->float_val());
263 break;
264 case StatsReport::Value::kString:
265 values->AppendString(value->string_val());
266 break;
267 case StatsReport::Value::kStaticString:
268 values->AppendString(value->static_string_val());
269 break;
270 case StatsReport::Value::kBool:
271 values->AppendBoolean(value->bool_val());
272 break;
273 case StatsReport::Value::kInt64: // int64 isn't supported, so use string.
274 case StatsReport::Value::kId:
275 default:
276 values->AppendString(value->ToString());
277 break;
281 return dict;
284 // Builds a DictionaryValue from the StatsReport.
285 // The caller takes the ownership of the returned value.
286 static base::DictionaryValue* GetDictValue(const StatsReport& report) {
287 scoped_ptr<base::DictionaryValue> stats, result;
289 stats.reset(GetDictValueStats(report));
290 if (!stats)
291 return NULL;
293 result.reset(new base::DictionaryValue());
294 // Note:
295 // The format must be consistent with what webrtc_internals.js expects.
296 // If you change it here, you must change webrtc_internals.js as well.
297 result->Set("stats", stats.release());
298 result->SetString("id", report.id()->ToString());
299 result->SetString("type", report.TypeToString());
301 return result.release();
304 class InternalStatsObserver : public webrtc::StatsObserver {
305 public:
306 InternalStatsObserver(int lid)
307 : lid_(lid), main_thread_(base::ThreadTaskRunnerHandle::Get()) {}
309 void OnComplete(const StatsReports& reports) override {
310 scoped_ptr<base::ListValue> list(new base::ListValue());
312 for (const auto* r : reports) {
313 base::DictionaryValue* report = GetDictValue(*r);
314 if (report)
315 list->Append(report);
318 if (!list->empty()) {
319 main_thread_->PostTask(FROM_HERE,
320 base::Bind(&InternalStatsObserver::OnCompleteImpl,
321 base::Passed(&list), lid_));
325 protected:
326 ~InternalStatsObserver() override {
327 // Will be destructed on libjingle's signaling thread.
328 // The signaling thread is where libjingle's objects live and from where
329 // libjingle makes callbacks. This may or may not be the same thread as
330 // the main thread.
333 private:
334 // Static since |this| will most likely have been deleted by the time we
335 // get here.
336 static void OnCompleteImpl(scoped_ptr<base::ListValue> list, int lid) {
337 DCHECK(!list->empty());
338 RenderThreadImpl::current()->Send(
339 new PeerConnectionTrackerHost_AddStats(lid, *list.get()));
342 const int lid_;
343 const scoped_refptr<base::SingleThreadTaskRunner> main_thread_;
346 PeerConnectionTracker::PeerConnectionTracker() : next_lid_(1) {
349 PeerConnectionTracker::~PeerConnectionTracker() {
352 bool PeerConnectionTracker::OnControlMessageReceived(
353 const IPC::Message& message) {
354 bool handled = true;
355 IPC_BEGIN_MESSAGE_MAP(PeerConnectionTracker, message)
356 IPC_MESSAGE_HANDLER(PeerConnectionTracker_GetAllStats, OnGetAllStats)
357 IPC_MESSAGE_HANDLER(PeerConnectionTracker_OnSuspend, OnSuspend)
358 IPC_MESSAGE_UNHANDLED(handled = false)
359 IPC_END_MESSAGE_MAP()
360 return handled;
363 void PeerConnectionTracker::OnGetAllStats() {
364 DCHECK(main_thread_.CalledOnValidThread());
366 const std::string empty_track_id;
367 for (PeerConnectionIdMap::iterator it = peer_connection_id_map_.begin();
368 it != peer_connection_id_map_.end(); ++it) {
369 rtc::scoped_refptr<InternalStatsObserver> observer(
370 new rtc::RefCountedObject<InternalStatsObserver>(it->second));
372 // The last type parameter is ignored when the track id is empty.
373 it->first->GetStats(
374 observer,
375 webrtc::PeerConnectionInterface::kStatsOutputLevelDebug,
376 empty_track_id, blink::WebMediaStreamSource::TypeAudio);
380 void PeerConnectionTracker::OnSuspend() {
381 DCHECK(main_thread_.CalledOnValidThread());
382 for (PeerConnectionIdMap::iterator it = peer_connection_id_map_.begin();
383 it != peer_connection_id_map_.end(); ++it) {
384 it->first->CloseClientPeerConnection();
388 void PeerConnectionTracker::RegisterPeerConnection(
389 RTCPeerConnectionHandler* pc_handler,
390 const webrtc::PeerConnectionInterface::RTCConfiguration& config,
391 const RTCMediaConstraints& constraints,
392 const blink::WebFrame* frame) {
393 DCHECK(main_thread_.CalledOnValidThread());
394 DVLOG(1) << "PeerConnectionTracker::RegisterPeerConnection()";
395 PeerConnectionInfo info;
397 info.lid = GetNextLocalID();
398 info.rtc_configuration =
399 "{ servers: " + SerializeServers(config.servers) + ", " +
400 "iceTransportType: " + SerializeIceTransportType(config.type) + ", " +
401 "bundlePolicy: " + SerializeBundlePolicy(config.bundle_policy) + ", " +
402 "rtcpMuxPolicy: " + SerializeRtcpMuxPolicy(config.rtcp_mux_policy) + " }";
404 info.constraints = SerializeMediaConstraints(constraints);
405 info.url = frame->document().url().spec();
406 RenderThreadImpl::current()->Send(
407 new PeerConnectionTrackerHost_AddPeerConnection(info));
409 DCHECK(peer_connection_id_map_.find(pc_handler) ==
410 peer_connection_id_map_.end());
411 peer_connection_id_map_[pc_handler] = info.lid;
414 void PeerConnectionTracker::UnregisterPeerConnection(
415 RTCPeerConnectionHandler* pc_handler) {
416 DCHECK(main_thread_.CalledOnValidThread());
417 DVLOG(1) << "PeerConnectionTracker::UnregisterPeerConnection()";
419 std::map<RTCPeerConnectionHandler*, int>::iterator it =
420 peer_connection_id_map_.find(pc_handler);
422 if (it == peer_connection_id_map_.end()) {
423 // The PeerConnection might not have been registered if its initilization
424 // failed.
425 return;
428 RenderThreadImpl::current()->Send(
429 new PeerConnectionTrackerHost_RemovePeerConnection(it->second));
431 peer_connection_id_map_.erase(it);
434 void PeerConnectionTracker::TrackCreateOffer(
435 RTCPeerConnectionHandler* pc_handler,
436 const RTCMediaConstraints& constraints) {
437 DCHECK(main_thread_.CalledOnValidThread());
438 SendPeerConnectionUpdate(
439 pc_handler, "createOffer",
440 "constraints: {" + SerializeMediaConstraints(constraints) + "}");
443 void PeerConnectionTracker::TrackCreateAnswer(
444 RTCPeerConnectionHandler* pc_handler,
445 const RTCMediaConstraints& constraints) {
446 DCHECK(main_thread_.CalledOnValidThread());
447 SendPeerConnectionUpdate(
448 pc_handler, "createAnswer",
449 "constraints: {" + SerializeMediaConstraints(constraints) + "}");
452 void PeerConnectionTracker::TrackSetSessionDescription(
453 RTCPeerConnectionHandler* pc_handler,
454 const std::string& sdp, const std::string& type, Source source) {
455 DCHECK(main_thread_.CalledOnValidThread());
456 string value = "type: " + type + ", sdp: " + sdp;
457 SendPeerConnectionUpdate(
458 pc_handler,
459 source == SOURCE_LOCAL ? "setLocalDescription" : "setRemoteDescription",
460 value);
463 void PeerConnectionTracker::TrackUpdateIce(
464 RTCPeerConnectionHandler* pc_handler,
465 const webrtc::PeerConnectionInterface::RTCConfiguration& config,
466 const RTCMediaConstraints& options) {
467 DCHECK(main_thread_.CalledOnValidThread());
468 string servers_string = "servers: " + SerializeServers(config.servers);
470 string transport_type =
471 "iceTransportType: " + SerializeIceTransportType(config.type);
473 string bundle_policy =
474 "bundlePolicy: " + SerializeBundlePolicy(config.bundle_policy);
476 string rtcp_mux_policy =
477 "rtcpMuxPolicy: " + SerializeRtcpMuxPolicy(config.rtcp_mux_policy);
479 string constraints =
480 "constraints: {" + SerializeMediaConstraints(options) + "}";
482 SendPeerConnectionUpdate(
483 pc_handler,
484 "updateIce",
485 servers_string + ", " + transport_type + ", " +
486 bundle_policy + ", " + rtcp_mux_policy + ", " +
487 constraints);
490 void PeerConnectionTracker::TrackAddIceCandidate(
491 RTCPeerConnectionHandler* pc_handler,
492 const blink::WebRTCICECandidate& candidate,
493 Source source,
494 bool succeeded) {
495 DCHECK(main_thread_.CalledOnValidThread());
496 string value =
497 "sdpMid: " +
498 base::UTF16ToUTF8(base::StringPiece16(candidate.sdpMid())) + ", " +
499 "sdpMLineIndex: " + base::IntToString(candidate.sdpMLineIndex()) +
500 ", " + "candidate: " +
501 base::UTF16ToUTF8(base::StringPiece16(candidate.candidate()));
503 // OnIceCandidate always succeeds as it's a callback from the browser.
504 DCHECK(source != SOURCE_LOCAL || succeeded);
506 string event =
507 (source == SOURCE_LOCAL) ? "onIceCandidate"
508 : (succeeded ? "addIceCandidate"
509 : "addIceCandidateFailed");
511 SendPeerConnectionUpdate(pc_handler, event, value);
514 void PeerConnectionTracker::TrackAddStream(
515 RTCPeerConnectionHandler* pc_handler,
516 const blink::WebMediaStream& stream,
517 Source source) {
518 DCHECK(main_thread_.CalledOnValidThread());
519 SendPeerConnectionUpdate(
520 pc_handler, source == SOURCE_LOCAL ? "addStream" : "onAddStream",
521 SerializeMediaDescriptor(stream));
524 void PeerConnectionTracker::TrackRemoveStream(
525 RTCPeerConnectionHandler* pc_handler,
526 const blink::WebMediaStream& stream,
527 Source source){
528 DCHECK(main_thread_.CalledOnValidThread());
529 SendPeerConnectionUpdate(
530 pc_handler, source == SOURCE_LOCAL ? "removeStream" : "onRemoveStream",
531 SerializeMediaDescriptor(stream));
534 void PeerConnectionTracker::TrackCreateDataChannel(
535 RTCPeerConnectionHandler* pc_handler,
536 const webrtc::DataChannelInterface* data_channel,
537 Source source) {
538 DCHECK(main_thread_.CalledOnValidThread());
539 string value = "label: " + data_channel->label() +
540 ", reliable: " + (data_channel->reliable() ? "true" : "false");
541 SendPeerConnectionUpdate(
542 pc_handler,
543 source == SOURCE_LOCAL ? "createLocalDataChannel" : "onRemoteDataChannel",
544 value);
547 void PeerConnectionTracker::TrackStop(RTCPeerConnectionHandler* pc_handler) {
548 DCHECK(main_thread_.CalledOnValidThread());
549 SendPeerConnectionUpdate(pc_handler, "stop", std::string());
552 void PeerConnectionTracker::TrackSignalingStateChange(
553 RTCPeerConnectionHandler* pc_handler,
554 WebRTCPeerConnectionHandlerClient::SignalingState state) {
555 DCHECK(main_thread_.CalledOnValidThread());
556 SendPeerConnectionUpdate(
557 pc_handler, "signalingStateChange", GetSignalingStateString(state));
560 void PeerConnectionTracker::TrackIceConnectionStateChange(
561 RTCPeerConnectionHandler* pc_handler,
562 WebRTCPeerConnectionHandlerClient::ICEConnectionState state) {
563 DCHECK(main_thread_.CalledOnValidThread());
564 SendPeerConnectionUpdate(
565 pc_handler, "iceConnectionStateChange",
566 GetIceConnectionStateString(state));
569 void PeerConnectionTracker::TrackIceGatheringStateChange(
570 RTCPeerConnectionHandler* pc_handler,
571 WebRTCPeerConnectionHandlerClient::ICEGatheringState state) {
572 DCHECK(main_thread_.CalledOnValidThread());
573 SendPeerConnectionUpdate(
574 pc_handler, "iceGatheringStateChange",
575 GetIceGatheringStateString(state));
578 void PeerConnectionTracker::TrackSessionDescriptionCallback(
579 RTCPeerConnectionHandler* pc_handler, Action action,
580 const string& callback_type, const string& value) {
581 DCHECK(main_thread_.CalledOnValidThread());
582 string update_type;
583 switch (action) {
584 case ACTION_SET_LOCAL_DESCRIPTION:
585 update_type = "setLocalDescription";
586 break;
587 case ACTION_SET_REMOTE_DESCRIPTION:
588 update_type = "setRemoteDescription";
589 break;
590 case ACTION_CREATE_OFFER:
591 update_type = "createOffer";
592 break;
593 case ACTION_CREATE_ANSWER:
594 update_type = "createAnswer";
595 break;
596 default:
597 NOTREACHED();
598 break;
600 update_type += callback_type;
602 SendPeerConnectionUpdate(pc_handler, update_type, value);
605 void PeerConnectionTracker::TrackOnRenegotiationNeeded(
606 RTCPeerConnectionHandler* pc_handler) {
607 DCHECK(main_thread_.CalledOnValidThread());
608 SendPeerConnectionUpdate(pc_handler, "onRenegotiationNeeded", std::string());
611 void PeerConnectionTracker::TrackCreateDTMFSender(
612 RTCPeerConnectionHandler* pc_handler,
613 const blink::WebMediaStreamTrack& track) {
614 DCHECK(main_thread_.CalledOnValidThread());
615 SendPeerConnectionUpdate(pc_handler, "createDTMFSender",
616 base::UTF16ToUTF8(base::StringPiece16(track.id())));
619 void PeerConnectionTracker::TrackGetUserMedia(
620 const blink::WebUserMediaRequest& user_media_request) {
621 DCHECK(main_thread_.CalledOnValidThread());
622 RTCMediaConstraints audio_constraints(
623 GetNativeMediaConstraints(user_media_request.audioConstraints()));
624 RTCMediaConstraints video_constraints(
625 GetNativeMediaConstraints(user_media_request.videoConstraints()));
627 RenderThreadImpl::current()->Send(new PeerConnectionTrackerHost_GetUserMedia(
628 user_media_request.securityOrigin().toString().utf8(),
629 user_media_request.audio(),
630 user_media_request.video(),
631 SerializeMediaConstraints(audio_constraints),
632 SerializeMediaConstraints(video_constraints)));
635 int PeerConnectionTracker::GetNextLocalID() {
636 DCHECK(main_thread_.CalledOnValidThread());
637 return next_lid_++;
640 void PeerConnectionTracker::SendPeerConnectionUpdate(
641 RTCPeerConnectionHandler* pc_handler,
642 const std::string& type,
643 const std::string& value) {
644 DCHECK(main_thread_.CalledOnValidThread());
645 if (peer_connection_id_map_.find(pc_handler) == peer_connection_id_map_.end())
646 return;
648 RenderThreadImpl::current()->Send(
649 new PeerConnectionTrackerHost_UpdatePeerConnection(
650 peer_connection_id_map_[pc_handler], type, value));
653 } // namespace content