Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(4)

Side by Side Diff: talk/session/media/channel.cc

Issue 1362913004: Remove unused SignalMediaError and infrastructure. (Closed) Base URL: https://chromium.googlesource.com/external/webrtc.git@master
Patch Set: more Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 /* 1 /*
2 * libjingle 2 * libjingle
3 * Copyright 2004 Google Inc. 3 * Copyright 2004 Google Inc.
4 * 4 *
5 * Redistribution and use in source and binary forms, with or without 5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met: 6 * modification, are permitted provided that the following conditions are met:
7 * 7 *
8 * 1. Redistributions of source code must retain the above copyright notice, 8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer. 9 * this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright notice, 10 * 2. Redistributions in binary form must reproduce the above copyright notice,
(...skipping 1283 matching lines...) Expand 10 before | Expand all | Expand 10 after
1294 StopMediaMonitor(); 1294 StopMediaMonitor();
1295 // this can't be done in the base class, since it calls a virtual 1295 // this can't be done in the base class, since it calls a virtual
1296 DisableMedia_w(); 1296 DisableMedia_w();
1297 Deinit(); 1297 Deinit();
1298 } 1298 }
1299 1299
1300 bool VoiceChannel::Init() { 1300 bool VoiceChannel::Init() {
1301 if (!BaseChannel::Init()) { 1301 if (!BaseChannel::Init()) {
1302 return false; 1302 return false;
1303 } 1303 }
1304 media_channel()->SignalMediaError.connect(
1305 this, &VoiceChannel::OnVoiceChannelError);
1306 srtp_filter()->SignalSrtpError.connect(
1307 this, &VoiceChannel::OnSrtpError);
1308 return true; 1304 return true;
1309 } 1305 }
1310 1306
1311 bool VoiceChannel::SetRemoteRenderer(uint32 ssrc, AudioRenderer* renderer) { 1307 bool VoiceChannel::SetRemoteRenderer(uint32 ssrc, AudioRenderer* renderer) {
1312 return InvokeOnWorker(Bind(&VoiceMediaChannel::SetRemoteRenderer, 1308 return InvokeOnWorker(Bind(&VoiceMediaChannel::SetRemoteRenderer,
1313 media_channel(), ssrc, renderer)); 1309 media_channel(), ssrc, renderer));
1314 } 1310 }
1315 1311
1316 bool VoiceChannel::SetAudioSend(uint32 ssrc, 1312 bool VoiceChannel::SetAudioSend(uint32 ssrc,
1317 bool mute, 1313 bool mute,
(...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after
1423 // media, this will disable the timeout. 1419 // media, this will disable the timeout.
1424 if (!received_media_ && !PacketIsRtcp(channel, data, len)) { 1420 if (!received_media_ && !PacketIsRtcp(channel, data, len)) {
1425 received_media_ = true; 1421 received_media_ = true;
1426 } 1422 }
1427 } 1423 }
1428 1424
1429 void VoiceChannel::ChangeState() { 1425 void VoiceChannel::ChangeState() {
1430 // Render incoming data if we're the active call, and we have the local 1426 // Render incoming data if we're the active call, and we have the local
1431 // content. We receive data on the default channel and multiplexed streams. 1427 // content. We receive data on the default channel and multiplexed streams.
1432 bool recv = IsReadyToReceive(); 1428 bool recv = IsReadyToReceive();
1433 if (!media_channel()->SetPlayout(recv)) { 1429 media_channel()->SetPlayout(recv);
1434 SendLastMediaError();
1435 }
1436 1430
1437 // Send outgoing data if we're the active call, we have the remote content, 1431 // Send outgoing data if we're the active call, we have the remote content,
1438 // and we have had some form of connectivity. 1432 // and we have had some form of connectivity.
1439 bool send = IsReadyToSend(); 1433 bool send = IsReadyToSend();
1440 SendFlags send_flag = send ? SEND_MICROPHONE : SEND_NOTHING; 1434 SendFlags send_flag = send ? SEND_MICROPHONE : SEND_NOTHING;
1441 if (!media_channel()->SetSend(send_flag)) { 1435 if (!media_channel()->SetSend(send_flag)) {
1442 LOG(LS_ERROR) << "Failed to SetSend " << send_flag << " on voice channel"; 1436 LOG(LS_ERROR) << "Failed to SetSend " << send_flag << " on voice channel";
1443 SendLastMediaError();
1444 } 1437 }
1445 1438
1446 LOG(LS_INFO) << "Changing voice state, recv=" << recv << " send=" << send; 1439 LOG(LS_INFO) << "Changing voice state, recv=" << recv << " send=" << send;
1447 } 1440 }
1448 1441
1449 const ContentInfo* VoiceChannel::GetFirstContent( 1442 const ContentInfo* VoiceChannel::GetFirstContent(
1450 const SessionDescription* sdesc) { 1443 const SessionDescription* sdesc) {
1451 return GetFirstAudioContent(sdesc); 1444 return GetFirstAudioContent(sdesc);
1452 } 1445 }
1453 1446
(...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after
1564 } 1557 }
1565 1558
1566 void VoiceChannel::OnMessage(rtc::Message *pmsg) { 1559 void VoiceChannel::OnMessage(rtc::Message *pmsg) {
1567 switch (pmsg->message_id) { 1560 switch (pmsg->message_id) {
1568 case MSG_EARLYMEDIATIMEOUT: 1561 case MSG_EARLYMEDIATIMEOUT:
1569 HandleEarlyMediaTimeout(); 1562 HandleEarlyMediaTimeout();
1570 break; 1563 break;
1571 case MSG_CHANNEL_ERROR: { 1564 case MSG_CHANNEL_ERROR: {
1572 VoiceChannelErrorMessageData* data = 1565 VoiceChannelErrorMessageData* data =
1573 static_cast<VoiceChannelErrorMessageData*>(pmsg->pdata); 1566 static_cast<VoiceChannelErrorMessageData*>(pmsg->pdata);
1574 SignalMediaError(this, data->ssrc, data->error);
1575 delete data; 1567 delete data;
1576 break; 1568 break;
1577 } 1569 }
1578 default: 1570 default:
1579 BaseChannel::OnMessage(pmsg); 1571 BaseChannel::OnMessage(pmsg);
1580 break; 1572 break;
1581 } 1573 }
1582 } 1574 }
1583 1575
1584 void VoiceChannel::OnConnectionMonitorUpdate( 1576 void VoiceChannel::OnConnectionMonitorUpdate(
1585 ConnectionMonitor* monitor, const std::vector<ConnectionInfo>& infos) { 1577 ConnectionMonitor* monitor, const std::vector<ConnectionInfo>& infos) {
1586 SignalConnectionMonitor(this, infos); 1578 SignalConnectionMonitor(this, infos);
1587 } 1579 }
1588 1580
1589 void VoiceChannel::OnMediaMonitorUpdate( 1581 void VoiceChannel::OnMediaMonitorUpdate(
1590 VoiceMediaChannel* media_channel, const VoiceMediaInfo& info) { 1582 VoiceMediaChannel* media_channel, const VoiceMediaInfo& info) {
1591 ASSERT(media_channel == this->media_channel()); 1583 ASSERT(media_channel == this->media_channel());
1592 SignalMediaMonitor(this, info); 1584 SignalMediaMonitor(this, info);
1593 } 1585 }
1594 1586
1595 void VoiceChannel::OnAudioMonitorUpdate(AudioMonitor* monitor, 1587 void VoiceChannel::OnAudioMonitorUpdate(AudioMonitor* monitor,
1596 const AudioInfo& info) { 1588 const AudioInfo& info) {
1597 SignalAudioMonitor(this, info); 1589 SignalAudioMonitor(this, info);
1598 } 1590 }
1599 1591
1600 void VoiceChannel::OnVoiceChannelError(
1601 uint32 ssrc, VoiceMediaChannel::Error err) {
1602 VoiceChannelErrorMessageData* data = new VoiceChannelErrorMessageData(
1603 ssrc, err);
1604 signaling_thread()->Post(this, MSG_CHANNEL_ERROR, data);
1605 }
1606
1607 void VoiceChannel::OnSrtpError(uint32 ssrc, SrtpFilter::Mode mode,
1608 SrtpFilter::Error error) {
1609 switch (error) {
1610 case SrtpFilter::ERROR_FAIL:
1611 OnVoiceChannelError(ssrc, (mode == SrtpFilter::PROTECT) ?
1612 VoiceMediaChannel::ERROR_REC_SRTP_ERROR :
1613 VoiceMediaChannel::ERROR_PLAY_SRTP_ERROR);
1614 break;
1615 case SrtpFilter::ERROR_AUTH:
1616 OnVoiceChannelError(ssrc, (mode == SrtpFilter::PROTECT) ?
1617 VoiceMediaChannel::ERROR_REC_SRTP_AUTH_FAILED :
1618 VoiceMediaChannel::ERROR_PLAY_SRTP_AUTH_FAILED);
1619 break;
1620 case SrtpFilter::ERROR_REPLAY:
1621 // Only receving channel should have this error.
1622 ASSERT(mode == SrtpFilter::UNPROTECT);
1623 OnVoiceChannelError(ssrc, VoiceMediaChannel::ERROR_PLAY_SRTP_REPLAY);
1624 break;
1625 default:
1626 break;
1627 }
1628 }
1629
1630 void VoiceChannel::GetSrtpCiphers(std::vector<std::string>* ciphers) const { 1592 void VoiceChannel::GetSrtpCiphers(std::vector<std::string>* ciphers) const {
1631 GetSupportedAudioCryptoSuites(ciphers); 1593 GetSupportedAudioCryptoSuites(ciphers);
1632 } 1594 }
1633 1595
1634 VideoChannel::VideoChannel(rtc::Thread* thread, 1596 VideoChannel::VideoChannel(rtc::Thread* thread,
1635 VideoMediaChannel* media_channel, 1597 VideoMediaChannel* media_channel,
1636 TransportController* transport_controller, 1598 TransportController* transport_controller,
1637 const std::string& content_name, 1599 const std::string& content_name,
1638 bool rtcp) 1600 bool rtcp)
1639 : BaseChannel(thread, 1601 : BaseChannel(thread,
1640 media_channel, 1602 media_channel,
1641 transport_controller, 1603 transport_controller,
1642 content_name, 1604 content_name,
1643 rtcp), 1605 rtcp),
1644 renderer_(NULL), 1606 renderer_(NULL),
1645 previous_we_(rtc::WE_CLOSE) {} 1607 previous_we_(rtc::WE_CLOSE) {}
1646 1608
1647 bool VideoChannel::Init() { 1609 bool VideoChannel::Init() {
1648 if (!BaseChannel::Init()) { 1610 if (!BaseChannel::Init()) {
1649 return false; 1611 return false;
1650 } 1612 }
1651 media_channel()->SignalMediaError.connect(
1652 this, &VideoChannel::OnVideoChannelError);
1653 srtp_filter()->SignalSrtpError.connect(
1654 this, &VideoChannel::OnSrtpError);
1655 return true; 1613 return true;
1656 } 1614 }
1657 1615
1658 void VoiceChannel::SendLastMediaError() {
1659 uint32 ssrc;
1660 VoiceMediaChannel::Error error;
1661 media_channel()->GetLastMediaError(&ssrc, &error);
1662 SignalMediaError(this, ssrc, error);
1663 }
1664
1665 VideoChannel::~VideoChannel() { 1616 VideoChannel::~VideoChannel() {
1666 std::vector<uint32> screencast_ssrcs; 1617 std::vector<uint32> screencast_ssrcs;
1667 ScreencastMap::iterator iter; 1618 ScreencastMap::iterator iter;
1668 while (!screencast_capturers_.empty()) { 1619 while (!screencast_capturers_.empty()) {
1669 if (!RemoveScreencast(screencast_capturers_.begin()->first)) { 1620 if (!RemoveScreencast(screencast_capturers_.begin()->first)) {
1670 LOG(LS_ERROR) << "Unable to delete screencast with ssrc " 1621 LOG(LS_ERROR) << "Unable to delete screencast with ssrc "
1671 << screencast_capturers_.begin()->first; 1622 << screencast_capturers_.begin()->first;
1672 ASSERT(false); 1623 ASSERT(false);
1673 break; 1624 break;
1674 } 1625 }
(...skipping 281 matching lines...) Expand 10 before | Expand all | Expand 10 after
1956 case MSG_SCREENCASTWINDOWEVENT: { 1907 case MSG_SCREENCASTWINDOWEVENT: {
1957 const ScreencastEventMessageData* data = 1908 const ScreencastEventMessageData* data =
1958 static_cast<ScreencastEventMessageData*>(pmsg->pdata); 1909 static_cast<ScreencastEventMessageData*>(pmsg->pdata);
1959 OnScreencastWindowEvent_s(data->ssrc, data->event); 1910 OnScreencastWindowEvent_s(data->ssrc, data->event);
1960 delete data; 1911 delete data;
1961 break; 1912 break;
1962 } 1913 }
1963 case MSG_CHANNEL_ERROR: { 1914 case MSG_CHANNEL_ERROR: {
1964 const VideoChannelErrorMessageData* data = 1915 const VideoChannelErrorMessageData* data =
1965 static_cast<VideoChannelErrorMessageData*>(pmsg->pdata); 1916 static_cast<VideoChannelErrorMessageData*>(pmsg->pdata);
1966 SignalMediaError(this, data->ssrc, data->error);
1967 delete data; 1917 delete data;
1968 break; 1918 break;
1969 } 1919 }
1970 default: 1920 default:
1971 BaseChannel::OnMessage(pmsg); 1921 BaseChannel::OnMessage(pmsg);
1972 break; 1922 break;
1973 } 1923 }
1974 } 1924 }
1975 1925
1976 void VideoChannel::OnConnectionMonitorUpdate( 1926 void VideoChannel::OnConnectionMonitorUpdate(
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after
2021 for (ScreencastMap::iterator iter = screencast_capturers_.begin(); 1971 for (ScreencastMap::iterator iter = screencast_capturers_.begin();
2022 iter != screencast_capturers_.end(); ++iter) { 1972 iter != screencast_capturers_.end(); ++iter) {
2023 if (iter->second == capturer) { 1973 if (iter->second == capturer) {
2024 *ssrc = iter->first; 1974 *ssrc = iter->first;
2025 return true; 1975 return true;
2026 } 1976 }
2027 } 1977 }
2028 return false; 1978 return false;
2029 } 1979 }
2030 1980
2031 void VideoChannel::OnVideoChannelError(uint32 ssrc,
2032 VideoMediaChannel::Error error) {
2033 VideoChannelErrorMessageData* data = new VideoChannelErrorMessageData(
2034 ssrc, error);
2035 signaling_thread()->Post(this, MSG_CHANNEL_ERROR, data);
2036 }
2037
2038 void VideoChannel::OnSrtpError(uint32 ssrc, SrtpFilter::Mode mode,
2039 SrtpFilter::Error error) {
2040 switch (error) {
2041 case SrtpFilter::ERROR_FAIL:
2042 OnVideoChannelError(ssrc, (mode == SrtpFilter::PROTECT) ?
2043 VideoMediaChannel::ERROR_REC_SRTP_ERROR :
2044 VideoMediaChannel::ERROR_PLAY_SRTP_ERROR);
2045 break;
2046 case SrtpFilter::ERROR_AUTH:
2047 OnVideoChannelError(ssrc, (mode == SrtpFilter::PROTECT) ?
2048 VideoMediaChannel::ERROR_REC_SRTP_AUTH_FAILED :
2049 VideoMediaChannel::ERROR_PLAY_SRTP_AUTH_FAILED);
2050 break;
2051 case SrtpFilter::ERROR_REPLAY:
2052 // Only receving channel should have this error.
2053 ASSERT(mode == SrtpFilter::UNPROTECT);
2054 // TODO(gangji): Turn on the signaling of replay error once we have
2055 // switched to the new mechanism for doing video retransmissions.
2056 // OnVideoChannelError(ssrc, VideoMediaChannel::ERROR_PLAY_SRTP_REPLAY);
2057 break;
2058 default:
2059 break;
2060 }
2061 }
2062
2063 void VideoChannel::GetSrtpCiphers(std::vector<std::string>* ciphers) const { 1981 void VideoChannel::GetSrtpCiphers(std::vector<std::string>* ciphers) const {
2064 GetSupportedVideoCryptoSuites(ciphers); 1982 GetSupportedVideoCryptoSuites(ciphers);
2065 } 1983 }
2066 1984
2067 DataChannel::DataChannel(rtc::Thread* thread, 1985 DataChannel::DataChannel(rtc::Thread* thread,
2068 DataMediaChannel* media_channel, 1986 DataMediaChannel* media_channel,
2069 TransportController* transport_controller, 1987 TransportController* transport_controller,
2070 const std::string& content_name, 1988 const std::string& content_name,
2071 bool rtcp) 1989 bool rtcp)
2072 : BaseChannel(thread, 1990 : BaseChannel(thread,
(...skipping 11 matching lines...) Expand all
2084 2002
2085 Deinit(); 2003 Deinit();
2086 } 2004 }
2087 2005
2088 bool DataChannel::Init() { 2006 bool DataChannel::Init() {
2089 if (!BaseChannel::Init()) { 2007 if (!BaseChannel::Init()) {
2090 return false; 2008 return false;
2091 } 2009 }
2092 media_channel()->SignalDataReceived.connect( 2010 media_channel()->SignalDataReceived.connect(
2093 this, &DataChannel::OnDataReceived); 2011 this, &DataChannel::OnDataReceived);
2094 media_channel()->SignalMediaError.connect(
2095 this, &DataChannel::OnDataChannelError);
2096 media_channel()->SignalReadyToSend.connect( 2012 media_channel()->SignalReadyToSend.connect(
2097 this, &DataChannel::OnDataChannelReadyToSend); 2013 this, &DataChannel::OnDataChannelReadyToSend);
2098 media_channel()->SignalStreamClosedRemotely.connect( 2014 media_channel()->SignalStreamClosedRemotely.connect(
2099 this, &DataChannel::OnStreamClosedRemotely); 2015 this, &DataChannel::OnStreamClosedRemotely);
2100 srtp_filter()->SignalSrtpError.connect(
2101 this, &DataChannel::OnSrtpError);
2102 return true; 2016 return true;
2103 } 2017 }
2104 2018
2105 bool DataChannel::SendData(const SendDataParams& params, 2019 bool DataChannel::SendData(const SendDataParams& params,
2106 const rtc::Buffer& payload, 2020 const rtc::Buffer& payload,
2107 SendDataResult* result) { 2021 SendDataResult* result) {
2108 return InvokeOnWorker(Bind(&DataMediaChannel::SendData, 2022 return InvokeOnWorker(Bind(&DataMediaChannel::SendData,
2109 media_channel(), params, payload, result)); 2023 media_channel(), params, payload, result));
2110 } 2024 }
2111 2025
(...skipping 187 matching lines...) Expand 10 before | Expand all | Expand 10 after
2299 case MSG_DATARECEIVED: { 2213 case MSG_DATARECEIVED: {
2300 DataReceivedMessageData* data = 2214 DataReceivedMessageData* data =
2301 static_cast<DataReceivedMessageData*>(pmsg->pdata); 2215 static_cast<DataReceivedMessageData*>(pmsg->pdata);
2302 SignalDataReceived(this, data->params, data->payload); 2216 SignalDataReceived(this, data->params, data->payload);
2303 delete data; 2217 delete data;
2304 break; 2218 break;
2305 } 2219 }
2306 case MSG_CHANNEL_ERROR: { 2220 case MSG_CHANNEL_ERROR: {
2307 const DataChannelErrorMessageData* data = 2221 const DataChannelErrorMessageData* data =
2308 static_cast<DataChannelErrorMessageData*>(pmsg->pdata); 2222 static_cast<DataChannelErrorMessageData*>(pmsg->pdata);
2309 SignalMediaError(this, data->ssrc, data->error);
2310 delete data; 2223 delete data;
2311 break; 2224 break;
2312 } 2225 }
2313 case MSG_STREAMCLOSEDREMOTELY: { 2226 case MSG_STREAMCLOSEDREMOTELY: {
2314 rtc::TypedMessageData<uint32>* data = 2227 rtc::TypedMessageData<uint32>* data =
2315 static_cast<rtc::TypedMessageData<uint32>*>(pmsg->pdata); 2228 static_cast<rtc::TypedMessageData<uint32>*>(pmsg->pdata);
2316 SignalStreamClosedRemotely(data->data()); 2229 SignalStreamClosedRemotely(data->data());
2317 delete data; 2230 delete data;
2318 break; 2231 break;
2319 } 2232 }
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
2365 } 2278 }
2366 2279
2367 void DataChannel::OnDataChannelReadyToSend(bool writable) { 2280 void DataChannel::OnDataChannelReadyToSend(bool writable) {
2368 // This is usded for congestion control to indicate that the stream is ready 2281 // This is usded for congestion control to indicate that the stream is ready
2369 // to send by the MediaChannel, as opposed to OnReadyToSend, which indicates 2282 // to send by the MediaChannel, as opposed to OnReadyToSend, which indicates
2370 // that the transport channel is ready. 2283 // that the transport channel is ready.
2371 signaling_thread()->Post(this, MSG_READYTOSENDDATA, 2284 signaling_thread()->Post(this, MSG_READYTOSENDDATA,
2372 new DataChannelReadyToSendMessageData(writable)); 2285 new DataChannelReadyToSendMessageData(writable));
2373 } 2286 }
2374 2287
2375 void DataChannel::OnSrtpError(uint32 ssrc, SrtpFilter::Mode mode,
2376 SrtpFilter::Error error) {
2377 switch (error) {
2378 case SrtpFilter::ERROR_FAIL:
2379 OnDataChannelError(ssrc, (mode == SrtpFilter::PROTECT) ?
2380 DataMediaChannel::ERROR_SEND_SRTP_ERROR :
2381 DataMediaChannel::ERROR_RECV_SRTP_ERROR);
2382 break;
2383 case SrtpFilter::ERROR_AUTH:
2384 OnDataChannelError(ssrc, (mode == SrtpFilter::PROTECT) ?
2385 DataMediaChannel::ERROR_SEND_SRTP_AUTH_FAILED :
2386 DataMediaChannel::ERROR_RECV_SRTP_AUTH_FAILED);
2387 break;
2388 case SrtpFilter::ERROR_REPLAY:
2389 // Only receving channel should have this error.
2390 ASSERT(mode == SrtpFilter::UNPROTECT);
2391 OnDataChannelError(ssrc, DataMediaChannel::ERROR_RECV_SRTP_REPLAY);
2392 break;
2393 default:
2394 break;
2395 }
2396 }
2397
2398 void DataChannel::GetSrtpCiphers(std::vector<std::string>* ciphers) const { 2288 void DataChannel::GetSrtpCiphers(std::vector<std::string>* ciphers) const {
2399 GetSupportedDataCryptoSuites(ciphers); 2289 GetSupportedDataCryptoSuites(ciphers);
2400 } 2290 }
2401 2291
2402 bool DataChannel::ShouldSetupDtlsSrtp() const { 2292 bool DataChannel::ShouldSetupDtlsSrtp() const {
2403 return (data_channel_type_ == DCT_RTP); 2293 return (data_channel_type_ == DCT_RTP);
2404 } 2294 }
2405 2295
2406 void DataChannel::OnStreamClosedRemotely(uint32 sid) { 2296 void DataChannel::OnStreamClosedRemotely(uint32 sid) {
2407 rtc::TypedMessageData<uint32>* message = 2297 rtc::TypedMessageData<uint32>* message =
2408 new rtc::TypedMessageData<uint32>(sid); 2298 new rtc::TypedMessageData<uint32>(sid);
2409 signaling_thread()->Post(this, MSG_STREAMCLOSEDREMOTELY, message); 2299 signaling_thread()->Post(this, MSG_STREAMCLOSEDREMOTELY, message);
2410 } 2300 }
2411 2301
2412 } // namespace cricket 2302 } // namespace cricket
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698