summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-15 03:35:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-15 03:35:49 +0000
commitd8bbc7858622b6d9c278469aab701ca0b609cddf (patch)
treeeff41dc61d9f714852212739e6b3738b82a2af87 /third_party/libwebrtc
parentReleasing progress-linux version 125.0.3-1~progress7.99u1. (diff)
downloadfirefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.tar.xz
firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.zip
Merging upstream version 126.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc')
-rw-r--r--third_party/libwebrtc/.vpython338
-rw-r--r--third_party/libwebrtc/BUILD.gn1
-rw-r--r--third_party/libwebrtc/DEPS92
-rw-r--r--third_party/libwebrtc/README.moz-ff-commit846
-rw-r--r--third_party/libwebrtc/README.mozilla564
-rw-r--r--third_party/libwebrtc/api/BUILD.gn22
-rw-r--r--third_party/libwebrtc/api/DEPS7
-rw-r--r--third_party/libwebrtc/api/audio_codecs/BUILD.gn1
-rw-r--r--third_party/libwebrtc/api/audio_codecs/audio_format.cc4
-rw-r--r--third_party/libwebrtc/api/audio_codecs/audio_format.h10
-rw-r--r--third_party/libwebrtc/api/call/call_factory_interface.h43
-rw-r--r--third_party/libwebrtc/api/candidate.cc18
-rw-r--r--third_party/libwebrtc/api/candidate.h54
-rw-r--r--third_party/libwebrtc/api/create_peerconnection_factory.cc3
-rw-r--r--third_party/libwebrtc/api/enable_media.cc5
-rw-r--r--third_party/libwebrtc/api/environment/environment_factory.cc10
-rw-r--r--third_party/libwebrtc/api/environment/environment_factory_gn/moz.build231
-rw-r--r--third_party/libwebrtc/api/fec_controller.h5
-rw-r--r--third_party/libwebrtc/api/metronome/BUILD.gn2
-rw-r--r--third_party/libwebrtc/api/metronome/metronome.h2
-rw-r--r--third_party/libwebrtc/api/metronome/test/BUILD.gn9
-rw-r--r--third_party/libwebrtc/api/metronome/test/fake_metronome.cc10
-rw-r--r--third_party/libwebrtc/api/metronome/test/fake_metronome.h17
-rw-r--r--third_party/libwebrtc/api/peer_connection_interface.h8
-rw-r--r--third_party/libwebrtc/api/rtc_event_log/rtc_event_log_factory.cc7
-rw-r--r--third_party/libwebrtc/api/rtc_event_log/rtc_event_log_factory.h5
-rw-r--r--third_party/libwebrtc/api/rtp_parameters.cc9
-rw-r--r--third_party/libwebrtc/api/rtp_parameters.h4
-rw-r--r--third_party/libwebrtc/api/stats/attribute.h96
-rw-r--r--third_party/libwebrtc/api/stats/rtc_stats.h329
-rw-r--r--third_party/libwebrtc/api/stats/rtc_stats_member.h185
-rw-r--r--third_party/libwebrtc/api/stats/rtcstats_objects.h36
-rw-r--r--third_party/libwebrtc/api/task_queue/BUILD.gn7
-rw-r--r--third_party/libwebrtc/api/task_queue/default_task_queue_factory_gn/moz.build (renamed from third_party/libwebrtc/api/callfactory_api_gn/moz.build)17
-rw-r--r--third_party/libwebrtc/api/test/create_network_emulation_manager.cc6
-rw-r--r--third_party/libwebrtc/api/test/create_network_emulation_manager.h4
-rw-r--r--third_party/libwebrtc/api/test/create_time_controller.cc13
-rw-r--r--third_party/libwebrtc/api/test/pclf/BUILD.gn1
-rw-r--r--third_party/libwebrtc/api/test/pclf/media_configuration.h1
-rw-r--r--third_party/libwebrtc/api/test/pclf/media_quality_test_params.h1
-rw-r--r--third_party/libwebrtc/api/test/pclf/peer_configurer.cc5
-rw-r--r--third_party/libwebrtc/api/test/pclf/peer_configurer.h2
-rw-r--r--third_party/libwebrtc/api/test/peerconnection_quality_test_fixture.h1
-rw-r--r--third_party/libwebrtc/api/test/video_quality_test_fixture.h2
-rw-r--r--third_party/libwebrtc/api/transport/rtp/dependency_descriptor.h21
-rw-r--r--third_party/libwebrtc/api/transport/stun.cc20
-rw-r--r--third_party/libwebrtc/api/video_codecs/BUILD.gn1
-rw-r--r--third_party/libwebrtc/api/video_codecs/av1_profile.cc6
-rw-r--r--third_party/libwebrtc/api/video_codecs/av1_profile.h6
-rw-r--r--third_party/libwebrtc/api/video_codecs/h264_profile_level_id.cc6
-rw-r--r--third_party/libwebrtc/api/video_codecs/h264_profile_level_id.h6
-rw-r--r--third_party/libwebrtc/api/video_codecs/h265_profile_tier_level.cc496
-rw-r--r--third_party/libwebrtc/api/video_codecs/h265_profile_tier_level.h218
-rw-r--r--third_party/libwebrtc/api/video_codecs/sdp_video_format.cc11
-rw-r--r--third_party/libwebrtc/api/video_codecs/sdp_video_format.h10
-rw-r--r--third_party/libwebrtc/api/video_codecs/test/h264_profile_level_id_unittest.cc6
-rw-r--r--third_party/libwebrtc/api/video_codecs/test/h265_profile_tier_level_unittest.cc496
-rw-r--r--third_party/libwebrtc/api/video_codecs/test/sdp_video_format_unittest.cc2
-rw-r--r--third_party/libwebrtc/api/video_codecs/video_encoder_factory_template_libaom_av1_adapter.h3
-rw-r--r--third_party/libwebrtc/api/video_codecs/video_encoder_factory_template_libvpx_vp8_adapter.h3
-rw-r--r--third_party/libwebrtc/api/video_codecs/vp9_profile.cc6
-rw-r--r--third_party/libwebrtc/api/video_codecs/vp9_profile.h6
-rw-r--r--third_party/libwebrtc/audio/BUILD.gn14
-rw-r--r--third_party/libwebrtc/audio/audio_receive_stream.cc2
-rw-r--r--third_party/libwebrtc/audio/audio_send_stream.cc3
-rw-r--r--third_party/libwebrtc/audio/audio_send_stream.h1
-rw-r--r--third_party/libwebrtc/audio/channel_receive_frame_transformer_delegate.h2
-rw-r--r--third_party/libwebrtc/audio/channel_send.cc52
-rw-r--r--third_party/libwebrtc/audio/channel_send_frame_transformer_delegate.cc2
-rw-r--r--third_party/libwebrtc/audio/channel_send_frame_transformer_delegate.h6
-rw-r--r--third_party/libwebrtc/audio/channel_send_frame_transformer_delegate_unittest.cc10
-rw-r--r--third_party/libwebrtc/audio/channel_send_unittest.cc22
-rw-r--r--third_party/libwebrtc/audio/voip/BUILD.gn1
-rw-r--r--third_party/libwebrtc/audio/voip/audio_egress.cc18
-rw-r--r--third_party/libwebrtc/audio/voip/audio_egress.h10
-rw-r--r--third_party/libwebrtc/call/BUILD.gn14
-rw-r--r--third_party/libwebrtc/call/call.cc150
-rw-r--r--third_party/libwebrtc/call/call.h6
-rw-r--r--third_party/libwebrtc/call/call_config.cc26
-rw-r--r--third_party/libwebrtc/call/call_config.h30
-rw-r--r--third_party/libwebrtc/call/create_call.cc (renamed from third_party/libwebrtc/call/call_factory.cc)31
-rw-r--r--third_party/libwebrtc/call/create_call.h (renamed from third_party/libwebrtc/call/call_factory.h)20
-rw-r--r--third_party/libwebrtc/call/rtp_transport_config.h20
-rw-r--r--third_party/libwebrtc/call/rtp_transport_controller_send.cc81
-rw-r--r--third_party/libwebrtc/call/rtp_transport_controller_send.h10
-rw-r--r--third_party/libwebrtc/call/rtp_transport_controller_send_factory.h6
-rw-r--r--third_party/libwebrtc/call/rtp_transport_controller_send_factory_interface.h7
-rw-r--r--third_party/libwebrtc/call/rtp_video_sender_unittest.cc26
-rw-r--r--third_party/libwebrtc/call/version.cc2
-rw-r--r--third_party/libwebrtc/common_video/h264/h264_common.h7
-rw-r--r--third_party/libwebrtc/common_video/h264/sps_parser.h5
-rw-r--r--third_party/libwebrtc/common_video/h265/h265_bitstream_parser.cc3
-rw-r--r--third_party/libwebrtc/docs/native-code/development/README.md14
-rw-r--r--third_party/libwebrtc/docs/native-code/development/fuzzers/README.md70
-rw-r--r--third_party/libwebrtc/examples/androidnativeapi/jni/android_call_client.cc3
-rw-r--r--third_party/libwebrtc/examples/androidvoip/BUILD.gn2
-rw-r--r--third_party/libwebrtc/examples/androidvoip/jni/android_voip_client.cc34
-rw-r--r--third_party/libwebrtc/examples/androidvoip/jni/android_voip_client.h16
-rw-r--r--third_party/libwebrtc/examples/objcnativeapi/objc/objc_call_client.mm3
-rwxr-xr-xthird_party/libwebrtc/experiments/field_trials.py9
-rw-r--r--third_party/libwebrtc/infra/OWNERS1
-rwxr-xr-xthird_party/libwebrtc/infra/config/config.star2
-rw-r--r--third_party/libwebrtc/infra/config/cr-buildbucket.cfg94
-rw-r--r--third_party/libwebrtc/infra/config/luci-milo.cfg6
-rw-r--r--third_party/libwebrtc/infra/config/luci-notify.cfg26
-rw-r--r--third_party/libwebrtc/infra/specs/client.webrtc.json1537
-rw-r--r--third_party/libwebrtc/infra/specs/internal.client.webrtc.json122
-rw-r--r--third_party/libwebrtc/infra/specs/mixins.pyl61
-rw-r--r--third_party/libwebrtc/infra/specs/mixins_webrtc.pyl42
-rw-r--r--third_party/libwebrtc/infra/specs/test_suites.pyl32
-rw-r--r--third_party/libwebrtc/infra/specs/tryserver.webrtc.json2456
-rw-r--r--third_party/libwebrtc/infra/specs/variants.pyl26
-rw-r--r--third_party/libwebrtc/infra/specs/waterfalls.pyl72
-rw-r--r--third_party/libwebrtc/logging/BUILD.gn10
-rw-r--r--third_party/libwebrtc/logging/rtc_event_log/rtc_event_log_impl.cc44
-rw-r--r--third_party/libwebrtc/logging/rtc_event_log/rtc_event_log_impl.h13
-rw-r--r--third_party/libwebrtc/media/BUILD.gn1
-rw-r--r--third_party/libwebrtc/media/base/codec.cc45
-rw-r--r--third_party/libwebrtc/media/base/codec.h11
-rw-r--r--third_party/libwebrtc/media/base/codec_unittest.cc61
-rw-r--r--third_party/libwebrtc/media/base/media_channel_impl.cc20
-rw-r--r--third_party/libwebrtc/media/base/media_channel_impl.h14
-rw-r--r--third_party/libwebrtc/media/base/sdp_video_format_utils.cc73
-rw-r--r--third_party/libwebrtc/media/base/sdp_video_format_utils.h24
-rw-r--r--third_party/libwebrtc/media/base/sdp_video_format_utils_unittest.cc57
-rw-r--r--third_party/libwebrtc/media/engine/internal_decoder_factory_unittest.cc10
-rw-r--r--third_party/libwebrtc/media/engine/internal_encoder_factory_unittest.cc13
-rw-r--r--third_party/libwebrtc/media/engine/simulcast_encoder_adapter_unittest.cc2
-rw-r--r--third_party/libwebrtc/media/engine/webrtc_media_engine.cc37
-rw-r--r--third_party/libwebrtc/media/engine/webrtc_media_engine.h42
-rw-r--r--third_party/libwebrtc/media/engine/webrtc_video_engine.cc14
-rw-r--r--third_party/libwebrtc/media/engine/webrtc_video_engine_unittest.cc8
-rw-r--r--third_party/libwebrtc/media/engine/webrtc_voice_engine.cc14
-rw-r--r--third_party/libwebrtc/media/engine/webrtc_voice_engine.h4
-rw-r--r--third_party/libwebrtc/media/engine/webrtc_voice_engine_unittest.cc36
-rw-r--r--third_party/libwebrtc/modules/audio_coding/codecs/builtin_audio_decoder_factory_unittest.cc2
-rw-r--r--third_party/libwebrtc/modules/audio_coding/codecs/opus/audio_encoder_opus_unittest.cc2
-rw-r--r--third_party/libwebrtc/modules/audio_coding/neteq/test/neteq_opus_quality_test.cc4
-rw-r--r--third_party/libwebrtc/modules/audio_coding/test/TestStereo.cc2
-rw-r--r--third_party/libwebrtc/modules/audio_device/BUILD.gn3
-rw-r--r--third_party/libwebrtc/modules/audio_device/fine_audio_buffer.cc7
-rw-r--r--third_party/libwebrtc/modules/audio_device/fine_audio_buffer.h11
-rw-r--r--third_party/libwebrtc/modules/audio_device/fine_audio_buffer_unittest.cc2
-rw-r--r--third_party/libwebrtc/modules/audio_device/include/test_audio_device.cc2
-rw-r--r--third_party/libwebrtc/modules/audio_device/mock_audio_device_buffer.h5
-rw-r--r--third_party/libwebrtc/modules/audio_device/test_audio_device_impl.cc8
-rw-r--r--third_party/libwebrtc/modules/audio_device/test_audio_device_impl.h4
-rw-r--r--third_party/libwebrtc/modules/audio_processing/BUILD.gn10
-rw-r--r--third_party/libwebrtc/modules/audio_processing/aec_dump/BUILD.gn18
-rw-r--r--third_party/libwebrtc/modules/audio_processing/aec_dump/aec_dump_factory.h36
-rw-r--r--third_party/libwebrtc/modules/audio_processing/aec_dump/aec_dump_impl.cc26
-rw-r--r--third_party/libwebrtc/modules/audio_processing/aec_dump/aec_dump_impl.h6
-rw-r--r--third_party/libwebrtc/modules/audio_processing/aec_dump/aec_dump_unittest.cc4
-rw-r--r--third_party/libwebrtc/modules/audio_processing/aec_dump/null_aec_dump_factory.cc23
-rw-r--r--third_party/libwebrtc/modules/audio_processing/audio_processing_impl.cc16
-rw-r--r--third_party/libwebrtc/modules/audio_processing/audio_processing_impl.h16
-rw-r--r--third_party/libwebrtc/modules/audio_processing/audio_processing_unittest.cc10
-rw-r--r--third_party/libwebrtc/modules/audio_processing/include/audio_processing.h20
-rw-r--r--third_party/libwebrtc/modules/audio_processing/include/mock_audio_processing.h6
-rw-r--r--third_party/libwebrtc/modules/audio_processing/test/audio_processing_simulator.cc2
-rw-r--r--third_party/libwebrtc/modules/audio_processing/test/debug_dump_test.cc2
-rw-r--r--third_party/libwebrtc/modules/congestion_controller/goog_cc/delay_based_bwe_unittest_helper.cc14
-rw-r--r--third_party/libwebrtc/modules/congestion_controller/goog_cc/delay_based_bwe_unittest_helper.h9
-rw-r--r--third_party/libwebrtc/modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.cc3
-rw-r--r--third_party/libwebrtc/modules/congestion_controller/rtp/BUILD.gn1
-rw-r--r--third_party/libwebrtc/modules/congestion_controller/rtp/control_handler.cc18
-rw-r--r--third_party/libwebrtc/modules/congestion_controller/rtp/control_handler.h6
-rw-r--r--third_party/libwebrtc/modules/pacing/BUILD.gn1
-rw-r--r--third_party/libwebrtc/modules/pacing/pacing_controller.cc22
-rw-r--r--third_party/libwebrtc/modules/pacing/pacing_controller.h44
-rw-r--r--third_party/libwebrtc/modules/pacing/pacing_controller_unittest.cc38
-rw-r--r--third_party/libwebrtc/modules/pacing/prioritized_packet_queue.cc149
-rw-r--r--third_party/libwebrtc/modules/pacing/prioritized_packet_queue.h26
-rw-r--r--third_party/libwebrtc/modules/pacing/prioritized_packet_queue_unittest.cc171
-rw-r--r--third_party/libwebrtc/modules/remote_bitrate_estimator/remote_estimator_proxy.cc7
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/receive_statistics_impl.cc8
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/receive_statistics_unittest.cc17
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_receiver.cc4
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_extension.cc13
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_extension.h10
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_h264.cc27
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_h264_unittest.cc186
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8.cc4
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8_unittest.cc12
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp9.cc5
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp9_unittest.cc5
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet.h8
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_to_send.cc11
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_to_send.h10
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_unittest.cc35
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1_unittest.cc6
-rw-r--r--third_party/libwebrtc/modules/video_capture/BUILD.gn1
-rw-r--r--third_party/libwebrtc/modules/video_capture/test/video_capture_unittest.cc21
-rw-r--r--third_party/libwebrtc/modules/video_coding/BUILD.gn3
-rw-r--r--third_party/libwebrtc/modules/video_coding/codecs/av1/dav1d_decoder.cc2
-rw-r--r--third_party/libwebrtc/modules/video_coding/codecs/av1/libaom_av1_unittest.cc3
-rw-r--r--third_party/libwebrtc/modules/video_coding/codecs/h264/h264_decoder_impl.cc6
-rw-r--r--third_party/libwebrtc/modules/video_coding/codecs/test/video_codec_test.cc34
-rw-r--r--third_party/libwebrtc/modules/video_coding/codecs/test/videocodec_test_fixture_impl.cc2
-rw-r--r--third_party/libwebrtc/modules/video_coding/fec_controller_default.cc30
-rw-r--r--third_party/libwebrtc/modules/video_coding/fec_controller_default.h11
-rw-r--r--third_party/libwebrtc/modules/video_coding/fec_controller_unittest.cc4
-rw-r--r--third_party/libwebrtc/modules/video_coding/nack_requester.cc56
-rw-r--r--third_party/libwebrtc/modules/video_coding/nack_requester.h10
-rw-r--r--third_party/libwebrtc/modules/video_coding/nack_requester_unittest.cc160
-rw-r--r--third_party/libwebrtc/modules/video_coding/utility/qp_parser.cc16
-rw-r--r--third_party/libwebrtc/modules/video_coding/utility/qp_parser.h18
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0001.patch10
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0006.patch2
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0007.patch2
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0008.patch6
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0009.patch2
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0030.patch114
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0031.patch4
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0033.patch12
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0034.patch2
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0042.patch6
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0044.patch12
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0046.patch4
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0047.patch139
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0049.patch2
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0050.patch14
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0052.patch48
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0053.patch2
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0054.patch43
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0064.patch4
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0068.patch12
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0069.patch4
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0070.patch4
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0071.patch4
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0076.patch4
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0078.patch2
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0079.patch2
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0081.patch4
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0083.patch2
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0084.patch4
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0085.patch2
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0097.patch36922
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0098.patch35372
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0099.patch78
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0100.patch88
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0101.patch145
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0102.patch212
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0103.patch134
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0104.patch95
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0105.patch145
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0106.patch187
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0107.patch28
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0108.patch27
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0109.patch243
-rw-r--r--third_party/libwebrtc/moz-patch-stack/0110.patch207
-rw-r--r--third_party/libwebrtc/moz-patch-stack/058bfe3ae3.no-op-cherry-pick-msg1
-rw-r--r--third_party/libwebrtc/moz-patch-stack/16ac10d9f7.no-op-cherry-pick-msg1
-rw-r--r--third_party/libwebrtc/moz-patch-stack/334e9133dc.no-op-cherry-pick-msg1
-rw-r--r--third_party/libwebrtc/moz-patch-stack/6a992129fb.no-op-cherry-pick-msg1
-rw-r--r--third_party/libwebrtc/moz-patch-stack/de3c726121.no-op-cherry-pick-msg1
-rw-r--r--third_party/libwebrtc/moz.build3
-rw-r--r--third_party/libwebrtc/net/dcsctp/public/dcsctp_socket.h11
-rw-r--r--third_party/libwebrtc/net/dcsctp/public/mock_dcsctp_socket.h8
-rw-r--r--third_party/libwebrtc/net/dcsctp/rx/traditional_reassembly_streams.cc57
-rw-r--r--third_party/libwebrtc/net/dcsctp/rx/traditional_reassembly_streams.h6
-rw-r--r--third_party/libwebrtc/net/dcsctp/socket/BUILD.gn2
-rw-r--r--third_party/libwebrtc/net/dcsctp/socket/callback_deferrer.cc119
-rw-r--r--third_party/libwebrtc/net/dcsctp/socket/callback_deferrer.h17
-rw-r--r--third_party/libwebrtc/net/dcsctp/socket/dcsctp_socket.cc130
-rw-r--r--third_party/libwebrtc/net/dcsctp/socket/dcsctp_socket.h10
-rw-r--r--third_party/libwebrtc/net/dcsctp/socket/dcsctp_socket_test.cc172
-rw-r--r--third_party/libwebrtc/net/dcsctp/socket/state_cookie.cc52
-rw-r--r--third_party/libwebrtc/net/dcsctp/socket/state_cookie.h28
-rw-r--r--third_party/libwebrtc/net/dcsctp/socket/state_cookie_test.cc14
-rw-r--r--third_party/libwebrtc/net/dcsctp/tx/rr_send_queue.cc5
-rw-r--r--third_party/libwebrtc/p2p/base/basic_ice_controller.cc6
-rw-r--r--third_party/libwebrtc/p2p/base/basic_ice_controller.h3
-rw-r--r--third_party/libwebrtc/p2p/base/connection.cc9
-rw-r--r--third_party/libwebrtc/p2p/base/ice_controller_interface.h15
-rw-r--r--third_party/libwebrtc/p2p/base/mock_ice_controller.h4
-rw-r--r--third_party/libwebrtc/p2p/base/p2p_transport_channel.cc50
-rw-r--r--third_party/libwebrtc/p2p/base/p2p_transport_channel.h4
-rw-r--r--third_party/libwebrtc/p2p/base/p2p_transport_channel_unittest.cc119
-rw-r--r--third_party/libwebrtc/p2p/base/port.cc7
-rw-r--r--third_party/libwebrtc/p2p/base/port.h5
-rw-r--r--third_party/libwebrtc/p2p/base/port_allocator.cc9
-rw-r--r--third_party/libwebrtc/p2p/base/port_unittest.cc91
-rw-r--r--third_party/libwebrtc/p2p/base/pseudo_tcp.cc5
-rw-r--r--third_party/libwebrtc/p2p/base/stun_dictionary.cc3
-rw-r--r--third_party/libwebrtc/p2p/base/stun_port.cc4
-rw-r--r--third_party/libwebrtc/p2p/base/stun_port_unittest.cc8
-rw-r--r--third_party/libwebrtc/p2p/base/stun_server_unittest.cc3
-rw-r--r--third_party/libwebrtc/p2p/base/turn_port.cc26
-rw-r--r--third_party/libwebrtc/p2p/base/turn_port.h9
-rw-r--r--third_party/libwebrtc/p2p/base/turn_port_unittest.cc3
-rw-r--r--third_party/libwebrtc/p2p/base/turn_server.cc3
-rw-r--r--third_party/libwebrtc/p2p/client/basic_port_allocator.cc35
-rw-r--r--third_party/libwebrtc/p2p/client/basic_port_allocator.h3
-rw-r--r--third_party/libwebrtc/p2p/client/basic_port_allocator_unittest.cc28
-rw-r--r--third_party/libwebrtc/p2p/stunprober/stun_prober.cc4
-rw-r--r--third_party/libwebrtc/pc/BUILD.gn155
-rw-r--r--third_party/libwebrtc/pc/connection_context.cc7
-rw-r--r--third_party/libwebrtc/pc/connection_context.h8
-rw-r--r--third_party/libwebrtc/pc/dtls_transport.cc34
-rw-r--r--third_party/libwebrtc/pc/dtls_transport.h18
-rw-r--r--third_party/libwebrtc/pc/jsep_session_description.cc36
-rw-r--r--third_party/libwebrtc/pc/legacy_stats_collector.cc63
-rw-r--r--third_party/libwebrtc/pc/legacy_stats_collector.h3
-rw-r--r--third_party/libwebrtc/pc/legacy_stats_collector_unittest.cc4
-rw-r--r--third_party/libwebrtc/pc/media_session.cc52
-rw-r--r--third_party/libwebrtc/pc/media_session_unittest.cc74
-rw-r--r--third_party/libwebrtc/pc/peer_connection.cc40
-rw-r--r--third_party/libwebrtc/pc/peer_connection.h3
-rw-r--r--third_party/libwebrtc/pc/peer_connection_crypto_unittest.cc11
-rw-r--r--third_party/libwebrtc/pc/peer_connection_encodings_integrationtest.cc29
-rw-r--r--third_party/libwebrtc/pc/peer_connection_end_to_end_unittest.cc6
-rw-r--r--third_party/libwebrtc/pc/peer_connection_factory.cc10
-rw-r--r--third_party/libwebrtc/pc/peer_connection_factory.h5
-rw-r--r--third_party/libwebrtc/pc/peer_connection_field_trial_tests.cc2
-rw-r--r--third_party/libwebrtc/pc/peer_connection_header_extension_unittest.cc3
-rw-r--r--third_party/libwebrtc/pc/peer_connection_histogram_unittest.cc1
-rw-r--r--third_party/libwebrtc/pc/peer_connection_integrationtest.cc24
-rw-r--r--third_party/libwebrtc/pc/peer_connection_interface_unittest.cc7
-rw-r--r--third_party/libwebrtc/pc/peer_connection_media_unittest.cc13
-rw-r--r--third_party/libwebrtc/pc/peer_connection_rampup_tests.cc8
-rw-r--r--third_party/libwebrtc/pc/peer_connection_rtp_unittest.cc37
-rw-r--r--third_party/libwebrtc/pc/peer_connection_signaling_unittest.cc42
-rw-r--r--third_party/libwebrtc/pc/peer_connection_simulcast_unittest.cc48
-rw-r--r--third_party/libwebrtc/pc/rtc_stats_collector.cc44
-rw-r--r--third_party/libwebrtc/pc/rtc_stats_collector.h5
-rw-r--r--third_party/libwebrtc/pc/rtc_stats_collector_unittest.cc36
-rw-r--r--third_party/libwebrtc/pc/rtc_stats_integrationtest.cc866
-rw-r--r--third_party/libwebrtc/pc/rtc_stats_traversal.cc2
-rw-r--r--third_party/libwebrtc/pc/rtp_transceiver.cc14
-rw-r--r--third_party/libwebrtc/pc/sctp_utils_unittest.cc8
-rw-r--r--third_party/libwebrtc/pc/sdp_offer_answer.cc27
-rw-r--r--third_party/libwebrtc/pc/sdp_offer_answer_unittest.cc119
-rw-r--r--third_party/libwebrtc/pc/session_description.h26
-rw-r--r--third_party/libwebrtc/pc/test/integration_test_helpers.cc1
-rw-r--r--third_party/libwebrtc/pc/test/integration_test_helpers.h11
-rw-r--r--third_party/libwebrtc/pc/test/svc_e2e_tests.cc9
-rw-r--r--third_party/libwebrtc/pc/webrtc_sdp.cc114
-rw-r--r--third_party/libwebrtc/pc/webrtc_sdp.h2
-rw-r--r--third_party/libwebrtc/pc/webrtc_sdp_unittest.cc131
-rw-r--r--third_party/libwebrtc/rtc_base/BUILD.gn16
-rw-r--r--third_party/libwebrtc/rtc_base/async_packet_socket.cc13
-rw-r--r--third_party/libwebrtc/rtc_base/async_packet_socket.h12
-rw-r--r--third_party/libwebrtc/rtc_base/async_packet_socket_unittest.cc43
-rw-r--r--third_party/libwebrtc/rtc_base/async_udp_socket.cc40
-rw-r--r--third_party/libwebrtc/rtc_base/async_udp_socket.h7
-rw-r--r--third_party/libwebrtc/rtc_base/bitstream_reader.h5
-rw-r--r--third_party/libwebrtc/rtc_base/byte_buffer.cc22
-rw-r--r--third_party/libwebrtc/rtc_base/byte_buffer.h56
-rw-r--r--third_party/libwebrtc/rtc_base/byte_buffer_unittest.cc41
-rw-r--r--third_party/libwebrtc/rtc_base/experiments/BUILD.gn3
-rw-r--r--third_party/libwebrtc/rtc_base/experiments/alr_experiment.cc21
-rw-r--r--third_party/libwebrtc/rtc_base/experiments/alr_experiment.h10
-rw-r--r--third_party/libwebrtc/rtc_base/gunit.cc43
-rw-r--r--third_party/libwebrtc/rtc_base/gunit.h12
-rw-r--r--third_party/libwebrtc/rtc_base/nat_server.cc76
-rw-r--r--third_party/libwebrtc/rtc_base/nat_server.h18
-rw-r--r--third_party/libwebrtc/rtc_base/nat_socket_factory.cc11
-rw-r--r--third_party/libwebrtc/rtc_base/nat_socket_factory.h1
-rw-r--r--third_party/libwebrtc/rtc_base/nat_unittest.cc23
-rw-r--r--third_party/libwebrtc/rtc_base/network/BUILD.gn1
-rw-r--r--third_party/libwebrtc/rtc_base/network/received_packet.cc6
-rw-r--r--third_party/libwebrtc/rtc_base/network/received_packet.h9
-rw-r--r--third_party/libwebrtc/rtc_base/server_socket_adapters.cc4
-rw-r--r--third_party/libwebrtc/rtc_base/socket.cc22
-rw-r--r--third_party/libwebrtc/rtc_base/socket.h18
-rw-r--r--third_party/libwebrtc/rtc_base/socket_adapters.cc2
-rw-r--r--third_party/libwebrtc/rtc_base/task_queue_for_test.cc22
-rw-r--r--third_party/libwebrtc/rtc_base/task_queue_for_test.h41
-rw-r--r--third_party/libwebrtc/rtc_base/task_queue_unittest.cc30
-rw-r--r--third_party/libwebrtc/rtc_base/thread_unittest.cc17
-rw-r--r--third_party/libwebrtc/rtc_base/virtual_socket_unittest.cc22
-rw-r--r--third_party/libwebrtc/rtc_tools/BUILD.gn1
-rw-r--r--third_party/libwebrtc/rtc_tools/network_tester/BUILD.gn3
-rw-r--r--third_party/libwebrtc/rtc_tools/network_tester/test_controller.cc25
-rw-r--r--third_party/libwebrtc/rtc_tools/network_tester/test_controller.h11
-rw-r--r--third_party/libwebrtc/rtc_tools/video_replay.cc8
-rw-r--r--third_party/libwebrtc/sdk/objc/api/peerconnection/RTCPeerConnectionFactory.mm3
-rw-r--r--third_party/libwebrtc/sdk/objc/api/peerconnection/RTCStatisticsReport.mm187
-rw-r--r--third_party/libwebrtc/sdk/objc/native/src/audio/audio_device_ios.h4
-rw-r--r--third_party/libwebrtc/sdk/objc/native/src/audio/audio_device_ios.mm12
-rw-r--r--third_party/libwebrtc/stats/BUILD.gn3
-rw-r--r--third_party/libwebrtc/stats/attribute.cc172
-rw-r--r--third_party/libwebrtc/stats/g3doc/stats.md27
-rw-r--r--third_party/libwebrtc/stats/rtc_stats.cc271
-rw-r--r--third_party/libwebrtc/stats/rtc_stats_member.cc62
-rw-r--r--third_party/libwebrtc/stats/rtc_stats_report_unittest.cc22
-rw-r--r--third_party/libwebrtc/stats/rtc_stats_unittest.cc76
-rw-r--r--third_party/libwebrtc/stats/rtcstats_objects.cc733
-rw-r--r--third_party/libwebrtc/stats/test/rtc_test_stats.cc70
-rw-r--r--third_party/libwebrtc/stats/test/rtc_test_stats.h2
-rw-r--r--third_party/libwebrtc/test/BUILD.gn11
-rw-r--r--third_party/libwebrtc/test/OWNERS1
-rw-r--r--third_party/libwebrtc/test/call_test.cc2
-rw-r--r--third_party/libwebrtc/test/fake_decoder.cc2
-rw-r--r--third_party/libwebrtc/test/frame_generator_capturer.cc10
-rw-r--r--third_party/libwebrtc/test/frame_generator_capturer.h6
-rw-r--r--third_party/libwebrtc/test/fuzzers/BUILD.gn38
-rw-r--r--third_party/libwebrtc/test/fuzzers/audio_processing_configs_fuzzer.cc17
-rw-r--r--third_party/libwebrtc/test/fuzzers/corpora/receive-side-cc/testcase-5414098152390656bin0 -> 87 bytes
-rw-r--r--third_party/libwebrtc/test/fuzzers/rtp_format_h264_fuzzer.cc75
-rw-r--r--third_party/libwebrtc/test/fuzzers/rtp_format_vp8_fuzzer.cc73
-rw-r--r--third_party/libwebrtc/test/fuzzers/rtp_format_vp9_fuzzer.cc73
-rw-r--r--third_party/libwebrtc/test/network/BUILD.gn4
-rw-r--r--third_party/libwebrtc/test/network/cross_traffic_unittest.cc2
-rw-r--r--third_party/libwebrtc/test/network/network_emulation.cc17
-rw-r--r--third_party/libwebrtc/test/network/network_emulation.h16
-rw-r--r--third_party/libwebrtc/test/network/network_emulation_manager.cc19
-rw-r--r--third_party/libwebrtc/test/network/network_emulation_manager.h4
-rw-r--r--third_party/libwebrtc/test/network/network_emulation_pc_unittest.cc3
-rw-r--r--third_party/libwebrtc/test/pc/e2e/BUILD.gn3
-rw-r--r--third_party/libwebrtc/test/pc/e2e/analyzer/audio/default_audio_quality_analyzer.cc24
-rw-r--r--third_party/libwebrtc/test/pc/e2e/analyzer/video/video_quality_metrics_reporter.cc16
-rw-r--r--third_party/libwebrtc/test/pc/e2e/cross_media_metrics_reporter.cc12
-rw-r--r--third_party/libwebrtc/test/pc/e2e/network_quality_metrics_reporter.cc9
-rw-r--r--third_party/libwebrtc/test/pc/e2e/peer_connection_quality_test.cc2
-rw-r--r--third_party/libwebrtc/test/pc/e2e/stats_based_network_quality_metrics_reporter.cc18
-rw-r--r--third_party/libwebrtc/test/pc/e2e/test_peer_factory.cc9
-rw-r--r--third_party/libwebrtc/test/pc/e2e/test_peer_factory.h6
-rw-r--r--third_party/libwebrtc/test/peer_scenario/peer_scenario_client.cc3
-rw-r--r--third_party/libwebrtc/test/run_loop_unittest.cc2
-rw-r--r--third_party/libwebrtc/test/scenario/audio_stream.cc2
-rw-r--r--third_party/libwebrtc/test/scenario/video_stream.cc3
-rw-r--r--third_party/libwebrtc/test/time_controller/BUILD.gn3
-rw-r--r--third_party/libwebrtc/test/time_controller/external_time_controller_unittest.cc42
-rw-r--r--third_party/libwebrtc/test/time_controller/real_time_controller.cc5
-rw-r--r--third_party/libwebrtc/test/time_controller/real_time_controller.h3
-rw-r--r--third_party/libwebrtc/test/time_controller/simulated_time_controller.cc3
-rw-r--r--third_party/libwebrtc/test/time_controller/simulated_time_controller.h1
-rw-r--r--third_party/libwebrtc/test/time_controller/simulated_time_controller_unittest.cc38
-rw-r--r--third_party/libwebrtc/test/video_codec_tester.cc338
-rw-r--r--third_party/libwebrtc/test/video_codec_tester_unittest.cc513
-rw-r--r--third_party/libwebrtc/tools_webrtc/OWNERS1
-rwxr-xr-xthird_party/libwebrtc/tools_webrtc/libs/generate_licenses.py2
-rw-r--r--third_party/libwebrtc/tools_webrtc/mb/mb_config.pyl14
-rw-r--r--third_party/libwebrtc/video/BUILD.gn20
-rw-r--r--third_party/libwebrtc/video/config/simulcast.cc6
-rw-r--r--third_party/libwebrtc/video/frame_cadence_adapter.cc344
-rw-r--r--third_party/libwebrtc/video/frame_cadence_adapter.h3
-rw-r--r--third_party/libwebrtc/video/frame_cadence_adapter_unittest.cc249
-rw-r--r--third_party/libwebrtc/video/full_stack_tests.cc2
-rw-r--r--third_party/libwebrtc/video/render/BUILD.gn1
-rw-r--r--third_party/libwebrtc/video/render/incoming_video_stream.cc16
-rw-r--r--third_party/libwebrtc/video/render/incoming_video_stream.h8
-rw-r--r--third_party/libwebrtc/video/rtp_video_stream_receiver2.cc121
-rw-r--r--third_party/libwebrtc/video/rtp_video_stream_receiver2.h13
-rw-r--r--third_party/libwebrtc/video/rtp_video_stream_receiver2_unittest.cc151
-rw-r--r--third_party/libwebrtc/video/video_gn/moz.build1
-rw-r--r--third_party/libwebrtc/video/video_receive_stream2.cc74
-rw-r--r--third_party/libwebrtc/video/video_receive_stream2.h37
-rw-r--r--third_party/libwebrtc/video/video_receive_stream2_unittest.cc63
-rw-r--r--third_party/libwebrtc/video/video_send_stream.cc344
-rw-r--r--third_party/libwebrtc/video/video_send_stream.h140
-rw-r--r--third_party/libwebrtc/video/video_send_stream_impl.cc420
-rw-r--r--third_party/libwebrtc/video/video_send_stream_impl.h113
-rw-r--r--third_party/libwebrtc/video/video_send_stream_impl_unittest.cc164
-rw-r--r--third_party/libwebrtc/video/video_send_stream_tests.cc6
-rw-r--r--third_party/libwebrtc/video/video_stream_encoder.cc125
-rw-r--r--third_party/libwebrtc/video/video_stream_encoder.h132
-rw-r--r--third_party/libwebrtc/video/video_stream_encoder_unittest.cc7
-rw-r--r--third_party/libwebrtc/webrtc_lib_link_test.cc3
-rw-r--r--third_party/libwebrtc/whitespace.txt2
462 files changed, 48734 insertions, 46561 deletions
diff --git a/third_party/libwebrtc/.vpython3 b/third_party/libwebrtc/.vpython3
index 3f571df261..2be8efaa0a 100644
--- a/third_party/libwebrtc/.vpython3
+++ b/third_party/libwebrtc/.vpython3
@@ -22,24 +22,24 @@
# Read more about `vpython` and how to modify this file here:
# https://chromium.googlesource.com/infra/infra/+/main/doc/users/vpython.md
-python_version: "3.8"
+python_version: "3.11"
# Used by:
# third_party/catapult
wheel: <
name: "infra/python/wheels/psutil/${vpython_platform}"
- version: "version:5.8.0.chromium.2"
+ version: "version:5.8.0.chromium.3"
>
# Used by tools_webrtc/perf/process_perf_results.py.
wheel: <
name: "infra/python/wheels/httplib2-py3"
- version: "version:0.19.1"
+ version: "version:0.22.0"
>
wheel: <
- name: "infra/python/wheels/pyparsing-py2_py3"
- version: "version:2.4.7"
+ name: "infra/python/wheels/pyparsing-py3"
+ version: "version:3.1.1"
>
@@ -47,7 +47,7 @@ wheel: <
# build/toolchain/win
wheel: <
name: "infra/python/wheels/pywin32/${vpython_platform}"
- version: "version:300"
+ version: "version:306"
match_tag: <
platform: "win32"
>
@@ -59,48 +59,48 @@ wheel: <
# GRPC used by iOS test.
wheel: <
name: "infra/python/wheels/grpcio/${vpython_platform}"
- version: "version:1.44.0"
+ version: "version:1.57.0"
>
wheel: <
name: "infra/python/wheels/six-py2_py3"
- version: "version:1.15.0"
+ version: "version:1.16.0"
>
wheel: <
name: "infra/python/wheels/pbr-py2_py3"
- version: "version:3.0.0"
+ version: "version:5.9.0"
>
wheel: <
name: "infra/python/wheels/funcsigs-py2_py3"
version: "version:1.0.2"
>
wheel: <
- name: "infra/python/wheels/mock-py2_py3"
- version: "version:2.0.0"
+ name: "infra/python/wheels/mock-py3"
+ version: "version:4.0.3"
>
wheel: <
name: "infra/python/wheels/protobuf-py3"
- version: "version:3.20.0"
+ version: "version:4.25.1"
>
wheel: <
name: "infra/python/wheels/requests-py3"
version: "version:2.31.0"
>
wheel: <
- name: "infra/python/wheels/idna-py2_py3"
- version: "version:2.8"
+ name: "infra/python/wheels/idna-py3"
+ version: "version:3.4"
>
wheel: <
- name: "infra/python/wheels/urllib3-py2_py3"
- version: "version:1.26.6"
+ name: "infra/python/wheels/urllib3-py3"
+ version: "version:2.1.0"
>
wheel: <
- name: "infra/python/wheels/certifi-py2_py3"
- version: "version:2020.11.8"
+ name: "infra/python/wheels/certifi-py3"
+ version: "version:2023.11.17"
>
wheel: <
name: "infra/python/wheels/charset_normalizer-py3"
- version: "version:2.0.4"
+ version: "version:3.3.2"
>
wheel: <
name: "infra/python/wheels/brotli/${vpython_platform}"
diff --git a/third_party/libwebrtc/BUILD.gn b/third_party/libwebrtc/BUILD.gn
index 7feca08e60..85ead4162f 100644
--- a/third_party/libwebrtc/BUILD.gn
+++ b/third_party/libwebrtc/BUILD.gn
@@ -580,6 +580,7 @@ if (!build_with_chromium) {
if (build_with_mozilla) {
deps += [
+ "api/environment:environment_factory",
"api/video:video_frame",
"api/video:video_rtp_headers",
"test:rtp_test_utils",
diff --git a/third_party/libwebrtc/DEPS b/third_party/libwebrtc/DEPS
index 0f25b04412..9f5cd9fd1f 100644
--- a/third_party/libwebrtc/DEPS
+++ b/third_party/libwebrtc/DEPS
@@ -10,7 +10,7 @@ vars = {
# chromium waterfalls. More info at: crbug.com/570091.
'checkout_configuration': 'default',
'checkout_instrumented_libraries': 'checkout_linux and checkout_configuration == "default"',
- 'chromium_revision': '60cf2ce5ba3417695d02754c90bd919eb438e4b5',
+ 'chromium_revision': 'e1fb84c37d20b7b85bfdd24e4ab19967ce1b77df',
# Fetch the prebuilt binaries for llvm-cov and llvm-profdata. Needed to
# process the raw profiles produced by instrumented targets (built with
@@ -25,7 +25,7 @@ vars = {
# By default, download the fuchsia sdk from the public sdk directory.
'fuchsia_sdk_cipd_prefix': 'fuchsia/sdk/core/',
- 'fuchsia_version': 'version:16.20231129.1.1',
+ 'fuchsia_version': 'version:17.20240120.1.1',
# By default, download the fuchsia images from the fuchsia GCS bucket.
'fuchsia_images_bucket': 'fuchsia',
'checkout_fuchsia': False,
@@ -40,7 +40,7 @@ vars = {
# RBE instance to use for running remote builds
'rbe_instance': 'projects/rbe-webrtc-developer/instances/default_instance',
# reclient CIPD package version
- 'reclient_version': 're_client_version:0.121.0.e622934-gomaip',
+ 'reclient_version': 're_client_version:0.126.0.4aaef37-gomaip',
# ninja CIPD package version
# https://chrome-infra-packages.appspot.com/p/infra/3pp/tools/ninja
@@ -50,30 +50,30 @@ vars = {
deps = {
# TODO(kjellander): Move this to be Android-only.
'src/base':
- 'https://chromium.googlesource.com/chromium/src/base@f0b935140fa4d6c206b3419056f8e647ec7e6583',
+ 'https://chromium.googlesource.com/chromium/src/base@36ecc8e397422620def3bb19a7ba392810ca2442',
'src/build':
- 'https://chromium.googlesource.com/chromium/src/build@bb826aaf00833bb61244a7ab5c4ca8c69c51314a',
+ 'https://chromium.googlesource.com/chromium/src/build@28cd6ea727d171ec990e6174308451d4178d7f8e',
'src/buildtools':
- 'https://chromium.googlesource.com/chromium/src/buildtools@b17c7e870e1d722d81f59738707392accf633011',
+ 'https://chromium.googlesource.com/chromium/src/buildtools@aadc2aa5f7382cdb5bc8e9309971356cf7722773',
# Gradle 6.6.1. Used for testing Android Studio project generation for WebRTC.
'src/examples/androidtests/third_party/gradle': {
'url': 'https://chromium.googlesource.com/external/github.com/gradle/gradle.git@f2d1fb54a951d8b11d25748e4711bec8d128d7e3',
'condition': 'checkout_android',
},
'src/ios': {
- 'url': 'https://chromium.googlesource.com/chromium/src/ios@f85ff5cfa70484822ca7181012597114ae7ad125',
+ 'url': 'https://chromium.googlesource.com/chromium/src/ios@e18cc47f9334d9dcf911c724467795542a472b51',
'condition': 'checkout_ios',
},
'src/testing':
- 'https://chromium.googlesource.com/chromium/src/testing@189d923e10bfcb856eff08164d6140f93938d854',
+ 'https://chromium.googlesource.com/chromium/src/testing@450bfd79ee0369ac1a5465a12820b5d94a5956be',
'src/third_party':
- 'https://chromium.googlesource.com/chromium/src/third_party@c35e8a3c66aaeb31689af01f6ef63509504b68ff',
+ 'https://chromium.googlesource.com/chromium/src/third_party@692fab5c0074bc6fa486dce1a4aa7b2cc5609928',
'src/buildtools/linux64': {
'packages': [
{
'package': 'gn/gn/linux-${{arch}}',
- 'version': 'git_revision:7367b0df0a0aa25440303998d54045bda73935a5',
+ 'version': 'git_revision:f99e015ac35f689cfdbf46e4eb174e5d2da78d8e',
}
],
'dep_type': 'cipd',
@@ -83,7 +83,7 @@ deps = {
'packages': [
{
'package': 'gn/gn/mac-${{arch}}',
- 'version': 'git_revision:7367b0df0a0aa25440303998d54045bda73935a5',
+ 'version': 'git_revision:f99e015ac35f689cfdbf46e4eb174e5d2da78d8e',
}
],
'dep_type': 'cipd',
@@ -93,7 +93,7 @@ deps = {
'packages': [
{
'package': 'gn/gn/windows-amd64',
- 'version': 'git_revision:7367b0df0a0aa25440303998d54045bda73935a5',
+ 'version': 'git_revision:f99e015ac35f689cfdbf46e4eb174e5d2da78d8e',
}
],
'dep_type': 'cipd',
@@ -115,11 +115,11 @@ deps = {
'src/third_party/clang-format/script':
'https://chromium.googlesource.com/external/github.com/llvm/llvm-project/clang/tools/clang-format.git@e5337933f2951cacd3aeacd238ce4578163ca0b9',
'src/third_party/libc++/src':
- 'https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libcxx.git@0ad014cff4509d293e62d1d8c7ffd080bcb2f2d6',
+ 'https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libcxx.git@28aa23ffb4c7344914a5b4ac7169f12e5a12333f',
'src/third_party/libc++abi/src':
- 'https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libcxxabi.git@4cb5c2cefedc025433f81735bacbc0f773fdcd8f',
+ 'https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libcxxabi.git@ea028d4d2b8a901f6302f5371c68a24480766e2b',
'src/third_party/libunwind/src':
- 'https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libunwind.git@bbe2764382995e4ec9a8c26c50018afc9520ea4f',
+ 'https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libunwind.git@f400fdb561d4416b59b8f8a33d8ec8b79da60495',
'src/third_party/ninja': {
'packages': [
@@ -185,11 +185,11 @@ deps = {
},
'src/third_party/boringssl/src':
- 'https://boringssl.googlesource.com/boringssl.git@1b7fdbd9101dedc3e0aa3fcf4ff74eacddb34ecc',
+ 'https://boringssl.googlesource.com/boringssl.git@414f69504d30d0848b69f6453ea7fb5e88004cb4',
'src/third_party/breakpad/breakpad':
- 'https://chromium.googlesource.com/breakpad/breakpad.git@f49c2f1a2023da0cb055874fba050563dfea57db',
+ 'https://chromium.googlesource.com/breakpad/breakpad.git@62ecd463583d09eb7d15b1d410055f30b2c7bcb4',
'src/third_party/catapult':
- 'https://chromium.googlesource.com/catapult.git@ee967548fe6a699fc295d81bd05c8116bcaf5e7e',
+ 'https://chromium.googlesource.com/catapult.git@3e413d7b62c09fda8713146714ba2146a0369d86',
'src/third_party/ced/src': {
'url': 'https://chromium.googlesource.com/external/github.com/google/compact_enc_det.git@ba412eaaacd3186085babcd901679a48863c7dd5',
},
@@ -202,9 +202,9 @@ deps = {
'src/third_party/crc32c/src':
'https://chromium.googlesource.com/external/github.com/google/crc32c.git@fa5ade41ee480003d9c5af6f43567ba22e4e17e6',
'src/third_party/depot_tools':
- 'https://chromium.googlesource.com/chromium/tools/depot_tools.git@b5393e57bb81eb1b6fbecbd7f466abcb61d278b4',
+ 'https://chromium.googlesource.com/chromium/tools/depot_tools.git@46cb7d0aca592cd20ddc2f6cb16ee386b2abbf0d',
'src/third_party/ffmpeg':
- 'https://chromium.googlesource.com/chromium/third_party/ffmpeg.git@866768f35c2226f4c805844207fd11c049ebe962',
+ 'https://chromium.googlesource.com/chromium/third_party/ffmpeg.git@17525de887d54b970ffdd421a0879c1db1952307',
'src/third_party/flatbuffers/src':
'https://chromium.googlesource.com/external/github.com/google/flatbuffers.git@bcb9ef187628fe07514e57756d05e6a6296f7dc5',
'src/third_party/grpc/src': {
@@ -216,15 +216,15 @@ deps = {
'condition': 'checkout_linux',
},
'src/third_party/freetype/src':
- 'https://chromium.googlesource.com/chromium/src/third_party/freetype2.git@8f255c89e14219ca2489043f699797ee106ec6e9',
+ 'https://chromium.googlesource.com/chromium/src/third_party/freetype2.git@57617782464411201ce7bbc93b086c1b4d7d84a5',
'src/third_party/harfbuzz-ng/src':
- 'https://chromium.googlesource.com/external/github.com/harfbuzz/harfbuzz.git@920c40cd43dd7b10b7ecba3d82a46f5fea88536f',
+ 'https://chromium.googlesource.com/external/github.com/harfbuzz/harfbuzz.git@155015f4bec434ecc2f94621665844218f05ce51',
'src/third_party/google_benchmark/src': {
'url': 'https://chromium.googlesource.com/external/github.com/google/benchmark.git@b177433f3ee2513b1075140c723d73ab8901790f',
},
# WebRTC-only dependency (not present in Chromium).
'src/third_party/gtest-parallel':
- 'https://chromium.googlesource.com/external/github.com/google/gtest-parallel@f4d65b555894b301699c7c3c52906f72ea052e83',
+ 'https://chromium.googlesource.com/external/github.com/google/gtest-parallel@96f4f904922f9bf66689e749c40f314845baaac8',
'src/third_party/google-truth/src': {
'url': 'https://chromium.googlesource.com/external/github.com/google/truth.git@33387149b465f82712a817e6744847fe136949b3',
'condition': 'checkout_android',
@@ -267,7 +267,7 @@ deps = {
'packages': [
{
'package': 'chromium/third_party/kotlin_stdlib',
- 'version': 'QEHg036Jc2HWG4-ao7usl1QUexRidGFFSgqqWUpmK-YC',
+ 'version': '7f5xFu_YQrbg_vacQ5mMcUFIkMPpvM_mQ8QERRKYBvUC',
},
],
'condition': 'checkout_android',
@@ -278,7 +278,7 @@ deps = {
'packages': [
{
'package': 'chromium/third_party/kotlinc',
- 'version': 'WKNG-_aQcnsBG-F7SS-yUGLlN9roxcWYt1K_8uw27zQC',
+ 'version': '8nR_4qTn61NDCwL0G03LrNZzpgmsu5bbyRGior3fZX8C',
},
],
'condition': 'checkout_android',
@@ -288,7 +288,7 @@ deps = {
'src/third_party/libFuzzer/src':
'https://chromium.googlesource.com/external/github.com/llvm/llvm-project/compiler-rt/lib/fuzzer.git@758bd21f103a501b362b1ca46fa8fcb692eaa303',
'src/third_party/fuzztest/src':
- 'https://chromium.googlesource.com/external/github.com/google/fuzztest.git@9e3dbc646516772c70f7a100be53967323d310cb',
+ 'https://chromium.googlesource.com/external/github.com/google/fuzztest.git@12e7428ab0847b1d1dc6c4b89203adfd1f16a1ad',
'src/third_party/libjpeg_turbo':
'https://chromium.googlesource.com/chromium/deps/libjpeg_turbo.git@9b894306ec3b28cea46e84c32b56773a98c483da',
'src/third_party/libsrtp':
@@ -296,15 +296,15 @@ deps = {
'src/third_party/dav1d/libdav1d':
'https://chromium.googlesource.com/external/github.com/videolan/dav1d.git@47107e384bd1dc25674acf04d000a8cdc6195234',
'src/third_party/libaom/source/libaom':
- 'https://aomedia.googlesource.com/aom.git@af3d2a707b5a89d5ffc77260698230505d9bcd35',
+ 'https://aomedia.googlesource.com/aom.git@646f28605eed1076d784451faa05a4e91e46ff6e',
'src/third_party/libunwindstack': {
'url': 'https://chromium.googlesource.com/chromium/src/third_party/libunwindstack.git@4dbfa0e8c844c8e243b297bc185e54a99ff94f9e',
'condition': 'checkout_android',
},
'src/third_party/perfetto':
- 'https://android.googlesource.com/platform/external/perfetto.git@d8a8260e8a08b166547eecd5b6ffcbdb30421109',
+ 'https://android.googlesource.com/platform/external/perfetto.git@d6af17fef257af28ee2417216ef87d5c5b743a1b',
'src/third_party/libvpx/source/libvpx':
- 'https://chromium.googlesource.com/webm/libvpx.git@741b8f6228984e888c99849d7675ea4132eaf268',
+ 'https://chromium.googlesource.com/webm/libvpx.git@b95d17572629c676bdcfd535fb3990b9f6f8fb11',
'src/third_party/libyuv':
'https://chromium.googlesource.com/libyuv/libyuv.git@04821d1e7d60845525e8db55c7bcd41ef5be9406',
'src/third_party/lss': {
@@ -318,20 +318,20 @@ deps = {
# Used by boringssl.
'src/third_party/nasm': {
- 'url': 'https://chromium.googlesource.com/chromium/deps/nasm.git@7fc833e889d1afda72c06220e5bed8fb43b2e5ce'
+ 'url': 'https://chromium.googlesource.com/chromium/deps/nasm.git@f477acb1049f5e043904b87b825c5915084a9a29'
},
'src/third_party/openh264/src':
'https://chromium.googlesource.com/external/github.com/cisco/openh264@09a4f3ec842a8932341b195c5b01e141c8a16eb7',
'src/third_party/re2/src':
- 'https://chromium.googlesource.com/external/github.com/google/re2.git@7e0c1a9e2417e70e5f0efc323267ac71d1fa0685',
+ 'https://chromium.googlesource.com/external/github.com/google/re2.git@826ad10e58a042faf57d7c329b0fd0a04b797e0b',
'src/third_party/r8': {
'packages': [
{
'package': 'chromium/third_party/r8',
- 'version': 'wtFJRWzGTig_UR3UW82YW63l-sTznrAPEatq-o7zNqYC',
+ 'version': 'K1NPmXz0aZCAGGtC5UESEmqwT5-x6QNNb0Jo0umsez4C',
},
],
'condition': 'checkout_android',
@@ -355,7 +355,7 @@ deps = {
'condition': 'checkout_android',
},
'src/tools':
- 'https://chromium.googlesource.com/chromium/src/tools@bcc6c5bc9871bcf8842e6a42397939235fa04860',
+ 'https://chromium.googlesource.com/chromium/src/tools@51d5368f2225c34a47d1be4feafebba3b6d19579',
'src/third_party/accessibility_test_framework': {
'packages': [
@@ -394,7 +394,7 @@ deps = {
'packages': [
{
'package': 'chromium/third_party/android_toolchain/android_toolchain',
- 'version': 'NSOM616pOQCfRfDAhC72ltgjyUQp9lAWCMzlmgB18dAC',
+ 'version': 'wpJvg81kuXdMM66r_l9Doa-pLfR6S26Jd1x40LpwWEoC',
},
],
'condition': 'checkout_android',
@@ -405,7 +405,7 @@ deps = {
'packages': [
{
'package': 'chromium/third_party/androidx',
- 'version': 'fBcslNfNCVI61lUhYka626dfmzui_5hT7AWrfFSdkgMC',
+ 'version': 'BW2v6j8vjcVQrdX9fXmf686JtkLjxn-KCWhAE1XT_n4C',
},
],
'condition': 'checkout_android',
@@ -416,7 +416,7 @@ deps = {
'packages': [
{
'package': 'chromium/third_party/android_build_tools/manifest_merger',
- 'version': 'SdNR04V227YL22FMmKoS4AdLYwv6MJe8HBAZKNhXoCsC',
+ 'version': 'tFbjqslkduDT_-y8WEZlsl9iulzcm3mgienslcU71poC',
},
],
'condition': 'checkout_android',
@@ -443,7 +443,7 @@ deps = {
},
{
'package': 'chromium/third_party/android_sdk/public/cmdline-tools',
- 'version': 'Sy00LuyBIUJdRGYKwg0zjWH8eAIUvgnnNiPkI8etaZYC',
+ 'version': 'BRpfUGFd3WoveSGTLVgkQF7ugIVyywGneVICP4c0010C',
},
],
'condition': 'checkout_android',
@@ -498,7 +498,7 @@ deps = {
'packages': [
{
'package': 'chromium/third_party/turbine',
- 'version': 'e8ccyNXO5wVjI0vv5W8kfA101BaaLNjNiVH1JddpdWkC',
+ 'version': 'ABguU2WKErRBdXX1LMt0zqZListLS_05X0Rp_V7pwAYC',
},
],
'condition': 'checkout_android',
@@ -509,11 +509,11 @@ deps = {
'packages': [
{
'package': 'infra/tools/luci/isolate/${{platform}}',
- 'version': 'git_revision:1ea45c1829514ff20c476f083462e7b8fdfaf9ae',
+ 'version': 'git_revision:0d11be367258bfe14a13ff1afcf43a0bc6aedb45',
},
{
'package': 'infra/tools/luci/swarming/${{platform}}',
- 'version': 'git_revision:1ea45c1829514ff20c476f083462e7b8fdfaf9ae',
+ 'version': 'git_revision:0d11be367258bfe14a13ff1afcf43a0bc6aedb45',
},
],
'dep_type': 'cipd',
@@ -893,7 +893,7 @@ deps = {
'packages': [
{
'package': 'chromium/third_party/android_deps/libs/com_google_android_apps_common_testing_accessibility_framework_accessibility_test_framework',
- 'version': 'version:2@4.0.0.cr1',
+ 'version': 'version:2@4.1.0.cr1',
},
],
'condition': 'checkout_android',
@@ -1729,7 +1729,7 @@ deps = {
'packages': [
{
'package': 'chromium/third_party/android_deps/libs/net_bytebuddy_byte_buddy',
- 'version': 'version:2@1.14.5.cr1',
+ 'version': 'version:2@1.14.10.cr1',
},
],
'condition': 'checkout_android',
@@ -1740,7 +1740,7 @@ deps = {
'packages': [
{
'package': 'chromium/third_party/android_deps/libs/net_bytebuddy_byte_buddy_agent',
- 'version': 'version:2@1.14.5.cr1',
+ 'version': 'version:2@1.14.10.cr1',
},
],
'condition': 'checkout_android',
@@ -1949,7 +1949,7 @@ deps = {
'packages': [
{
'package': 'chromium/third_party/android_deps/libs/org_mockito_mockito_android',
- 'version': 'version:2@5.4.0.cr1',
+ 'version': 'version:2@5.8.0.cr1',
},
],
'condition': 'checkout_android',
@@ -1960,7 +1960,7 @@ deps = {
'packages': [
{
'package': 'chromium/third_party/android_deps/libs/org_mockito_mockito_core',
- 'version': 'version:2@5.4.0.cr1',
+ 'version': 'version:2@5.8.0.cr1',
},
],
'condition': 'checkout_android',
@@ -1971,7 +1971,7 @@ deps = {
'packages': [
{
'package': 'chromium/third_party/android_deps/libs/org_mockito_mockito_subclass',
- 'version': 'version:2@5.4.0.cr1',
+ 'version': 'version:2@5.8.0.cr1',
},
],
'condition': 'checkout_android',
diff --git a/third_party/libwebrtc/README.moz-ff-commit b/third_party/libwebrtc/README.moz-ff-commit
index 4a3b8c67ea..92e573e293 100644
--- a/third_party/libwebrtc/README.moz-ff-commit
+++ b/third_party/libwebrtc/README.moz-ff-commit
@@ -27690,3 +27690,849 @@ ece5cb8371
# MOZ_LIBWEBRTC_SRC=/Users/jan-ivar/moz/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
# base of lastest vendoring
6713461a2f
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+e79e722834
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+61c5e86dca
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+e125a3371f
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+f887e07234
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+242ed95fd7
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+3d9c3687a4
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+91a7beb057
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+efe02a5a92
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+15e6c17174
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+659fe7e6f2
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+8a29d89e99
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+d6601ce66b
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+539bca9ebb
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+abd7814e47
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+9384c9ea66
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+65bee96054
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+33c7edd58a
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+f8e67ba680
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+8c30149f46
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+223334933f
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+b54bf8a9af
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+5f3ac43551
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+151003d341
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+a88ea8a36f
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+63e273ad4b
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+5e3eb52497
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+eb14497d7c
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+86b1cf776e
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+b17d53d971
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+a3d2c58e38
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+42b0184458
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+5f9239bfd5
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+ae86daf830
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+dfb54b5747
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+cdd92da549
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+7b4b39809f
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+b875b8b98b
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+fa5d9b6df0
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+9ae1e41205
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+ddf6084096
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+bb91f77858
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+b3488d08db
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+23c653d2d8
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+ca98de9714
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+601ac2eea8
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+776fe6d923
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+871af9225f
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+f418f48702
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+5fe4953d2b
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+21edbe5d0d
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+ce73d3795b
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+a3e26ad6b2
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+d7ccf6df4d
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+14a7e8b300
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+0b11bd4c2e
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+e6df126b79
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+a8cd2babcd
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+d76e0898a9
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+161d2c8452
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+63d03f586b
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+399a9768f2
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+6c9c958c69
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+336fb4faf4
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+042e57deea
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+2c03790842
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+ce98e62974
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+d92e95c26e
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+6a5d925b48
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+d9e2cc2bbd
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+c202f9635e
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+6f0f158af0
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+3ba809d6a6
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+d093d0db7f
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+07e05efe1a
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+5176511644
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+1f8914d240
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+c30fb63f95
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+f0ddae8c22
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+ac60ad7acd
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+8f59f54120
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+0f86cd126b
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+c9d44b3fb9
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+5cfce0efba
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+57b06646be
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+ac2541be3a
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+acdc89d653
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+944b01eb97
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+e0e03ba73a
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+ca8353648d
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+3e801c3208
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+2b311ea96a
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+52da14c44f
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+3bb0ead42d
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+c3c455cfee
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+c5daa63cef
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+ca58f0eb9d
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+e56055220b
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+70ad987c64
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+b9ba02c025
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+8f4df7bec9
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+b2e9babeb7
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+3216c28493
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+06a8ecadf2
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+5692649b9b
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+f698a39eec
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+afedc5e7a3
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+ee27f38be9
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+c5d921899b
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+aab4a4c753
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+6f6bb66108
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+de464c2f56
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+18a42e3272
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+5366794f3a
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+4931512cb0
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+771b524606
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+51563cc36c
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+267f9bdd53
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+c039e836f3
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+662863ec93
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+331065829a
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+b74a3e591f
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+8ac008119c
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+66344aca9c
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+9e5c979743
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+0a01ffcd3f
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+e6f244e003
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+4d6e8ad95c
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+268ca56196
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+25c454a3e2
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+025a675577
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+448c4967e2
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+b57b6a299b
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+0f26166648
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+3cce50ae4b
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+d2a19311f1
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+ee2fcbab42
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+849a624c33
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+f27515bfe3
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+97439b9531
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+932e12d0fd
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+f40443424e
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+0f075331e2
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+0f2e3e1f99
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+d2771c6153
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+b78dd9373f
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+6dd30183a2
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+764ac7ec0a
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+8a74636d46
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+ae46957a51
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+67f0de8614
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+7121680f38
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+de17252e8e
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+8e2ab67045
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+b39c2a8464
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+6b559cd1a7
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+78a57efb29
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+c41977d303
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+293af4b5e0
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+a3bde0a844
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+3c8c1afaea
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+dc64596510
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+b64eef1234
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+55a61898a8
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+f089d7ea54
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+80a8683e30
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+9702f6c9fb
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+6791c9d17e
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+1d6bf3156b
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+0a96289bcb
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+b9405c4748
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+956879d86f
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+5d091cec5d
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+3a20023719
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+b7ec05777a
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+dda037db07
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+c95ad5fe9d
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+d0ad6ef0bf
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+441fb375f4
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+ff76f1ca48
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+a742df24fd
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+98b0da181b
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+f81af2f8fd
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+187ca72ab7
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+b330a79559
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+21b3c5a5ad
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+1ecf29c1ce
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+bb0044eb90
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+844225a76a
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+4e3b101bc9
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+c56052001d
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+24b034c51b
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+7978cf1b43
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+5aea42860b
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+634cb403e6
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+bfee961786
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+77605363b9
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+0bf7a6080f
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+d123ca8e41
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+2ab1997d9d
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+40dcdf7fac
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+60885b5d89
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+7cb56f5fbd
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+e707bc40ab
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+dc48289b46
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+f8587da72b
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+7852f5d37f
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+36fe51014d
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+a1e5ce67b6
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+239d5e8f24
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+e5d60f8621
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+edd804816c
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+caf9f1bf83
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+e052eee7a3
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+cddcbccb23
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+3b7afb284f
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+5aaa9ed41e
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+e126e45403
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+eb4a3140fd
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+1ac0bea35f
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+199fd755bd
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+972546ed50
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+f868b76376
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+3b500e60e8
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+3b8347e37d
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+84c48ae751
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+9e4a97bb02
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+18d1d0f793
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+b51c4b01f6
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+54be7084e0
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+2c22da6220
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+1fee69cfff
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+d257cb7333
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+111e381822
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+361d74bc36
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+df3b3bd06f
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+02d9eceb3c
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+7f457533a2
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+787c8f8845
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+7aff4d1a40
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+0f1b9a9589
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+c0ac4df7a5
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+680025a612
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+192c0628cb
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+b1799b0814
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+68b580f116
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+0b6899272c
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+7d637a9788
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+fb99c6ebb5
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+df0b363cf0
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+3abf8be180
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+55cdc29b9d
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+4d706a9fd1
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+3b2b2afdaa
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+f49d96d6e4
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+434f4cb44f
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+3e623ef57d
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+d7478a8453
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+ed1d084d0a
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+4c335b70e8
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+7209548090
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+37e9b378fd
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+0206a971f5
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+0a7fc84887
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+a9ef127f6f
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+6a992129fb
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+67ea392f27
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+25be2f802a
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+7ee67e1ee9
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+1c1c2602cb
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+5dc6c14747
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+798e451878
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+7a1f85fb85
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+11f87b2b29
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+1768705d99
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+9074f0b7d7
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+348438154a
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+f1fc6ab3ba
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+79ac694d9b
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+6fa743fbab
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+fd54a619a5
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+be2786cd23
+# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
+# base of lastest vendoring
+6b419a0536
diff --git a/third_party/libwebrtc/README.mozilla b/third_party/libwebrtc/README.mozilla
index 655cb25566..5c7f6efb78 100644
--- a/third_party/libwebrtc/README.mozilla
+++ b/third_party/libwebrtc/README.mozilla
@@ -18484,3 +18484,567 @@ libwebrtc updated from /Users/jan-ivar/moz/elm/.moz-fast-forward/moz-libwebrtc c
libwebrtc updated from /Users/jan-ivar/moz/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-02-12T16:42:26.420193.
# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/jan-ivar/moz/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
libwebrtc updated from /Users/jan-ivar/moz/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-02-12T16:43:32.839122.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-13T22:00:42.108496.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T15:42:35.942170.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T15:44:18.824799.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T15:45:34.915101.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T15:47:20.492179.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T15:49:40.891794.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T16:43:37.008021.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T16:45:09.965177.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T16:46:21.422057.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T16:47:33.409515.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T16:48:45.871351.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T16:50:01.271275.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T16:51:16.827894.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T18:07:26.463390.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:11:33.791917.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:13:00.017045.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:14:25.413262.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:16:12.942136.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:18:19.356774.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:19:40.082034.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:22:14.069601.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:24:02.087471.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:25:53.484374.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:27:15.109589.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:28:34.573821.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:29:54.571786.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:31:43.988030.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:33:04.325559.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:34:37.715350.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:35:59.384845.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:37:19.083468.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:38:40.366112.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:40:01.042328.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:41:22.922579.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:42:40.796168.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:44:01.866969.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:46:14.227464.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:47:34.259756.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:48:54.539611.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:50:16.000949.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:51:41.447662.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:53:00.923973.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:54:29.395657.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:55:54.283391.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:57:15.422985.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T19:58:38.669645.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:00:03.568520.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:01:50.828135.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:04:03.879758.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:05:26.189276.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:06:45.060687.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:07:58.859477.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:09:20.464570.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:10:42.288639.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:11:59.570671.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:14:03.511857.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:15:17.072186.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:16:34.066379.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:17:48.349602.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:19:42.628346.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:21:17.631303.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:22:31.624316.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:24:13.983809.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:25:22.658161.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:26:34.806604.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:27:46.859589.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:28:59.283970.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:30:11.996104.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:31:21.123596.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:33:21.352530.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:35:41.428688.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:37:17.141894.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:38:30.450293.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:39:46.198877.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:40:54.608394.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:42:04.066296.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:43:15.727034.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:45:10.690840.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:46:21.969373.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:47:30.322254.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:49:23.347032.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:50:32.312229.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:51:48.335711.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:53:02.866331.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:54:14.902975.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:55:30.333394.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:56:40.984188.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:57:55.287810.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T20:59:35.071902.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:01:15.149628.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:02:48.941874.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:03:58.318794.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:05:11.087856.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:06:19.770456.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:07:28.648026.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:08:41.460348.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:10:19.590293.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:11:30.905331.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:12:39.401379.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:14:45.074265.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:15:54.093476.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:17:09.104785.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:18:23.374947.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:19:37.059314.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:20:49.749727.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:22:02.610021.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:23:15.703287.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:25:42.759867.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:26:57.339704.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:28:07.729493.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:29:17.690896.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:30:32.663485.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:31:58.591610.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:33:08.677685.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:34:21.327660.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:35:51.700412.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:37:03.723493.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:38:15.965816.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:39:24.733197.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:40:36.169342.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:41:48.840415.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:43:05.348977.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:44:16.927159.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:46:43.933424.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:49:04.079413.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:50:20.806726.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:51:38.242942.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:52:49.351266.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:53:59.120973.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:55:15.092838.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:56:26.884545.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:57:42.444562.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T21:58:50.455390.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:00:05.021969.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:01:17.658072.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:02:30.948278.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:03:39.598365.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:04:47.277094.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:05:58.770776.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:07:14.147734.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:08:27.293988.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:09:36.656223.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:10:50.773764.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:12:01.496938.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:13:16.476585.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:14:26.643582.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:15:36.007691.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:16:51.216373.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:18:04.616987.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:19:16.858324.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:20:28.437676.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:21:45.360819.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:24:10.642287.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:26:22.740877.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:27:55.306685.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:29:03.531038.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:30:38.750451.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:32:38.015290.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:33:49.698722.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:34:59.426162.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:36:11.543930.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:38:10.651773.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:40:27.065384.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:42:52.666535.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:45:16.926825.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:46:31.574236.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:47:43.520106.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:49:42.234752.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:51:54.139078.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:53:51.763723.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:56:18.736341.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T22:58:17.685315.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:00:20.336945.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:01:31.801002.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:02:49.010124.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:04:51.553999.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:07:16.277804.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:08:29.411608.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:09:40.110611.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:10:52.217149.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:11:59.781588.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:14:13.783835.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:16:31.155469.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:17:42.501234.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:18:54.664507.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:20:04.000127.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:22:42.622489.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:25:03.350868.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:26:35.305987.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:28:53.839989.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:30:32.808935.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:31:51.981203.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:34:11.995582.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:35:29.616214.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:37:08.592909.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:38:26.553567.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:39:37.552013.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:40:49.290676.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:41:58.571401.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:43:10.926279.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:44:20.946693.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:45:31.633098.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:46:47.932562.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:48:05.458053.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:49:21.560351.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:50:35.400140.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:51:50.067513.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:53:03.998623.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:54:15.805017.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:55:25.997255.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:56:38.134731.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:58:50.539558.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-15T23:59:58.788052.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T01:34:04.295199.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T01:35:15.314915.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T01:36:28.420177.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T01:38:59.864702.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T01:40:12.824543.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T01:41:24.295214.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T01:42:35.675361.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T01:43:47.045677.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T01:45:44.550449.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T01:46:55.862291.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T01:48:06.902438.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T01:49:19.515185.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T01:50:49.741108.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T01:52:18.193963.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T01:54:02.333387.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T01:56:02.782038.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T01:57:30.595976.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T01:59:46.180543.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T02:01:23.845280.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T02:02:54.585533.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T02:04:31.690496.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T02:05:40.809989.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T02:07:14.458090.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T02:09:21.725567.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T02:11:16.348403.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T02:12:28.063395.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T02:14:06.362961.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T02:15:34.201576.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T02:16:52.691778.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T02:18:23.146178.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T02:20:06.149543.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T02:22:21.730477.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T14:37:22.392687.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T14:44:23.387567.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T14:46:35.379421.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T14:48:18.309663.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T14:49:44.685365.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T14:51:05.591347.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T14:52:38.734868.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T14:54:20.944106.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T14:55:40.633564.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T14:58:04.545142.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T15:00:24.783166.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T18:06:35.472245.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T18:17:25.186617.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T18:18:58.510262.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T18:21:17.255249.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T18:22:36.187201.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T18:24:57.656964.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T18:27:22.764887.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T18:28:43.118617.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T18:30:02.691240.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T18:31:39.850466.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T18:33:39.717278.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T18:34:58.906200.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T18:36:13.778028.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T18:37:28.295151.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T18:38:46.778997.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T18:40:05.274113.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T18:41:19.021637.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T18:42:37.489739.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T18:44:38.711283.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T18:45:53.859240.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T18:47:13.439963.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T18:49:36.898617.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T18:51:07.133641.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T18:52:18.956418.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T18:53:57.098291.
+# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-03-16T18:55:14.637807.
diff --git a/third_party/libwebrtc/api/BUILD.gn b/third_party/libwebrtc/api/BUILD.gn
index 10a4c8c95f..1628660c3c 100644
--- a/third_party/libwebrtc/api/BUILD.gn
+++ b/third_party/libwebrtc/api/BUILD.gn
@@ -26,15 +26,6 @@ rtc_source_set("call_api") {
sources = [ "call/audio_sink.h" ]
}
-rtc_source_set("callfactory_api") {
- visibility = [ "*" ]
- sources = [ "call/call_factory_interface.h" ]
- deps = [
- "../call:rtp_interfaces",
- "../rtc_base/system:rtc_export",
- ]
-}
-
rtc_source_set("enable_media") {
visibility = [ "*" ]
sources = [
@@ -545,6 +536,7 @@ rtc_library("rtp_parameters") {
":array_view",
":priority",
":rtp_transceiver_direction",
+ "../media:media_constants",
"../rtc_base:checks",
"../rtc_base:stringutils",
"../rtc_base/system:rtc_export",
@@ -604,7 +596,6 @@ if (!build_with_mozilla) {
deps = [
":array_view",
":audio_quality_analyzer_api",
- ":callfactory_api",
":fec_controller_api",
":frame_generator_api",
":function_view",
@@ -673,6 +664,7 @@ if (rtc_include_tests) {
"test/create_network_emulation_manager.h",
]
deps = [
+ ":field_trials_view",
":network_emulation_manager_api",
"../test/network:emulated_network",
]
@@ -811,8 +803,10 @@ rtc_source_set("rtc_stats_api") {
visibility = [ "*" ]
cflags = []
sources = [
+ "stats/attribute.h",
"stats/rtc_stats.h",
"stats/rtc_stats_collector_callback.h",
+ "stats/rtc_stats_member.h",
"stats/rtc_stats_report.h",
"stats/rtcstats_objects.h",
]
@@ -828,7 +822,10 @@ rtc_source_set("rtc_stats_api") {
"units:timestamp",
]
- absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
+ absl_deps = [
+ "//third_party/abseil-cpp/absl/types:optional",
+ "//third_party/abseil-cpp/absl/types:variant",
+ ]
}
rtc_library("audio_options_api") {
@@ -930,6 +927,7 @@ rtc_source_set("fec_controller_api") {
deps = [
"../modules:module_fec_api",
+ "environment",
"video:video_frame_type",
]
}
@@ -1434,12 +1432,12 @@ if (rtc_include_tests) {
":time_controller",
"../call",
"../call:call_interfaces",
- "../call:rtp_interfaces",
"../pc:media_factory",
"../rtc_base:checks",
"../system_wrappers",
"../test/time_controller",
"environment",
+ "environment:environment_factory",
]
absl_deps = [ "//third_party/abseil-cpp/absl/base:nullability" ]
}
diff --git a/third_party/libwebrtc/api/DEPS b/third_party/libwebrtc/api/DEPS
index 3a650b6253..5a5c285856 100644
--- a/third_party/libwebrtc/api/DEPS
+++ b/third_party/libwebrtc/api/DEPS
@@ -159,13 +159,6 @@ specific_include_rules = {
"+modules/audio_processing/include/audio_processing.h",
],
- "fake_metronome\.h": [
- "+rtc_base/synchronization/mutex.h",
- "+rtc_base/task_queue.h",
- "+rtc_base/task_utils/repeating_task.h",
- "+rtc_base/thread_annotations.h",
- ],
-
"make_ref_counted\.h": [
"+rtc_base/ref_counted_object.h",
],
diff --git a/third_party/libwebrtc/api/audio_codecs/BUILD.gn b/third_party/libwebrtc/api/audio_codecs/BUILD.gn
index 158ab74cce..2719942488 100644
--- a/third_party/libwebrtc/api/audio_codecs/BUILD.gn
+++ b/third_party/libwebrtc/api/audio_codecs/BUILD.gn
@@ -35,6 +35,7 @@ rtc_library("audio_codecs_api") {
"..:ref_count",
"..:scoped_refptr",
"../../api:field_trials_view",
+ "../../api:rtp_parameters",
"../../rtc_base:buffer",
"../../rtc_base:checks",
"../../rtc_base:event_tracer",
diff --git a/third_party/libwebrtc/api/audio_codecs/audio_format.cc b/third_party/libwebrtc/api/audio_codecs/audio_format.cc
index 2a529a49ee..8dc11fd80f 100644
--- a/third_party/libwebrtc/api/audio_codecs/audio_format.cc
+++ b/third_party/libwebrtc/api/audio_codecs/audio_format.cc
@@ -27,7 +27,7 @@ SdpAudioFormat::SdpAudioFormat(absl::string_view name,
SdpAudioFormat::SdpAudioFormat(absl::string_view name,
int clockrate_hz,
size_t num_channels,
- const Parameters& param)
+ const CodecParameterMap& param)
: name(name),
clockrate_hz(clockrate_hz),
num_channels(num_channels),
@@ -36,7 +36,7 @@ SdpAudioFormat::SdpAudioFormat(absl::string_view name,
SdpAudioFormat::SdpAudioFormat(absl::string_view name,
int clockrate_hz,
size_t num_channels,
- Parameters&& param)
+ CodecParameterMap&& param)
: name(name),
clockrate_hz(clockrate_hz),
num_channels(num_channels),
diff --git a/third_party/libwebrtc/api/audio_codecs/audio_format.h b/third_party/libwebrtc/api/audio_codecs/audio_format.h
index 0cf67799b8..edccc17e7d 100644
--- a/third_party/libwebrtc/api/audio_codecs/audio_format.h
+++ b/third_party/libwebrtc/api/audio_codecs/audio_format.h
@@ -17,6 +17,7 @@
#include <string>
#include "absl/strings/string_view.h"
+#include "api/rtp_parameters.h"
#include "rtc_base/checks.h"
#include "rtc_base/system/rtc_export.h"
@@ -24,7 +25,8 @@ namespace webrtc {
// SDP specification for a single audio codec.
struct RTC_EXPORT SdpAudioFormat {
- using Parameters = std::map<std::string, std::string>;
+ using Parameters [[deprecated(("Use webrtc::CodecParameterMap"))]] =
+ std::map<std::string, std::string>;
SdpAudioFormat(const SdpAudioFormat&);
SdpAudioFormat(SdpAudioFormat&&);
@@ -32,11 +34,11 @@ struct RTC_EXPORT SdpAudioFormat {
SdpAudioFormat(absl::string_view name,
int clockrate_hz,
size_t num_channels,
- const Parameters& param);
+ const CodecParameterMap& param);
SdpAudioFormat(absl::string_view name,
int clockrate_hz,
size_t num_channels,
- Parameters&& param);
+ CodecParameterMap&& param);
~SdpAudioFormat();
// Returns true if this format is compatible with `o`. In SDP terminology:
@@ -55,7 +57,7 @@ struct RTC_EXPORT SdpAudioFormat {
std::string name;
int clockrate_hz;
size_t num_channels;
- Parameters parameters;
+ CodecParameterMap parameters;
};
// Information about how an audio format is treated by the codec implementation.
diff --git a/third_party/libwebrtc/api/call/call_factory_interface.h b/third_party/libwebrtc/api/call/call_factory_interface.h
deleted file mode 100644
index db53d724a6..0000000000
--- a/third_party/libwebrtc/api/call/call_factory_interface.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2017 The WebRTC project authors. All Rights Reserved.
- *
- * Use of this source code is governed by a BSD-style license
- * that can be found in the LICENSE file in the root of the source
- * tree. An additional intellectual property rights grant can be found
- * in the file PATENTS. All contributing project authors may
- * be found in the AUTHORS file in the root of the source tree.
- */
-
-#ifndef API_CALL_CALL_FACTORY_INTERFACE_H_
-#define API_CALL_CALL_FACTORY_INTERFACE_H_
-
-#include <memory>
-
-#include "rtc_base/system/rtc_export.h"
-
-namespace webrtc {
-
-// These classes are not part of the API, and are treated as opaque pointers.
-class Call;
-struct CallConfig;
-
-// This interface exists to allow webrtc to be optionally built without media
-// support (i.e., if only being used for data channels). PeerConnectionFactory
-// is constructed with a CallFactoryInterface, which may or may not be null.
-// TODO(bugs.webrtc.org/15574): Delete this interface when
-// `PeerConnectionFactoryDependencies::call_factory` is removed in favor of
-// `PeerConnectionFactoryDependencies::media_factory`.
-class CallFactoryInterface {
- public:
- virtual ~CallFactoryInterface() = default;
-
- virtual std::unique_ptr<Call> CreateCall(const CallConfig& config) = 0;
-};
-
-[[deprecated("bugs.webrtc.org/15574")]] //
-RTC_EXPORT std::unique_ptr<CallFactoryInterface>
-CreateCallFactory();
-
-} // namespace webrtc
-
-#endif // API_CALL_CALL_FACTORY_INTERFACE_H_
diff --git a/third_party/libwebrtc/api/candidate.cc b/third_party/libwebrtc/api/candidate.cc
index 90cb326823..865f8e5787 100644
--- a/third_party/libwebrtc/api/candidate.cc
+++ b/third_party/libwebrtc/api/candidate.cc
@@ -17,6 +17,11 @@
namespace cricket {
+const char LOCAL_PORT_TYPE[] = "local";
+const char STUN_PORT_TYPE[] = "stun";
+const char PRFLX_PORT_TYPE[] = "prflx";
+const char RELAY_PORT_TYPE[] = "relay";
+
Candidate::Candidate()
: id_(rtc::CreateRandomString(8)),
component_(0),
@@ -57,6 +62,19 @@ Candidate::Candidate(const Candidate&) = default;
Candidate::~Candidate() = default;
+bool Candidate::is_local() const {
+ return type_ == LOCAL_PORT_TYPE;
+}
+bool Candidate::is_stun() const {
+ return type_ == STUN_PORT_TYPE;
+}
+bool Candidate::is_prflx() const {
+ return type_ == PRFLX_PORT_TYPE;
+}
+bool Candidate::is_relay() const {
+ return type_ == RELAY_PORT_TYPE;
+}
+
bool Candidate::IsEquivalent(const Candidate& c) const {
// We ignore the network name, since that is just debug information, and
// the priority and the network cost, since they should be the same if the
diff --git a/third_party/libwebrtc/api/candidate.h b/third_party/libwebrtc/api/candidate.h
index 8141d8ce38..d48f4fc559 100644
--- a/third_party/libwebrtc/api/candidate.h
+++ b/third_party/libwebrtc/api/candidate.h
@@ -26,6 +26,13 @@
namespace cricket {
+// TODO(tommi): These are temporarily here, moved from `port.h` and will
+// eventually be removed once we use enums instead of strings for these values.
+RTC_EXPORT extern const char LOCAL_PORT_TYPE[];
+RTC_EXPORT extern const char STUN_PORT_TYPE[];
+RTC_EXPORT extern const char PRFLX_PORT_TYPE[];
+RTC_EXPORT extern const char RELAY_PORT_TYPE[];
+
// TURN servers are limited to 32 in accordance with
// https://w3c.github.io/webrtc-pc/#dom-rtcconfiguration-iceservers
static constexpr size_t kMaxTurnServers = 32;
@@ -73,27 +80,6 @@ class RTC_EXPORT Candidate {
uint32_t priority() const { return priority_; }
void set_priority(const uint32_t priority) { priority_ = priority; }
- // TODO(pthatcher): Remove once Chromium's jingle/glue/utils.cc
- // doesn't use it.
- // Maps old preference (which was 0.0-1.0) to match priority (which
- // is 0-2^32-1) to to match RFC 5245, section 4.1.2.1. Also see
- // https://docs.google.com/a/google.com/document/d/
- // 1iNQDiwDKMh0NQOrCqbj3DKKRT0Dn5_5UJYhmZO-t7Uc/edit
- float preference() const {
- // The preference value is clamped to two decimal precision.
- return static_cast<float>(((priority_ >> 24) * 100 / 127) / 100.0);
- }
-
- // TODO(pthatcher): Remove once Chromium's jingle/glue/utils.cc
- // doesn't use it.
- void set_preference(float preference) {
- // Limiting priority to UINT_MAX when value exceeds uint32_t max.
- // This can happen for e.g. when preference = 3.
- uint64_t prio_val = static_cast<uint64_t>(preference * 127) << 24;
- priority_ = static_cast<uint32_t>(
- std::min(prio_val, static_cast<uint64_t>(UINT_MAX)));
- }
-
// TODO(honghaiz): Change to usernameFragment or ufrag.
const std::string& username() const { return username_; }
void set_username(absl::string_view username) { Assign(username_, username); }
@@ -111,6 +97,32 @@ class RTC_EXPORT Candidate {
Assign(type_, type);
}
+ // Provide these simple checkers to abstract away dependency on the port types
+ // that are currently defined outside of Candidate. This will ease the change
+ // from the string type to an enum.
+ bool is_local() const;
+ bool is_stun() const;
+ bool is_prflx() const;
+ bool is_relay() const;
+
+ // Returns the type preference, a value between 0-126 inclusive, with 0 being
+ // the lowest preference value, as described in RFC 5245.
+ // https://datatracker.ietf.org/doc/html/rfc5245#section-4.1.2.1
+ int type_preference() const {
+ // From https://datatracker.ietf.org/doc/html/rfc5245#section-4.1.4 :
+ // It is RECOMMENDED that default candidates be chosen based on the
+ // likelihood of those candidates to work with the peer that is being
+ // contacted.
+ // I.e. it is recommended that relayed > reflexive > host.
+ if (is_local())
+ return 1; // Host.
+ if (is_stun())
+ return 2; // Reflexive.
+ if (is_relay())
+ return 3; // Relayed.
+ return 0; // Unknown, lowest preference.
+ }
+
const std::string& network_name() const { return network_name_; }
void set_network_name(absl::string_view network_name) {
Assign(network_name_, network_name);
diff --git a/third_party/libwebrtc/api/create_peerconnection_factory.cc b/third_party/libwebrtc/api/create_peerconnection_factory.cc
index 5d3aace05f..bd77f74882 100644
--- a/third_party/libwebrtc/api/create_peerconnection_factory.cc
+++ b/third_party/libwebrtc/api/create_peerconnection_factory.cc
@@ -48,8 +48,7 @@ rtc::scoped_refptr<PeerConnectionFactoryInterface> CreatePeerConnectionFactory(
dependencies.signaling_thread = signaling_thread;
dependencies.task_queue_factory =
CreateDefaultTaskQueueFactory(field_trials.get());
- dependencies.event_log_factory = std::make_unique<RtcEventLogFactory>(
- dependencies.task_queue_factory.get());
+ dependencies.event_log_factory = std::make_unique<RtcEventLogFactory>();
dependencies.trials = std::move(field_trials);
if (network_thread) {
diff --git a/third_party/libwebrtc/api/enable_media.cc b/third_party/libwebrtc/api/enable_media.cc
index a05b1b328a..91938cc320 100644
--- a/third_party/libwebrtc/api/enable_media.cc
+++ b/third_party/libwebrtc/api/enable_media.cc
@@ -15,7 +15,7 @@
#include "api/environment/environment.h"
#include "api/peer_connection_interface.h"
-#include "call/call_factory.h"
+#include "call/create_call.h"
#include "media/engine/webrtc_media_engine.h"
#include "media/engine/webrtc_video_engine.h"
#include "media/engine/webrtc_voice_engine.h"
@@ -37,8 +37,7 @@ class MediaFactoryImpl : public MediaFactory {
~MediaFactoryImpl() override = default;
std::unique_ptr<Call> CreateCall(const CallConfig& config) override {
- CallFactory call_factory;
- return static_cast<CallFactoryInterface&>(call_factory).CreateCall(config);
+ return webrtc::CreateCall(config);
}
std::unique_ptr<MediaEngineInterface> CreateMediaEngine(
diff --git a/third_party/libwebrtc/api/environment/environment_factory.cc b/third_party/libwebrtc/api/environment/environment_factory.cc
index c0b681aa08..6f0ec40dbe 100644
--- a/third_party/libwebrtc/api/environment/environment_factory.cc
+++ b/third_party/libwebrtc/api/environment/environment_factory.cc
@@ -97,12 +97,22 @@ Environment EnvironmentFactory::CreateWithDefaults() && {
if (field_trials_ == nullptr) {
Set(std::make_unique<FieldTrialBasedConfig>());
}
+#if defined(WEBRTC_MOZILLA_BUILD)
+ // We want to use our clock, not GetRealTimeClockRaw, and we avoid
+ // building the code under third_party/libwebrtc/task_queue. To
+ // ensure we're setting up things correctly, namely providing an
+ // Environment object with a preset task_queue_factory and clock,
+ // we'll do a release assert here.
+ RTC_CHECK(clock_);
+ RTC_CHECK(task_queue_factory_);
+#else
if (clock_ == nullptr) {
Set(Clock::GetRealTimeClock());
}
if (task_queue_factory_ == nullptr) {
Set(CreateDefaultTaskQueueFactory(field_trials_));
}
+#endif
if (event_log_ == nullptr) {
Set(std::make_unique<RtcEventLogNull>());
}
diff --git a/third_party/libwebrtc/api/environment/environment_factory_gn/moz.build b/third_party/libwebrtc/api/environment/environment_factory_gn/moz.build
new file mode 100644
index 0000000000..77a2224baf
--- /dev/null
+++ b/third_party/libwebrtc/api/environment/environment_factory_gn/moz.build
@@ -0,0 +1,231 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+ ### This moz.build was AUTOMATICALLY GENERATED from a GN config, ###
+ ### DO NOT edit it by hand. ###
+
+COMPILE_FLAGS["OS_INCLUDES"] = []
+AllowCompilerWarnings()
+
+DEFINES["ABSL_ALLOCATOR_NOTHROW"] = "1"
+DEFINES["RTC_DAV1D_IN_INTERNAL_DECODER_FACTORY"] = True
+DEFINES["RTC_ENABLE_VP9"] = True
+DEFINES["WEBRTC_ENABLE_PROTOBUF"] = "0"
+DEFINES["WEBRTC_LIBRARY_IMPL"] = True
+DEFINES["WEBRTC_MOZILLA_BUILD"] = True
+DEFINES["WEBRTC_NON_STATIC_TRACE_EVENT_HANDLERS"] = "0"
+DEFINES["WEBRTC_STRICT_FIELD_TRIALS"] = "0"
+
+FINAL_LIBRARY = "webrtc"
+
+
+LOCAL_INCLUDES += [
+ "!/ipc/ipdl/_ipdlheaders",
+ "!/third_party/libwebrtc/gen",
+ "/ipc/chromium/src",
+ "/third_party/libwebrtc/",
+ "/third_party/libwebrtc/third_party/abseil-cpp/",
+ "/tools/profiler/public"
+]
+
+UNIFIED_SOURCES += [
+ "/third_party/libwebrtc/api/environment/environment_factory.cc"
+]
+
+if not CONFIG["MOZ_DEBUG"]:
+
+ DEFINES["DYNAMIC_ANNOTATIONS_ENABLED"] = "0"
+ DEFINES["NDEBUG"] = True
+ DEFINES["NVALGRIND"] = True
+
+if CONFIG["MOZ_DEBUG"] == "1":
+
+ DEFINES["DYNAMIC_ANNOTATIONS_ENABLED"] = "1"
+
+if CONFIG["OS_TARGET"] == "Android":
+
+ DEFINES["ANDROID"] = True
+ DEFINES["ANDROID_NDK_VERSION_ROLL"] = "r22_1"
+ DEFINES["HAVE_SYS_UIO_H"] = True
+ DEFINES["WEBRTC_ANDROID"] = True
+ DEFINES["WEBRTC_ANDROID_OPENSLES"] = True
+ DEFINES["WEBRTC_ENABLE_LIBEVENT"] = True
+ DEFINES["WEBRTC_LINUX"] = True
+ DEFINES["WEBRTC_POSIX"] = True
+ DEFINES["_GNU_SOURCE"] = True
+ DEFINES["__STDC_CONSTANT_MACROS"] = True
+ DEFINES["__STDC_FORMAT_MACROS"] = True
+
+ OS_LIBS += [
+ "log"
+ ]
+
+if CONFIG["OS_TARGET"] == "Darwin":
+
+ DEFINES["WEBRTC_MAC"] = True
+ DEFINES["WEBRTC_POSIX"] = True
+ DEFINES["_LIBCPP_HAS_NO_ALIGNED_ALLOCATION"] = True
+ DEFINES["__ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES"] = "0"
+ DEFINES["__STDC_CONSTANT_MACROS"] = True
+ DEFINES["__STDC_FORMAT_MACROS"] = True
+
+if CONFIG["OS_TARGET"] == "Linux":
+
+ DEFINES["USE_AURA"] = "1"
+ DEFINES["USE_GLIB"] = "1"
+ DEFINES["USE_NSS_CERTS"] = "1"
+ DEFINES["USE_OZONE"] = "1"
+ DEFINES["USE_UDEV"] = True
+ DEFINES["WEBRTC_ENABLE_LIBEVENT"] = True
+ DEFINES["WEBRTC_LINUX"] = True
+ DEFINES["WEBRTC_POSIX"] = True
+ DEFINES["_FILE_OFFSET_BITS"] = "64"
+ DEFINES["_LARGEFILE64_SOURCE"] = True
+ DEFINES["_LARGEFILE_SOURCE"] = True
+ DEFINES["__STDC_CONSTANT_MACROS"] = True
+ DEFINES["__STDC_FORMAT_MACROS"] = True
+
+ OS_LIBS += [
+ "rt"
+ ]
+
+if CONFIG["OS_TARGET"] == "OpenBSD":
+
+ DEFINES["USE_GLIB"] = "1"
+ DEFINES["USE_OZONE"] = "1"
+ DEFINES["USE_X11"] = "1"
+ DEFINES["WEBRTC_BSD"] = True
+ DEFINES["WEBRTC_ENABLE_LIBEVENT"] = True
+ DEFINES["WEBRTC_POSIX"] = True
+ DEFINES["_FILE_OFFSET_BITS"] = "64"
+ DEFINES["_LARGEFILE64_SOURCE"] = True
+ DEFINES["_LARGEFILE_SOURCE"] = True
+ DEFINES["__STDC_CONSTANT_MACROS"] = True
+ DEFINES["__STDC_FORMAT_MACROS"] = True
+
+if CONFIG["OS_TARGET"] == "WINNT":
+
+ DEFINES["CERT_CHAIN_PARA_HAS_EXTRA_FIELDS"] = True
+ DEFINES["NOMINMAX"] = True
+ DEFINES["NTDDI_VERSION"] = "0x0A000000"
+ DEFINES["PSAPI_VERSION"] = "2"
+ DEFINES["RTC_ENABLE_WIN_WGC"] = True
+ DEFINES["UNICODE"] = True
+ DEFINES["USE_AURA"] = "1"
+ DEFINES["WEBRTC_WIN"] = True
+ DEFINES["WIN32"] = True
+ DEFINES["WIN32_LEAN_AND_MEAN"] = True
+ DEFINES["WINAPI_FAMILY"] = "WINAPI_FAMILY_DESKTOP_APP"
+ DEFINES["WINVER"] = "0x0A00"
+ DEFINES["_ATL_NO_OPENGL"] = True
+ DEFINES["_CRT_RAND_S"] = True
+ DEFINES["_CRT_SECURE_NO_DEPRECATE"] = True
+ DEFINES["_ENABLE_EXTENDED_ALIGNED_STORAGE"] = True
+ DEFINES["_HAS_EXCEPTIONS"] = "0"
+ DEFINES["_HAS_NODISCARD"] = True
+ DEFINES["_SCL_SECURE_NO_DEPRECATE"] = True
+ DEFINES["_SECURE_ATL"] = True
+ DEFINES["_UNICODE"] = True
+ DEFINES["_WIN32_WINNT"] = "0x0A00"
+ DEFINES["_WINDOWS"] = True
+ DEFINES["__STD_C"] = True
+
+ OS_LIBS += [
+ "crypt32",
+ "iphlpapi",
+ "secur32",
+ "winmm"
+ ]
+
+if CONFIG["TARGET_CPU"] == "aarch64":
+
+ DEFINES["WEBRTC_ARCH_ARM64"] = True
+ DEFINES["WEBRTC_HAS_NEON"] = True
+
+if CONFIG["TARGET_CPU"] == "arm":
+
+ CXXFLAGS += [
+ "-mfpu=neon"
+ ]
+
+ DEFINES["WEBRTC_ARCH_ARM"] = True
+ DEFINES["WEBRTC_ARCH_ARM_V7"] = True
+ DEFINES["WEBRTC_HAS_NEON"] = True
+
+if CONFIG["TARGET_CPU"] == "mips32":
+
+ DEFINES["MIPS32_LE"] = True
+ DEFINES["MIPS_FPU_LE"] = True
+ DEFINES["_GNU_SOURCE"] = True
+
+if CONFIG["TARGET_CPU"] == "mips64":
+
+ DEFINES["_GNU_SOURCE"] = True
+
+if CONFIG["TARGET_CPU"] == "x86":
+
+ DEFINES["WEBRTC_ENABLE_AVX2"] = True
+
+if CONFIG["TARGET_CPU"] == "x86_64":
+
+ DEFINES["WEBRTC_ENABLE_AVX2"] = True
+
+if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "Android":
+
+ DEFINES["_DEBUG"] = True
+
+if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "Darwin":
+
+ DEFINES["_DEBUG"] = True
+
+if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "Linux":
+
+ DEFINES["_DEBUG"] = True
+
+if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "OpenBSD":
+
+ DEFINES["_DEBUG"] = True
+
+if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "WINNT":
+
+ DEFINES["_HAS_ITERATOR_DEBUGGING"] = "0"
+
+if CONFIG["MOZ_X11"] == "1" and CONFIG["OS_TARGET"] == "Linux":
+
+ DEFINES["USE_X11"] = "1"
+
+if CONFIG["OS_TARGET"] == "Android" and CONFIG["TARGET_CPU"] == "arm":
+
+ OS_LIBS += [
+ "unwind"
+ ]
+
+if CONFIG["OS_TARGET"] == "Android" and CONFIG["TARGET_CPU"] == "x86":
+
+ CXXFLAGS += [
+ "-msse2"
+ ]
+
+if CONFIG["OS_TARGET"] == "Linux" and CONFIG["TARGET_CPU"] == "aarch64":
+
+ DEFINES["_GNU_SOURCE"] = True
+
+if CONFIG["OS_TARGET"] == "Linux" and CONFIG["TARGET_CPU"] == "arm":
+
+ DEFINES["_GNU_SOURCE"] = True
+
+if CONFIG["OS_TARGET"] == "Linux" and CONFIG["TARGET_CPU"] == "x86":
+
+ CXXFLAGS += [
+ "-msse2"
+ ]
+
+ DEFINES["_GNU_SOURCE"] = True
+
+if CONFIG["OS_TARGET"] == "Linux" and CONFIG["TARGET_CPU"] == "x86_64":
+
+ DEFINES["_GNU_SOURCE"] = True
+
+Library("environment_factory_gn")
diff --git a/third_party/libwebrtc/api/fec_controller.h b/third_party/libwebrtc/api/fec_controller.h
index a9be656d6e..5c2aa3b786 100644
--- a/third_party/libwebrtc/api/fec_controller.h
+++ b/third_party/libwebrtc/api/fec_controller.h
@@ -14,6 +14,7 @@
#include <memory>
#include <vector>
+#include "api/environment/environment.h"
#include "api/video/video_frame_type.h"
#include "modules/include/module_fec_types.h"
@@ -87,8 +88,10 @@ class FecController {
class FecControllerFactoryInterface {
public:
- virtual std::unique_ptr<FecController> CreateFecController() = 0;
virtual ~FecControllerFactoryInterface() = default;
+
+ virtual std::unique_ptr<FecController> CreateFecController(
+ const Environment& env) = 0;
};
} // namespace webrtc
diff --git a/third_party/libwebrtc/api/metronome/BUILD.gn b/third_party/libwebrtc/api/metronome/BUILD.gn
index 3d3d876df0..f879d5f2fb 100644
--- a/third_party/libwebrtc/api/metronome/BUILD.gn
+++ b/third_party/libwebrtc/api/metronome/BUILD.gn
@@ -13,7 +13,7 @@ rtc_source_set("metronome") {
sources = [ "metronome.h" ]
deps = [
"../../rtc_base/system:rtc_export",
- "../task_queue",
"../units:time_delta",
]
+ absl_deps = [ "//third_party/abseil-cpp/absl/functional:any_invocable" ]
}
diff --git a/third_party/libwebrtc/api/metronome/metronome.h b/third_party/libwebrtc/api/metronome/metronome.h
index a312b1c862..4d50a3ecd0 100644
--- a/third_party/libwebrtc/api/metronome/metronome.h
+++ b/third_party/libwebrtc/api/metronome/metronome.h
@@ -11,7 +11,7 @@
#ifndef API_METRONOME_METRONOME_H_
#define API_METRONOME_METRONOME_H_
-#include "api/task_queue/task_queue_base.h"
+#include "absl/functional/any_invocable.h"
#include "api/units/time_delta.h"
#include "rtc_base/system/rtc_export.h"
diff --git a/third_party/libwebrtc/api/metronome/test/BUILD.gn b/third_party/libwebrtc/api/metronome/test/BUILD.gn
index f415d98a0b..94ecf9f727 100644
--- a/third_party/libwebrtc/api/metronome/test/BUILD.gn
+++ b/third_party/libwebrtc/api/metronome/test/BUILD.gn
@@ -16,15 +16,8 @@ rtc_library("fake_metronome") {
]
deps = [
"..:metronome",
- "../..:priority",
- "../..:sequence_checker",
- "../../../rtc_base:macromagic",
- "../../../rtc_base:rtc_event",
- "../../../rtc_base:rtc_task_queue",
- "../../../rtc_base/synchronization:mutex",
- "../../../rtc_base/task_utils:repeating_task",
- "../../../test:test_support",
"../../task_queue",
"../../units:time_delta",
]
+ absl_deps = [ "//third_party/abseil-cpp/absl/functional:any_invocable" ]
}
diff --git a/third_party/libwebrtc/api/metronome/test/fake_metronome.cc b/third_party/libwebrtc/api/metronome/test/fake_metronome.cc
index 025f7ce5a6..bd54d5b0ba 100644
--- a/third_party/libwebrtc/api/metronome/test/fake_metronome.cc
+++ b/third_party/libwebrtc/api/metronome/test/fake_metronome.cc
@@ -13,13 +13,9 @@
#include <utility>
#include <vector>
-#include "api/priority.h"
-#include "api/sequence_checker.h"
+#include "absl/functional/any_invocable.h"
#include "api/task_queue/task_queue_base.h"
-#include "api/task_queue/task_queue_factory.h"
#include "api/units/time_delta.h"
-#include "rtc_base/event.h"
-#include "rtc_base/task_utils/repeating_task.h"
namespace webrtc::test {
@@ -49,6 +45,10 @@ void ForcedTickMetronome::Tick() {
FakeMetronome::FakeMetronome(TimeDelta tick_period)
: tick_period_(tick_period) {}
+void FakeMetronome::SetTickPeriod(TimeDelta tick_period) {
+ tick_period_ = tick_period;
+}
+
void FakeMetronome::RequestCallOnNextTick(
absl::AnyInvocable<void() &&> callback) {
TaskQueueBase* current = TaskQueueBase::Current();
diff --git a/third_party/libwebrtc/api/metronome/test/fake_metronome.h b/third_party/libwebrtc/api/metronome/test/fake_metronome.h
index 73c938e9cd..9702062cf6 100644
--- a/third_party/libwebrtc/api/metronome/test/fake_metronome.h
+++ b/third_party/libwebrtc/api/metronome/test/fake_metronome.h
@@ -11,18 +11,12 @@
#ifndef API_METRONOME_TEST_FAKE_METRONOME_H_
#define API_METRONOME_TEST_FAKE_METRONOME_H_
-#include <memory>
-#include <set>
+#include <cstddef>
#include <vector>
+#include "absl/functional/any_invocable.h"
#include "api/metronome/metronome.h"
-#include "api/task_queue/task_queue_base.h"
-#include "api/task_queue/task_queue_factory.h"
#include "api/units/time_delta.h"
-#include "rtc_base/synchronization/mutex.h"
-#include "rtc_base/task_queue.h"
-#include "rtc_base/task_utils/repeating_task.h"
-#include "rtc_base/thread_annotations.h"
namespace webrtc::test {
@@ -48,19 +42,18 @@ class ForcedTickMetronome : public Metronome {
// FakeMetronome is a metronome that ticks based on a repeating task at the
// `tick_period` provided in the constructor. It is designed for use with
// simulated task queues for unit tests.
-//
-// `Stop()` must be called before destruction, as it cancels the metronome tick
-// on the proper task queue.
class FakeMetronome : public Metronome {
public:
explicit FakeMetronome(TimeDelta tick_period);
+ void SetTickPeriod(TimeDelta tick_period);
+
// Metronome implementation.
void RequestCallOnNextTick(absl::AnyInvocable<void() &&> callback) override;
TimeDelta TickPeriod() const override;
private:
- const TimeDelta tick_period_;
+ TimeDelta tick_period_;
std::vector<absl::AnyInvocable<void() &&>> callbacks_;
};
diff --git a/third_party/libwebrtc/api/peer_connection_interface.h b/third_party/libwebrtc/api/peer_connection_interface.h
index 74c4702cd2..3c225eb28a 100644
--- a/third_party/libwebrtc/api/peer_connection_interface.h
+++ b/third_party/libwebrtc/api/peer_connection_interface.h
@@ -686,7 +686,6 @@ class RTC_EXPORT PeerConnectionInterface : public webrtc::RefCountInterface {
PortAllocatorConfig port_allocator_config;
// The burst interval of the pacer, see TaskQueuePacedSender constructor.
- // TODO(hbos): Deprecated, Remove once Chromium is not setting it.
absl::optional<TimeDelta> pacer_burst_interval;
//
@@ -1441,7 +1440,12 @@ struct RTC_EXPORT PeerConnectionFactoryDependencies final {
std::unique_ptr<FieldTrialsView> trials;
std::unique_ptr<RtpTransportControllerSendFactoryInterface>
transport_controller_send_factory;
- std::unique_ptr<Metronome> metronome;
+ // Metronome used for decoding, must be called on the worker thread.
+ std::unique_ptr<Metronome> decode_metronome;
+ // Metronome used for encoding, must be called on the worker thread.
+ // TODO(b/304158952): Consider merging into a single metronome for all codec
+ // usage.
+ std::unique_ptr<Metronome> encode_metronome;
// Media specific dependencies. Unused when `media_factory == nullptr`.
rtc::scoped_refptr<AudioDeviceModule> adm;
diff --git a/third_party/libwebrtc/api/rtc_event_log/rtc_event_log_factory.cc b/third_party/libwebrtc/api/rtc_event_log/rtc_event_log_factory.cc
index 30fc6f126f..bfe272d2a8 100644
--- a/third_party/libwebrtc/api/rtc_event_log/rtc_event_log_factory.cc
+++ b/third_party/libwebrtc/api/rtc_event_log/rtc_event_log_factory.cc
@@ -31,12 +31,7 @@ absl::Nonnull<std::unique_ptr<RtcEventLog>> RtcEventLogFactory::Create(
if (env.field_trials().IsEnabled("WebRTC-RtcEventLogKillSwitch")) {
return std::make_unique<RtcEventLogNull>();
}
- RtcEventLog::EncodingType encoding_type =
- env.field_trials().IsDisabled("WebRTC-RtcEventLogNewFormat")
- ? RtcEventLog::EncodingType::Legacy
- : RtcEventLog::EncodingType::NewFormat;
- return std::make_unique<RtcEventLogImpl>(
- RtcEventLogImpl::CreateEncoder(encoding_type), &env.task_queue_factory());
+ return std::make_unique<RtcEventLogImpl>(env);
#endif
}
diff --git a/third_party/libwebrtc/api/rtc_event_log/rtc_event_log_factory.h b/third_party/libwebrtc/api/rtc_event_log/rtc_event_log_factory.h
index 21a670e1a7..1deb0612bf 100644
--- a/third_party/libwebrtc/api/rtc_event_log/rtc_event_log_factory.h
+++ b/third_party/libwebrtc/api/rtc_event_log/rtc_event_log_factory.h
@@ -26,10 +26,9 @@ class RTC_EXPORT RtcEventLogFactory : public RtcEventLogFactoryInterface {
public:
RtcEventLogFactory() = default;
- // TODO(bugs.webrtc.org/15656): deprecate and delete constructor taking
- // task queue factory in favor of using task queue factory provided through
- // the Environment parameter in Create function.
+ [[deprecated("Use default constructor")]] //
explicit RtcEventLogFactory(TaskQueueFactory* task_queue_factory) {}
+
~RtcEventLogFactory() override = default;
absl::Nonnull<std::unique_ptr<RtcEventLog>> Create(
diff --git a/third_party/libwebrtc/api/rtp_parameters.cc b/third_party/libwebrtc/api/rtp_parameters.cc
index cf8b3ad3dc..ad0f3c9396 100644
--- a/third_party/libwebrtc/api/rtp_parameters.cc
+++ b/third_party/libwebrtc/api/rtp_parameters.cc
@@ -15,6 +15,7 @@
#include <utility>
#include "api/array_view.h"
+#include "media/base/media_constants.h"
#include "rtc_base/strings/string_builder.h"
namespace webrtc {
@@ -47,6 +48,14 @@ RtcpFeedback::~RtcpFeedback() = default;
RtpCodec::RtpCodec() = default;
RtpCodec::RtpCodec(const RtpCodec&) = default;
RtpCodec::~RtpCodec() = default;
+bool RtpCodec::IsResiliencyCodec() const {
+ return name == cricket::kRtxCodecName || name == cricket::kRedCodecName ||
+ name == cricket::kUlpfecCodecName ||
+ name == cricket::kFlexfecCodecName;
+}
+bool RtpCodec::IsMediaCodec() const {
+ return !IsResiliencyCodec() && name != cricket::kComfortNoiseCodecName;
+}
RtpCodecCapability::RtpCodecCapability() = default;
RtpCodecCapability::~RtpCodecCapability() = default;
diff --git a/third_party/libwebrtc/api/rtp_parameters.h b/third_party/libwebrtc/api/rtp_parameters.h
index 09473a6ce9..025817cf37 100644
--- a/third_party/libwebrtc/api/rtp_parameters.h
+++ b/third_party/libwebrtc/api/rtp_parameters.h
@@ -29,6 +29,8 @@
namespace webrtc {
+using CodecParameterMap = std::map<std::string, std::string>;
+
// These structures are intended to mirror those defined by:
// http://draft.ortc.org/#rtcrtpdictionaries*
// Contains everything specified as of 2017 Jan 24.
@@ -165,6 +167,8 @@ struct RTC_EXPORT RtpCodec {
parameters == o.parameters;
}
bool operator!=(const RtpCodec& o) const { return !(*this == o); }
+ bool IsResiliencyCodec() const;
+ bool IsMediaCodec() const;
};
// RtpCodecCapability is to RtpCodecParameters as RtpCapabilities is to
diff --git a/third_party/libwebrtc/api/stats/attribute.h b/third_party/libwebrtc/api/stats/attribute.h
new file mode 100644
index 0000000000..09211f469c
--- /dev/null
+++ b/third_party/libwebrtc/api/stats/attribute.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2024 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef API_STATS_ATTRIBUTE_H_
+#define API_STATS_ATTRIBUTE_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "absl/types/variant.h"
+#include "api/stats/rtc_stats_member.h"
+#include "rtc_base/system/rtc_export.h"
+
+namespace webrtc {
+
+// A light-weight wrapper of an RTCStats attribute (an individual metric).
+class RTC_EXPORT Attribute {
+ public:
+ // TODO(https://crbug.com/webrtc/15164): Replace uses of RTCStatsMember<T>
+ // with absl::optional<T> and update these pointer types.
+ typedef absl::variant<const RTCStatsMember<bool>*,
+ const RTCStatsMember<int32_t>*,
+ const RTCStatsMember<uint32_t>*,
+ const RTCStatsMember<int64_t>*,
+ const RTCStatsMember<uint64_t>*,
+ const RTCStatsMember<double>*,
+ const RTCStatsMember<std::string>*,
+ const RTCStatsMember<std::vector<bool>>*,
+ const RTCStatsMember<std::vector<int32_t>>*,
+ const RTCStatsMember<std::vector<uint32_t>>*,
+ const RTCStatsMember<std::vector<int64_t>>*,
+ const RTCStatsMember<std::vector<uint64_t>>*,
+ const RTCStatsMember<std::vector<double>>*,
+ const RTCStatsMember<std::vector<std::string>>*,
+ const RTCStatsMember<std::map<std::string, uint64_t>>*,
+ const RTCStatsMember<std::map<std::string, double>>*>
+ StatVariant;
+
+ template <typename T>
+ explicit Attribute(const char* name, const RTCStatsMember<T>* attribute)
+ : name_(name), attribute_(attribute) {}
+
+ const char* name() const;
+ const StatVariant& as_variant() const;
+
+ bool has_value() const;
+ template <typename T>
+ bool holds_alternative() const {
+ return absl::holds_alternative<const RTCStatsMember<T>*>(attribute_);
+ }
+ template <typename T>
+ absl::optional<T> as_optional() const {
+ RTC_CHECK(holds_alternative<T>());
+ if (!has_value()) {
+ return absl::nullopt;
+ }
+ return absl::optional<T>(get<T>());
+ }
+ template <typename T>
+ const T& get() const {
+ RTC_CHECK(holds_alternative<T>());
+ RTC_CHECK(has_value());
+ return absl::get<const RTCStatsMember<T>*>(attribute_)->value();
+ }
+
+ bool is_sequence() const;
+ bool is_string() const;
+ std::string ToString() const;
+
+ bool operator==(const Attribute& other) const;
+ bool operator!=(const Attribute& other) const;
+
+ private:
+ const char* name_;
+ StatVariant attribute_;
+};
+
+struct RTC_EXPORT AttributeInit {
+ AttributeInit(const char* name, const Attribute::StatVariant& variant);
+
+ const char* name;
+ Attribute::StatVariant variant;
+};
+
+} // namespace webrtc
+
+#endif // API_STATS_ATTRIBUTE_H_
diff --git a/third_party/libwebrtc/api/stats/rtc_stats.h b/third_party/libwebrtc/api/stats/rtc_stats.h
index 6cc39a309f..edd293f5c9 100644
--- a/third_party/libwebrtc/api/stats/rtc_stats.h
+++ b/third_party/libwebrtc/api/stats/rtc_stats.h
@@ -20,7 +20,8 @@
#include <utility>
#include <vector>
-#include "absl/types/optional.h"
+#include "api/stats/attribute.h"
+#include "api/stats/rtc_stats_member.h"
#include "api/units/timestamp.h"
#include "rtc_base/checks.h"
#include "rtc_base/system/rtc_export.h"
@@ -28,8 +29,6 @@
namespace webrtc {
-class RTCStatsMemberInterface;
-
// Abstract base class for RTCStats-derived dictionaries, see
// https://w3c.github.io/webrtc-stats/.
//
@@ -40,8 +39,8 @@ class RTCStatsMemberInterface;
// Use the `WEBRTC_RTCSTATS_IMPL` macro when implementing subclasses, see macro
// for details.
//
-// Derived classes list their dictionary members, RTCStatsMember<T>, as public
-// fields, allowing the following:
+// Derived classes list their dictionary attributes (RTCStatsMember<T> to soon
+// be replaced by absl::optional<T>) as public fields, allowing the following:
//
// RTCFooStats foo("fooId", Timestamp::Micros(GetCurrentTime()));
// foo.bar = 42;
@@ -49,17 +48,18 @@ class RTCStatsMemberInterface;
// foo.baz->push_back("hello world");
// uint32_t x = *foo.bar;
//
-// Pointers to all the members are available with `Members`, allowing iteration:
+// Pointers to all the attributes are available with `Attributes()`, allowing
+// iteration:
//
-// for (const RTCStatsMemberInterface* member : foo.Members()) {
-// printf("%s = %s\n", member->name(), member->ValueToString().c_str());
+// for (const auto& attribute : foo.Attributes()) {
+// printf("%s = %s\n", attribute.name(), attribute.ValueToString().c_str());
// }
class RTC_EXPORT RTCStats {
public:
RTCStats(const std::string& id, Timestamp timestamp)
: id_(id), timestamp_(timestamp) {}
-
- virtual ~RTCStats() {}
+ RTCStats(const RTCStats& other);
+ virtual ~RTCStats();
virtual std::unique_ptr<RTCStats> copy() const = 0;
@@ -69,18 +69,30 @@ class RTC_EXPORT RTCStats {
// Returns the static member variable `kType` of the implementing class.
virtual const char* type() const = 0;
- // Returns a vector of pointers to all the `RTCStatsMemberInterface` members
- // of this class. This allows for iteration of members. For a given class,
- // `Members` always returns the same members in the same order.
- std::vector<const RTCStatsMemberInterface*> Members() const;
+ // Returns all attributes of this stats object, i.e. a list of its individual
+ // metrics as viewed via the Attribute wrapper.
+ std::vector<Attribute> Attributes() const;
+ template <typename T>
+ Attribute GetAttribute(const RTCStatsMember<T>& stat) const {
+ for (const auto& attribute : Attributes()) {
+ if (!attribute.holds_alternative<T>()) {
+ continue;
+ }
+ if (absl::get<const RTCStatsMember<T>*>(attribute.as_variant()) ==
+ &stat) {
+ return attribute;
+ }
+ }
+ RTC_CHECK_NOTREACHED();
+ }
// Checks if the two stats objects are of the same type and have the same
- // member values. Timestamps are not compared. These operators are exposed for
- // testing.
+ // attribute values. Timestamps are not compared. These operators are exposed
+ // for testing.
bool operator==(const RTCStats& other) const;
bool operator!=(const RTCStats& other) const;
// Creates a JSON readable string representation of the stats
- // object, listing all of its members (names and values).
+ // object, listing all of its attributes (names and values).
std::string ToJson() const;
// Downcasts the stats object to an `RTCStats` subclass `T`. DCHECKs that the
@@ -92,12 +104,8 @@ class RTC_EXPORT RTCStats {
}
protected:
- // Gets a vector of all members of this `RTCStats` object, including members
- // derived from parent classes. `additional_capacity` is how many more members
- // shall be reserved in the vector (so that subclasses can allocate a vector
- // with room for both parent and child members without it having to resize).
- virtual std::vector<const RTCStatsMemberInterface*>
- MembersOfThisObjectAndAncestors(size_t additional_capacity) const;
+ virtual std::vector<Attribute> AttributesImpl(
+ size_t additional_capacity) const;
std::string const id_;
Timestamp timestamp_;
@@ -109,9 +117,8 @@ class RTC_EXPORT RTCStats {
//
// These macros declare (in _DECL) and define (in _IMPL) the static `kType` and
// overrides methods as required by subclasses of `RTCStats`: `copy`, `type` and
-// `MembersOfThisObjectAndAncestors`. The |...| argument is a list of addresses
-// to each member defined in the implementing class. The list must have at least
-// one member.
+// `AttributesImpl`. The |...| argument is a list of addresses to each attribute
+// defined in the implementing class. The list must have at least one attribute.
//
// (Since class names need to be known to implement these methods this cannot be
// part of the base `RTCStats`. While these methods could be implemented using
@@ -144,247 +151,45 @@ class RTC_EXPORT RTCStats {
// bar("bar") {
// }
//
-#define WEBRTC_RTCSTATS_DECL() \
- protected: \
- std::vector<const webrtc::RTCStatsMemberInterface*> \
- MembersOfThisObjectAndAncestors(size_t local_var_additional_capacity) \
- const override; \
- \
- public: \
- static const char kType[]; \
- \
- std::unique_ptr<webrtc::RTCStats> copy() const override; \
- const char* type() const override
-
-#define WEBRTC_RTCSTATS_IMPL(this_class, parent_class, type_str, ...) \
- const char this_class::kType[] = type_str; \
- \
- std::unique_ptr<webrtc::RTCStats> this_class::copy() const { \
- return std::make_unique<this_class>(*this); \
- } \
- \
- const char* this_class::type() const { \
- return this_class::kType; \
- } \
- \
- std::vector<const webrtc::RTCStatsMemberInterface*> \
- this_class::MembersOfThisObjectAndAncestors( \
- size_t local_var_additional_capacity) const { \
- const webrtc::RTCStatsMemberInterface* local_var_members[] = { \
- __VA_ARGS__}; \
- size_t local_var_members_count = \
- sizeof(local_var_members) / sizeof(local_var_members[0]); \
- std::vector<const webrtc::RTCStatsMemberInterface*> \
- local_var_members_vec = parent_class::MembersOfThisObjectAndAncestors( \
- local_var_members_count + local_var_additional_capacity); \
- RTC_DCHECK_GE( \
- local_var_members_vec.capacity() - local_var_members_vec.size(), \
- local_var_members_count + local_var_additional_capacity); \
- local_var_members_vec.insert(local_var_members_vec.end(), \
- &local_var_members[0], \
- &local_var_members[local_var_members_count]); \
- return local_var_members_vec; \
- }
-
-// A version of WEBRTC_RTCSTATS_IMPL() where "..." is omitted, used to avoid a
-// compile error on windows. This is used if the stats dictionary does not
-// declare any members of its own (but perhaps its parent dictionary does).
-#define WEBRTC_RTCSTATS_IMPL_NO_MEMBERS(this_class, parent_class, type_str) \
- const char this_class::kType[] = type_str; \
- \
- std::unique_ptr<webrtc::RTCStats> this_class::copy() const { \
- return std::make_unique<this_class>(*this); \
- } \
+#define WEBRTC_RTCSTATS_DECL() \
+ protected: \
+ std::vector<webrtc::Attribute> AttributesImpl(size_t additional_capacity) \
+ const override; \
\
- const char* this_class::type() const { \
- return this_class::kType; \
- } \
+ public: \
+ static const char kType[]; \
\
- std::vector<const webrtc::RTCStatsMemberInterface*> \
- this_class::MembersOfThisObjectAndAncestors( \
- size_t local_var_additional_capacity) const { \
- return parent_class::MembersOfThisObjectAndAncestors(0); \
- }
-
-// Interface for `RTCStats` members, which have a name and a value of a type
-// defined in a subclass. Only the types listed in `Type` are supported, these
-// are implemented by `RTCStatsMember<T>`. The value of a member may be
-// undefined, the value can only be read if `is_defined`.
-class RTCStatsMemberInterface {
- public:
- // Member value types.
- enum Type {
- kBool, // bool
- kInt32, // int32_t
- kUint32, // uint32_t
- kInt64, // int64_t
- kUint64, // uint64_t
- kDouble, // double
- kString, // std::string
-
- kSequenceBool, // std::vector<bool>
- kSequenceInt32, // std::vector<int32_t>
- kSequenceUint32, // std::vector<uint32_t>
- kSequenceInt64, // std::vector<int64_t>
- kSequenceUint64, // std::vector<uint64_t>
- kSequenceDouble, // std::vector<double>
- kSequenceString, // std::vector<std::string>
-
- kMapStringUint64, // std::map<std::string, uint64_t>
- kMapStringDouble, // std::map<std::string, double>
- };
-
- virtual ~RTCStatsMemberInterface() {}
-
- const char* name() const { return name_; }
- virtual Type type() const = 0;
- virtual bool is_sequence() const = 0;
- virtual bool is_string() const = 0;
- virtual bool is_defined() const = 0;
- // Type and value comparator. The names are not compared. These operators are
- // exposed for testing.
- bool operator==(const RTCStatsMemberInterface& other) const {
- return IsEqual(other);
- }
- bool operator!=(const RTCStatsMemberInterface& other) const {
- return !(*this == other);
- }
- virtual std::string ValueToString() const = 0;
- // This is the same as ValueToString except for kInt64 and kUint64 types,
- // where the value is represented as a double instead of as an integer.
- // Since JSON stores numbers as floating point numbers, very large integers
- // cannot be accurately represented, so we prefer to display them as doubles
- // instead.
- virtual std::string ValueToJson() const = 0;
-
- template <typename T>
- const T& cast_to() const {
- RTC_DCHECK_EQ(type(), T::StaticType());
- return static_cast<const T&>(*this);
- }
-
- protected:
- explicit RTCStatsMemberInterface(const char* name) : name_(name) {}
-
- virtual bool IsEqual(const RTCStatsMemberInterface& other) const = 0;
-
- const char* const name_;
-};
-
-// Template implementation of `RTCStatsMemberInterface`.
-// The supported types are the ones described by
-// `RTCStatsMemberInterface::Type`.
-template <typename T>
-class RTCStatsMember : public RTCStatsMemberInterface {
- public:
- explicit RTCStatsMember(const char* name)
- : RTCStatsMemberInterface(name), value_() {}
- RTCStatsMember(const char* name, const T& value)
- : RTCStatsMemberInterface(name), value_(value) {}
- RTCStatsMember(const char* name, T&& value)
- : RTCStatsMemberInterface(name), value_(std::move(value)) {}
- explicit RTCStatsMember(const RTCStatsMember<T>& other)
- : RTCStatsMemberInterface(other.name_), value_(other.value_) {}
- explicit RTCStatsMember(RTCStatsMember<T>&& other)
- : RTCStatsMemberInterface(other.name_), value_(std::move(other.value_)) {}
-
- static Type StaticType();
- Type type() const override { return StaticType(); }
- bool is_sequence() const override;
- bool is_string() const override;
- bool is_defined() const override { return value_.has_value(); }
- std::string ValueToString() const override;
- std::string ValueToJson() const override;
-
- template <typename U>
- inline T ValueOrDefault(U default_value) const {
- return value_.value_or(default_value);
- }
-
- // Assignment operators.
- T& operator=(const T& value) {
- value_ = value;
- return value_.value();
- }
- T& operator=(const T&& value) {
- value_ = std::move(value);
- return value_.value();
- }
-
- // Getter methods that look the same as absl::optional<T>. Please prefer these
- // in order to unblock replacing RTCStatsMember<T> with absl::optional<T> in
- // the future (https://crbug.com/webrtc/15164).
- bool has_value() const { return value_.has_value(); }
- const T& value() const { return value_.value(); }
- T& value() { return value_.value(); }
- T& operator*() {
- RTC_DCHECK(value_);
- return *value_;
- }
- const T& operator*() const {
- RTC_DCHECK(value_);
- return *value_;
- }
- T* operator->() {
- RTC_DCHECK(value_);
- return &(*value_);
- }
- const T* operator->() const {
- RTC_DCHECK(value_);
- return &(*value_);
- }
+ std::unique_ptr<webrtc::RTCStats> copy() const override; \
+ const char* type() const override
- protected:
- bool IsEqual(const RTCStatsMemberInterface& other) const override {
- if (type() != other.type())
- return false;
- const RTCStatsMember<T>& other_t =
- static_cast<const RTCStatsMember<T>&>(other);
- return value_ == other_t.value_;
+#define WEBRTC_RTCSTATS_IMPL(this_class, parent_class, type_str, ...) \
+ const char this_class::kType[] = type_str; \
+ \
+ std::unique_ptr<webrtc::RTCStats> this_class::copy() const { \
+ return std::make_unique<this_class>(*this); \
+ } \
+ \
+ const char* this_class::type() const { \
+ return this_class::kType; \
+ } \
+ \
+ std::vector<webrtc::Attribute> this_class::AttributesImpl( \
+ size_t additional_capacity) const { \
+ webrtc::AttributeInit attribute_inits[] = {__VA_ARGS__}; \
+ size_t attribute_inits_size = \
+ sizeof(attribute_inits) / sizeof(attribute_inits[0]); \
+ std::vector<webrtc::Attribute> attributes = parent_class::AttributesImpl( \
+ attribute_inits_size + additional_capacity); \
+ for (size_t i = 0; i < attribute_inits_size; ++i) { \
+ attributes.push_back(absl::visit( \
+ [&](const auto* field) { \
+ return Attribute(attribute_inits[i].name, field); \
+ }, \
+ attribute_inits[i].variant)); \
+ } \
+ return attributes; \
}
- private:
- absl::optional<T> value_;
-};
-
-namespace rtc_stats_internal {
-
-typedef std::map<std::string, uint64_t> MapStringUint64;
-typedef std::map<std::string, double> MapStringDouble;
-
-} // namespace rtc_stats_internal
-
-#define WEBRTC_DECLARE_RTCSTATSMEMBER(T) \
- template <> \
- RTC_EXPORT RTCStatsMemberInterface::Type RTCStatsMember<T>::StaticType(); \
- template <> \
- RTC_EXPORT bool RTCStatsMember<T>::is_sequence() const; \
- template <> \
- RTC_EXPORT bool RTCStatsMember<T>::is_string() const; \
- template <> \
- RTC_EXPORT std::string RTCStatsMember<T>::ValueToString() const; \
- template <> \
- RTC_EXPORT std::string RTCStatsMember<T>::ValueToJson() const; \
- extern template class RTC_EXPORT_TEMPLATE_DECLARE(RTC_EXPORT) \
- RTCStatsMember<T>
-
-WEBRTC_DECLARE_RTCSTATSMEMBER(bool);
-WEBRTC_DECLARE_RTCSTATSMEMBER(int32_t);
-WEBRTC_DECLARE_RTCSTATSMEMBER(uint32_t);
-WEBRTC_DECLARE_RTCSTATSMEMBER(int64_t);
-WEBRTC_DECLARE_RTCSTATSMEMBER(uint64_t);
-WEBRTC_DECLARE_RTCSTATSMEMBER(double);
-WEBRTC_DECLARE_RTCSTATSMEMBER(std::string);
-WEBRTC_DECLARE_RTCSTATSMEMBER(std::vector<bool>);
-WEBRTC_DECLARE_RTCSTATSMEMBER(std::vector<int32_t>);
-WEBRTC_DECLARE_RTCSTATSMEMBER(std::vector<uint32_t>);
-WEBRTC_DECLARE_RTCSTATSMEMBER(std::vector<int64_t>);
-WEBRTC_DECLARE_RTCSTATSMEMBER(std::vector<uint64_t>);
-WEBRTC_DECLARE_RTCSTATSMEMBER(std::vector<double>);
-WEBRTC_DECLARE_RTCSTATSMEMBER(std::vector<std::string>);
-WEBRTC_DECLARE_RTCSTATSMEMBER(rtc_stats_internal::MapStringUint64);
-WEBRTC_DECLARE_RTCSTATSMEMBER(rtc_stats_internal::MapStringDouble);
-
} // namespace webrtc
#endif // API_STATS_RTC_STATS_H_
diff --git a/third_party/libwebrtc/api/stats/rtc_stats_member.h b/third_party/libwebrtc/api/stats/rtc_stats_member.h
new file mode 100644
index 0000000000..9039569ede
--- /dev/null
+++ b/third_party/libwebrtc/api/stats/rtc_stats_member.h
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2023 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef API_STATS_RTC_STATS_MEMBER_H_
+#define API_STATS_RTC_STATS_MEMBER_H_
+
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "absl/types/optional.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/system/rtc_export.h"
+#include "rtc_base/system/rtc_export_template.h"
+
+namespace webrtc {
+
+// Interface for `RTCStats` members, which have a name and a value of a type
+// defined in a subclass. Only the types listed in `Type` are supported, these
+// are implemented by `RTCStatsMember<T>`. The value of a member may be
+// undefined, the value can only be read if `is_defined`.
+class RTCStatsMemberInterface {
+ public:
+ // Member value types.
+ enum Type {
+ kBool, // bool
+ kInt32, // int32_t
+ kUint32, // uint32_t
+ kInt64, // int64_t
+ kUint64, // uint64_t
+ kDouble, // double
+ kString, // std::string
+
+ kSequenceBool, // std::vector<bool>
+ kSequenceInt32, // std::vector<int32_t>
+ kSequenceUint32, // std::vector<uint32_t>
+ kSequenceInt64, // std::vector<int64_t>
+ kSequenceUint64, // std::vector<uint64_t>
+ kSequenceDouble, // std::vector<double>
+ kSequenceString, // std::vector<std::string>
+
+ kMapStringUint64, // std::map<std::string, uint64_t>
+ kMapStringDouble, // std::map<std::string, double>
+ };
+
+ virtual ~RTCStatsMemberInterface() {}
+
+ virtual Type type() const = 0;
+ virtual bool is_sequence() const = 0;
+ virtual bool is_string() const = 0;
+ virtual bool has_value() const = 0;
+ // Type and value comparator. The names are not compared. These operators are
+ // exposed for testing.
+ bool operator==(const RTCStatsMemberInterface& other) const {
+ return IsEqual(other);
+ }
+ bool operator!=(const RTCStatsMemberInterface& other) const {
+ return !(*this == other);
+ }
+
+ virtual const RTCStatsMemberInterface* member_ptr() const { return this; }
+ template <typename T>
+ const T& cast_to() const {
+ RTC_DCHECK_EQ(type(), T::StaticType());
+ return static_cast<const T&>(*member_ptr());
+ }
+
+ protected:
+ virtual bool IsEqual(const RTCStatsMemberInterface& other) const = 0;
+};
+
+// Template implementation of `RTCStatsMemberInterface`.
+// The supported types are the ones described by
+// `RTCStatsMemberInterface::Type`.
+template <typename T>
+class RTCStatsMember : public RTCStatsMemberInterface {
+ public:
+ RTCStatsMember() {}
+ explicit RTCStatsMember(const T& value) : value_(value) {}
+
+ static Type StaticType();
+ Type type() const override { return StaticType(); }
+ bool is_sequence() const override;
+ bool is_string() const override;
+
+ template <typename U>
+ inline T value_or(U default_value) const {
+ return value_.value_or(default_value);
+ }
+ // TODO(https://crbug.com/webrtc/15164): Migrate to value_or() and delete.
+ template <typename U>
+ inline T ValueOrDefault(U default_value) const {
+ return value_or(default_value);
+ }
+
+ // Assignment operators.
+ T& operator=(const T& value) {
+ value_ = value;
+ return value_.value();
+ }
+ T& operator=(const T&& value) {
+ value_ = std::move(value);
+ return value_.value();
+ }
+
+ // Getter methods that look the same as absl::optional<T>. Please prefer these
+ // in order to unblock replacing RTCStatsMember<T> with absl::optional<T> in
+ // the future (https://crbug.com/webrtc/15164).
+ bool has_value() const override { return value_.has_value(); }
+ const T& value() const { return value_.value(); }
+ T& value() { return value_.value(); }
+ T& operator*() {
+ RTC_DCHECK(value_);
+ return *value_;
+ }
+ const T& operator*() const {
+ RTC_DCHECK(value_);
+ return *value_;
+ }
+ T* operator->() {
+ RTC_DCHECK(value_);
+ return &(*value_);
+ }
+ const T* operator->() const {
+ RTC_DCHECK(value_);
+ return &(*value_);
+ }
+
+ bool IsEqual(const RTCStatsMemberInterface& other) const override {
+ if (type() != other.type())
+ return false;
+ const RTCStatsMember<T>& other_t =
+ static_cast<const RTCStatsMember<T>&>(other);
+ return value_ == other_t.value_;
+ }
+
+ private:
+ absl::optional<T> value_;
+};
+
+namespace rtc_stats_internal {
+
+typedef std::map<std::string, uint64_t> MapStringUint64;
+typedef std::map<std::string, double> MapStringDouble;
+
+} // namespace rtc_stats_internal
+
+#define WEBRTC_DECLARE_RTCSTATSMEMBER(T) \
+ template <> \
+ RTC_EXPORT RTCStatsMemberInterface::Type RTCStatsMember<T>::StaticType(); \
+ template <> \
+ RTC_EXPORT bool RTCStatsMember<T>::is_sequence() const; \
+ template <> \
+ RTC_EXPORT bool RTCStatsMember<T>::is_string() const; \
+ extern template class RTC_EXPORT_TEMPLATE_DECLARE(RTC_EXPORT) \
+ RTCStatsMember<T>
+
+WEBRTC_DECLARE_RTCSTATSMEMBER(bool);
+WEBRTC_DECLARE_RTCSTATSMEMBER(int32_t);
+WEBRTC_DECLARE_RTCSTATSMEMBER(uint32_t);
+WEBRTC_DECLARE_RTCSTATSMEMBER(int64_t);
+WEBRTC_DECLARE_RTCSTATSMEMBER(uint64_t);
+WEBRTC_DECLARE_RTCSTATSMEMBER(double);
+WEBRTC_DECLARE_RTCSTATSMEMBER(std::string);
+WEBRTC_DECLARE_RTCSTATSMEMBER(std::vector<bool>);
+WEBRTC_DECLARE_RTCSTATSMEMBER(std::vector<int32_t>);
+WEBRTC_DECLARE_RTCSTATSMEMBER(std::vector<uint32_t>);
+WEBRTC_DECLARE_RTCSTATSMEMBER(std::vector<int64_t>);
+WEBRTC_DECLARE_RTCSTATSMEMBER(std::vector<uint64_t>);
+WEBRTC_DECLARE_RTCSTATSMEMBER(std::vector<double>);
+WEBRTC_DECLARE_RTCSTATSMEMBER(std::vector<std::string>);
+WEBRTC_DECLARE_RTCSTATSMEMBER(rtc_stats_internal::MapStringUint64);
+WEBRTC_DECLARE_RTCSTATSMEMBER(rtc_stats_internal::MapStringDouble);
+
+} // namespace webrtc
+
+#endif // API_STATS_RTC_STATS_MEMBER_H_
diff --git a/third_party/libwebrtc/api/stats/rtcstats_objects.h b/third_party/libwebrtc/api/stats/rtcstats_objects.h
index c28b635660..351c2cbefe 100644
--- a/third_party/libwebrtc/api/stats/rtcstats_objects.h
+++ b/third_party/libwebrtc/api/stats/rtcstats_objects.h
@@ -27,9 +27,7 @@ namespace webrtc {
class RTC_EXPORT RTCCertificateStats final : public RTCStats {
public:
WEBRTC_RTCSTATS_DECL();
-
RTCCertificateStats(std::string id, Timestamp timestamp);
- RTCCertificateStats(const RTCCertificateStats& other);
~RTCCertificateStats() override;
RTCStatsMember<std::string> fingerprint;
@@ -42,9 +40,7 @@ class RTC_EXPORT RTCCertificateStats final : public RTCStats {
class RTC_EXPORT RTCCodecStats final : public RTCStats {
public:
WEBRTC_RTCSTATS_DECL();
-
RTCCodecStats(std::string id, Timestamp timestamp);
- RTCCodecStats(const RTCCodecStats& other);
~RTCCodecStats() override;
RTCStatsMember<std::string> transport_id;
@@ -59,9 +55,7 @@ class RTC_EXPORT RTCCodecStats final : public RTCStats {
class RTC_EXPORT RTCDataChannelStats final : public RTCStats {
public:
WEBRTC_RTCSTATS_DECL();
-
RTCDataChannelStats(std::string id, Timestamp timestamp);
- RTCDataChannelStats(const RTCDataChannelStats& other);
~RTCDataChannelStats() override;
RTCStatsMember<std::string> label;
@@ -78,9 +72,7 @@ class RTC_EXPORT RTCDataChannelStats final : public RTCStats {
class RTC_EXPORT RTCIceCandidatePairStats final : public RTCStats {
public:
WEBRTC_RTCSTATS_DECL();
-
RTCIceCandidatePairStats(std::string id, Timestamp timestamp);
- RTCIceCandidatePairStats(const RTCIceCandidatePairStats& other);
~RTCIceCandidatePairStats() override;
RTCStatsMember<std::string> transport_id;
@@ -118,8 +110,6 @@ class RTC_EXPORT RTCIceCandidatePairStats final : public RTCStats {
class RTC_EXPORT RTCIceCandidateStats : public RTCStats {
public:
WEBRTC_RTCSTATS_DECL();
-
- RTCIceCandidateStats(const RTCIceCandidateStats& other);
~RTCIceCandidateStats() override;
RTCStatsMember<std::string> transport_id;
@@ -175,9 +165,7 @@ class RTC_EXPORT RTCRemoteIceCandidateStats final
class RTC_EXPORT RTCPeerConnectionStats final : public RTCStats {
public:
WEBRTC_RTCSTATS_DECL();
-
RTCPeerConnectionStats(std::string id, Timestamp timestamp);
- RTCPeerConnectionStats(const RTCPeerConnectionStats& other);
~RTCPeerConnectionStats() override;
RTCStatsMember<uint32_t> data_channels_opened;
@@ -188,8 +176,6 @@ class RTC_EXPORT RTCPeerConnectionStats final : public RTCStats {
class RTC_EXPORT RTCRtpStreamStats : public RTCStats {
public:
WEBRTC_RTCSTATS_DECL();
-
- RTCRtpStreamStats(const RTCRtpStreamStats& other);
~RTCRtpStreamStats() override;
RTCStatsMember<uint32_t> ssrc;
@@ -205,8 +191,6 @@ class RTC_EXPORT RTCRtpStreamStats : public RTCStats {
class RTC_EXPORT RTCReceivedRtpStreamStats : public RTCRtpStreamStats {
public:
WEBRTC_RTCSTATS_DECL();
-
- RTCReceivedRtpStreamStats(const RTCReceivedRtpStreamStats& other);
~RTCReceivedRtpStreamStats() override;
RTCStatsMember<double> jitter;
@@ -220,8 +204,6 @@ class RTC_EXPORT RTCReceivedRtpStreamStats : public RTCRtpStreamStats {
class RTC_EXPORT RTCSentRtpStreamStats : public RTCRtpStreamStats {
public:
WEBRTC_RTCSTATS_DECL();
-
- RTCSentRtpStreamStats(const RTCSentRtpStreamStats& other);
~RTCSentRtpStreamStats() override;
RTCStatsMember<uint64_t> packets_sent;
@@ -236,9 +218,7 @@ class RTC_EXPORT RTCInboundRtpStreamStats final
: public RTCReceivedRtpStreamStats {
public:
WEBRTC_RTCSTATS_DECL();
-
RTCInboundRtpStreamStats(std::string id, Timestamp timestamp);
- RTCInboundRtpStreamStats(const RTCInboundRtpStreamStats& other);
~RTCInboundRtpStreamStats() override;
RTCStatsMember<std::string> playout_id;
@@ -341,9 +321,7 @@ class RTC_EXPORT RTCOutboundRtpStreamStats final
: public RTCSentRtpStreamStats {
public:
WEBRTC_RTCSTATS_DECL();
-
RTCOutboundRtpStreamStats(std::string id, Timestamp timestamp);
- RTCOutboundRtpStreamStats(const RTCOutboundRtpStreamStats& other);
~RTCOutboundRtpStreamStats() override;
RTCStatsMember<std::string> media_source_id;
@@ -393,9 +371,7 @@ class RTC_EXPORT RTCRemoteInboundRtpStreamStats final
: public RTCReceivedRtpStreamStats {
public:
WEBRTC_RTCSTATS_DECL();
-
RTCRemoteInboundRtpStreamStats(std::string id, Timestamp timestamp);
- RTCRemoteInboundRtpStreamStats(const RTCRemoteInboundRtpStreamStats& other);
~RTCRemoteInboundRtpStreamStats() override;
RTCStatsMember<std::string> local_id;
@@ -410,9 +386,7 @@ class RTC_EXPORT RTCRemoteOutboundRtpStreamStats final
: public RTCSentRtpStreamStats {
public:
WEBRTC_RTCSTATS_DECL();
-
RTCRemoteOutboundRtpStreamStats(std::string id, Timestamp timestamp);
- RTCRemoteOutboundRtpStreamStats(const RTCRemoteOutboundRtpStreamStats& other);
~RTCRemoteOutboundRtpStreamStats() override;
RTCStatsMember<std::string> local_id;
@@ -427,8 +401,6 @@ class RTC_EXPORT RTCRemoteOutboundRtpStreamStats final
class RTC_EXPORT RTCMediaSourceStats : public RTCStats {
public:
WEBRTC_RTCSTATS_DECL();
-
- RTCMediaSourceStats(const RTCMediaSourceStats& other);
~RTCMediaSourceStats() override;
RTCStatsMember<std::string> track_identifier;
@@ -442,9 +414,7 @@ class RTC_EXPORT RTCMediaSourceStats : public RTCStats {
class RTC_EXPORT RTCAudioSourceStats final : public RTCMediaSourceStats {
public:
WEBRTC_RTCSTATS_DECL();
-
RTCAudioSourceStats(std::string id, Timestamp timestamp);
- RTCAudioSourceStats(const RTCAudioSourceStats& other);
~RTCAudioSourceStats() override;
RTCStatsMember<double> audio_level;
@@ -458,9 +428,7 @@ class RTC_EXPORT RTCAudioSourceStats final : public RTCMediaSourceStats {
class RTC_EXPORT RTCVideoSourceStats final : public RTCMediaSourceStats {
public:
WEBRTC_RTCSTATS_DECL();
-
RTCVideoSourceStats(std::string id, Timestamp timestamp);
- RTCVideoSourceStats(const RTCVideoSourceStats& other);
~RTCVideoSourceStats() override;
RTCStatsMember<uint32_t> width;
@@ -473,9 +441,7 @@ class RTC_EXPORT RTCVideoSourceStats final : public RTCMediaSourceStats {
class RTC_EXPORT RTCTransportStats final : public RTCStats {
public:
WEBRTC_RTCSTATS_DECL();
-
RTCTransportStats(std::string id, Timestamp timestamp);
- RTCTransportStats(const RTCTransportStats& other);
~RTCTransportStats() override;
RTCStatsMember<uint64_t> bytes_sent;
@@ -501,9 +467,7 @@ class RTC_EXPORT RTCTransportStats final : public RTCStats {
class RTC_EXPORT RTCAudioPlayoutStats final : public RTCStats {
public:
WEBRTC_RTCSTATS_DECL();
-
RTCAudioPlayoutStats(const std::string& id, Timestamp timestamp);
- RTCAudioPlayoutStats(const RTCAudioPlayoutStats& other);
~RTCAudioPlayoutStats() override;
RTCStatsMember<std::string> kind;
diff --git a/third_party/libwebrtc/api/task_queue/BUILD.gn b/third_party/libwebrtc/api/task_queue/BUILD.gn
index 9b2f747e78..c24c22a1f6 100644
--- a/third_party/libwebrtc/api/task_queue/BUILD.gn
+++ b/third_party/libwebrtc/api/task_queue/BUILD.gn
@@ -88,6 +88,10 @@ rtc_library("task_queue_test") {
}
rtc_library("default_task_queue_factory") {
+# Mozilla - disable this entire target to avoid inclusion of code we want
+# to avoid. Better here than trying to wack-a-mole for places that list
+# it as a dependency.
+if (!build_with_mozilla) {
visibility = [ "*" ]
if (!is_ios && !is_android) {
# Internally webrtc shouldn't rely on any specific TaskQueue implementation
@@ -119,13 +123,14 @@ rtc_library("default_task_queue_factory") {
} else if (is_mac || is_ios) {
sources += [ "default_task_queue_factory_gcd.cc" ]
deps += [ "../../rtc_base:rtc_task_queue_gcd" ]
- } else if (is_win && current_os != "winuwp") {
+ } else if (is_win && current_os != "winuwp" && !build_with_chromium) {
sources += [ "default_task_queue_factory_win.cc" ]
deps += [ "../../rtc_base:rtc_task_queue_win" ]
} else {
sources += [ "default_task_queue_factory_stdlib.cc" ]
deps += [ "../../rtc_base:rtc_task_queue_stdlib" ]
}
+} # of if (!build_with_mozilla) {
}
rtc_library("pending_task_safety_flag") {
diff --git a/third_party/libwebrtc/api/callfactory_api_gn/moz.build b/third_party/libwebrtc/api/task_queue/default_task_queue_factory_gn/moz.build
index 157a34ec8e..0911b84473 100644
--- a/third_party/libwebrtc/api/callfactory_api_gn/moz.build
+++ b/third_party/libwebrtc/api/task_queue/default_task_queue_factory_gn/moz.build
@@ -54,10 +54,6 @@ if CONFIG["OS_TARGET"] == "Android":
DEFINES["__STDC_CONSTANT_MACROS"] = True
DEFINES["__STDC_FORMAT_MACROS"] = True
- OS_LIBS += [
- "log"
- ]
-
if CONFIG["OS_TARGET"] == "Darwin":
DEFINES["WEBRTC_MAC"] = True
@@ -83,10 +79,6 @@ if CONFIG["OS_TARGET"] == "Linux":
DEFINES["__STDC_CONSTANT_MACROS"] = True
DEFINES["__STDC_FORMAT_MACROS"] = True
- OS_LIBS += [
- "rt"
- ]
-
if CONFIG["OS_TARGET"] == "OpenBSD":
DEFINES["USE_GLIB"] = "1"
@@ -128,13 +120,6 @@ if CONFIG["OS_TARGET"] == "WINNT":
DEFINES["_WINDOWS"] = True
DEFINES["__STD_C"] = True
- OS_LIBS += [
- "crypt32",
- "iphlpapi",
- "secur32",
- "winmm"
- ]
-
if CONFIG["TARGET_CPU"] == "aarch64":
DEFINES["WEBRTC_ARCH_ARM64"] = True
@@ -210,4 +195,4 @@ if CONFIG["OS_TARGET"] == "Linux" and CONFIG["TARGET_CPU"] == "x86_64":
DEFINES["_GNU_SOURCE"] = True
-Library("callfactory_api_gn")
+Library("default_task_queue_factory_gn")
diff --git a/third_party/libwebrtc/api/test/create_network_emulation_manager.cc b/third_party/libwebrtc/api/test/create_network_emulation_manager.cc
index f5d5a1bc88..14a7a6a171 100644
--- a/third_party/libwebrtc/api/test/create_network_emulation_manager.cc
+++ b/third_party/libwebrtc/api/test/create_network_emulation_manager.cc
@@ -13,15 +13,17 @@
#include <memory>
+#include "api/field_trials_view.h"
#include "test/network/network_emulation_manager.h"
namespace webrtc {
std::unique_ptr<NetworkEmulationManager> CreateNetworkEmulationManager(
TimeMode time_mode,
- EmulatedNetworkStatsGatheringMode stats_gathering_mode) {
+ EmulatedNetworkStatsGatheringMode stats_gathering_mode,
+ const FieldTrialsView* field_trials) {
return std::make_unique<test::NetworkEmulationManagerImpl>(
- time_mode, stats_gathering_mode);
+ time_mode, stats_gathering_mode, field_trials);
}
} // namespace webrtc
diff --git a/third_party/libwebrtc/api/test/create_network_emulation_manager.h b/third_party/libwebrtc/api/test/create_network_emulation_manager.h
index 941b2b1c52..2f2dfeda28 100644
--- a/third_party/libwebrtc/api/test/create_network_emulation_manager.h
+++ b/third_party/libwebrtc/api/test/create_network_emulation_manager.h
@@ -13,6 +13,7 @@
#include <memory>
+#include "api/field_trials_view.h"
#include "api/test/network_emulation_manager.h"
namespace webrtc {
@@ -21,7 +22,8 @@ namespace webrtc {
std::unique_ptr<NetworkEmulationManager> CreateNetworkEmulationManager(
TimeMode time_mode = TimeMode::kRealTime,
EmulatedNetworkStatsGatheringMode stats_gathering_mode =
- EmulatedNetworkStatsGatheringMode::kDefault);
+ EmulatedNetworkStatsGatheringMode::kDefault,
+ const FieldTrialsView* field_trials = nullptr);
} // namespace webrtc
diff --git a/third_party/libwebrtc/api/test/create_time_controller.cc b/third_party/libwebrtc/api/test/create_time_controller.cc
index 7523e05208..cbf1f09aa1 100644
--- a/third_party/libwebrtc/api/test/create_time_controller.cc
+++ b/third_party/libwebrtc/api/test/create_time_controller.cc
@@ -16,10 +16,10 @@
#include "absl/base/nullability.h"
#include "api/enable_media_with_defaults.h"
#include "api/environment/environment.h"
+#include "api/environment/environment_factory.h"
#include "api/peer_connection_interface.h"
#include "call/call.h"
-#include "call/rtp_transport_config.h"
-#include "call/rtp_transport_controller_send_factory_interface.h"
+#include "call/call_config.h"
#include "pc/media_factory.h"
#include "rtc_base/checks.h"
#include "system_wrappers/include/clock.h"
@@ -49,9 +49,12 @@ void EnableMediaWithDefaultsAndTimeController(
: clock_(clock), media_factory_(std::move(media_factory)) {}
std::unique_ptr<Call> CreateCall(const CallConfig& config) override {
- return Call::Create(config, clock_,
- config.rtp_transport_controller_send_factory->Create(
- config.ExtractTransportConfig(), clock_));
+ EnvironmentFactory env_factory(config.env);
+ env_factory.Set(clock_);
+
+ CallConfig config_with_custom_clock = config;
+ config_with_custom_clock.env = env_factory.Create();
+ return media_factory_->CreateCall(config_with_custom_clock);
}
std::unique_ptr<cricket::MediaEngineInterface> CreateMediaEngine(
diff --git a/third_party/libwebrtc/api/test/pclf/BUILD.gn b/third_party/libwebrtc/api/test/pclf/BUILD.gn
index 372ff51f49..4f62984e83 100644
--- a/third_party/libwebrtc/api/test/pclf/BUILD.gn
+++ b/third_party/libwebrtc/api/test/pclf/BUILD.gn
@@ -20,7 +20,6 @@ rtc_source_set("media_configuration") {
"../..:array_view",
"../..:audio_options_api",
"../..:audio_quality_analyzer_api",
- "../..:callfactory_api",
"../..:fec_controller_api",
"../..:frame_generator_api",
"../..:function_view",
diff --git a/third_party/libwebrtc/api/test/pclf/media_configuration.h b/third_party/libwebrtc/api/test/pclf/media_configuration.h
index 5c3440c293..ad29e17e7d 100644
--- a/third_party/libwebrtc/api/test/pclf/media_configuration.h
+++ b/third_party/libwebrtc/api/test/pclf/media_configuration.h
@@ -26,7 +26,6 @@
#include "api/array_view.h"
#include "api/audio/audio_mixer.h"
#include "api/audio_options.h"
-#include "api/call/call_factory_interface.h"
#include "api/fec_controller.h"
#include "api/function_view.h"
#include "api/media_stream_interface.h"
diff --git a/third_party/libwebrtc/api/test/pclf/media_quality_test_params.h b/third_party/libwebrtc/api/test/pclf/media_quality_test_params.h
index aad04c3eb6..8a3a13a33b 100644
--- a/third_party/libwebrtc/api/test/pclf/media_quality_test_params.h
+++ b/third_party/libwebrtc/api/test/pclf/media_quality_test_params.h
@@ -136,6 +136,7 @@ struct Params {
// provided into VideoEncoder::SetRates(...).
double video_encoder_bitrate_multiplier = 1.0;
+ PeerConnectionFactoryInterface::Options peer_connection_factory_options;
PeerConnectionInterface::RTCConfiguration rtc_configuration;
PeerConnectionInterface::RTCOfferAnswerOptions rtc_offer_answer_options;
BitrateSettings bitrate_settings;
diff --git a/third_party/libwebrtc/api/test/pclf/peer_configurer.cc b/third_party/libwebrtc/api/test/pclf/peer_configurer.cc
index 5e385452b1..ac0d02818f 100644
--- a/third_party/libwebrtc/api/test/pclf/peer_configurer.cc
+++ b/third_party/libwebrtc/api/test/pclf/peer_configurer.cc
@@ -205,6 +205,11 @@ PeerConfigurer* PeerConfigurer::SetAecDumpPath(absl::string_view path) {
params_->aec_dump_path = std::string(path);
return this;
}
+PeerConfigurer* PeerConfigurer::SetPCFOptions(
+ PeerConnectionFactoryInterface::Options options) {
+ params_->peer_connection_factory_options = std::move(options);
+ return this;
+}
PeerConfigurer* PeerConfigurer::SetRTCConfiguration(
PeerConnectionInterface::RTCConfiguration configuration) {
params_->rtc_configuration = std::move(configuration);
diff --git a/third_party/libwebrtc/api/test/pclf/peer_configurer.h b/third_party/libwebrtc/api/test/pclf/peer_configurer.h
index c0faf8573a..1c6fb4c0e6 100644
--- a/third_party/libwebrtc/api/test/pclf/peer_configurer.h
+++ b/third_party/libwebrtc/api/test/pclf/peer_configurer.h
@@ -158,6 +158,8 @@ class PeerConfigurer {
// If is set, an AEC dump will be saved in that location and it will be
// available for further analysis.
PeerConfigurer* SetAecDumpPath(absl::string_view path);
+ PeerConfigurer* SetPCFOptions(
+ PeerConnectionFactoryInterface::Options options);
PeerConfigurer* SetRTCConfiguration(
PeerConnectionInterface::RTCConfiguration configuration);
PeerConfigurer* SetRTCOfferAnswerOptions(
diff --git a/third_party/libwebrtc/api/test/peerconnection_quality_test_fixture.h b/third_party/libwebrtc/api/test/peerconnection_quality_test_fixture.h
index 034e13ff3b..7e19eb1629 100644
--- a/third_party/libwebrtc/api/test/peerconnection_quality_test_fixture.h
+++ b/third_party/libwebrtc/api/test/peerconnection_quality_test_fixture.h
@@ -26,7 +26,6 @@
#include "absl/types/optional.h"
#include "api/array_view.h"
#include "api/audio/audio_mixer.h"
-#include "api/call/call_factory_interface.h"
#include "api/fec_controller.h"
#include "api/function_view.h"
#include "api/media_stream_interface.h"
diff --git a/third_party/libwebrtc/api/test/video_quality_test_fixture.h b/third_party/libwebrtc/api/test/video_quality_test_fixture.h
index b45faef286..cbe547b60d 100644
--- a/third_party/libwebrtc/api/test/video_quality_test_fixture.h
+++ b/third_party/libwebrtc/api/test/video_quality_test_fixture.h
@@ -61,7 +61,7 @@ class VideoQualityTestFixtureInterface {
bool automatic_scaling = false;
std::string clip_path; // "Generator" to generate frames instead.
size_t capture_device_index = 0;
- SdpVideoFormat::Parameters sdp_params;
+ CodecParameterMap sdp_params;
double encoder_overshoot_factor = 0.0;
} video[2];
struct Audio {
diff --git a/third_party/libwebrtc/api/transport/rtp/dependency_descriptor.h b/third_party/libwebrtc/api/transport/rtp/dependency_descriptor.h
index 0db600918e..f546a0aa3f 100644
--- a/third_party/libwebrtc/api/transport/rtp/dependency_descriptor.h
+++ b/third_party/libwebrtc/api/transport/rtp/dependency_descriptor.h
@@ -78,6 +78,27 @@ struct FrameDependencyStructure {
std::vector<FrameDependencyTemplate> templates;
};
+class DependencyDescriptorMandatory {
+ public:
+ void set_frame_number(int frame_number) { frame_number_ = frame_number; }
+ int frame_number() const { return frame_number_; }
+
+ void set_template_id(int template_id) { template_id_ = template_id; }
+ int template_id() const { return template_id_; }
+
+ void set_first_packet_in_frame(bool first) { first_packet_in_frame_ = first; }
+ bool first_packet_in_frame() const { return first_packet_in_frame_; }
+
+ void set_last_packet_in_frame(bool last) { last_packet_in_frame_ = last; }
+ bool last_packet_in_frame() const { return last_packet_in_frame_; }
+
+ private:
+ int frame_number_;
+ int template_id_;
+ bool first_packet_in_frame_;
+ bool last_packet_in_frame_;
+};
+
struct DependencyDescriptor {
static constexpr int kMaxSpatialIds = 4;
static constexpr int kMaxTemporalIds = 8;
diff --git a/third_party/libwebrtc/api/transport/stun.cc b/third_party/libwebrtc/api/transport/stun.cc
index 7ef6852260..ca90515952 100644
--- a/third_party/libwebrtc/api/transport/stun.cc
+++ b/third_party/libwebrtc/api/transport/stun.cc
@@ -587,7 +587,7 @@ bool StunMessage::AddFingerprint() {
bool StunMessage::Read(ByteBufferReader* buf) {
// Keep a copy of the buffer data around for later verification.
- buffer_.assign(buf->Data(), buf->Length());
+ buffer_.assign(reinterpret_cast<const char*>(buf->Data()), buf->Length());
if (!buf->ReadUInt16(&type_)) {
return false;
@@ -603,8 +603,8 @@ bool StunMessage::Read(ByteBufferReader* buf) {
return false;
}
- std::string magic_cookie;
- if (!buf->ReadString(&magic_cookie, kStunMagicCookieLength)) {
+ absl::string_view magic_cookie;
+ if (!buf->ReadStringView(&magic_cookie, kStunMagicCookieLength)) {
return false;
}
@@ -814,7 +814,7 @@ void StunAttribute::ConsumePadding(ByteBufferReader* buf) const {
void StunAttribute::WritePadding(ByteBufferWriter* buf) const {
int remainder = length_ % 4;
if (remainder > 0) {
- char zeroes[4] = {0};
+ uint8_t zeroes[4] = {0};
buf->WriteBytes(zeroes, 4 - remainder);
}
}
@@ -949,12 +949,12 @@ bool StunAddressAttribute::Write(ByteBufferWriter* buf) const {
switch (address_.family()) {
case AF_INET: {
in_addr v4addr = address_.ipaddr().ipv4_address();
- buf->WriteBytes(reinterpret_cast<char*>(&v4addr), sizeof(v4addr));
+ buf->WriteBytes(reinterpret_cast<uint8_t*>(&v4addr), sizeof(v4addr));
break;
}
case AF_INET6: {
in6_addr v6addr = address_.ipaddr().ipv6_address();
- buf->WriteBytes(reinterpret_cast<char*>(&v6addr), sizeof(v6addr));
+ buf->WriteBytes(reinterpret_cast<uint8_t*>(&v6addr), sizeof(v6addr));
break;
}
}
@@ -1039,12 +1039,14 @@ bool StunXorAddressAttribute::Write(ByteBufferWriter* buf) const {
switch (xored_ip.family()) {
case AF_INET: {
in_addr v4addr = xored_ip.ipv4_address();
- buf->WriteBytes(reinterpret_cast<const char*>(&v4addr), sizeof(v4addr));
+ buf->WriteBytes(reinterpret_cast<const uint8_t*>(&v4addr),
+ sizeof(v4addr));
break;
}
case AF_INET6: {
in6_addr v6addr = xored_ip.ipv6_address();
- buf->WriteBytes(reinterpret_cast<const char*>(&v6addr), sizeof(v6addr));
+ buf->WriteBytes(reinterpret_cast<const uint8_t*>(&v6addr),
+ sizeof(v6addr));
break;
}
}
@@ -1170,7 +1172,7 @@ bool StunByteStringAttribute::Write(ByteBufferWriter* buf) const {
if (!LengthValid(type(), length())) {
return false;
}
- buf->WriteBytes(reinterpret_cast<const char*>(bytes_), length());
+ buf->WriteBytes(bytes_, length());
WritePadding(buf);
return true;
}
diff --git a/third_party/libwebrtc/api/video_codecs/BUILD.gn b/third_party/libwebrtc/api/video_codecs/BUILD.gn
index 94c9cc8b87..3865f4fee7 100644
--- a/third_party/libwebrtc/api/video_codecs/BUILD.gn
+++ b/third_party/libwebrtc/api/video_codecs/BUILD.gn
@@ -83,6 +83,7 @@ rtc_library("video_codecs_api") {
"..:fec_controller_api",
"..:scoped_refptr",
"../../api:array_view",
+ "../../api:rtp_parameters",
"../../modules/video_coding:codec_globals_headers",
"../../rtc_base:checks",
"../../rtc_base:logging",
diff --git a/third_party/libwebrtc/api/video_codecs/av1_profile.cc b/third_party/libwebrtc/api/video_codecs/av1_profile.cc
index eefe166d80..59d7b13e51 100644
--- a/third_party/libwebrtc/api/video_codecs/av1_profile.cc
+++ b/third_party/libwebrtc/api/video_codecs/av1_profile.cc
@@ -50,7 +50,7 @@ absl::optional<AV1Profile> StringToAV1Profile(absl::string_view str) {
}
absl::optional<AV1Profile> ParseSdpForAV1Profile(
- const SdpVideoFormat::Parameters& params) {
+ const CodecParameterMap& params) {
const auto profile_it = params.find(kAV1FmtpProfile);
if (profile_it == params.end())
return AV1Profile::kProfile0;
@@ -58,8 +58,8 @@ absl::optional<AV1Profile> ParseSdpForAV1Profile(
return StringToAV1Profile(profile_str);
}
-bool AV1IsSameProfile(const SdpVideoFormat::Parameters& params1,
- const SdpVideoFormat::Parameters& params2) {
+bool AV1IsSameProfile(const CodecParameterMap& params1,
+ const CodecParameterMap& params2) {
const absl::optional<AV1Profile> profile = ParseSdpForAV1Profile(params1);
const absl::optional<AV1Profile> other_profile =
ParseSdpForAV1Profile(params2);
diff --git a/third_party/libwebrtc/api/video_codecs/av1_profile.h b/third_party/libwebrtc/api/video_codecs/av1_profile.h
index 2254d5ecd3..bc9767631c 100644
--- a/third_party/libwebrtc/api/video_codecs/av1_profile.h
+++ b/third_party/libwebrtc/api/video_codecs/av1_profile.h
@@ -45,12 +45,12 @@ absl::optional<AV1Profile> StringToAV1Profile(absl::string_view profile);
// specified and an empty value if the profile key is present but contains an
// invalid value.
RTC_EXPORT absl::optional<AV1Profile> ParseSdpForAV1Profile(
- const SdpVideoFormat::Parameters& params);
+ const CodecParameterMap& params);
// Returns true if the parameters have the same AV1 profile or neither contains
// an AV1 profile, otherwise false.
-bool AV1IsSameProfile(const SdpVideoFormat::Parameters& params1,
- const SdpVideoFormat::Parameters& params2);
+bool AV1IsSameProfile(const CodecParameterMap& params1,
+ const CodecParameterMap& params2);
} // namespace webrtc
diff --git a/third_party/libwebrtc/api/video_codecs/h264_profile_level_id.cc b/third_party/libwebrtc/api/video_codecs/h264_profile_level_id.cc
index 5844ca0e32..9bd9c9e4ab 100644
--- a/third_party/libwebrtc/api/video_codecs/h264_profile_level_id.cc
+++ b/third_party/libwebrtc/api/video_codecs/h264_profile_level_id.cc
@@ -178,7 +178,7 @@ absl::optional<H264Level> H264SupportedLevel(int max_frame_pixel_count,
}
absl::optional<H264ProfileLevelId> ParseSdpForH264ProfileLevelId(
- const SdpVideoFormat::Parameters& params) {
+ const CodecParameterMap& params) {
// TODO(magjed): The default should really be kProfileBaseline and kLevel1
// according to the spec: https://tools.ietf.org/html/rfc6184#section-8.1. In
// order to not break backwards compatibility with older versions of WebRTC
@@ -243,8 +243,8 @@ absl::optional<std::string> H264ProfileLevelIdToString(
return {str};
}
-bool H264IsSameProfile(const SdpVideoFormat::Parameters& params1,
- const SdpVideoFormat::Parameters& params2) {
+bool H264IsSameProfile(const CodecParameterMap& params1,
+ const CodecParameterMap& params2) {
const absl::optional<H264ProfileLevelId> profile_level_id =
ParseSdpForH264ProfileLevelId(params1);
const absl::optional<H264ProfileLevelId> other_profile_level_id =
diff --git a/third_party/libwebrtc/api/video_codecs/h264_profile_level_id.h b/third_party/libwebrtc/api/video_codecs/h264_profile_level_id.h
index 4b46ad329d..37709fae64 100644
--- a/third_party/libwebrtc/api/video_codecs/h264_profile_level_id.h
+++ b/third_party/libwebrtc/api/video_codecs/h264_profile_level_id.h
@@ -67,7 +67,7 @@ absl::optional<H264ProfileLevelId> ParseH264ProfileLevelId(const char* str);
// returned if the profile-level-id key is missing. Nothing will be returned if
// the key is present but the string is invalid.
RTC_EXPORT absl::optional<H264ProfileLevelId> ParseSdpForH264ProfileLevelId(
- const SdpVideoFormat::Parameters& params);
+ const CodecParameterMap& params);
// Given that a decoder supports up to a given frame size (in pixels) at up to a
// given number of frames per second, return the highest H.264 level where it
@@ -84,8 +84,8 @@ RTC_EXPORT absl::optional<std::string> H264ProfileLevelIdToString(
// Returns true if the parameters have the same H264 profile (Baseline, High,
// etc).
-RTC_EXPORT bool H264IsSameProfile(const SdpVideoFormat::Parameters& params1,
- const SdpVideoFormat::Parameters& params2);
+RTC_EXPORT bool H264IsSameProfile(const CodecParameterMap& params1,
+ const CodecParameterMap& params2);
} // namespace webrtc
diff --git a/third_party/libwebrtc/api/video_codecs/h265_profile_tier_level.cc b/third_party/libwebrtc/api/video_codecs/h265_profile_tier_level.cc
index f5b376e287..f4dcebb25a 100644
--- a/third_party/libwebrtc/api/video_codecs/h265_profile_tier_level.cc
+++ b/third_party/libwebrtc/api/video_codecs/h265_profile_tier_level.cc
@@ -1,248 +1,248 @@
-/*
- * Copyright (c) 2023 The WebRTC project authors. All Rights Reserved.
- *
- * Use of this source code is governed by a BSD-style license
- * that can be found in the LICENSE file in the root of the source
- * tree. An additional intellectual property rights grant can be found
- * in the file PATENTS. All contributing project authors may
- * be found in the AUTHORS file in the root of the source tree.
- */
-
-#include "api/video_codecs/h265_profile_tier_level.h"
-
-#include <string>
-
-#include "rtc_base/string_to_number.h"
-
-namespace webrtc {
-
-namespace {
-
-const char kH265FmtpProfile[] = "profile-id";
-const char kH265FmtpTier[] = "tier-flag";
-const char kH265FmtpLevel[] = "level-id";
-
-} // anonymous namespace
-
-// Annex A of https://www.itu.int/rec/T-REC-H.265 (08/21), section A.3.
-absl::optional<H265Profile> StringToH265Profile(const std::string& profile) {
- absl::optional<int> i = rtc::StringToNumber<int>(profile);
- if (!i.has_value()) {
- return absl::nullopt;
- }
-
- switch (i.value()) {
- case 1:
- return H265Profile::kProfileMain;
- case 2:
- return H265Profile::kProfileMain10;
- case 3:
- return H265Profile::kProfileMainStill;
- case 4:
- return H265Profile::kProfileRangeExtensions;
- case 5:
- return H265Profile::kProfileHighThroughput;
- case 6:
- return H265Profile::kProfileMultiviewMain;
- case 7:
- return H265Profile::kProfileScalableMain;
- case 8:
- return H265Profile::kProfile3dMain;
- case 9:
- return H265Profile::kProfileScreenContentCoding;
- case 10:
- return H265Profile::kProfileScalableRangeExtensions;
- case 11:
- return H265Profile::kProfileHighThroughputScreenContentCoding;
- default:
- return absl::nullopt;
- }
-}
-
-// Annex A of https://www.itu.int/rec/T-REC-H.265 (08/21), section A.4,
-// tiers and levels.
-absl::optional<H265Tier> StringToH265Tier(const std::string& tier) {
- absl::optional<int> i = rtc::StringToNumber<int>(tier);
- if (!i.has_value()) {
- return absl::nullopt;
- }
-
- switch (i.value()) {
- case 0:
- return H265Tier::kTier0;
- case 1:
- return H265Tier::kTier1;
- default:
- return absl::nullopt;
- }
-}
-
-absl::optional<H265Level> StringToH265Level(const std::string& level) {
- const absl::optional<int> i = rtc::StringToNumber<int>(level);
- if (!i.has_value())
- return absl::nullopt;
-
- switch (i.value()) {
- case 30:
- return H265Level::kLevel1;
- case 60:
- return H265Level::kLevel2;
- case 63:
- return H265Level::kLevel2_1;
- case 90:
- return H265Level::kLevel3;
- case 93:
- return H265Level::kLevel3_1;
- case 120:
- return H265Level::kLevel4;
- case 123:
- return H265Level::kLevel4_1;
- case 150:
- return H265Level::kLevel5;
- case 153:
- return H265Level::kLevel5_1;
- case 156:
- return H265Level::kLevel5_2;
- case 180:
- return H265Level::kLevel6;
- case 183:
- return H265Level::kLevel6_1;
- case 186:
- return H265Level::kLevel6_2;
- default:
- return absl::nullopt;
- }
-}
-
-std::string H265ProfileToString(H265Profile profile) {
- switch (profile) {
- case H265Profile::kProfileMain:
- return "1";
- case H265Profile::kProfileMain10:
- return "2";
- case H265Profile::kProfileMainStill:
- return "3";
- case H265Profile::kProfileRangeExtensions:
- return "4";
- case H265Profile::kProfileHighThroughput:
- return "5";
- case H265Profile::kProfileMultiviewMain:
- return "6";
- case H265Profile::kProfileScalableMain:
- return "7";
- case H265Profile::kProfile3dMain:
- return "8";
- case H265Profile::kProfileScreenContentCoding:
- return "9";
- case H265Profile::kProfileScalableRangeExtensions:
- return "10";
- case H265Profile::kProfileHighThroughputScreenContentCoding:
- return "11";
- }
-}
-
-std::string H265TierToString(H265Tier tier) {
- switch (tier) {
- case H265Tier::kTier0:
- return "0";
- case H265Tier::kTier1:
- return "1";
- }
-}
-
-std::string H265LevelToString(H265Level level) {
- switch (level) {
- case H265Level::kLevel1:
- return "30";
- case H265Level::kLevel2:
- return "60";
- case H265Level::kLevel2_1:
- return "63";
- case H265Level::kLevel3:
- return "90";
- case H265Level::kLevel3_1:
- return "93";
- case H265Level::kLevel4:
- return "120";
- case H265Level::kLevel4_1:
- return "123";
- case H265Level::kLevel5:
- return "150";
- case H265Level::kLevel5_1:
- return "153";
- case H265Level::kLevel5_2:
- return "156";
- case H265Level::kLevel6:
- return "180";
- case H265Level::kLevel6_1:
- return "183";
- case H265Level::kLevel6_2:
- return "186";
- }
-}
-
-absl::optional<H265ProfileTierLevel> ParseSdpForH265ProfileTierLevel(
- const SdpVideoFormat::Parameters& params) {
- static const H265ProfileTierLevel kDefaultProfileTierLevel(
- H265Profile::kProfileMain, H265Tier::kTier0, H265Level::kLevel3_1);
- bool profile_tier_level_specified = false;
-
- absl::optional<H265Profile> profile;
- const auto profile_it = params.find(kH265FmtpProfile);
- if (profile_it != params.end()) {
- profile_tier_level_specified = true;
- const std::string& profile_str = profile_it->second;
- profile = StringToH265Profile(profile_str);
- if (!profile) {
- return absl::nullopt;
- }
- } else {
- profile = H265Profile::kProfileMain;
- }
- absl::optional<H265Tier> tier;
- const auto tier_it = params.find(kH265FmtpTier);
- if (tier_it != params.end()) {
- profile_tier_level_specified = true;
- const std::string& tier_str = tier_it->second;
- tier = StringToH265Tier(tier_str);
- if (!tier) {
- return absl::nullopt;
- }
- } else {
- tier = H265Tier::kTier0;
- }
- absl::optional<H265Level> level;
- const auto level_it = params.find(kH265FmtpLevel);
- if (level_it != params.end()) {
- profile_tier_level_specified = true;
- const std::string& level_str = level_it->second;
- level = StringToH265Level(level_str);
- if (!level) {
- return absl::nullopt;
- }
- } else {
- level = H265Level::kLevel3_1;
- }
-
- // Spec Table A.9, level 1 to level 3.1 does not allow high tiers.
- if (level <= H265Level::kLevel3_1 && tier == H265Tier::kTier1) {
- return absl::nullopt;
- }
-
- return !profile_tier_level_specified
- ? kDefaultProfileTierLevel
- : H265ProfileTierLevel(profile.value(), tier.value(),
- level.value());
-}
-
-bool H265IsSameProfileTierLevel(const SdpVideoFormat::Parameters& params1,
- const SdpVideoFormat::Parameters& params2) {
- const absl::optional<H265ProfileTierLevel> ptl1 =
- ParseSdpForH265ProfileTierLevel(params1);
- const absl::optional<H265ProfileTierLevel> ptl2 =
- ParseSdpForH265ProfileTierLevel(params2);
- return ptl1 && ptl2 && ptl1->profile == ptl2->profile &&
- ptl1->tier == ptl2->tier && ptl1->level == ptl2->level;
-}
-
-} // namespace webrtc
+/*
+ * Copyright (c) 2023 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "api/video_codecs/h265_profile_tier_level.h"
+
+#include <string>
+
+#include "rtc_base/string_to_number.h"
+
+namespace webrtc {
+
+namespace {
+
+const char kH265FmtpProfile[] = "profile-id";
+const char kH265FmtpTier[] = "tier-flag";
+const char kH265FmtpLevel[] = "level-id";
+
+} // anonymous namespace
+
+// Annex A of https://www.itu.int/rec/T-REC-H.265 (08/21), section A.3.
+absl::optional<H265Profile> StringToH265Profile(const std::string& profile) {
+ absl::optional<int> i = rtc::StringToNumber<int>(profile);
+ if (!i.has_value()) {
+ return absl::nullopt;
+ }
+
+ switch (i.value()) {
+ case 1:
+ return H265Profile::kProfileMain;
+ case 2:
+ return H265Profile::kProfileMain10;
+ case 3:
+ return H265Profile::kProfileMainStill;
+ case 4:
+ return H265Profile::kProfileRangeExtensions;
+ case 5:
+ return H265Profile::kProfileHighThroughput;
+ case 6:
+ return H265Profile::kProfileMultiviewMain;
+ case 7:
+ return H265Profile::kProfileScalableMain;
+ case 8:
+ return H265Profile::kProfile3dMain;
+ case 9:
+ return H265Profile::kProfileScreenContentCoding;
+ case 10:
+ return H265Profile::kProfileScalableRangeExtensions;
+ case 11:
+ return H265Profile::kProfileHighThroughputScreenContentCoding;
+ default:
+ return absl::nullopt;
+ }
+}
+
+// Annex A of https://www.itu.int/rec/T-REC-H.265 (08/21), section A.4,
+// tiers and levels.
+absl::optional<H265Tier> StringToH265Tier(const std::string& tier) {
+ absl::optional<int> i = rtc::StringToNumber<int>(tier);
+ if (!i.has_value()) {
+ return absl::nullopt;
+ }
+
+ switch (i.value()) {
+ case 0:
+ return H265Tier::kTier0;
+ case 1:
+ return H265Tier::kTier1;
+ default:
+ return absl::nullopt;
+ }
+}
+
+absl::optional<H265Level> StringToH265Level(const std::string& level) {
+ const absl::optional<int> i = rtc::StringToNumber<int>(level);
+ if (!i.has_value())
+ return absl::nullopt;
+
+ switch (i.value()) {
+ case 30:
+ return H265Level::kLevel1;
+ case 60:
+ return H265Level::kLevel2;
+ case 63:
+ return H265Level::kLevel2_1;
+ case 90:
+ return H265Level::kLevel3;
+ case 93:
+ return H265Level::kLevel3_1;
+ case 120:
+ return H265Level::kLevel4;
+ case 123:
+ return H265Level::kLevel4_1;
+ case 150:
+ return H265Level::kLevel5;
+ case 153:
+ return H265Level::kLevel5_1;
+ case 156:
+ return H265Level::kLevel5_2;
+ case 180:
+ return H265Level::kLevel6;
+ case 183:
+ return H265Level::kLevel6_1;
+ case 186:
+ return H265Level::kLevel6_2;
+ default:
+ return absl::nullopt;
+ }
+}
+
+std::string H265ProfileToString(H265Profile profile) {
+ switch (profile) {
+ case H265Profile::kProfileMain:
+ return "1";
+ case H265Profile::kProfileMain10:
+ return "2";
+ case H265Profile::kProfileMainStill:
+ return "3";
+ case H265Profile::kProfileRangeExtensions:
+ return "4";
+ case H265Profile::kProfileHighThroughput:
+ return "5";
+ case H265Profile::kProfileMultiviewMain:
+ return "6";
+ case H265Profile::kProfileScalableMain:
+ return "7";
+ case H265Profile::kProfile3dMain:
+ return "8";
+ case H265Profile::kProfileScreenContentCoding:
+ return "9";
+ case H265Profile::kProfileScalableRangeExtensions:
+ return "10";
+ case H265Profile::kProfileHighThroughputScreenContentCoding:
+ return "11";
+ }
+}
+
+std::string H265TierToString(H265Tier tier) {
+ switch (tier) {
+ case H265Tier::kTier0:
+ return "0";
+ case H265Tier::kTier1:
+ return "1";
+ }
+}
+
+std::string H265LevelToString(H265Level level) {
+ switch (level) {
+ case H265Level::kLevel1:
+ return "30";
+ case H265Level::kLevel2:
+ return "60";
+ case H265Level::kLevel2_1:
+ return "63";
+ case H265Level::kLevel3:
+ return "90";
+ case H265Level::kLevel3_1:
+ return "93";
+ case H265Level::kLevel4:
+ return "120";
+ case H265Level::kLevel4_1:
+ return "123";
+ case H265Level::kLevel5:
+ return "150";
+ case H265Level::kLevel5_1:
+ return "153";
+ case H265Level::kLevel5_2:
+ return "156";
+ case H265Level::kLevel6:
+ return "180";
+ case H265Level::kLevel6_1:
+ return "183";
+ case H265Level::kLevel6_2:
+ return "186";
+ }
+}
+
+absl::optional<H265ProfileTierLevel> ParseSdpForH265ProfileTierLevel(
+ const CodecParameterMap& params) {
+ static const H265ProfileTierLevel kDefaultProfileTierLevel(
+ H265Profile::kProfileMain, H265Tier::kTier0, H265Level::kLevel3_1);
+ bool profile_tier_level_specified = false;
+
+ absl::optional<H265Profile> profile;
+ const auto profile_it = params.find(kH265FmtpProfile);
+ if (profile_it != params.end()) {
+ profile_tier_level_specified = true;
+ const std::string& profile_str = profile_it->second;
+ profile = StringToH265Profile(profile_str);
+ if (!profile) {
+ return absl::nullopt;
+ }
+ } else {
+ profile = H265Profile::kProfileMain;
+ }
+ absl::optional<H265Tier> tier;
+ const auto tier_it = params.find(kH265FmtpTier);
+ if (tier_it != params.end()) {
+ profile_tier_level_specified = true;
+ const std::string& tier_str = tier_it->second;
+ tier = StringToH265Tier(tier_str);
+ if (!tier) {
+ return absl::nullopt;
+ }
+ } else {
+ tier = H265Tier::kTier0;
+ }
+ absl::optional<H265Level> level;
+ const auto level_it = params.find(kH265FmtpLevel);
+ if (level_it != params.end()) {
+ profile_tier_level_specified = true;
+ const std::string& level_str = level_it->second;
+ level = StringToH265Level(level_str);
+ if (!level) {
+ return absl::nullopt;
+ }
+ } else {
+ level = H265Level::kLevel3_1;
+ }
+
+ // Spec Table A.9, level 1 to level 3.1 does not allow high tiers.
+ if (level <= H265Level::kLevel3_1 && tier == H265Tier::kTier1) {
+ return absl::nullopt;
+ }
+
+ return !profile_tier_level_specified
+ ? kDefaultProfileTierLevel
+ : H265ProfileTierLevel(profile.value(), tier.value(),
+ level.value());
+}
+
+bool H265IsSameProfileTierLevel(const CodecParameterMap& params1,
+ const CodecParameterMap& params2) {
+ const absl::optional<H265ProfileTierLevel> ptl1 =
+ ParseSdpForH265ProfileTierLevel(params1);
+ const absl::optional<H265ProfileTierLevel> ptl2 =
+ ParseSdpForH265ProfileTierLevel(params2);
+ return ptl1 && ptl2 && ptl1->profile == ptl2->profile &&
+ ptl1->tier == ptl2->tier && ptl1->level == ptl2->level;
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/api/video_codecs/h265_profile_tier_level.h b/third_party/libwebrtc/api/video_codecs/h265_profile_tier_level.h
index 3056d2b623..efbdf287ed 100644
--- a/third_party/libwebrtc/api/video_codecs/h265_profile_tier_level.h
+++ b/third_party/libwebrtc/api/video_codecs/h265_profile_tier_level.h
@@ -1,109 +1,109 @@
-/*
- * Copyright (c) 2023 The WebRTC project authors. All Rights Reserved.
- *
- * Use of this source code is governed by a BSD-style license
- * that can be found in the LICENSE file in the root of the source
- * tree. An additional intellectual property rights grant can be found
- * in the file PATENTS. All contributing project authors may
- * be found in the AUTHORS file in the root of the source tree.
- */
-
-#ifndef API_VIDEO_CODECS_H265_PROFILE_TIER_LEVEL_H_
-#define API_VIDEO_CODECS_H265_PROFILE_TIER_LEVEL_H_
-
-#include <string>
-
-#include "absl/types/optional.h"
-#include "api/video_codecs/sdp_video_format.h"
-#include "rtc_base/system/rtc_export.h"
-
-namespace webrtc {
-
-// Profiles can be found at:
-// https://www.itu.int/rec/T-REC-H.265
-// The enum values match the number specified in the SDP.
-enum class H265Profile {
- kProfileMain = 1,
- kProfileMain10 = 2,
- kProfileMainStill = 3,
- kProfileRangeExtensions = 4,
- kProfileHighThroughput = 5,
- kProfileMultiviewMain = 6,
- kProfileScalableMain = 7,
- kProfile3dMain = 8,
- kProfileScreenContentCoding = 9,
- kProfileScalableRangeExtensions = 10,
- kProfileHighThroughputScreenContentCoding = 11,
-};
-
-// Tiers can be found at https://www.itu.int/rec/T-REC-H.265
-enum class H265Tier {
- kTier0,
- kTier1,
-};
-
-// All values are equal to 30 times the level number.
-enum class H265Level {
- kLevel1 = 30,
- kLevel2 = 60,
- kLevel2_1 = 63,
- kLevel3 = 90,
- kLevel3_1 = 93,
- kLevel4 = 120,
- kLevel4_1 = 123,
- kLevel5 = 150,
- kLevel5_1 = 153,
- kLevel5_2 = 156,
- kLevel6 = 180,
- kLevel6_1 = 183,
- kLevel6_2 = 186,
-};
-
-struct H265ProfileTierLevel {
- constexpr H265ProfileTierLevel(H265Profile profile,
- H265Tier tier,
- H265Level level)
- : profile(profile), tier(tier), level(level) {}
- H265Profile profile;
- H265Tier tier;
- H265Level level;
-};
-
-// Helper function to convert H265Profile to std::string.
-RTC_EXPORT std::string H265ProfileToString(H265Profile profile);
-
-// Helper function to convert H265Tier to std::string.
-RTC_EXPORT std::string H265TierToString(H265Tier tier);
-
-// Helper function to convert H265Level to std::string.
-RTC_EXPORT std::string H265LevelToString(H265Level level);
-
-// Helper function to get H265Profile from profile string.
-RTC_EXPORT absl::optional<H265Profile> StringToH265Profile(
- const std::string& profile);
-
-// Helper function to get H265Tier from tier string.
-RTC_EXPORT absl::optional<H265Tier> StringToH265Tier(const std::string& tier);
-
-// Helper function to get H265Level from level string.
-RTC_EXPORT absl::optional<H265Level> StringToH265Level(
- const std::string& level);
-
-// Parses an SDP key-value map of format parameters to retrive an H265
-// profile/tier/level. Returns an H265ProfileTierlevel by setting its
-// members. profile defaults to `kProfileMain` if no profile-id is specified.
-// tier defaults to "kTier0" if no tier-flag is specified.
-// level defaults to "kLevel3_1" if no level-id is specified.
-// Returns empty value if any of the profile/tier/level key is present but
-// contains an invalid value.
-RTC_EXPORT absl::optional<H265ProfileTierLevel> ParseSdpForH265ProfileTierLevel(
- const SdpVideoFormat::Parameters& params);
-
-// Returns true if the parameters have the same H265 profile or neither contains
-// an H265 profile, otherwise false.
-bool H265IsSameProfileTierLevel(const SdpVideoFormat::Parameters& params1,
- const SdpVideoFormat::Parameters& params2);
-
-} // namespace webrtc
-
-#endif // API_VIDEO_CODECS_H265_PROFILE_TIER_LEVEL_H_
+/*
+ * Copyright (c) 2023 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef API_VIDEO_CODECS_H265_PROFILE_TIER_LEVEL_H_
+#define API_VIDEO_CODECS_H265_PROFILE_TIER_LEVEL_H_
+
+#include <string>
+
+#include "absl/types/optional.h"
+#include "api/video_codecs/sdp_video_format.h"
+#include "rtc_base/system/rtc_export.h"
+
+namespace webrtc {
+
+// Profiles can be found at:
+// https://www.itu.int/rec/T-REC-H.265
+// The enum values match the number specified in the SDP.
+enum class H265Profile {
+ kProfileMain = 1,
+ kProfileMain10 = 2,
+ kProfileMainStill = 3,
+ kProfileRangeExtensions = 4,
+ kProfileHighThroughput = 5,
+ kProfileMultiviewMain = 6,
+ kProfileScalableMain = 7,
+ kProfile3dMain = 8,
+ kProfileScreenContentCoding = 9,
+ kProfileScalableRangeExtensions = 10,
+ kProfileHighThroughputScreenContentCoding = 11,
+};
+
+// Tiers can be found at https://www.itu.int/rec/T-REC-H.265
+enum class H265Tier {
+ kTier0,
+ kTier1,
+};
+
+// All values are equal to 30 times the level number.
+enum class H265Level {
+ kLevel1 = 30,
+ kLevel2 = 60,
+ kLevel2_1 = 63,
+ kLevel3 = 90,
+ kLevel3_1 = 93,
+ kLevel4 = 120,
+ kLevel4_1 = 123,
+ kLevel5 = 150,
+ kLevel5_1 = 153,
+ kLevel5_2 = 156,
+ kLevel6 = 180,
+ kLevel6_1 = 183,
+ kLevel6_2 = 186,
+};
+
+struct H265ProfileTierLevel {
+ constexpr H265ProfileTierLevel(H265Profile profile,
+ H265Tier tier,
+ H265Level level)
+ : profile(profile), tier(tier), level(level) {}
+ H265Profile profile;
+ H265Tier tier;
+ H265Level level;
+};
+
+// Helper function to convert H265Profile to std::string.
+RTC_EXPORT std::string H265ProfileToString(H265Profile profile);
+
+// Helper function to convert H265Tier to std::string.
+RTC_EXPORT std::string H265TierToString(H265Tier tier);
+
+// Helper function to convert H265Level to std::string.
+RTC_EXPORT std::string H265LevelToString(H265Level level);
+
+// Helper function to get H265Profile from profile string.
+RTC_EXPORT absl::optional<H265Profile> StringToH265Profile(
+ const std::string& profile);
+
+// Helper function to get H265Tier from tier string.
+RTC_EXPORT absl::optional<H265Tier> StringToH265Tier(const std::string& tier);
+
+// Helper function to get H265Level from level string.
+RTC_EXPORT absl::optional<H265Level> StringToH265Level(
+ const std::string& level);
+
+// Parses an SDP key-value map of format parameters to retrive an H265
+// profile/tier/level. Returns an H265ProfileTierlevel by setting its
+// members. profile defaults to `kProfileMain` if no profile-id is specified.
+// tier defaults to "kTier0" if no tier-flag is specified.
+// level defaults to "kLevel3_1" if no level-id is specified.
+// Returns empty value if any of the profile/tier/level key is present but
+// contains an invalid value.
+RTC_EXPORT absl::optional<H265ProfileTierLevel> ParseSdpForH265ProfileTierLevel(
+ const CodecParameterMap& params);
+
+// Returns true if the parameters have the same H265 profile or neither contains
+// an H265 profile, otherwise false.
+RTC_EXPORT bool H265IsSameProfileTierLevel(const CodecParameterMap& params1,
+ const CodecParameterMap& params2);
+
+} // namespace webrtc
+
+#endif // API_VIDEO_CODECS_H265_PROFILE_TIER_LEVEL_H_
diff --git a/third_party/libwebrtc/api/video_codecs/sdp_video_format.cc b/third_party/libwebrtc/api/video_codecs/sdp_video_format.cc
index 51ae18cd78..0f313e84a9 100644
--- a/third_party/libwebrtc/api/video_codecs/sdp_video_format.cc
+++ b/third_party/libwebrtc/api/video_codecs/sdp_video_format.cc
@@ -28,8 +28,7 @@ namespace webrtc {
namespace {
-std::string H264GetPacketizationModeOrDefault(
- const SdpVideoFormat::Parameters& params) {
+std::string H264GetPacketizationModeOrDefault(const CodecParameterMap& params) {
constexpr char kH264FmtpPacketizationMode[] = "packetization-mode";
const auto it = params.find(kH264FmtpPacketizationMode);
if (it != params.end()) {
@@ -40,8 +39,8 @@ std::string H264GetPacketizationModeOrDefault(
return "0";
}
-bool H264IsSamePacketizationMode(const SdpVideoFormat::Parameters& left,
- const SdpVideoFormat::Parameters& right) {
+bool H264IsSamePacketizationMode(const CodecParameterMap& left,
+ const CodecParameterMap& right) {
return H264GetPacketizationModeOrDefault(left) ==
H264GetPacketizationModeOrDefault(right);
}
@@ -77,12 +76,12 @@ bool IsSameCodecSpecific(const SdpVideoFormat& format1,
SdpVideoFormat::SdpVideoFormat(const std::string& name) : name(name) {}
SdpVideoFormat::SdpVideoFormat(const std::string& name,
- const Parameters& parameters)
+ const CodecParameterMap& parameters)
: name(name), parameters(parameters) {}
SdpVideoFormat::SdpVideoFormat(
const std::string& name,
- const Parameters& parameters,
+ const CodecParameterMap& parameters,
const absl::InlinedVector<ScalabilityMode, kScalabilityModeCount>&
scalability_modes)
: name(name),
diff --git a/third_party/libwebrtc/api/video_codecs/sdp_video_format.h b/third_party/libwebrtc/api/video_codecs/sdp_video_format.h
index faaa66c241..af9537b5a3 100644
--- a/third_party/libwebrtc/api/video_codecs/sdp_video_format.h
+++ b/third_party/libwebrtc/api/video_codecs/sdp_video_format.h
@@ -17,6 +17,7 @@
#include "absl/container/inlined_vector.h"
#include "absl/types/optional.h"
#include "api/array_view.h"
+#include "api/rtp_parameters.h"
#include "api/video_codecs/scalability_mode.h"
#include "rtc_base/system/rtc_export.h"
@@ -25,13 +26,14 @@ namespace webrtc {
// SDP specification for a single video codec.
// NOTE: This class is still under development and may change without notice.
struct RTC_EXPORT SdpVideoFormat {
- using Parameters = std::map<std::string, std::string>;
+ using Parameters [[deprecated(("Use webrtc::CodecParameterMap"))]] =
+ std::map<std::string, std::string>;
explicit SdpVideoFormat(const std::string& name);
- SdpVideoFormat(const std::string& name, const Parameters& parameters);
+ SdpVideoFormat(const std::string& name, const CodecParameterMap& parameters);
SdpVideoFormat(
const std::string& name,
- const Parameters& parameters,
+ const CodecParameterMap& parameters,
const absl::InlinedVector<ScalabilityMode, kScalabilityModeCount>&
scalability_modes);
SdpVideoFormat(const SdpVideoFormat&);
@@ -58,7 +60,7 @@ struct RTC_EXPORT SdpVideoFormat {
}
std::string name;
- Parameters parameters;
+ CodecParameterMap parameters;
absl::InlinedVector<ScalabilityMode, kScalabilityModeCount> scalability_modes;
};
diff --git a/third_party/libwebrtc/api/video_codecs/test/h264_profile_level_id_unittest.cc b/third_party/libwebrtc/api/video_codecs/test/h264_profile_level_id_unittest.cc
index 47098d2682..404d3e2cb3 100644
--- a/third_party/libwebrtc/api/video_codecs/test/h264_profile_level_id_unittest.cc
+++ b/third_party/libwebrtc/api/video_codecs/test/h264_profile_level_id_unittest.cc
@@ -145,7 +145,7 @@ TEST(H264ProfileLevelId, TestToStringInvalid) {
TEST(H264ProfileLevelId, TestParseSdpProfileLevelIdEmpty) {
const absl::optional<H264ProfileLevelId> profile_level_id =
- ParseSdpForH264ProfileLevelId(SdpVideoFormat::Parameters());
+ ParseSdpForH264ProfileLevelId(CodecParameterMap());
EXPECT_TRUE(profile_level_id);
EXPECT_EQ(H264Profile::kProfileConstrainedBaseline,
profile_level_id->profile);
@@ -153,7 +153,7 @@ TEST(H264ProfileLevelId, TestParseSdpProfileLevelIdEmpty) {
}
TEST(H264ProfileLevelId, TestParseSdpProfileLevelIdConstrainedHigh) {
- SdpVideoFormat::Parameters params;
+ CodecParameterMap params;
params["profile-level-id"] = "640c2a";
const absl::optional<H264ProfileLevelId> profile_level_id =
ParseSdpForH264ProfileLevelId(params);
@@ -163,7 +163,7 @@ TEST(H264ProfileLevelId, TestParseSdpProfileLevelIdConstrainedHigh) {
}
TEST(H264ProfileLevelId, TestParseSdpProfileLevelIdInvalid) {
- SdpVideoFormat::Parameters params;
+ CodecParameterMap params;
params["profile-level-id"] = "foobar";
EXPECT_FALSE(ParseSdpForH264ProfileLevelId(params));
}
diff --git a/third_party/libwebrtc/api/video_codecs/test/h265_profile_tier_level_unittest.cc b/third_party/libwebrtc/api/video_codecs/test/h265_profile_tier_level_unittest.cc
index a9fdf966a5..85c0f09cd0 100644
--- a/third_party/libwebrtc/api/video_codecs/test/h265_profile_tier_level_unittest.cc
+++ b/third_party/libwebrtc/api/video_codecs/test/h265_profile_tier_level_unittest.cc
@@ -1,248 +1,248 @@
-/*
- * Copyright (c) 2023 The WebRTC project authors. All Rights Reserved.
- *
- * Use of this source code is governed by a BSD-style license
- * that can be found in the LICENSE file in the root of the source
- * tree. An additional intellectual property rights grant can be found
- * in the file PATENTS. All contributing project authors may
- * be found in the AUTHORS file in the root of the source tree.
- */
-
-#include "api/video_codecs/h265_profile_tier_level.h"
-
-#include <string>
-
-#include "absl/types/optional.h"
-#include "test/gtest.h"
-
-namespace webrtc {
-
-TEST(H265ProfileTierLevel, TestLevelToString) {
- EXPECT_EQ(H265LevelToString(H265Level::kLevel1), "30");
- EXPECT_EQ(H265LevelToString(H265Level::kLevel2), "60");
- EXPECT_EQ(H265LevelToString(H265Level::kLevel2_1), "63");
- EXPECT_EQ(H265LevelToString(H265Level::kLevel3), "90");
- EXPECT_EQ(H265LevelToString(H265Level::kLevel3_1), "93");
- EXPECT_EQ(H265LevelToString(H265Level::kLevel4), "120");
- EXPECT_EQ(H265LevelToString(H265Level::kLevel4_1), "123");
- EXPECT_EQ(H265LevelToString(H265Level::kLevel5), "150");
- EXPECT_EQ(H265LevelToString(H265Level::kLevel5_1), "153");
- EXPECT_EQ(H265LevelToString(H265Level::kLevel5_2), "156");
- EXPECT_EQ(H265LevelToString(H265Level::kLevel6), "180");
- EXPECT_EQ(H265LevelToString(H265Level::kLevel6_1), "183");
- EXPECT_EQ(H265LevelToString(H265Level::kLevel6_2), "186");
-}
-
-TEST(H265ProfileTierLevel, TestProfileToString) {
- EXPECT_EQ(H265ProfileToString(H265Profile::kProfileMain), "1");
- EXPECT_EQ(H265ProfileToString(H265Profile::kProfileMain10), "2");
- EXPECT_EQ(H265ProfileToString(H265Profile::kProfileMainStill), "3");
- EXPECT_EQ(H265ProfileToString(H265Profile::kProfileRangeExtensions), "4");
- EXPECT_EQ(H265ProfileToString(H265Profile::kProfileHighThroughput), "5");
- EXPECT_EQ(H265ProfileToString(H265Profile::kProfileMultiviewMain), "6");
- EXPECT_EQ(H265ProfileToString(H265Profile::kProfileScalableMain), "7");
- EXPECT_EQ(H265ProfileToString(H265Profile::kProfile3dMain), "8");
- EXPECT_EQ(H265ProfileToString(H265Profile::kProfileScreenContentCoding), "9");
- EXPECT_EQ(H265ProfileToString(H265Profile::kProfileScalableRangeExtensions),
- "10");
- EXPECT_EQ(H265ProfileToString(
- H265Profile::kProfileHighThroughputScreenContentCoding),
- "11");
-}
-
-TEST(H265ProfileTierLevel, TestTierToString) {
- EXPECT_EQ(H265TierToString(H265Tier::kTier0), "0");
- EXPECT_EQ(H265TierToString(H265Tier::kTier1), "1");
-}
-
-TEST(H265ProfileTierLevel, TestStringToProfile) {
- // Invalid profiles.
- EXPECT_FALSE(StringToH265Profile("0"));
- EXPECT_FALSE(StringToH265Profile("12"));
-
- // Malformed profiles
- EXPECT_FALSE(StringToH265Profile(""));
- EXPECT_FALSE(StringToH265Profile(" 1"));
- EXPECT_FALSE(StringToH265Profile("12x"));
- EXPECT_FALSE(StringToH265Profile("x12"));
- EXPECT_FALSE(StringToH265Profile("gggg"));
-
- // Valid profiles.
- EXPECT_EQ(StringToH265Profile("1"), H265Profile::kProfileMain);
- EXPECT_EQ(StringToH265Profile("2"), H265Profile::kProfileMain10);
- EXPECT_EQ(StringToH265Profile("4"), H265Profile::kProfileRangeExtensions);
-}
-
-TEST(H265ProfileTierLevel, TestStringToLevel) {
- // Invalid levels.
- EXPECT_FALSE(StringToH265Level("0"));
- EXPECT_FALSE(StringToH265Level("200"));
-
- // Malformed levels.
- EXPECT_FALSE(StringToH265Level(""));
- EXPECT_FALSE(StringToH265Level(" 30"));
- EXPECT_FALSE(StringToH265Level("30x"));
- EXPECT_FALSE(StringToH265Level("x30"));
- EXPECT_FALSE(StringToH265Level("ggggg"));
-
- // Valid levels.
- EXPECT_EQ(StringToH265Level("30"), H265Level::kLevel1);
- EXPECT_EQ(StringToH265Level("93"), H265Level::kLevel3_1);
- EXPECT_EQ(StringToH265Level("183"), H265Level::kLevel6_1);
-}
-
-TEST(H265ProfileTierLevel, TestStringToTier) {
- // Invalid tiers.
- EXPECT_FALSE(StringToH265Tier("4"));
- EXPECT_FALSE(StringToH265Tier("-1"));
-
- // Malformed tiers.
- EXPECT_FALSE(StringToH265Tier(""));
- EXPECT_FALSE(StringToH265Tier(" 1"));
- EXPECT_FALSE(StringToH265Tier("t1"));
-
- // Valid tiers.
- EXPECT_EQ(StringToH265Tier("0"), H265Tier::kTier0);
- EXPECT_EQ(StringToH265Tier("1"), H265Tier::kTier1);
-}
-
-TEST(H265ProfileTierLevel, TestParseSdpProfileTierLevelAllEmpty) {
- const absl::optional<H265ProfileTierLevel> profile_tier_level =
- ParseSdpForH265ProfileTierLevel(SdpVideoFormat::Parameters());
- EXPECT_TRUE(profile_tier_level);
- EXPECT_EQ(H265Profile::kProfileMain, profile_tier_level->profile);
- EXPECT_EQ(H265Level::kLevel3_1, profile_tier_level->level);
- EXPECT_EQ(H265Tier::kTier0, profile_tier_level->tier);
-}
-
-TEST(H265ProfileTierLevel, TestParseSdpProfileTierLevelPartialEmpty) {
- SdpVideoFormat::Parameters params;
- params["profile-id"] = "1";
- params["tier-flag"] = "0";
- absl::optional<H265ProfileTierLevel> profile_tier_level =
- ParseSdpForH265ProfileTierLevel(params);
- EXPECT_TRUE(profile_tier_level);
- EXPECT_EQ(H265Profile::kProfileMain, profile_tier_level->profile);
- EXPECT_EQ(H265Level::kLevel3_1, profile_tier_level->level);
- EXPECT_EQ(H265Tier::kTier0, profile_tier_level->tier);
-
- params.clear();
- params["profile-id"] = "2";
- profile_tier_level = ParseSdpForH265ProfileTierLevel(params);
- EXPECT_TRUE(profile_tier_level);
- EXPECT_EQ(H265Profile::kProfileMain10, profile_tier_level->profile);
- EXPECT_EQ(H265Level::kLevel3_1, profile_tier_level->level);
- EXPECT_EQ(H265Tier::kTier0, profile_tier_level->tier);
-
- params.clear();
- params["level-id"] = "180";
- profile_tier_level = ParseSdpForH265ProfileTierLevel(params);
- EXPECT_TRUE(profile_tier_level);
- EXPECT_EQ(H265Profile::kProfileMain, profile_tier_level->profile);
- EXPECT_EQ(H265Level::kLevel6, profile_tier_level->level);
- EXPECT_EQ(H265Tier::kTier0, profile_tier_level->tier);
-}
-
-TEST(H265ProfileTierLevel, TestParseSdpProfileTierLevelInvalid) {
- SdpVideoFormat::Parameters params;
-
- // Invalid profile-tier-level combination.
- params["profile-id"] = "1";
- params["tier-flag"] = "1";
- params["level-id"] = "93";
- absl::optional<H265ProfileTierLevel> profile_tier_level =
- ParseSdpForH265ProfileTierLevel(params);
- EXPECT_FALSE(profile_tier_level);
- params.clear();
- params["profile-id"] = "1";
- params["tier-flag"] = "4";
- params["level-id"] = "180";
- profile_tier_level = ParseSdpForH265ProfileTierLevel(params);
- EXPECT_FALSE(profile_tier_level);
-
- // Valid profile-tier-level combination.
- params.clear();
- params["profile-id"] = "1";
- params["tier-flag"] = "0";
- params["level-id"] = "153";
- profile_tier_level = ParseSdpForH265ProfileTierLevel(params);
- EXPECT_TRUE(profile_tier_level);
-}
-
-TEST(H265ProfileTierLevel, TestToStringRoundTrip) {
- SdpVideoFormat::Parameters params;
- params["profile-id"] = "1";
- params["tier-flag"] = "0";
- params["level-id"] = "93";
- absl::optional<H265ProfileTierLevel> profile_tier_level =
- ParseSdpForH265ProfileTierLevel(params);
- EXPECT_TRUE(profile_tier_level);
- EXPECT_EQ("1", H265ProfileToString(profile_tier_level->profile));
- EXPECT_EQ("0", H265TierToString(profile_tier_level->tier));
- EXPECT_EQ("93", H265LevelToString(profile_tier_level->level));
-
- params.clear();
- params["profile-id"] = "2";
- params["tier-flag"] = "1";
- params["level-id"] = "180";
- profile_tier_level = ParseSdpForH265ProfileTierLevel(params);
- EXPECT_TRUE(profile_tier_level);
- EXPECT_EQ("2", H265ProfileToString(profile_tier_level->profile));
- EXPECT_EQ("1", H265TierToString(profile_tier_level->tier));
- EXPECT_EQ("180", H265LevelToString(profile_tier_level->level));
-}
-
-TEST(H265ProfileTierLevel, TestProfileTierLevelCompare) {
- SdpVideoFormat::Parameters params1;
- SdpVideoFormat::Parameters params2;
-
- // None of profile-id/tier-flag/level-id is specified,
- EXPECT_TRUE(H265IsSameProfileTierLevel(params1, params2));
-
- // Same non-empty PTL
- params1["profile-id"] = "1";
- params1["tier-flag"] = "0";
- params1["level-id"] = "120";
- params2["profile-id"] = "1";
- params2["tier-flag"] = "0";
- params2["level-id"] = "120";
- EXPECT_TRUE(H265IsSameProfileTierLevel(params1, params2));
-
- // Different profiles.
- params1.clear();
- params2.clear();
- params1["profile-id"] = "1";
- params2["profile-id"] = "2";
- EXPECT_FALSE(H265IsSameProfileTierLevel(params1, params2));
-
- // Different levels.
- params1.clear();
- params2.clear();
- params1["profile-id"] = "1";
- params2["profile-id"] = "1";
- params1["level-id"] = "93";
- params2["level-id"] = "183";
- EXPECT_FALSE(H265IsSameProfileTierLevel(params1, params2));
-
- // Different tiers.
- params1.clear();
- params2.clear();
- params1["profile-id"] = "1";
- params2["profile-id"] = "1";
- params1["level-id"] = "93";
- params2["level-id"] = "93";
- params1["tier-flag"] = "0";
- params2["tier-flag"] = "1";
- EXPECT_FALSE(H265IsSameProfileTierLevel(params1, params2));
-
- // One of the SdpVideoFormat::Parameters is invalid.
- params1.clear();
- params2.clear();
- params1["profile-id"] = "1";
- params2["profile-id"] = "1";
- params1["tier-flag"] = "0";
- params2["tier-flag"] = "4";
- EXPECT_FALSE(H265IsSameProfileTierLevel(params1, params2));
-}
-
-} // namespace webrtc
+/*
+ * Copyright (c) 2023 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "api/video_codecs/h265_profile_tier_level.h"
+
+#include <string>
+
+#include "absl/types/optional.h"
+#include "test/gtest.h"
+
+namespace webrtc {
+
+TEST(H265ProfileTierLevel, TestLevelToString) {
+ EXPECT_EQ(H265LevelToString(H265Level::kLevel1), "30");
+ EXPECT_EQ(H265LevelToString(H265Level::kLevel2), "60");
+ EXPECT_EQ(H265LevelToString(H265Level::kLevel2_1), "63");
+ EXPECT_EQ(H265LevelToString(H265Level::kLevel3), "90");
+ EXPECT_EQ(H265LevelToString(H265Level::kLevel3_1), "93");
+ EXPECT_EQ(H265LevelToString(H265Level::kLevel4), "120");
+ EXPECT_EQ(H265LevelToString(H265Level::kLevel4_1), "123");
+ EXPECT_EQ(H265LevelToString(H265Level::kLevel5), "150");
+ EXPECT_EQ(H265LevelToString(H265Level::kLevel5_1), "153");
+ EXPECT_EQ(H265LevelToString(H265Level::kLevel5_2), "156");
+ EXPECT_EQ(H265LevelToString(H265Level::kLevel6), "180");
+ EXPECT_EQ(H265LevelToString(H265Level::kLevel6_1), "183");
+ EXPECT_EQ(H265LevelToString(H265Level::kLevel6_2), "186");
+}
+
+TEST(H265ProfileTierLevel, TestProfileToString) {
+ EXPECT_EQ(H265ProfileToString(H265Profile::kProfileMain), "1");
+ EXPECT_EQ(H265ProfileToString(H265Profile::kProfileMain10), "2");
+ EXPECT_EQ(H265ProfileToString(H265Profile::kProfileMainStill), "3");
+ EXPECT_EQ(H265ProfileToString(H265Profile::kProfileRangeExtensions), "4");
+ EXPECT_EQ(H265ProfileToString(H265Profile::kProfileHighThroughput), "5");
+ EXPECT_EQ(H265ProfileToString(H265Profile::kProfileMultiviewMain), "6");
+ EXPECT_EQ(H265ProfileToString(H265Profile::kProfileScalableMain), "7");
+ EXPECT_EQ(H265ProfileToString(H265Profile::kProfile3dMain), "8");
+ EXPECT_EQ(H265ProfileToString(H265Profile::kProfileScreenContentCoding), "9");
+ EXPECT_EQ(H265ProfileToString(H265Profile::kProfileScalableRangeExtensions),
+ "10");
+ EXPECT_EQ(H265ProfileToString(
+ H265Profile::kProfileHighThroughputScreenContentCoding),
+ "11");
+}
+
+TEST(H265ProfileTierLevel, TestTierToString) {
+ EXPECT_EQ(H265TierToString(H265Tier::kTier0), "0");
+ EXPECT_EQ(H265TierToString(H265Tier::kTier1), "1");
+}
+
+TEST(H265ProfileTierLevel, TestStringToProfile) {
+ // Invalid profiles.
+ EXPECT_FALSE(StringToH265Profile("0"));
+ EXPECT_FALSE(StringToH265Profile("12"));
+
+ // Malformed profiles
+ EXPECT_FALSE(StringToH265Profile(""));
+ EXPECT_FALSE(StringToH265Profile(" 1"));
+ EXPECT_FALSE(StringToH265Profile("12x"));
+ EXPECT_FALSE(StringToH265Profile("x12"));
+ EXPECT_FALSE(StringToH265Profile("gggg"));
+
+ // Valid profiles.
+ EXPECT_EQ(StringToH265Profile("1"), H265Profile::kProfileMain);
+ EXPECT_EQ(StringToH265Profile("2"), H265Profile::kProfileMain10);
+ EXPECT_EQ(StringToH265Profile("4"), H265Profile::kProfileRangeExtensions);
+}
+
+TEST(H265ProfileTierLevel, TestStringToLevel) {
+ // Invalid levels.
+ EXPECT_FALSE(StringToH265Level("0"));
+ EXPECT_FALSE(StringToH265Level("200"));
+
+ // Malformed levels.
+ EXPECT_FALSE(StringToH265Level(""));
+ EXPECT_FALSE(StringToH265Level(" 30"));
+ EXPECT_FALSE(StringToH265Level("30x"));
+ EXPECT_FALSE(StringToH265Level("x30"));
+ EXPECT_FALSE(StringToH265Level("ggggg"));
+
+ // Valid levels.
+ EXPECT_EQ(StringToH265Level("30"), H265Level::kLevel1);
+ EXPECT_EQ(StringToH265Level("93"), H265Level::kLevel3_1);
+ EXPECT_EQ(StringToH265Level("183"), H265Level::kLevel6_1);
+}
+
+TEST(H265ProfileTierLevel, TestStringToTier) {
+ // Invalid tiers.
+ EXPECT_FALSE(StringToH265Tier("4"));
+ EXPECT_FALSE(StringToH265Tier("-1"));
+
+ // Malformed tiers.
+ EXPECT_FALSE(StringToH265Tier(""));
+ EXPECT_FALSE(StringToH265Tier(" 1"));
+ EXPECT_FALSE(StringToH265Tier("t1"));
+
+ // Valid tiers.
+ EXPECT_EQ(StringToH265Tier("0"), H265Tier::kTier0);
+ EXPECT_EQ(StringToH265Tier("1"), H265Tier::kTier1);
+}
+
+TEST(H265ProfileTierLevel, TestParseSdpProfileTierLevelAllEmpty) {
+ const absl::optional<H265ProfileTierLevel> profile_tier_level =
+ ParseSdpForH265ProfileTierLevel(CodecParameterMap());
+ EXPECT_TRUE(profile_tier_level);
+ EXPECT_EQ(H265Profile::kProfileMain, profile_tier_level->profile);
+ EXPECT_EQ(H265Level::kLevel3_1, profile_tier_level->level);
+ EXPECT_EQ(H265Tier::kTier0, profile_tier_level->tier);
+}
+
+TEST(H265ProfileTierLevel, TestParseSdpProfileTierLevelPartialEmpty) {
+ CodecParameterMap params;
+ params["profile-id"] = "1";
+ params["tier-flag"] = "0";
+ absl::optional<H265ProfileTierLevel> profile_tier_level =
+ ParseSdpForH265ProfileTierLevel(params);
+ EXPECT_TRUE(profile_tier_level);
+ EXPECT_EQ(H265Profile::kProfileMain, profile_tier_level->profile);
+ EXPECT_EQ(H265Level::kLevel3_1, profile_tier_level->level);
+ EXPECT_EQ(H265Tier::kTier0, profile_tier_level->tier);
+
+ params.clear();
+ params["profile-id"] = "2";
+ profile_tier_level = ParseSdpForH265ProfileTierLevel(params);
+ EXPECT_TRUE(profile_tier_level);
+ EXPECT_EQ(H265Profile::kProfileMain10, profile_tier_level->profile);
+ EXPECT_EQ(H265Level::kLevel3_1, profile_tier_level->level);
+ EXPECT_EQ(H265Tier::kTier0, profile_tier_level->tier);
+
+ params.clear();
+ params["level-id"] = "180";
+ profile_tier_level = ParseSdpForH265ProfileTierLevel(params);
+ EXPECT_TRUE(profile_tier_level);
+ EXPECT_EQ(H265Profile::kProfileMain, profile_tier_level->profile);
+ EXPECT_EQ(H265Level::kLevel6, profile_tier_level->level);
+ EXPECT_EQ(H265Tier::kTier0, profile_tier_level->tier);
+}
+
+TEST(H265ProfileTierLevel, TestParseSdpProfileTierLevelInvalid) {
+ CodecParameterMap params;
+
+ // Invalid profile-tier-level combination.
+ params["profile-id"] = "1";
+ params["tier-flag"] = "1";
+ params["level-id"] = "93";
+ absl::optional<H265ProfileTierLevel> profile_tier_level =
+ ParseSdpForH265ProfileTierLevel(params);
+ EXPECT_FALSE(profile_tier_level);
+ params.clear();
+ params["profile-id"] = "1";
+ params["tier-flag"] = "4";
+ params["level-id"] = "180";
+ profile_tier_level = ParseSdpForH265ProfileTierLevel(params);
+ EXPECT_FALSE(profile_tier_level);
+
+ // Valid profile-tier-level combination.
+ params.clear();
+ params["profile-id"] = "1";
+ params["tier-flag"] = "0";
+ params["level-id"] = "153";
+ profile_tier_level = ParseSdpForH265ProfileTierLevel(params);
+ EXPECT_TRUE(profile_tier_level);
+}
+
+TEST(H265ProfileTierLevel, TestToStringRoundTrip) {
+ CodecParameterMap params;
+ params["profile-id"] = "1";
+ params["tier-flag"] = "0";
+ params["level-id"] = "93";
+ absl::optional<H265ProfileTierLevel> profile_tier_level =
+ ParseSdpForH265ProfileTierLevel(params);
+ EXPECT_TRUE(profile_tier_level);
+ EXPECT_EQ("1", H265ProfileToString(profile_tier_level->profile));
+ EXPECT_EQ("0", H265TierToString(profile_tier_level->tier));
+ EXPECT_EQ("93", H265LevelToString(profile_tier_level->level));
+
+ params.clear();
+ params["profile-id"] = "2";
+ params["tier-flag"] = "1";
+ params["level-id"] = "180";
+ profile_tier_level = ParseSdpForH265ProfileTierLevel(params);
+ EXPECT_TRUE(profile_tier_level);
+ EXPECT_EQ("2", H265ProfileToString(profile_tier_level->profile));
+ EXPECT_EQ("1", H265TierToString(profile_tier_level->tier));
+ EXPECT_EQ("180", H265LevelToString(profile_tier_level->level));
+}
+
+TEST(H265ProfileTierLevel, TestProfileTierLevelCompare) {
+ CodecParameterMap params1;
+ CodecParameterMap params2;
+
+ // None of profile-id/tier-flag/level-id is specified,
+ EXPECT_TRUE(H265IsSameProfileTierLevel(params1, params2));
+
+ // Same non-empty PTL
+ params1["profile-id"] = "1";
+ params1["tier-flag"] = "0";
+ params1["level-id"] = "120";
+ params2["profile-id"] = "1";
+ params2["tier-flag"] = "0";
+ params2["level-id"] = "120";
+ EXPECT_TRUE(H265IsSameProfileTierLevel(params1, params2));
+
+ // Different profiles.
+ params1.clear();
+ params2.clear();
+ params1["profile-id"] = "1";
+ params2["profile-id"] = "2";
+ EXPECT_FALSE(H265IsSameProfileTierLevel(params1, params2));
+
+ // Different levels.
+ params1.clear();
+ params2.clear();
+ params1["profile-id"] = "1";
+ params2["profile-id"] = "1";
+ params1["level-id"] = "93";
+ params2["level-id"] = "183";
+ EXPECT_FALSE(H265IsSameProfileTierLevel(params1, params2));
+
+ // Different tiers.
+ params1.clear();
+ params2.clear();
+ params1["profile-id"] = "1";
+ params2["profile-id"] = "1";
+ params1["level-id"] = "93";
+ params2["level-id"] = "93";
+ params1["tier-flag"] = "0";
+ params2["tier-flag"] = "1";
+ EXPECT_FALSE(H265IsSameProfileTierLevel(params1, params2));
+
+ // One of the CodecParameterMap is invalid.
+ params1.clear();
+ params2.clear();
+ params1["profile-id"] = "1";
+ params2["profile-id"] = "1";
+ params1["tier-flag"] = "0";
+ params2["tier-flag"] = "4";
+ EXPECT_FALSE(H265IsSameProfileTierLevel(params1, params2));
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/api/video_codecs/test/sdp_video_format_unittest.cc b/third_party/libwebrtc/api/video_codecs/test/sdp_video_format_unittest.cc
index 797a9a2e72..26e50d6945 100644
--- a/third_party/libwebrtc/api/video_codecs/test/sdp_video_format_unittest.cc
+++ b/third_party/libwebrtc/api/video_codecs/test/sdp_video_format_unittest.cc
@@ -18,7 +18,7 @@
namespace webrtc {
typedef SdpVideoFormat Sdp;
-typedef SdpVideoFormat::Parameters Params;
+typedef CodecParameterMap Params;
TEST(SdpVideoFormatTest, SameCodecNameNoParameters) {
EXPECT_TRUE(Sdp("H264").IsSameCodec(Sdp("h264")));
diff --git a/third_party/libwebrtc/api/video_codecs/video_encoder_factory_template_libaom_av1_adapter.h b/third_party/libwebrtc/api/video_codecs/video_encoder_factory_template_libaom_av1_adapter.h
index 417df1e192..0f801ad3c7 100644
--- a/third_party/libwebrtc/api/video_codecs/video_encoder_factory_template_libaom_av1_adapter.h
+++ b/third_party/libwebrtc/api/video_codecs/video_encoder_factory_template_libaom_av1_adapter.h
@@ -24,8 +24,7 @@ struct LibaomAv1EncoderTemplateAdapter {
static std::vector<SdpVideoFormat> SupportedFormats() {
absl::InlinedVector<ScalabilityMode, kScalabilityModeCount>
scalability_modes = LibaomAv1EncoderSupportedScalabilityModes();
- return {
- SdpVideoFormat("AV1", SdpVideoFormat::Parameters(), scalability_modes)};
+ return {SdpVideoFormat("AV1", CodecParameterMap(), scalability_modes)};
}
static std::unique_ptr<VideoEncoder> CreateEncoder(
diff --git a/third_party/libwebrtc/api/video_codecs/video_encoder_factory_template_libvpx_vp8_adapter.h b/third_party/libwebrtc/api/video_codecs/video_encoder_factory_template_libvpx_vp8_adapter.h
index 0f0a9bacd5..c60aa04795 100644
--- a/third_party/libwebrtc/api/video_codecs/video_encoder_factory_template_libvpx_vp8_adapter.h
+++ b/third_party/libwebrtc/api/video_codecs/video_encoder_factory_template_libvpx_vp8_adapter.h
@@ -28,8 +28,7 @@ struct LibvpxVp8EncoderTemplateAdapter {
scalability_modes.push_back(scalability_mode);
}
- return {
- SdpVideoFormat("VP8", SdpVideoFormat::Parameters(), scalability_modes)};
+ return {SdpVideoFormat("VP8", CodecParameterMap(), scalability_modes)};
}
static std::unique_ptr<VideoEncoder> CreateEncoder(
diff --git a/third_party/libwebrtc/api/video_codecs/vp9_profile.cc b/third_party/libwebrtc/api/video_codecs/vp9_profile.cc
index 7e627cc080..ccd3937296 100644
--- a/third_party/libwebrtc/api/video_codecs/vp9_profile.cc
+++ b/third_party/libwebrtc/api/video_codecs/vp9_profile.cc
@@ -54,7 +54,7 @@ absl::optional<VP9Profile> StringToVP9Profile(const std::string& str) {
}
absl::optional<VP9Profile> ParseSdpForVP9Profile(
- const SdpVideoFormat::Parameters& params) {
+ const CodecParameterMap& params) {
const auto profile_it = params.find(kVP9FmtpProfileId);
if (profile_it == params.end())
return VP9Profile::kProfile0;
@@ -62,8 +62,8 @@ absl::optional<VP9Profile> ParseSdpForVP9Profile(
return StringToVP9Profile(profile_str);
}
-bool VP9IsSameProfile(const SdpVideoFormat::Parameters& params1,
- const SdpVideoFormat::Parameters& params2) {
+bool VP9IsSameProfile(const CodecParameterMap& params1,
+ const CodecParameterMap& params2) {
const absl::optional<VP9Profile> profile = ParseSdpForVP9Profile(params1);
const absl::optional<VP9Profile> other_profile =
ParseSdpForVP9Profile(params2);
diff --git a/third_party/libwebrtc/api/video_codecs/vp9_profile.h b/third_party/libwebrtc/api/video_codecs/vp9_profile.h
index b570bc3bb6..27f84cbecc 100644
--- a/third_party/libwebrtc/api/video_codecs/vp9_profile.h
+++ b/third_party/libwebrtc/api/video_codecs/vp9_profile.h
@@ -42,12 +42,12 @@ absl::optional<VP9Profile> StringToVP9Profile(const std::string& str);
// profile key is missing. Nothing will be returned if the key is present but
// the string is invalid.
RTC_EXPORT absl::optional<VP9Profile> ParseSdpForVP9Profile(
- const SdpVideoFormat::Parameters& params);
+ const CodecParameterMap& params);
// Returns true if the parameters have the same VP9 profile, or neither contains
// VP9 profile.
-bool VP9IsSameProfile(const SdpVideoFormat::Parameters& params1,
- const SdpVideoFormat::Parameters& params2);
+bool VP9IsSameProfile(const CodecParameterMap& params1,
+ const CodecParameterMap& params2);
} // namespace webrtc
diff --git a/third_party/libwebrtc/audio/BUILD.gn b/third_party/libwebrtc/audio/BUILD.gn
index 09562b9131..7ece107407 100644
--- a/third_party/libwebrtc/audio/BUILD.gn
+++ b/third_party/libwebrtc/audio/BUILD.gn
@@ -97,7 +97,6 @@ rtc_library("audio") {
"../rtc_base:refcount",
"../rtc_base:rtc_event",
"../rtc_base:rtc_numerics",
- "../rtc_base:rtc_task_queue",
"../rtc_base:safe_conversions",
"../rtc_base:safe_minmax",
"../rtc_base:stringutils",
@@ -175,7 +174,8 @@ if (rtc_include_tests) {
"../api/audio_codecs/opus:audio_decoder_opus",
"../api/audio_codecs/opus:audio_encoder_opus",
"../api/crypto:frame_decryptor_interface",
- "../api/rtc_event_log",
+ "../api/environment",
+ "../api/environment:environment_factory",
"../api/task_queue:default_task_queue_factory",
"../api/task_queue/test:mock_task_queue_base",
"../api/units:time_delta",
@@ -223,7 +223,10 @@ if (rtc_include_tests) {
"utility:utility_tests",
"//testing/gtest",
]
- absl_deps = [ "//third_party/abseil-cpp/absl/memory" ]
+ absl_deps = [
+ "//third_party/abseil-cpp/absl/memory",
+ "//third_party/abseil-cpp/absl/types:optional",
+ ]
}
rtc_library("channel_receive_unittest") {
@@ -247,6 +250,9 @@ if (rtc_include_tests) {
"../test:test_support",
"../test/time_controller",
]
- absl_deps = [ "//third_party/abseil-cpp/absl/strings" ]
+ absl_deps = [
+ "//third_party/abseil-cpp/absl/strings",
+ "//third_party/abseil-cpp/absl/types:optional",
+ ]
}
}
diff --git a/third_party/libwebrtc/audio/audio_receive_stream.cc b/third_party/libwebrtc/audio/audio_receive_stream.cc
index c49b83f95f..32a8e1c172 100644
--- a/third_party/libwebrtc/audio/audio_receive_stream.cc
+++ b/third_party/libwebrtc/audio/audio_receive_stream.cc
@@ -186,6 +186,7 @@ void AudioReceiveStreamImpl::Start() {
if (playing_) {
return;
}
+ RTC_LOG(LS_INFO) << "AudioReceiveStreamImpl::Start: " << remote_ssrc();
channel_receive_->StartPlayout();
playing_ = true;
audio_state()->AddReceivingStream(this);
@@ -196,6 +197,7 @@ void AudioReceiveStreamImpl::Stop() {
if (!playing_) {
return;
}
+ RTC_LOG(LS_INFO) << "AudioReceiveStreamImpl::Stop: " << remote_ssrc();
channel_receive_->StopPlayout();
playing_ = false;
audio_state()->RemoveReceivingStream(this);
diff --git a/third_party/libwebrtc/audio/audio_send_stream.cc b/third_party/libwebrtc/audio/audio_send_stream.cc
index e7ebb2bf4e..8dc78b18fa 100644
--- a/third_party/libwebrtc/audio/audio_send_stream.cc
+++ b/third_party/libwebrtc/audio/audio_send_stream.cc
@@ -355,6 +355,7 @@ void AudioSendStream::Start() {
if (sending_) {
return;
}
+ RTC_LOG(LS_INFO) << "AudioSendStream::Start: " << config_.rtp.ssrc;
if (!config_.has_dscp && config_.min_bitrate_bps != -1 &&
config_.max_bitrate_bps != -1 &&
(allocate_audio_without_feedback_ || TransportSeqNumId(config_) != 0)) {
@@ -376,7 +377,7 @@ void AudioSendStream::Stop() {
if (!sending_) {
return;
}
-
+ RTC_LOG(LS_INFO) << "AudioSendStream::Stop: " << config_.rtp.ssrc;
RemoveBitrateObserver();
channel_send_->StopSend();
sending_ = false;
diff --git a/third_party/libwebrtc/audio/audio_send_stream.h b/third_party/libwebrtc/audio/audio_send_stream.h
index 62ccd524cb..09fd712d40 100644
--- a/third_party/libwebrtc/audio/audio_send_stream.h
+++ b/third_party/libwebrtc/audio/audio_send_stream.h
@@ -28,7 +28,6 @@
#include "rtc_base/experiments/struct_parameters_parser.h"
#include "rtc_base/race_checker.h"
#include "rtc_base/synchronization/mutex.h"
-#include "rtc_base/task_queue.h"
namespace webrtc {
class RtcEventLog;
diff --git a/third_party/libwebrtc/audio/channel_receive_frame_transformer_delegate.h b/third_party/libwebrtc/audio/channel_receive_frame_transformer_delegate.h
index 37ff75c2e9..d572ffe8d1 100644
--- a/third_party/libwebrtc/audio/channel_receive_frame_transformer_delegate.h
+++ b/third_party/libwebrtc/audio/channel_receive_frame_transformer_delegate.h
@@ -16,8 +16,8 @@
#include "api/frame_transformer_interface.h"
#include "api/sequence_checker.h"
+#include "api/task_queue/task_queue_base.h"
#include "rtc_base/system/no_unique_address.h"
-#include "rtc_base/task_queue.h"
#include "rtc_base/thread.h"
namespace webrtc {
diff --git a/third_party/libwebrtc/audio/channel_send.cc b/third_party/libwebrtc/audio/channel_send.cc
index 3c59be52b4..ae264a4c77 100644
--- a/third_party/libwebrtc/audio/channel_send.cc
+++ b/third_party/libwebrtc/audio/channel_send.cc
@@ -39,7 +39,7 @@
#include "rtc_base/rate_limiter.h"
#include "rtc_base/strings/string_builder.h"
#include "rtc_base/synchronization/mutex.h"
-#include "rtc_base/task_queue.h"
+#include "rtc_base/system/no_unique_address.h"
#include "rtc_base/time_utils.h"
#include "rtc_base/trace_event.h"
#include "system_wrappers/include/clock.h"
@@ -196,7 +196,7 @@ class ChannelSend : public ChannelSendInterface,
rtc::ArrayView<const uint8_t> payload,
int64_t absolute_capture_timestamp_ms,
rtc::ArrayView<const uint32_t> csrcs)
- RTC_RUN_ON(encoder_queue_);
+ RTC_RUN_ON(encoder_queue_checker_);
void OnReceivedRtt(int64_t rtt_ms);
@@ -207,7 +207,7 @@ class ChannelSend : public ChannelSendInterface,
// specific threads we know about. The goal is to eventually split up
// voe::Channel into parts with single-threaded semantics, and thereby reduce
// the need for locks.
- SequenceChecker worker_thread_checker_;
+ RTC_NO_UNIQUE_ADDRESS SequenceChecker worker_thread_checker_;
// Methods accessed from audio and video threads are checked for sequential-
// only access. We don't necessarily own and control these threads, so thread
// checkers cannot be used. E.g. Chromium may transfer "ownership" from one
@@ -231,9 +231,9 @@ class ChannelSend : public ChannelSendInterface,
absl::optional<int64_t> last_capture_timestamp_ms_
RTC_GUARDED_BY(audio_thread_race_checker_);
- RmsLevel rms_level_ RTC_GUARDED_BY(encoder_queue_);
+ RmsLevel rms_level_ RTC_GUARDED_BY(encoder_queue_checker_);
bool input_mute_ RTC_GUARDED_BY(volume_settings_mutex_) = false;
- bool previous_frame_muted_ RTC_GUARDED_BY(encoder_queue_) = false;
+ bool previous_frame_muted_ RTC_GUARDED_BY(encoder_queue_checker_) = false;
const std::unique_ptr<RtcpCounterObserver> rtcp_counter_observer_;
@@ -242,7 +242,7 @@ class ChannelSend : public ChannelSendInterface,
const std::unique_ptr<RtpPacketSenderProxy> rtp_packet_pacer_proxy_;
const std::unique_ptr<RateLimiter> retransmission_rate_limiter_;
- SequenceChecker construction_thread_;
+ RTC_NO_UNIQUE_ADDRESS SequenceChecker construction_thread_;
std::atomic<bool> include_audio_level_indication_ = false;
std::atomic<bool> encoder_queue_is_active_ = false;
@@ -250,7 +250,7 @@ class ChannelSend : public ChannelSendInterface,
// E2EE Audio Frame Encryption
rtc::scoped_refptr<FrameEncryptorInterface> frame_encryptor_
- RTC_GUARDED_BY(encoder_queue_);
+ RTC_GUARDED_BY(encoder_queue_checker_);
// E2EE Frame Encryption Options
const webrtc::CryptoOptions crypto_options_;
@@ -258,15 +258,14 @@ class ChannelSend : public ChannelSendInterface,
// receives callbacks with the transformed frames; delegates calls to
// ChannelSend::SendRtpAudio to send the transformed audio.
rtc::scoped_refptr<ChannelSendFrameTransformerDelegate>
- frame_transformer_delegate_ RTC_GUARDED_BY(encoder_queue_);
+ frame_transformer_delegate_ RTC_GUARDED_BY(encoder_queue_checker_);
mutable Mutex rtcp_counter_mutex_;
RtcpPacketTypeCounter rtcp_packet_type_counter_
RTC_GUARDED_BY(rtcp_counter_mutex_);
- // Defined last to ensure that there are no running tasks when the other
- // members are destroyed.
- rtc::TaskQueue encoder_queue_;
+ std::unique_ptr<TaskQueueBase, TaskQueueDeleter> encoder_queue_;
+ RTC_NO_UNIQUE_ADDRESS SequenceChecker encoder_queue_checker_;
SdpAudioFormat encoder_format_;
};
@@ -299,7 +298,7 @@ class RtpPacketSenderProxy : public RtpPacketSender {
}
private:
- SequenceChecker thread_checker_;
+ RTC_NO_UNIQUE_ADDRESS SequenceChecker thread_checker_;
Mutex mutex_;
RtpPacketSender* rtp_packet_pacer_ RTC_GUARDED_BY(&mutex_);
};
@@ -310,7 +309,7 @@ int32_t ChannelSend::SendData(AudioFrameType frameType,
const uint8_t* payloadData,
size_t payloadSize,
int64_t absolute_capture_timestamp_ms) {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ RTC_DCHECK_RUN_ON(&encoder_queue_checker_);
rtc::ArrayView<const uint8_t> payload(payloadData, payloadSize);
if (frame_transformer_delegate_) {
// Asynchronously transform the payload before sending it. After the payload
@@ -438,6 +437,7 @@ ChannelSend::ChannelSend(
encoder_queue_(task_queue_factory->CreateTaskQueue(
"AudioEncoder",
TaskQueueFactory::Priority::NORMAL)),
+ encoder_queue_checker_(encoder_queue_.get()),
encoder_format_("x-unknown", 0, 0) {
audio_coding_ = AudioCodingModule::Create();
@@ -490,6 +490,10 @@ ChannelSend::~ChannelSend() {
StopSend();
int error = audio_coding_->RegisterTransportCallback(NULL);
RTC_DCHECK_EQ(0, error);
+
+ // Delete the encoder task queue first to ensure that there are no running
+ // tasks when the other members are destroyed.
+ encoder_queue_ = nullptr;
}
void ChannelSend::StartSend() {
@@ -519,8 +523,8 @@ void ChannelSend::StopSend() {
// Wait until all pending encode tasks are executed and clear any remaining
// buffers in the encoder.
rtc::Event flush;
- encoder_queue_.PostTask([this, &flush]() {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ encoder_queue_->PostTask([this, &flush]() {
+ RTC_DCHECK_RUN_ON(&encoder_queue_checker_);
CallEncoder([](AudioEncoder* encoder) { encoder->Reset(); });
flush.Set();
});
@@ -794,9 +798,9 @@ void ChannelSend::ProcessAndEncodeAudio(
// Profile time between when the audio frame is added to the task queue and
// when the task is actually executed.
audio_frame->UpdateProfileTimeStamp();
- encoder_queue_.PostTask(
+ encoder_queue_->PostTask(
[this, audio_frame = std::move(audio_frame)]() mutable {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ RTC_DCHECK_RUN_ON(&encoder_queue_checker_);
if (!encoder_queue_is_active_.load()) {
return;
}
@@ -858,8 +862,8 @@ int64_t ChannelSend::GetRTT() const {
void ChannelSend::SetFrameEncryptor(
rtc::scoped_refptr<FrameEncryptorInterface> frame_encryptor) {
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
- encoder_queue_.PostTask([this, frame_encryptor]() mutable {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ encoder_queue_->PostTask([this, frame_encryptor]() mutable {
+ RTC_DCHECK_RUN_ON(&encoder_queue_checker_);
frame_encryptor_ = std::move(frame_encryptor);
});
}
@@ -870,9 +874,9 @@ void ChannelSend::SetEncoderToPacketizerFrameTransformer(
if (!frame_transformer)
return;
- encoder_queue_.PostTask(
+ encoder_queue_->PostTask(
[this, frame_transformer = std::move(frame_transformer)]() mutable {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ RTC_DCHECK_RUN_ON(&encoder_queue_checker_);
InitFrameTransformerDelegate(std::move(frame_transformer));
});
}
@@ -885,7 +889,7 @@ void ChannelSend::OnReceivedRtt(int64_t rtt_ms) {
void ChannelSend::InitFrameTransformerDelegate(
rtc::scoped_refptr<webrtc::FrameTransformerInterface> frame_transformer) {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ RTC_DCHECK_RUN_ON(&encoder_queue_checker_);
RTC_DCHECK(frame_transformer);
RTC_DCHECK(!frame_transformer_delegate_);
@@ -897,7 +901,7 @@ void ChannelSend::InitFrameTransformerDelegate(
rtc::ArrayView<const uint8_t> payload,
int64_t absolute_capture_timestamp_ms,
rtc::ArrayView<const uint32_t> csrcs) {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ RTC_DCHECK_RUN_ON(&encoder_queue_checker_);
return SendRtpAudio(
frameType, payloadType,
rtp_timestamp_with_offset - rtp_rtcp_->StartTimestamp(), payload,
@@ -906,7 +910,7 @@ void ChannelSend::InitFrameTransformerDelegate(
frame_transformer_delegate_ =
rtc::make_ref_counted<ChannelSendFrameTransformerDelegate>(
std::move(send_audio_callback), std::move(frame_transformer),
- &encoder_queue_);
+ encoder_queue_.get());
frame_transformer_delegate_->Init();
}
diff --git a/third_party/libwebrtc/audio/channel_send_frame_transformer_delegate.cc b/third_party/libwebrtc/audio/channel_send_frame_transformer_delegate.cc
index 2eea0d2387..ac32410aed 100644
--- a/third_party/libwebrtc/audio/channel_send_frame_transformer_delegate.cc
+++ b/third_party/libwebrtc/audio/channel_send_frame_transformer_delegate.cc
@@ -114,7 +114,7 @@ class TransformableOutgoingAudioFrame
ChannelSendFrameTransformerDelegate::ChannelSendFrameTransformerDelegate(
SendFrameCallback send_frame_callback,
rtc::scoped_refptr<FrameTransformerInterface> frame_transformer,
- rtc::TaskQueue* encoder_queue)
+ TaskQueueBase* encoder_queue)
: send_frame_callback_(send_frame_callback),
frame_transformer_(std::move(frame_transformer)),
encoder_queue_(encoder_queue) {}
diff --git a/third_party/libwebrtc/audio/channel_send_frame_transformer_delegate.h b/third_party/libwebrtc/audio/channel_send_frame_transformer_delegate.h
index 97fc14f737..30e63ff98b 100644
--- a/third_party/libwebrtc/audio/channel_send_frame_transformer_delegate.h
+++ b/third_party/libwebrtc/audio/channel_send_frame_transformer_delegate.h
@@ -16,10 +16,10 @@
#include "api/frame_transformer_interface.h"
#include "api/sequence_checker.h"
+#include "api/task_queue/task_queue_base.h"
#include "modules/audio_coding/include/audio_coding_module_typedefs.h"
#include "rtc_base/buffer.h"
#include "rtc_base/synchronization/mutex.h"
-#include "rtc_base/task_queue.h"
namespace webrtc {
@@ -40,7 +40,7 @@ class ChannelSendFrameTransformerDelegate : public TransformedFrameCallback {
ChannelSendFrameTransformerDelegate(
SendFrameCallback send_frame_callback,
rtc::scoped_refptr<FrameTransformerInterface> frame_transformer,
- rtc::TaskQueue* encoder_queue);
+ TaskQueueBase* encoder_queue);
// Registers `this` as callback for `frame_transformer_`, to get the
// transformed frames.
@@ -79,7 +79,7 @@ class ChannelSendFrameTransformerDelegate : public TransformedFrameCallback {
mutable Mutex send_lock_;
SendFrameCallback send_frame_callback_ RTC_GUARDED_BY(send_lock_);
rtc::scoped_refptr<FrameTransformerInterface> frame_transformer_;
- rtc::TaskQueue* encoder_queue_ RTC_GUARDED_BY(send_lock_);
+ TaskQueueBase* const encoder_queue_;
bool short_circuit_ RTC_GUARDED_BY(send_lock_) = false;
};
diff --git a/third_party/libwebrtc/audio/channel_send_frame_transformer_delegate_unittest.cc b/third_party/libwebrtc/audio/channel_send_frame_transformer_delegate_unittest.cc
index 4dcd15cd95..483a2cce78 100644
--- a/third_party/libwebrtc/audio/channel_send_frame_transformer_delegate_unittest.cc
+++ b/third_party/libwebrtc/audio/channel_send_frame_transformer_delegate_unittest.cc
@@ -78,7 +78,7 @@ std::unique_ptr<TransformableAudioFrameInterface> CreateFrame() {
MockChannelSend mock_channel;
rtc::scoped_refptr<ChannelSendFrameTransformerDelegate> delegate =
rtc::make_ref_counted<ChannelSendFrameTransformerDelegate>(
- mock_channel.callback(), mock_frame_transformer, &channel_queue);
+ mock_channel.callback(), mock_frame_transformer, channel_queue.Get());
std::unique_ptr<TransformableFrameInterface> frame;
ON_CALL(*mock_frame_transformer, Transform)
@@ -131,7 +131,7 @@ TEST(ChannelSendFrameTransformerDelegateTest,
MockChannelSend mock_channel;
rtc::scoped_refptr<ChannelSendFrameTransformerDelegate> delegate =
rtc::make_ref_counted<ChannelSendFrameTransformerDelegate>(
- mock_channel.callback(), mock_frame_transformer, &channel_queue);
+ mock_channel.callback(), mock_frame_transformer, channel_queue.Get());
rtc::scoped_refptr<TransformedFrameCallback> callback;
EXPECT_CALL(*mock_frame_transformer, RegisterTransformedFrameCallback)
.WillOnce(SaveArg<0>(&callback));
@@ -160,7 +160,7 @@ TEST(ChannelSendFrameTransformerDelegateTest,
MockChannelSend mock_channel;
rtc::scoped_refptr<ChannelSendFrameTransformerDelegate> delegate =
rtc::make_ref_counted<ChannelSendFrameTransformerDelegate>(
- mock_channel.callback(), mock_frame_transformer, &channel_queue);
+ mock_channel.callback(), mock_frame_transformer, channel_queue.Get());
rtc::scoped_refptr<TransformedFrameCallback> callback;
EXPECT_CALL(*mock_frame_transformer, RegisterTransformedFrameCallback)
.WillOnce(SaveArg<0>(&callback));
@@ -192,7 +192,7 @@ TEST(ChannelSendFrameTransformerDelegateTest,
MockChannelSend mock_channel;
rtc::scoped_refptr<ChannelSendFrameTransformerDelegate> delegate =
rtc::make_ref_counted<ChannelSendFrameTransformerDelegate>(
- mock_channel.callback(), mock_frame_transformer, &channel_queue);
+ mock_channel.callback(), mock_frame_transformer, channel_queue.Get());
delegate->Reset();
EXPECT_CALL(mock_channel, SendFrame).Times(0);
@@ -207,7 +207,7 @@ TEST(ChannelSendFrameTransformerDelegateTest, ShortCircuitingSkipsTransform) {
MockChannelSend mock_channel;
rtc::scoped_refptr<ChannelSendFrameTransformerDelegate> delegate =
rtc::make_ref_counted<ChannelSendFrameTransformerDelegate>(
- mock_channel.callback(), mock_frame_transformer, &channel_queue);
+ mock_channel.callback(), mock_frame_transformer, channel_queue.Get());
delegate->StartShortCircuiting();
diff --git a/third_party/libwebrtc/audio/channel_send_unittest.cc b/third_party/libwebrtc/audio/channel_send_unittest.cc
index 58d7c93c1e..c86dcefadc 100644
--- a/third_party/libwebrtc/audio/channel_send_unittest.cc
+++ b/third_party/libwebrtc/audio/channel_send_unittest.cc
@@ -14,7 +14,8 @@
#include "api/audio/audio_frame.h"
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
-#include "api/rtc_event_log/rtc_event_log.h"
+#include "api/environment/environment.h"
+#include "api/environment/environment_factory.h"
#include "api/scoped_refptr.h"
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
@@ -53,18 +54,17 @@ class ChannelSendTest : public ::testing::Test {
protected:
ChannelSendTest()
: time_controller_(Timestamp::Seconds(1)),
+ env_(CreateEnvironment(&field_trials_,
+ time_controller_.GetClock(),
+ time_controller_.CreateTaskQueueFactory())),
transport_controller_(
- time_controller_.GetClock(),
- RtpTransportConfig{
- .bitrate_config = GetBitrateConfig(),
- .event_log = &event_log_,
- .task_queue_factory = time_controller_.GetTaskQueueFactory(),
- .trials = &field_trials_,
- }) {
+ RtpTransportConfig{.env = env_,
+ .bitrate_config = GetBitrateConfig()}) {
channel_ = voe::CreateChannelSend(
time_controller_.GetClock(), time_controller_.GetTaskQueueFactory(),
- &transport_, nullptr, &event_log_, nullptr, crypto_options_, false,
- kRtcpIntervalMs, kSsrc, nullptr, &transport_controller_, field_trials_);
+ &transport_, nullptr, &env_.event_log(), nullptr, crypto_options_,
+ false, kRtcpIntervalMs, kSsrc, nullptr, &transport_controller_,
+ env_.field_trials());
encoder_factory_ = CreateBuiltinAudioEncoderFactory();
SdpAudioFormat opus = SdpAudioFormat("opus", kRtpRateHz, 2);
std::unique_ptr<AudioEncoder> encoder =
@@ -94,7 +94,7 @@ class ChannelSendTest : public ::testing::Test {
GlobalSimulatedTimeController time_controller_;
webrtc::test::ScopedKeyValueConfig field_trials_;
- RtcEventLogNull event_log_;
+ Environment env_;
NiceMock<MockTransport> transport_;
CryptoOptions crypto_options_;
RtpTransportControllerSend transport_controller_;
diff --git a/third_party/libwebrtc/audio/voip/BUILD.gn b/third_party/libwebrtc/audio/voip/BUILD.gn
index e807e2276b..75f20a6ed2 100644
--- a/third_party/libwebrtc/audio/voip/BUILD.gn
+++ b/third_party/libwebrtc/audio/voip/BUILD.gn
@@ -94,7 +94,6 @@ rtc_library("audio_egress") {
"../../modules/rtp_rtcp",
"../../modules/rtp_rtcp:rtp_rtcp_format",
"../../rtc_base:logging",
- "../../rtc_base:rtc_task_queue",
"../../rtc_base:timeutils",
"../../rtc_base/synchronization:mutex",
"../../rtc_base/system:no_unique_address",
diff --git a/third_party/libwebrtc/audio/voip/audio_egress.cc b/third_party/libwebrtc/audio/voip/audio_egress.cc
index 95a1a3351e..09396cd28d 100644
--- a/third_party/libwebrtc/audio/voip/audio_egress.cc
+++ b/third_party/libwebrtc/audio/voip/audio_egress.cc
@@ -13,6 +13,7 @@
#include <utility>
#include <vector>
+#include "api/sequence_checker.h"
#include "rtc_base/logging.h"
namespace webrtc {
@@ -25,12 +26,17 @@ AudioEgress::AudioEgress(RtpRtcpInterface* rtp_rtcp,
audio_coding_(AudioCodingModule::Create()),
encoder_queue_(task_queue_factory->CreateTaskQueue(
"AudioEncoder",
- TaskQueueFactory::Priority::NORMAL)) {
+ TaskQueueFactory::Priority::NORMAL)),
+ encoder_queue_checker_(encoder_queue_.get()) {
audio_coding_->RegisterTransportCallback(this);
}
AudioEgress::~AudioEgress() {
audio_coding_->RegisterTransportCallback(nullptr);
+
+ // Delete first to ensure that there are no running tasks when the other
+ // members are destroyed.
+ encoder_queue_ = nullptr;
}
bool AudioEgress::IsSending() const {
@@ -73,9 +79,9 @@ void AudioEgress::SendAudioData(std::unique_ptr<AudioFrame> audio_frame) {
RTC_DCHECK_GT(audio_frame->samples_per_channel_, 0);
RTC_DCHECK_LE(audio_frame->num_channels_, 8);
- encoder_queue_.PostTask(
+ encoder_queue_->PostTask(
[this, audio_frame = std::move(audio_frame)]() mutable {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ RTC_DCHECK_RUN_ON(&encoder_queue_checker_);
if (!rtp_rtcp_->SendingMedia()) {
return;
}
@@ -112,7 +118,7 @@ int32_t AudioEgress::SendData(AudioFrameType frame_type,
uint32_t timestamp,
const uint8_t* payload_data,
size_t payload_size) {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ RTC_DCHECK_RUN_ON(&encoder_queue_checker_);
rtc::ArrayView<const uint8_t> payload(payload_data, payload_size);
@@ -175,8 +181,8 @@ bool AudioEgress::SendTelephoneEvent(int dtmf_event, int duration_ms) {
}
void AudioEgress::SetMute(bool mute) {
- encoder_queue_.PostTask([this, mute] {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ encoder_queue_->PostTask([this, mute] {
+ RTC_DCHECK_RUN_ON(&encoder_queue_checker_);
encoder_context_.mute_ = mute;
});
}
diff --git a/third_party/libwebrtc/audio/voip/audio_egress.h b/third_party/libwebrtc/audio/voip/audio_egress.h
index 989e5bda59..6d1489db34 100644
--- a/third_party/libwebrtc/audio/voip/audio_egress.h
+++ b/third_party/libwebrtc/audio/voip/audio_egress.h
@@ -16,6 +16,7 @@
#include "api/audio_codecs/audio_format.h"
#include "api/sequence_checker.h"
+#include "api/task_queue/task_queue_base.h"
#include "api/task_queue/task_queue_factory.h"
#include "audio/audio_level.h"
#include "audio/utility/audio_frame_operations.h"
@@ -25,7 +26,7 @@
#include "modules/rtp_rtcp/source/rtp_rtcp_interface.h"
#include "modules/rtp_rtcp/source/rtp_sender_audio.h"
#include "rtc_base/synchronization/mutex.h"
-#include "rtc_base/task_queue.h"
+#include "rtc_base/system/no_unique_address.h"
#include "rtc_base/time_utils.h"
namespace webrtc {
@@ -146,11 +147,10 @@ class AudioEgress : public AudioSender, public AudioPacketizationCallback {
bool previously_muted_ = false;
};
- EncoderContext encoder_context_ RTC_GUARDED_BY(encoder_queue_);
+ EncoderContext encoder_context_ RTC_GUARDED_BY(encoder_queue_checker_);
- // Defined last to ensure that there are no running tasks when the other
- // members are destroyed.
- rtc::TaskQueue encoder_queue_;
+ std::unique_ptr<TaskQueueBase, TaskQueueDeleter> encoder_queue_;
+ RTC_NO_UNIQUE_ADDRESS SequenceChecker encoder_queue_checker_;
};
} // namespace webrtc
diff --git a/third_party/libwebrtc/call/BUILD.gn b/third_party/libwebrtc/call/BUILD.gn
index 626ed95066..50a8257631 100644
--- a/third_party/libwebrtc/call/BUILD.gn
+++ b/third_party/libwebrtc/call/BUILD.gn
@@ -46,10 +46,8 @@ rtc_library("call_interfaces") {
":rtp_interfaces",
":video_stream_api",
"../api:fec_controller_api",
- "../api:field_trials_view",
"../api:frame_transformer_interface",
"../api:network_state_predictor_api",
- "../api:rtc_error",
"../api:rtp_headers",
"../api:rtp_parameters",
"../api:rtp_sender_setparameters_callback",
@@ -116,20 +114,20 @@ rtc_library("rtp_interfaces") {
deps = [
"../api:array_view",
"../api:fec_controller_api",
- "../api:field_trials_view",
"../api:frame_transformer_interface",
"../api:network_state_predictor_api",
"../api:rtp_headers",
"../api:rtp_parameters",
"../api/crypto:options",
+ "../api/environment",
"../api/rtc_event_log",
"../api/transport:bitrate_settings",
"../api/transport:network_control",
+ "../api/units:time_delta",
"../api/units:timestamp",
"../common_video:frame_counts",
"../modules/rtp_rtcp:rtp_rtcp_format",
"../rtc_base:checks",
- "../rtc_base:rtc_task_queue",
"../rtc_base:stringutils",
]
absl_deps = [
@@ -191,6 +189,7 @@ rtc_library("rtp_sender") {
"../api:rtp_parameters",
"../api:sequence_checker",
"../api:transport_api",
+ "../api/environment",
"../api/rtc_event_log",
"../api/task_queue:pending_task_safety_flag",
"../api/task_queue:task_queue",
@@ -280,8 +279,8 @@ rtc_library("bitrate_allocator") {
rtc_library("call") {
sources = [
"call.cc",
- "call_factory.cc",
- "call_factory.h",
+ "create_call.cc",
+ "create_call.h",
"degraded_call.cc",
"degraded_call.h",
"flexfec_receive_stream_impl.cc",
@@ -301,7 +300,6 @@ rtc_library("call") {
":version",
":video_stream_api",
"../api:array_view",
- "../api:callfactory_api",
"../api:fec_controller_api",
"../api:field_trials_view",
"../api:rtp_headers",
@@ -355,7 +353,7 @@ rtc_library("call") {
]
if (build_with_mozilla) { # See Bug 1820869.
sources -= [
- "call_factory.cc",
+ "create_call.cc",
"degraded_call.cc",
]
deps -= [
diff --git a/third_party/libwebrtc/call/call.cc b/third_party/libwebrtc/call/call.cc
index 63dc370f1a..71511b2559 100644
--- a/third_party/libwebrtc/call/call.cc
+++ b/third_party/libwebrtc/call/call.cc
@@ -70,7 +70,7 @@
#include "video/send_delay_stats.h"
#include "video/stats_counter.h"
#include "video/video_receive_stream2.h"
-#include "video/video_send_stream.h"
+#include "video/video_send_stream_impl.h"
namespace webrtc {
@@ -183,10 +183,8 @@ class Call final : public webrtc::Call,
public TargetTransferRateObserver,
public BitrateAllocator::LimitObserver {
public:
- Call(Clock* clock,
- const CallConfig& config,
- std::unique_ptr<RtpTransportControllerSendInterface> transport_send,
- TaskQueueFactory* task_queue_factory);
+ Call(const CallConfig& config,
+ std::unique_ptr<RtpTransportControllerSendInterface> transport_send);
~Call() override;
Call(const Call&) = delete;
@@ -345,8 +343,7 @@ class Call final : public webrtc::Call,
// callbacks have been registered.
void EnsureStarted() RTC_RUN_ON(worker_thread_);
- Clock* const clock_;
- TaskQueueFactory* const task_queue_factory_;
+ const Environment env_;
TaskQueueBase* const worker_thread_;
TaskQueueBase* const network_thread_;
const std::unique_ptr<DecodeSynchronizer> decode_sync_;
@@ -356,8 +353,6 @@ class Call final : public webrtc::Call,
const std::unique_ptr<CallStats> call_stats_;
const std::unique_ptr<BitrateAllocator> bitrate_allocator_;
const CallConfig config_ RTC_GUARDED_BY(worker_thread_);
- // Maps to config_.trials, can be used from any thread via `trials()`.
- const FieldTrialsView& trials_;
NetworkState audio_network_state_ RTC_GUARDED_BY(worker_thread_);
NetworkState video_network_state_ RTC_GUARDED_BY(worker_thread_);
@@ -393,9 +388,10 @@ class Call final : public webrtc::Call,
// should be accessed on the network thread.
std::map<uint32_t, AudioSendStream*> audio_send_ssrcs_
RTC_GUARDED_BY(worker_thread_);
- std::map<uint32_t, VideoSendStream*> video_send_ssrcs_
+ std::map<uint32_t, VideoSendStreamImpl*> video_send_ssrcs_
+ RTC_GUARDED_BY(worker_thread_);
+ std::set<VideoSendStreamImpl*> video_send_streams_
RTC_GUARDED_BY(worker_thread_);
- std::set<VideoSendStream*> video_send_streams_ RTC_GUARDED_BY(worker_thread_);
// True if `video_send_streams_` is empty, false if not. The atomic variable
// is used to decide UMA send statistics behavior and enables avoiding a
// PostTask().
@@ -413,8 +409,6 @@ class Call final : public webrtc::Call,
RtpPayloadStateMap suspended_video_payload_states_
RTC_GUARDED_BY(worker_thread_);
- webrtc::RtcEventLog* const event_log_;
-
// TODO(bugs.webrtc.org/11993) ready to move stats access to the network
// thread.
ReceiveStats receive_stats_ RTC_GUARDED_BY(worker_thread_);
@@ -460,25 +454,17 @@ class Call final : public webrtc::Call,
};
} // namespace internal
-/* Mozilla: Avoid this since it could use GetRealTimeClock().
std::unique_ptr<Call> Call::Create(const CallConfig& config) {
- Clock* clock =
- config.env.has_value() ? &config.env->clock() : Clock::GetRealTimeClock();
- return Create(config, clock,
- RtpTransportControllerSendFactory().Create(
- config.ExtractTransportConfig(), clock));
-}
- */
+ std::unique_ptr<RtpTransportControllerSendInterface> transport_send;
+ if (config.rtp_transport_controller_send_factory != nullptr) {
+ transport_send = config.rtp_transport_controller_send_factory->Create(
+ config.ExtractTransportConfig());
+ } else {
+ transport_send = RtpTransportControllerSendFactory().Create(
+ config.ExtractTransportConfig());
+ }
-std::unique_ptr<Call> Call::Create(
- const CallConfig& config,
- Clock* clock,
- std::unique_ptr<RtpTransportControllerSendInterface>
- transportControllerSend) {
- RTC_DCHECK(config.task_queue_factory);
- return std::make_unique<internal::Call>(clock, config,
- std::move(transportControllerSend),
- config.task_queue_factory);
+ return std::make_unique<internal::Call>(config, std::move(transport_send));
}
// This method here to avoid subclasses has to implement this method.
@@ -644,47 +630,41 @@ void Call::SendStats::SetMinAllocatableRate(BitrateAllocationLimits limits) {
min_allocated_send_bitrate_bps_ = limits.min_allocatable_rate.bps();
}
-Call::Call(Clock* clock,
- const CallConfig& config,
- std::unique_ptr<RtpTransportControllerSendInterface> transport_send,
- TaskQueueFactory* task_queue_factory)
- : clock_(clock),
- task_queue_factory_(task_queue_factory),
+Call::Call(const CallConfig& config,
+ std::unique_ptr<RtpTransportControllerSendInterface> transport_send)
+ : env_(config.env),
worker_thread_(GetCurrentTaskQueueOrThread()),
// If `network_task_queue_` was set to nullptr, network related calls
// must be made on `worker_thread_` (i.e. they're one and the same).
network_thread_(config.network_task_queue_ ? config.network_task_queue_
: worker_thread_),
- decode_sync_(config.metronome
- ? std::make_unique<DecodeSynchronizer>(clock_,
- config.metronome,
- worker_thread_)
- : nullptr),
+ decode_sync_(
+ config.decode_metronome
+ ? std::make_unique<DecodeSynchronizer>(&env_.clock(),
+ config.decode_metronome,
+ worker_thread_)
+ : nullptr),
num_cpu_cores_(CpuInfo::DetectNumberOfCores()),
- call_stats_(new CallStats(clock_, worker_thread_)),
+ call_stats_(new CallStats(&env_.clock(), worker_thread_)),
bitrate_allocator_(new BitrateAllocator(this)),
config_(config),
- trials_(*config.trials),
audio_network_state_(kNetworkDown),
video_network_state_(kNetworkDown),
aggregate_network_up_(false),
- event_log_(config.event_log),
- receive_stats_(clock_),
- send_stats_(clock_),
- receive_side_cc_(clock,
+ receive_stats_(&env_.clock()),
+ send_stats_(&env_.clock()),
+ receive_side_cc_(&env_.clock(),
absl::bind_front(&PacketRouter::SendCombinedRtcpPacket,
transport_send->packet_router()),
absl::bind_front(&PacketRouter::SendRemb,
transport_send->packet_router()),
/*network_state_estimator=*/nullptr),
receive_time_calculator_(
- ReceiveTimeCalculator::CreateFromFieldTrial(*config.trials)),
- video_send_delay_stats_(new SendDelayStats(clock_)),
- start_of_call_(clock_->CurrentTime()),
+ ReceiveTimeCalculator::CreateFromFieldTrial(env_.field_trials())),
+ video_send_delay_stats_(new SendDelayStats(&env_.clock())),
+ start_of_call_(env_.clock().CurrentTime()),
transport_send_ptr_(transport_send.get()),
transport_send_(std::move(transport_send)) {
- RTC_DCHECK(config.event_log != nullptr);
- RTC_DCHECK(config.trials != nullptr);
RTC_DCHECK(network_thread_);
RTC_DCHECK(worker_thread_->IsCurrent());
@@ -702,7 +682,7 @@ Call::Call(Clock* clock,
receive_side_cc_periodic_task_ = RepeatingTaskHandle::Start(
worker_thread_,
[receive_side_cc] { return receive_side_cc->MaybeProcess(); },
- TaskQueueBase::DelayPrecision::kLow, clock_);
+ TaskQueueBase::DelayPrecision::kLow, &env_.clock());
}
Call::~Call() {
@@ -720,7 +700,7 @@ Call::~Call() {
RTC_HISTOGRAM_COUNTS_100000(
"WebRTC.Call.LifetimeInSeconds",
- (clock_->CurrentTime() - start_of_call_).seconds());
+ (env_.clock().CurrentTime() - start_of_call_).seconds());
}
void Call::EnsureStarted() {
@@ -765,8 +745,8 @@ webrtc::AudioSendStream* Call::CreateAudioSendStream(
}
AudioSendStream* send_stream = new AudioSendStream(
- clock_, config, config_.audio_state, task_queue_factory_,
- transport_send_.get(), bitrate_allocator_.get(), event_log_,
+ &env_.clock(), config, config_.audio_state, &env_.task_queue_factory(),
+ transport_send_.get(), bitrate_allocator_.get(), &env_.event_log(),
call_stats_->AsRtcpRttStats(), suspended_rtp_state, trials());
RTC_DCHECK(audio_send_ssrcs_.find(config.rtp.ssrc) ==
audio_send_ssrcs_.end());
@@ -818,12 +798,12 @@ webrtc::AudioReceiveStreamInterface* Call::CreateAudioReceiveStream(
TRACE_EVENT0("webrtc", "Call::CreateAudioReceiveStream");
RTC_DCHECK_RUN_ON(worker_thread_);
EnsureStarted();
- event_log_->Log(std::make_unique<RtcEventAudioReceiveStreamConfig>(
+ env_.event_log().Log(std::make_unique<RtcEventAudioReceiveStreamConfig>(
CreateRtcLogStreamConfig(config)));
AudioReceiveStreamImpl* receive_stream = new AudioReceiveStreamImpl(
- clock_, transport_send_->packet_router(), config_.neteq_factory, config,
- config_.audio_state, event_log_);
+ &env_.clock(), transport_send_->packet_router(), config_.neteq_factory,
+ config, config_.audio_state, &env_.event_log());
audio_receive_streams_.insert(receive_stream);
// TODO(bugs.webrtc.org/11993): Make the registration on the network thread
@@ -885,7 +865,7 @@ webrtc::VideoSendStream* Call::CreateVideoSendStream(
video_send_delay_stats_->AddSsrcs(config);
for (size_t ssrc_index = 0; ssrc_index < config.rtp.ssrcs.size();
++ssrc_index) {
- event_log_->Log(std::make_unique<RtcEventVideoSendStreamConfig>(
+ env_.event_log().Log(std::make_unique<RtcEventVideoSendStreamConfig>(
CreateRtcLogStreamConfig(config, ssrc_index)));
}
@@ -894,13 +874,14 @@ webrtc::VideoSendStream* Call::CreateVideoSendStream(
// Copy ssrcs from `config` since `config` is moved.
std::vector<uint32_t> ssrcs = config.rtp.ssrcs;
- VideoSendStream* send_stream = new VideoSendStream(
- clock_, num_cpu_cores_, task_queue_factory_, network_thread_,
+ VideoSendStreamImpl* send_stream = new VideoSendStreamImpl(
+ &env_.clock(), num_cpu_cores_, &env_.task_queue_factory(),
call_stats_->AsRtcpRttStats(), transport_send_.get(),
- bitrate_allocator_.get(), video_send_delay_stats_.get(), event_log_,
- std::move(config), std::move(encoder_config), suspended_video_send_ssrcs_,
+ config_.encode_metronome, bitrate_allocator_.get(),
+ video_send_delay_stats_.get(), &env_.event_log(), std::move(config),
+ std::move(encoder_config), suspended_video_send_ssrcs_,
suspended_video_payload_states_, std::move(fec_controller),
- *config_.trials);
+ env_.field_trials());
for (uint32_t ssrc : ssrcs) {
RTC_DCHECK(video_send_ssrcs_.find(ssrc) == video_send_ssrcs_.end());
@@ -928,8 +909,8 @@ webrtc::VideoSendStream* Call::CreateVideoSendStream(
}
std::unique_ptr<FecController> fec_controller =
config_.fec_controller_factory
- ? config_.fec_controller_factory->CreateFecController()
- : std::make_unique<FecControllerDefault>(clock_);
+ ? config_.fec_controller_factory->CreateFecController(env_)
+ : std::make_unique<FecControllerDefault>(env_);
return CreateVideoSendStream(std::move(config), std::move(encoder_config),
std::move(fec_controller));
}
@@ -939,12 +920,12 @@ void Call::DestroyVideoSendStream(webrtc::VideoSendStream* send_stream) {
RTC_DCHECK(send_stream != nullptr);
RTC_DCHECK_RUN_ON(worker_thread_);
- VideoSendStream* send_stream_impl =
- static_cast<VideoSendStream*>(send_stream);
+ VideoSendStreamImpl* send_stream_impl =
+ static_cast<VideoSendStreamImpl*>(send_stream);
auto it = video_send_ssrcs_.begin();
while (it != video_send_ssrcs_.end()) {
- if (it->second == static_cast<VideoSendStream*>(send_stream)) {
+ if (it->second == static_cast<VideoSendStreamImpl*>(send_stream)) {
send_stream_impl = it->second;
video_send_ssrcs_.erase(it++);
} else {
@@ -960,8 +941,8 @@ void Call::DestroyVideoSendStream(webrtc::VideoSendStream* send_stream) {
if (video_send_streams_.empty())
video_send_streams_empty_.store(true, std::memory_order_relaxed);
- VideoSendStream::RtpStateMap rtp_states;
- VideoSendStream::RtpPayloadStateMap rtp_payload_states;
+ VideoSendStreamImpl::RtpStateMap rtp_states;
+ VideoSendStreamImpl::RtpPayloadStateMap rtp_payload_states;
send_stream_impl->StopPermanentlyAndGetRtpStates(&rtp_states,
&rtp_payload_states);
for (const auto& kv : rtp_states) {
@@ -984,7 +965,7 @@ webrtc::VideoReceiveStreamInterface* Call::CreateVideoReceiveStream(
EnsureStarted();
- event_log_->Log(std::make_unique<RtcEventVideoReceiveStreamConfig>(
+ env_.event_log().Log(std::make_unique<RtcEventVideoReceiveStreamConfig>(
CreateRtcLogStreamConfig(configuration)));
// TODO(bugs.webrtc.org/11993): Move the registration between `receive_stream`
@@ -994,10 +975,10 @@ webrtc::VideoReceiveStreamInterface* Call::CreateVideoReceiveStream(
// TODO(crbug.com/1381982): Re-enable decode synchronizer once the Chromium
// API has adapted to the new Metronome interface.
VideoReceiveStream2* receive_stream = new VideoReceiveStream2(
- task_queue_factory_, this, num_cpu_cores_,
- transport_send_->packet_router(), std::move(configuration),
- call_stats_.get(), clock_, std::make_unique<VCMTiming>(clock_, trials()),
- &nack_periodic_processor_, decode_sync_.get(), event_log_);
+ env_, this, num_cpu_cores_, transport_send_->packet_router(),
+ std::move(configuration), call_stats_.get(),
+ std::make_unique<VCMTiming>(&env_.clock(), trials()),
+ &nack_periodic_processor_, decode_sync_.get());
// TODO(bugs.webrtc.org/11993): Set this up asynchronously on the network
// thread.
receive_stream->RegisterWithTransport(&video_receiver_controller_);
@@ -1040,7 +1021,7 @@ FlexfecReceiveStream* Call::CreateFlexfecReceiveStream(
// OnRtpPacket until the constructor is finished and the object is
// in a valid state, since OnRtpPacket runs on the same thread.
FlexfecReceiveStreamImpl* receive_stream = new FlexfecReceiveStreamImpl(
- clock_, std::move(config), &video_receiver_controller_,
+ &env_.clock(), std::move(config), &video_receiver_controller_,
call_stats_->AsRtcpRttStats());
// TODO(bugs.webrtc.org/11993): Set this up asynchronously on the network
@@ -1104,7 +1085,7 @@ Call::Stats Call::GetStats() const {
}
const FieldTrialsView& Call::trials() const {
- return trials_;
+ return env_.field_trials();
}
TaskQueueBase* Call::network_thread() const {
@@ -1244,7 +1225,7 @@ void Call::OnSentPacket(const rtc::SentPacket& sent_packet) {
// on a ProcessThread. This is alright as is since we forward the call to
// implementations that either just do a PostTask or use locking.
video_send_delay_stats_->OnSentPacket(sent_packet.packet_id,
- clock_->CurrentTime());
+ env_.clock().CurrentTime());
transport_send_->OnSentPacket(sent_packet);
}
@@ -1341,7 +1322,7 @@ void Call::DeliverRtcpPacket(rtc::CopyOnWriteBuffer packet) {
rtcp_delivered = true;
}
- for (VideoSendStream* stream : video_send_streams_) {
+ for (VideoSendStreamImpl* stream : video_send_streams_) {
stream->DeliverRtcp(packet.cdata(), packet.size());
rtcp_delivered = true;
}
@@ -1352,7 +1333,7 @@ void Call::DeliverRtcpPacket(rtc::CopyOnWriteBuffer packet) {
}
if (rtcp_delivered) {
- event_log_->Log(std::make_unique<RtcEventRtcpPacketIncoming>(packet));
+ env_.event_log().Log(std::make_unique<RtcEventRtcpPacketIncoming>(packet));
}
}
@@ -1368,13 +1349,14 @@ void Call::DeliverRtpPacket(
// Repair packet_time_us for clock resets by comparing a new read of
// the same clock (TimeUTCMicros) to a monotonic clock reading.
packet_time_us = receive_time_calculator_->ReconcileReceiveTimes(
- packet_time_us, rtc::TimeUTCMicros(), clock_->TimeInMicroseconds());
+ packet_time_us, rtc::TimeUTCMicros(),
+ env_.clock().TimeInMicroseconds());
packet.set_arrival_time(Timestamp::Micros(packet_time_us));
}
NotifyBweOfReceivedPacket(packet, media_type);
- event_log_->Log(std::make_unique<RtcEventRtpPacketIncoming>(packet));
+ env_.event_log().Log(std::make_unique<RtcEventRtpPacketIncoming>(packet));
if (media_type != MediaType::AUDIO && media_type != MediaType::VIDEO) {
return;
}
diff --git a/third_party/libwebrtc/call/call.h b/third_party/libwebrtc/call/call.h
index b36872f5b5..e7d37c0abd 100644
--- a/third_party/libwebrtc/call/call.h
+++ b/third_party/libwebrtc/call/call.h
@@ -25,7 +25,6 @@
#include "call/call_config.h"
#include "call/flexfec_receive_stream.h"
#include "call/packet_receiver.h"
-#include "call/rtp_transport_controller_send_interface.h"
#include "call/video_receive_stream.h"
#include "call/video_send_stream.h"
#include "rtc_base/copy_on_write_buffer.h"
@@ -49,11 +48,6 @@ class Call {
using Stats = CallBasicStats;
static std::unique_ptr<Call> Create(const CallConfig& config);
- static std::unique_ptr<Call> Create(
- const CallConfig& config,
- Clock* clock,
- std::unique_ptr<RtpTransportControllerSendInterface>
- transportControllerSend);
virtual AudioSendStream* CreateAudioSendStream(
const AudioSendStream::Config& config) = 0;
diff --git a/third_party/libwebrtc/call/call_config.cc b/third_party/libwebrtc/call/call_config.cc
index 5832969b9c..0a6ad2c2ec 100644
--- a/third_party/libwebrtc/call/call_config.cc
+++ b/third_party/libwebrtc/call/call_config.cc
@@ -10,37 +10,27 @@
#include "call/call_config.h"
-#include "rtc_base/checks.h"
+#include "api/environment/environment.h"
+#include "api/task_queue/task_queue_base.h"
namespace webrtc {
CallConfig::CallConfig(const Environment& env,
TaskQueueBase* network_task_queue)
: env(env),
- event_log(&env.event_log()),
- task_queue_factory(&env.task_queue_factory()),
- trials(&env.field_trials()),
network_task_queue_(network_task_queue) {}
-CallConfig::CallConfig(RtcEventLog* event_log,
- TaskQueueBase* network_task_queue /* = nullptr*/)
- : event_log(event_log), network_task_queue_(network_task_queue) {
- RTC_DCHECK(event_log);
-}
-
CallConfig::CallConfig(const CallConfig& config) = default;
RtpTransportConfig CallConfig::ExtractTransportConfig() const {
- RtpTransportConfig transportConfig;
- transportConfig.bitrate_config = bitrate_config;
- transportConfig.event_log = event_log;
- transportConfig.network_controller_factory = network_controller_factory;
- transportConfig.network_state_predictor_factory =
+ RtpTransportConfig transport_config = {.env = env};
+ transport_config.bitrate_config = bitrate_config;
+ transport_config.network_controller_factory = network_controller_factory;
+ transport_config.network_state_predictor_factory =
network_state_predictor_factory;
- transportConfig.task_queue_factory = task_queue_factory;
- transportConfig.trials = trials;
+ transport_config.pacer_burst_interval = pacer_burst_interval;
- return transportConfig;
+ return transport_config;
}
CallConfig::~CallConfig() = default;
diff --git a/third_party/libwebrtc/call/call_config.h b/third_party/libwebrtc/call/call_config.h
index 1b1f696fee..6fd9179a43 100644
--- a/third_party/libwebrtc/call/call_config.h
+++ b/third_party/libwebrtc/call/call_config.h
@@ -10,15 +10,11 @@
#ifndef CALL_CALL_CONFIG_H_
#define CALL_CALL_CONFIG_H_
-#include "absl/types/optional.h"
#include "api/environment/environment.h"
#include "api/fec_controller.h"
-#include "api/field_trials_view.h"
#include "api/metronome/metronome.h"
#include "api/neteq/neteq_factory.h"
#include "api/network_state_predictor.h"
-#include "api/rtc_error.h"
-#include "api/task_queue/task_queue_factory.h"
#include "api/transport/bitrate_settings.h"
#include "api/transport/network_control.h"
#include "call/audio_state.h"
@@ -28,7 +24,6 @@
namespace webrtc {
class AudioProcessing;
-class RtcEventLog;
struct CallConfig {
// If `network_task_queue` is set to nullptr, Call will assume that network
@@ -37,19 +32,13 @@ struct CallConfig {
explicit CallConfig(const Environment& env,
TaskQueueBase* network_task_queue = nullptr);
- // TODO(bugs.webrtc.org/15656): Deprecate and delete constructor below.
- explicit CallConfig(RtcEventLog* event_log,
- TaskQueueBase* network_task_queue = nullptr);
-
CallConfig(const CallConfig&);
~CallConfig();
RtpTransportConfig ExtractTransportConfig() const;
- // TODO(bugs.webrtc.org/15656): Make non-optional when constructor that
- // doesn't pass Environment is removed.
- absl::optional<Environment> env;
+ Environment env;
// Bitrate config used until valid bitrate estimates are calculated. Also
// used to cap total bitrate used. This comes from the remote connection.
@@ -61,16 +50,9 @@ struct CallConfig {
// Audio Processing Module to be used in this call.
AudioProcessing* audio_processing = nullptr;
- // RtcEventLog to use for this call. Required.
- // Use webrtc::RtcEventLog::CreateNull() for a null implementation.
- RtcEventLog* const event_log = nullptr;
-
// FecController to use for this call.
FecControllerFactoryInterface* fec_controller_factory = nullptr;
- // Task Queue Factory to be used in this call. Required.
- TaskQueueFactory* task_queue_factory = nullptr;
-
// NetworkStatePredictor to use for this call.
NetworkStatePredictorFactoryInterface* network_state_predictor_factory =
nullptr;
@@ -81,16 +63,16 @@ struct CallConfig {
// NetEq factory to use for this call.
NetEqFactory* neteq_factory = nullptr;
- // Key-value mapping of internal configurations to apply,
- // e.g. field trials.
- const FieldTrialsView* trials = nullptr;
-
TaskQueueBase* const network_task_queue_ = nullptr;
// RtpTransportControllerSend to use for this call.
RtpTransportControllerSendFactoryInterface*
rtp_transport_controller_send_factory = nullptr;
- Metronome* metronome = nullptr;
+ Metronome* decode_metronome = nullptr;
+ Metronome* encode_metronome = nullptr;
+
+ // The burst interval of the pacer, see TaskQueuePacedSender constructor.
+ absl::optional<TimeDelta> pacer_burst_interval;
// Enables send packet batching from the egress RTP sender.
bool enable_send_packet_batching = false;
diff --git a/third_party/libwebrtc/call/call_factory.cc b/third_party/libwebrtc/call/create_call.cc
index 78a4f1635f..8b565745a8 100644
--- a/third_party/libwebrtc/call/call_factory.cc
+++ b/third_party/libwebrtc/call/create_call.cc
@@ -8,7 +8,7 @@
* be found in the AUTHORS file in the root of the source tree.
*/
-#include "call/call_factory.h"
+#include "call/create_call.h"
#include <stdio.h>
@@ -22,7 +22,6 @@
#include "api/units/time_delta.h"
#include "call/call.h"
#include "call/degraded_call.h"
-#include "call/rtp_transport_config.h"
#include "rtc_base/checks.h"
#include "rtc_base/experiments/field_trial_list.h"
#include "rtc_base/experiments/field_trial_parser.h"
@@ -78,29 +77,14 @@ std::vector<TimeScopedNetworkConfig> GetNetworkConfigs(
} // namespace
-CallFactory::CallFactory() {
- call_thread_.Detach();
-}
-
-std::unique_ptr<Call> CallFactory::CreateCall(const CallConfig& config) {
- RTC_DCHECK_RUN_ON(&call_thread_);
- RTC_DCHECK(config.trials);
-
+std::unique_ptr<Call> CreateCall(const CallConfig& config) {
std::vector<DegradedCall::TimeScopedNetworkConfig> send_degradation_configs =
- GetNetworkConfigs(*config.trials, /*send=*/true);
+ GetNetworkConfigs(config.env.field_trials(), /*send=*/true);
std::vector<DegradedCall::TimeScopedNetworkConfig>
receive_degradation_configs =
- GetNetworkConfigs(*config.trials, /*send=*/false);
+ GetNetworkConfigs(config.env.field_trials(), /*send=*/false);
- RtpTransportConfig transportConfig = config.ExtractTransportConfig();
-
- RTC_CHECK(false);
- return nullptr;
- /* Mozilla: Avoid this since it could use GetRealTimeClock().
- std::unique_ptr<Call> call =
- Call::Create(config, Clock::GetRealTimeClock(),
- config.rtp_transport_controller_send_factory->Create(
- transportConfig, Clock::GetRealTimeClock()));
+ std::unique_ptr<Call> call = Call::Create(config);
if (!send_degradation_configs.empty() ||
!receive_degradation_configs.empty()) {
@@ -109,11 +93,6 @@ std::unique_ptr<Call> CallFactory::CreateCall(const CallConfig& config) {
}
return call;
- */
-}
-
-std::unique_ptr<CallFactoryInterface> CreateCallFactory() {
- return std::make_unique<CallFactory>();
}
} // namespace webrtc
diff --git a/third_party/libwebrtc/call/call_factory.h b/third_party/libwebrtc/call/create_call.h
index f75b1bd71b..afba08800e 100644
--- a/third_party/libwebrtc/call/call_factory.h
+++ b/third_party/libwebrtc/call/create_call.h
@@ -8,30 +8,18 @@
* be found in the AUTHORS file in the root of the source tree.
*/
-#ifndef CALL_CALL_FACTORY_H_
-#define CALL_CALL_FACTORY_H_
+#ifndef CALL_CREATE_CALL_H_
+#define CALL_CREATE_CALL_H_
#include <memory>
-#include "api/call/call_factory_interface.h"
-#include "api/sequence_checker.h"
#include "call/call.h"
#include "call/call_config.h"
-#include "rtc_base/system/no_unique_address.h"
namespace webrtc {
-class CallFactory : public CallFactoryInterface {
- public:
- CallFactory();
- ~CallFactory() override = default;
-
- private:
- std::unique_ptr<Call> CreateCall(const CallConfig& config) override;
-
- RTC_NO_UNIQUE_ADDRESS SequenceChecker call_thread_;
-};
+std::unique_ptr<Call> CreateCall(const CallConfig& config);
} // namespace webrtc
-#endif // CALL_CALL_FACTORY_H_
+#endif // CALL_CREATE_CALL_H_
diff --git a/third_party/libwebrtc/call/rtp_transport_config.h b/third_party/libwebrtc/call/rtp_transport_config.h
index f2030b3672..cce5214fc8 100644
--- a/third_party/libwebrtc/call/rtp_transport_config.h
+++ b/third_party/libwebrtc/call/rtp_transport_config.h
@@ -13,27 +13,22 @@
#include <memory>
-#include "api/field_trials_view.h"
+#include "absl/types/optional.h"
+#include "api/environment/environment.h"
#include "api/network_state_predictor.h"
-#include "api/rtc_event_log/rtc_event_log.h"
#include "api/transport/bitrate_settings.h"
#include "api/transport/network_control.h"
-#include "rtc_base/task_queue.h"
+#include "api/units/time_delta.h"
namespace webrtc {
struct RtpTransportConfig {
+ Environment env;
+
// Bitrate config used until valid bitrate estimates are calculated. Also
// used to cap total bitrate used. This comes from the remote connection.
BitrateConstraints bitrate_config;
- // RtcEventLog to use for this call. Required.
- // Use webrtc::RtcEventLog::CreateNull() for a null implementation.
- RtcEventLog* event_log = nullptr;
-
- // Task Queue Factory to be used in this call. Required.
- TaskQueueFactory* task_queue_factory = nullptr;
-
// NetworkStatePredictor to use for this call.
NetworkStatePredictorFactoryInterface* network_state_predictor_factory =
nullptr;
@@ -41,9 +36,8 @@ struct RtpTransportConfig {
// Network controller factory to use for this call.
NetworkControllerFactoryInterface* network_controller_factory = nullptr;
- // Key-value mapping of internal configurations to apply,
- // e.g. field trials.
- const FieldTrialsView* trials = nullptr;
+ // The burst interval of the pacer, see TaskQueuePacedSender constructor.
+ absl::optional<TimeDelta> pacer_burst_interval;
};
} // namespace webrtc
diff --git a/third_party/libwebrtc/call/rtp_transport_controller_send.cc b/third_party/libwebrtc/call/rtp_transport_controller_send.cc
index 8d24f7551e..600d4e2620 100644
--- a/third_party/libwebrtc/call/rtp_transport_controller_send.cc
+++ b/third_party/libwebrtc/call/rtp_transport_controller_send.cc
@@ -62,55 +62,56 @@ TargetRateConstraints ConvertConstraints(const BitrateConstraints& contraints,
contraints.start_bitrate_bps, clock);
}
-bool IsEnabled(const FieldTrialsView& trials, absl::string_view key) {
- return absl::StartsWith(trials.Lookup(key), "Enabled");
-}
-
bool IsRelayed(const rtc::NetworkRoute& route) {
return route.local.uses_turn() || route.remote.uses_turn();
}
} // namespace
RtpTransportControllerSend::RtpTransportControllerSend(
- Clock* clock,
const RtpTransportConfig& config)
- : clock_(clock),
- event_log_(config.event_log),
- task_queue_factory_(config.task_queue_factory),
+ : env_(config.env),
task_queue_(TaskQueueBase::Current()),
bitrate_configurator_(config.bitrate_config),
pacer_started_(false),
- pacer_(clock, &packet_router_, *config.trials, TimeDelta::Millis(5), 3),
+ pacer_(&env_.clock(),
+ &packet_router_,
+ env_.field_trials(),
+ TimeDelta::Millis(5),
+ 3),
observer_(nullptr),
controller_factory_override_(config.network_controller_factory),
controller_factory_fallback_(
std::make_unique<GoogCcNetworkControllerFactory>(
config.network_state_predictor_factory)),
process_interval_(controller_factory_fallback_->GetProcessInterval()),
- last_report_block_time_(Timestamp::Millis(clock_->TimeInMilliseconds())),
+ last_report_block_time_(
+ Timestamp::Millis(env_.clock().TimeInMilliseconds())),
reset_feedback_on_route_change_(
- !IsEnabled(*config.trials, "WebRTC-Bwe-NoFeedbackReset")),
- add_pacing_to_cwin_(
- IsEnabled(*config.trials,
- "WebRTC-AddPacingToCongestionWindowPushback")),
+ !env_.field_trials().IsEnabled("WebRTC-Bwe-NoFeedbackReset")),
+ add_pacing_to_cwin_(env_.field_trials().IsEnabled(
+ "WebRTC-AddPacingToCongestionWindowPushback")),
relay_bandwidth_cap_("relay_cap", DataRate::PlusInfinity()),
transport_overhead_bytes_per_packet_(0),
network_available_(false),
congestion_window_size_(DataSize::PlusInfinity()),
is_congested_(false),
- retransmission_rate_limiter_(clock, kRetransmitWindowSizeMs),
- field_trials_(*config.trials) {
- ParseFieldTrial({&relay_bandwidth_cap_},
- config.trials->Lookup("WebRTC-Bwe-NetworkRouteConstraints"));
+ retransmission_rate_limiter_(&env_.clock(), kRetransmitWindowSizeMs) {
+ ParseFieldTrial(
+ {&relay_bandwidth_cap_},
+ env_.field_trials().Lookup("WebRTC-Bwe-NetworkRouteConstraints"));
initial_config_.constraints =
- ConvertConstraints(config.bitrate_config, clock_);
- initial_config_.event_log = config.event_log;
- initial_config_.key_value_config = config.trials;
+ ConvertConstraints(config.bitrate_config, &env_.clock());
+ initial_config_.event_log = &env_.event_log();
+ initial_config_.key_value_config = &env_.field_trials();
RTC_DCHECK(config.bitrate_config.start_bitrate_bps > 0);
pacer_.SetPacingRates(
DataRate::BitsPerSec(config.bitrate_config.start_bitrate_bps),
DataRate::Zero());
+ if (config.pacer_burst_interval) {
+ // Default burst interval overriden by config.
+ pacer_.SetSendBurstInterval(*config.pacer_burst_interval);
+ }
}
RtpTransportControllerSend::~RtpTransportControllerSend() {
@@ -133,14 +134,14 @@ RtpVideoSenderInterface* RtpTransportControllerSend::CreateRtpVideoSender(
rtc::scoped_refptr<FrameTransformerInterface> frame_transformer) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
video_rtp_senders_.push_back(std::make_unique<RtpVideoSender>(
- clock_, suspended_ssrcs, states, rtp_config, rtcp_report_interval_ms,
- send_transport, observers,
+ &env_.clock(), suspended_ssrcs, states, rtp_config,
+ rtcp_report_interval_ms, send_transport, observers,
// TODO(holmer): Remove this circular dependency by injecting
// the parts of RtpTransportControllerSendInterface that are really used.
this, event_log, &retransmission_rate_limiter_, std::move(fec_controller),
frame_encryption_config.frame_encryptor,
frame_encryption_config.crypto_options, std::move(frame_transformer),
- field_trials_, task_queue_factory_));
+ env_.field_trials(), &env_.task_queue_factory()));
return video_rtp_senders_.back().get();
}
@@ -302,13 +303,11 @@ void RtpTransportControllerSend::OnNetworkRouteChanged(
<< " bps.";
RTC_DCHECK_GT(bitrate_config.start_bitrate_bps, 0);
- if (event_log_) {
- event_log_->Log(std::make_unique<RtcEventRouteChange>(
- network_route.connected, network_route.packet_overhead));
- }
+ env_.event_log().Log(std::make_unique<RtcEventRouteChange>(
+ network_route.connected, network_route.packet_overhead));
NetworkRouteChange msg;
- msg.at_time = Timestamp::Millis(clock_->TimeInMilliseconds());
- msg.constraints = ConvertConstraints(bitrate_config, clock_);
+ msg.at_time = Timestamp::Millis(env_.clock().TimeInMilliseconds());
+ msg.constraints = ConvertConstraints(bitrate_config, &env_.clock());
transport_overhead_bytes_per_packet_ = network_route.packet_overhead;
if (reset_feedback_on_route_change_) {
transport_feedback_adapter_.SetNetworkRoute(network_route);
@@ -327,7 +326,7 @@ void RtpTransportControllerSend::OnNetworkAvailability(bool network_available) {
RTC_LOG(LS_VERBOSE) << "SignalNetworkState "
<< (network_available ? "Up" : "Down");
NetworkAvailability msg;
- msg.at_time = Timestamp::Millis(clock_->TimeInMilliseconds());
+ msg.at_time = Timestamp::Millis(env_.clock().TimeInMilliseconds());
msg.network_available = network_available;
network_available_ = network_available;
if (network_available) {
@@ -425,7 +424,7 @@ void RtpTransportControllerSend::OnReceivedPacket(
void RtpTransportControllerSend::UpdateBitrateConstraints(
const BitrateConstraints& updated) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
- TargetRateConstraints msg = ConvertConstraints(updated, clock_);
+ TargetRateConstraints msg = ConvertConstraints(updated, &env_.clock());
if (controller_) {
PostUpdates(controller_->OnTargetRateConstraints(msg));
} else {
@@ -528,7 +527,8 @@ void RtpTransportControllerSend::OnRttUpdate(Timestamp receive_time,
void RtpTransportControllerSend::OnAddPacket(
const RtpPacketSendInfo& packet_info) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
- Timestamp creation_time = Timestamp::Millis(clock_->TimeInMilliseconds());
+ Timestamp creation_time =
+ Timestamp::Millis(env_.clock().TimeInMilliseconds());
feedback_demuxer_.AddPacket(packet_info);
transport_feedback_adapter_.AddPacket(
packet_info, transport_overhead_bytes_per_packet_, creation_time);
@@ -554,11 +554,9 @@ void RtpTransportControllerSend::OnTransportFeedback(
void RtpTransportControllerSend::OnRemoteNetworkEstimate(
NetworkStateEstimate estimate) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
- if (event_log_) {
- event_log_->Log(std::make_unique<RtcEventRemoteEstimate>(
- estimate.link_capacity_lower, estimate.link_capacity_upper));
- }
- estimate.update_time = Timestamp::Millis(clock_->TimeInMilliseconds());
+ env_.event_log().Log(std::make_unique<RtcEventRemoteEstimate>(
+ estimate.link_capacity_lower, estimate.link_capacity_upper));
+ estimate.update_time = Timestamp::Millis(env_.clock().TimeInMilliseconds());
if (controller_)
PostUpdates(controller_->OnNetworkStateEstimate(estimate));
}
@@ -572,7 +570,7 @@ void RtpTransportControllerSend::MaybeCreateControllers() {
control_handler_ = std::make_unique<CongestionControlHandler>();
initial_config_.constraints.at_time =
- Timestamp::Millis(clock_->TimeInMilliseconds());
+ Timestamp::Millis(env_.clock().TimeInMilliseconds());
initial_config_.stream_based_config = streams_config_;
// TODO(srte): Use fallback controller if no feedback is available.
@@ -623,14 +621,15 @@ void RtpTransportControllerSend::StartProcessPeriodicTasks() {
void RtpTransportControllerSend::UpdateControllerWithTimeInterval() {
RTC_DCHECK(controller_);
ProcessInterval msg;
- msg.at_time = Timestamp::Millis(clock_->TimeInMilliseconds());
+ msg.at_time = Timestamp::Millis(env_.clock().TimeInMilliseconds());
if (add_pacing_to_cwin_)
msg.pacer_queue = pacer_.QueueSizeData();
PostUpdates(controller_->OnProcessInterval(msg));
}
void RtpTransportControllerSend::UpdateStreamsConfig() {
- streams_config_.at_time = Timestamp::Millis(clock_->TimeInMilliseconds());
+ streams_config_.at_time =
+ Timestamp::Millis(env_.clock().TimeInMilliseconds());
if (controller_)
PostUpdates(controller_->OnStreamsConfig(streams_config_));
}
diff --git a/third_party/libwebrtc/call/rtp_transport_controller_send.h b/third_party/libwebrtc/call/rtp_transport_controller_send.h
index 1aace1ce65..c0bca41a2b 100644
--- a/third_party/libwebrtc/call/rtp_transport_controller_send.h
+++ b/third_party/libwebrtc/call/rtp_transport_controller_send.h
@@ -18,6 +18,7 @@
#include <vector>
#include "absl/strings/string_view.h"
+#include "api/environment/environment.h"
#include "api/network_state_predictor.h"
#include "api/sequence_checker.h"
#include "api/task_queue/task_queue_base.h"
@@ -41,7 +42,6 @@
#include "rtc_base/task_utils/repeating_task.h"
namespace webrtc {
-class Clock;
class FrameEncryptorInterface;
class RtcEventLog;
@@ -51,7 +51,7 @@ class RtpTransportControllerSend final
public TransportFeedbackObserver,
public NetworkStateEstimateObserver {
public:
- RtpTransportControllerSend(Clock* clock, const RtpTransportConfig& config);
+ explicit RtpTransportControllerSend(const RtpTransportConfig& config);
~RtpTransportControllerSend() override;
RtpTransportControllerSend(const RtpTransportControllerSend&) = delete;
@@ -146,9 +146,7 @@ class RtpTransportControllerSend final
void ProcessSentPacketUpdates(NetworkControlUpdate updates)
RTC_RUN_ON(sequence_checker_);
- Clock* const clock_;
- RtcEventLog* const event_log_;
- TaskQueueFactory* const task_queue_factory_;
+ const Environment env_;
SequenceChecker sequence_checker_;
TaskQueueBase* task_queue_;
PacketRouter packet_router_;
@@ -207,8 +205,6 @@ class RtpTransportControllerSend final
RateLimiter retransmission_rate_limiter_;
ScopedTaskSafety safety_;
-
- const FieldTrialsView& field_trials_;
};
} // namespace webrtc
diff --git a/third_party/libwebrtc/call/rtp_transport_controller_send_factory.h b/third_party/libwebrtc/call/rtp_transport_controller_send_factory.h
index 6349302e45..cd5a3c58ae 100644
--- a/third_party/libwebrtc/call/rtp_transport_controller_send_factory.h
+++ b/third_party/libwebrtc/call/rtp_transport_controller_send_factory.h
@@ -22,10 +22,8 @@ class RtpTransportControllerSendFactory
: public RtpTransportControllerSendFactoryInterface {
public:
std::unique_ptr<RtpTransportControllerSendInterface> Create(
- const RtpTransportConfig& config,
- Clock* clock) override {
- RTC_CHECK(config.trials);
- return std::make_unique<RtpTransportControllerSend>(clock, config);
+ const RtpTransportConfig& config) override {
+ return std::make_unique<RtpTransportControllerSend>(config);
}
virtual ~RtpTransportControllerSendFactory() {}
diff --git a/third_party/libwebrtc/call/rtp_transport_controller_send_factory_interface.h b/third_party/libwebrtc/call/rtp_transport_controller_send_factory_interface.h
index 0f4c36c221..8683a34c9e 100644
--- a/third_party/libwebrtc/call/rtp_transport_controller_send_factory_interface.h
+++ b/third_party/libwebrtc/call/rtp_transport_controller_send_factory_interface.h
@@ -20,11 +20,10 @@ namespace webrtc {
// controller.
class RtpTransportControllerSendFactoryInterface {
public:
- virtual std::unique_ptr<RtpTransportControllerSendInterface> Create(
- const RtpTransportConfig& config,
- Clock* clock) = 0;
+ virtual ~RtpTransportControllerSendFactoryInterface() = default;
- virtual ~RtpTransportControllerSendFactoryInterface() {}
+ virtual std::unique_ptr<RtpTransportControllerSendInterface> Create(
+ const RtpTransportConfig& config) = 0;
};
} // namespace webrtc
#endif // CALL_RTP_TRANSPORT_CONTROLLER_SEND_FACTORY_INTERFACE_H_
diff --git a/third_party/libwebrtc/call/rtp_video_sender_unittest.cc b/third_party/libwebrtc/call/rtp_video_sender_unittest.cc
index 9646a81cfd..cf099afaa3 100644
--- a/third_party/libwebrtc/call/rtp_video_sender_unittest.cc
+++ b/third_party/libwebrtc/call/rtp_video_sender_unittest.cc
@@ -16,6 +16,8 @@
#include <utility>
#include "absl/functional/any_invocable.h"
+#include "api/environment/environment.h"
+#include "api/environment/environment_factory.h"
#include "call/rtp_transport_controller_send.h"
#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
#include "modules/rtp_rtcp/source/byte_io.h"
@@ -118,23 +120,21 @@ class RtpVideoSenderTestFixture {
rtc::scoped_refptr<FrameTransformerInterface> frame_transformer,
const FieldTrialsView* field_trials = nullptr)
: time_controller_(Timestamp::Millis(1000000)),
+ env_(CreateEnvironment(&field_trials_,
+ field_trials,
+ time_controller_.GetClock(),
+ time_controller_.CreateTaskQueueFactory())),
config_(CreateVideoSendStreamConfig(&transport_,
ssrcs,
rtx_ssrcs,
payload_type)),
bitrate_config_(GetBitrateConfig()),
transport_controller_(
- time_controller_.GetClock(),
- RtpTransportConfig{
- .bitrate_config = bitrate_config_,
- .event_log = &event_log_,
- .task_queue_factory = time_controller_.GetTaskQueueFactory(),
- .trials = field_trials ? field_trials : &field_trials_,
- }),
+ RtpTransportConfig{.env = env_, .bitrate_config = bitrate_config_}),
stats_proxy_(time_controller_.GetClock(),
config_,
VideoEncoderConfig::ContentType::kRealtimeVideo,
- field_trials ? *field_trials : field_trials_),
+ env_.field_trials()),
retransmission_rate_limiter_(time_controller_.GetClock(),
kRetransmitWindowSizeMs) {
transport_controller_.EnsureStarted();
@@ -144,10 +144,10 @@ class RtpVideoSenderTestFixture {
config_.rtp, config_.rtcp_report_interval_ms, &transport_,
CreateObservers(&encoder_feedback_, &stats_proxy_, &stats_proxy_,
&stats_proxy_, frame_count_observer, &stats_proxy_),
- &transport_controller_, &event_log_, &retransmission_rate_limiter_,
- std::make_unique<FecControllerDefault>(time_controller_.GetClock()),
- nullptr, CryptoOptions{}, frame_transformer,
- field_trials ? *field_trials : field_trials_,
+ &transport_controller_, &env_.event_log(),
+ &retransmission_rate_limiter_,
+ std::make_unique<FecControllerDefault>(env_), nullptr, CryptoOptions{},
+ frame_transformer, env_.field_trials(),
time_controller_.GetTaskQueueFactory());
}
@@ -197,7 +197,7 @@ class RtpVideoSenderTestFixture {
NiceMock<MockTransport> transport_;
NiceMock<MockRtcpIntraFrameObserver> encoder_feedback_;
GlobalSimulatedTimeController time_controller_;
- RtcEventLogNull event_log_;
+ Environment env_;
VideoSendStream::Config config_;
BitrateConstraints bitrate_config_;
RtpTransportControllerSend transport_controller_;
diff --git a/third_party/libwebrtc/call/version.cc b/third_party/libwebrtc/call/version.cc
index 5770253625..85fdf004ea 100644
--- a/third_party/libwebrtc/call/version.cc
+++ b/third_party/libwebrtc/call/version.cc
@@ -13,7 +13,7 @@
namespace webrtc {
// The timestamp is always in UTC.
-const char* const kSourceTimestamp = "WebRTC source stamp 2023-12-03T04:02:06";
+const char* const kSourceTimestamp = "WebRTC source stamp 2024-01-21T04:12:31";
void LoadWebRTCVersionInRegister() {
// Using volatile to instruct the compiler to not optimize `p` away even
diff --git a/third_party/libwebrtc/common_video/h264/h264_common.h b/third_party/libwebrtc/common_video/h264/h264_common.h
index 0b1843ee38..1bc9867d3f 100644
--- a/third_party/libwebrtc/common_video/h264/h264_common.h
+++ b/third_party/libwebrtc/common_video/h264/h264_common.h
@@ -17,6 +17,7 @@
#include <vector>
#include "rtc_base/buffer.h"
+#include "rtc_base/system/rtc_export.h"
namespace webrtc {
@@ -59,11 +60,11 @@ struct NaluIndex {
};
// Returns a vector of the NALU indices in the given buffer.
-std::vector<NaluIndex> FindNaluIndices(const uint8_t* buffer,
- size_t buffer_size);
+RTC_EXPORT std::vector<NaluIndex> FindNaluIndices(const uint8_t* buffer,
+ size_t buffer_size);
// Get the NAL type from the header byte immediately following start sequence.
-NaluType ParseNaluType(uint8_t data);
+RTC_EXPORT NaluType ParseNaluType(uint8_t data);
// Methods for parsing and writing RBSP. See section 7.4.1 of the H264 spec.
//
diff --git a/third_party/libwebrtc/common_video/h264/sps_parser.h b/third_party/libwebrtc/common_video/h264/sps_parser.h
index da328b48b0..a69bd19690 100644
--- a/third_party/libwebrtc/common_video/h264/sps_parser.h
+++ b/third_party/libwebrtc/common_video/h264/sps_parser.h
@@ -13,15 +13,16 @@
#include "absl/types/optional.h"
#include "rtc_base/bitstream_reader.h"
+#include "rtc_base/system/rtc_export.h"
namespace webrtc {
// A class for parsing out sequence parameter set (SPS) data from an H264 NALU.
-class SpsParser {
+class RTC_EXPORT SpsParser {
public:
// The parsed state of the SPS. Only some select values are stored.
// Add more as they are actually needed.
- struct SpsState {
+ struct RTC_EXPORT SpsState {
SpsState();
SpsState(const SpsState&);
~SpsState();
diff --git a/third_party/libwebrtc/common_video/h265/h265_bitstream_parser.cc b/third_party/libwebrtc/common_video/h265/h265_bitstream_parser.cc
index f8dc242c7d..f270f228c1 100644
--- a/third_party/libwebrtc/common_video/h265/h265_bitstream_parser.cc
+++ b/third_party/libwebrtc/common_video/h265/h265_bitstream_parser.cc
@@ -12,6 +12,7 @@
#include <stdlib.h>
#include <cstdint>
+#include <limits>
#include <vector>
#include "common_video/h265/h265_common.h"
@@ -129,6 +130,8 @@ H265BitstreamParser::Result H265BitstreamParser::ParseNonParameterSetNalu(
uint32_t slice_segment_address_bits =
H265::Log2Ceiling(pic_height_in_ctbs_y * pic_width_in_ctbs_y);
+ TRUE_OR_RETURN(slice_segment_address_bits !=
+ std::numeric_limits<uint32_t>::max());
slice_reader.ConsumeBits(slice_segment_address_bits);
}
diff --git a/third_party/libwebrtc/docs/native-code/development/README.md b/third_party/libwebrtc/docs/native-code/development/README.md
index 8a2678e6cf..02d148c7d7 100644
--- a/third_party/libwebrtc/docs/native-code/development/README.md
+++ b/third_party/libwebrtc/docs/native-code/development/README.md
@@ -98,11 +98,7 @@ configuration untouched (stored in the args.gn file), do:
$ gn clean out/Default
```
-To build the fuzzers residing in the [test/fuzzers][fuzzers] directory, use
-```
-$ gn gen out/fuzzers --args='use_libfuzzer=true optimize_for_fuzzing=true'
-```
-Depending on the fuzzer additional arguments like `is_asan`, `is_msan` or `is_ubsan_security` might be required.
+To build the fuzzers residing in the [test/fuzzers][fuzzers-dir] directory, read the instructions at the [fuzzers][fuzzers] page.
See the [GN][gn-doc] documentation for all available options. There are also more
platform specific tips on the [Android][webrtc-android-development] and
@@ -129,6 +125,11 @@ $ autoninja all -C out/Default
See [Ninja build rules][ninja-build-rules] to read more about difference between `ninja` and `ninja all`.
+To build a particular target (like a fuzzer which is not included in the main target) use
+
+```
+autoninja -C out/Default h264_depacketizer_fuzzer
+```
## Using Another Build System
@@ -288,4 +289,5 @@ Target name `turnserver`. Used for unit tests.
[rfc-5766]: https://tools.ietf.org/html/rfc5766
[m80-log]: https://webrtc.googlesource.com/src/+log/branch-heads/3987
[m80]: https://webrtc.googlesource.com/src/+/branch-heads/3987
-[fuzzers]: https://webrtc.googlesource.com/src/+/main/test/fuzzers/
+[fuzzers-dir]: https://webrtc.googlesource.com/src/+/main/test/fuzzers/
+[fuzzers]: https://webrtc.googlesource.com/src/+/main/docs/native-code/development/fuzzers/
diff --git a/third_party/libwebrtc/docs/native-code/development/fuzzers/README.md b/third_party/libwebrtc/docs/native-code/development/fuzzers/README.md
new file mode 100644
index 0000000000..cac77cdc07
--- /dev/null
+++ b/third_party/libwebrtc/docs/native-code/development/fuzzers/README.md
@@ -0,0 +1,70 @@
+# Fuzzing in WebRTC
+
+## Intro
+WebRTC currently uses libfuzzer for fuzz testing however FuzzTest is a new approach which we have not yet looked into but we will in the future.
+
+Before continuing, read the [libfuzzer][libfuzzer-getting-started] and [FuzzTest][fuzztest-getting-started] getting started docs to get familar.
+
+## Compiling locally
+To build the fuzzers residing in the [test/fuzzers][fuzzers] directory, use
+```
+$ gn gen out/fuzzers --args='use_libfuzzer=true optimize_for_fuzzing=true'
+```
+Depending on the fuzzer additional arguments like `is_asan`, `is_msan` or `is_ubsan_security` might be required.
+
+See the [GN][gn-doc] documentation for all available options. There are also more
+platform specific tips on the [Android][webrtc-android-development] and
+[iOS][webrtc-ios-development] instructions.
+
+## Add new fuzzers
+Create a new `.cc` file in the [test/fuzzers][fuzzers] directory, use existing files as a guide.
+
+Add a new `webrtc_fuzzers_test` build rule in the [test/fuzzers/BUILD.gn][BUILD.gn], use existing rules as a guide.
+
+Ensure it compiles and executes locally then add it to a gerrit CL and upload it for review, e.g.
+
+```
+$ autoninja -C out/fuzzers test/fuzzers:h264_depacketizer_fuzzer
+```
+
+It can then be executed like so:
+```
+$ out/fuzzers/bin/run_h264_depacketizer_fuzzer
+```
+
+## Running fuzzers automatically
+All fuzzer tests in the [test/fuzzers/BUILD.gn][BUILD.gn] file are compiled per CL on the [libfuzzer bot][libfuzzer-bot].
+This is only to verify that it compiles, this bot does not do any fuzz testing.
+
+When WebRTC is [rolled][webrtc-autoroller] into to Chromium, the libfuzz bots in the [chromium.fuzz][chromium-fuzz] will compile it, zip it and then upload to https://clusterfuzz.com for execution.
+
+You can verify that the fuzz test is being executed by:
+ - Navigate to a bot in the [chromium.fuzz][chromium-fuzz] libfuzzer waterfall, e.g. [ Libfuzzer Upload Linux ASan bot/linux bot][linux-bot].
+ - Click on the latest `build#` link.
+ - Search for `//third_party/webrtc/test/fuzzers` in the `raw_io.output_text_refs_` file in the `calculate_all_fuzzers` step.
+ - Verify that the new fuzzer (as it's named in the `webrtc_fuzzers_test` build rule) is present.
+ - Also verify that it's _NOT_ in the `no_clusterfuzz` file in the `calculate_no_clusterfuzz` step. If it is, file a bug at https://bugs.webrtc.org.
+
+Bugs are filed automatically in https://crbug.com in the blink > WebRTC component and assigned based on [test/fuzzers/OWNERS][OWNERS] file or the commit history.
+
+If you are a non-googler, you can only view data from https://clusterfuzz.com if your account is CC'ed on the reported bug.
+
+## Additional reading
+
+[Libfuzzer in Chromium][libfuzzer-chromium]
+
+
+[libfuzzer-chromium]: https://chromium.googlesource.com/chromium/src/+/HEAD/testing/libfuzzer/README.md
+[libfuzzer-bot]: https://ci.chromium.org/ui/p/webrtc/builders/luci.webrtc.ci/Linux64%20Release%20%28Libfuzzer%29
+[fuzzers]: https://webrtc.googlesource.com/src/+/main/test/fuzzers/
+[OWNERS]: https://webrtc.googlesource.com/src/+/main/test/fuzzers/OWNERS
+[BUILD.gn]: https://webrtc.googlesource.com/src/+/main/test/fuzzers/BUILD.gn
+[gn]: https://gn.googlesource.com/gn/+/main/README.md
+[gn-doc]: https://gn.googlesource.com/gn/+/main/docs/reference.md#IDE-options
+[webrtc-android-development]: https://webrtc.googlesource.com/src/+/main/docs/native-code/android/
+[webrtc-ios-development]: https://webrtc.googlesource.com/src/+/main/docs/native-code/ios/
+[chromium-fuzz]: https://ci.chromium.org/p/chromium/g/chromium.fuzz/console
+[linux-bot]: https://ci.chromium.org/ui/p/chromium/builders/ci/Libfuzzer%20Upload%20Linux%20ASan/
+[libfuzzer-getting-started]: https://chromium.googlesource.com/chromium/src/+/main/testing/libfuzzer/getting_started_with_libfuzzer.md
+[fuzztest-getting-started]: https://chromium.googlesource.com/chromium/src/+/main/testing/libfuzzer/getting_started.md
+[webrtc-autoroller]: https://autoroll.skia.org/r/webrtc-chromium-autoroll
diff --git a/third_party/libwebrtc/examples/androidnativeapi/jni/android_call_client.cc b/third_party/libwebrtc/examples/androidnativeapi/jni/android_call_client.cc
index 40af78cdac..0e895c520b 100644
--- a/third_party/libwebrtc/examples/androidnativeapi/jni/android_call_client.cc
+++ b/third_party/libwebrtc/examples/androidnativeapi/jni/android_call_client.cc
@@ -154,8 +154,7 @@ void AndroidCallClient::CreatePeerConnectionFactory() {
pcf_deps.worker_thread = worker_thread_.get();
pcf_deps.signaling_thread = signaling_thread_.get();
pcf_deps.task_queue_factory = webrtc::CreateDefaultTaskQueueFactory();
- pcf_deps.event_log_factory = std::make_unique<webrtc::RtcEventLogFactory>(
- pcf_deps.task_queue_factory.get());
+ pcf_deps.event_log_factory = std::make_unique<webrtc::RtcEventLogFactory>();
pcf_deps.video_encoder_factory =
std::make_unique<webrtc::InternalEncoderFactory>();
diff --git a/third_party/libwebrtc/examples/androidvoip/BUILD.gn b/third_party/libwebrtc/examples/androidvoip/BUILD.gn
index cea05ea128..d390815406 100644
--- a/third_party/libwebrtc/examples/androidvoip/BUILD.gn
+++ b/third_party/libwebrtc/examples/androidvoip/BUILD.gn
@@ -71,7 +71,7 @@ if (is_android) {
"//api/task_queue:default_task_queue_factory",
"//api/voip:voip_api",
"//api/voip:voip_engine_factory",
- "//rtc_base/third_party/sigslot:sigslot",
+ "//rtc_base/network:received_packet",
"//sdk/android:native_api_audio_device_module",
"//sdk/android:native_api_base",
"//sdk/android:native_api_jni",
diff --git a/third_party/libwebrtc/examples/androidvoip/jni/android_voip_client.cc b/third_party/libwebrtc/examples/androidvoip/jni/android_voip_client.cc
index 8a0a3badb9..69327990e0 100644
--- a/third_party/libwebrtc/examples/androidvoip/jni/android_voip_client.cc
+++ b/third_party/libwebrtc/examples/androidvoip/jni/android_voip_client.cc
@@ -313,8 +313,10 @@ void AndroidVoipClient::StartSession(JNIEnv* env) {
/*isSuccessful=*/false);
return;
}
- rtp_socket_->SignalReadPacket.connect(
- this, &AndroidVoipClient::OnSignalReadRTPPacket);
+ rtp_socket_->RegisterReceivedPacketCallback(
+ [&](rtc::AsyncPacketSocket* socket, const rtc::ReceivedPacket& packet) {
+ OnSignalReadRTPPacket(socket, packet);
+ });
rtcp_socket_.reset(rtc::AsyncUDPSocket::Create(voip_thread_->socketserver(),
rtcp_local_address_));
@@ -324,8 +326,10 @@ void AndroidVoipClient::StartSession(JNIEnv* env) {
/*isSuccessful=*/false);
return;
}
- rtcp_socket_->SignalReadPacket.connect(
- this, &AndroidVoipClient::OnSignalReadRTCPPacket);
+ rtcp_socket_->RegisterReceivedPacketCallback(
+ [&](rtc::AsyncPacketSocket* socket, const rtc::ReceivedPacket& packet) {
+ OnSignalReadRTCPPacket(socket, packet);
+ });
Java_VoipClient_onStartSessionCompleted(env_, j_voip_client_,
/*isSuccessful=*/true);
}
@@ -467,12 +471,11 @@ void AndroidVoipClient::ReadRTPPacket(const std::vector<uint8_t>& packet_copy) {
RTC_CHECK(result == webrtc::VoipResult::kOk);
}
-void AndroidVoipClient::OnSignalReadRTPPacket(rtc::AsyncPacketSocket* socket,
- const char* rtp_packet,
- size_t size,
- const rtc::SocketAddress& addr,
- const int64_t& timestamp) {
- std::vector<uint8_t> packet_copy(rtp_packet, rtp_packet + size);
+void AndroidVoipClient::OnSignalReadRTPPacket(
+ rtc::AsyncPacketSocket* socket,
+ const rtc::ReceivedPacket& packet) {
+ std::vector<uint8_t> packet_copy(packet.payload().begin(),
+ packet.payload().end());
voip_thread_->PostTask([this, packet_copy = std::move(packet_copy)] {
ReadRTPPacket(packet_copy);
});
@@ -492,12 +495,11 @@ void AndroidVoipClient::ReadRTCPPacket(
RTC_CHECK(result == webrtc::VoipResult::kOk);
}
-void AndroidVoipClient::OnSignalReadRTCPPacket(rtc::AsyncPacketSocket* socket,
- const char* rtcp_packet,
- size_t size,
- const rtc::SocketAddress& addr,
- const int64_t& timestamp) {
- std::vector<uint8_t> packet_copy(rtcp_packet, rtcp_packet + size);
+void AndroidVoipClient::OnSignalReadRTCPPacket(
+ rtc::AsyncPacketSocket* socket,
+ const rtc::ReceivedPacket& packet) {
+ std::vector<uint8_t> packet_copy(packet.payload().begin(),
+ packet.payload().end());
voip_thread_->PostTask([this, packet_copy = std::move(packet_copy)] {
ReadRTCPPacket(packet_copy);
});
diff --git a/third_party/libwebrtc/examples/androidvoip/jni/android_voip_client.h b/third_party/libwebrtc/examples/androidvoip/jni/android_voip_client.h
index e2f1c64590..1d9a13b29d 100644
--- a/third_party/libwebrtc/examples/androidvoip/jni/android_voip_client.h
+++ b/third_party/libwebrtc/examples/androidvoip/jni/android_voip_client.h
@@ -23,8 +23,8 @@
#include "api/voip/voip_engine.h"
#include "rtc_base/async_packet_socket.h"
#include "rtc_base/async_udp_socket.h"
+#include "rtc_base/network/received_packet.h"
#include "rtc_base/socket_address.h"
-#include "rtc_base/third_party/sigslot/sigslot.h"
#include "rtc_base/thread.h"
#include "sdk/android/native_api/jni/scoped_java_ref.h"
@@ -40,8 +40,7 @@ namespace webrtc_examples {
// with consistent thread usage requirement with ProcessThread used
// within VoipEngine, as well as providing asynchronicity to the
// caller. AndroidVoipClient is meant to be used by Java through JNI.
-class AndroidVoipClient : public webrtc::Transport,
- public sigslot::has_slots<> {
+class AndroidVoipClient : public webrtc::Transport {
public:
// Returns a pointer to an AndroidVoipClient object. Clients should
// use this factory method to create AndroidVoipClient objects. The
@@ -122,17 +121,10 @@ class AndroidVoipClient : public webrtc::Transport,
const webrtc::PacketOptions& options) override;
bool SendRtcp(rtc::ArrayView<const uint8_t> packet) override;
- // Slots for sockets to connect to.
void OnSignalReadRTPPacket(rtc::AsyncPacketSocket* socket,
- const char* rtp_packet,
- size_t size,
- const rtc::SocketAddress& addr,
- const int64_t& timestamp);
+ const rtc::ReceivedPacket& packet);
void OnSignalReadRTCPPacket(rtc::AsyncPacketSocket* socket,
- const char* rtcp_packet,
- size_t size,
- const rtc::SocketAddress& addr,
- const int64_t& timestamp);
+ const rtc::ReceivedPacket& packet);
private:
AndroidVoipClient(JNIEnv* env,
diff --git a/third_party/libwebrtc/examples/objcnativeapi/objc/objc_call_client.mm b/third_party/libwebrtc/examples/objcnativeapi/objc/objc_call_client.mm
index 996c6a9c7f..2601beed71 100644
--- a/third_party/libwebrtc/examples/objcnativeapi/objc/objc_call_client.mm
+++ b/third_party/libwebrtc/examples/objcnativeapi/objc/objc_call_client.mm
@@ -126,8 +126,7 @@ void ObjCCallClient::CreatePeerConnectionFactory() {
[[RTC_OBJC_TYPE(RTCDefaultVideoDecoderFactory) alloc] init]);
dependencies.audio_processing = webrtc::AudioProcessingBuilder().Create();
webrtc::EnableMedia(dependencies);
- dependencies.event_log_factory =
- std::make_unique<webrtc::RtcEventLogFactory>(dependencies.task_queue_factory.get());
+ dependencies.event_log_factory = std::make_unique<webrtc::RtcEventLogFactory>();
pcf_ = webrtc::CreateModularPeerConnectionFactory(std::move(dependencies));
RTC_LOG(LS_INFO) << "PeerConnectionFactory created: " << pcf_.get();
}
diff --git a/third_party/libwebrtc/experiments/field_trials.py b/third_party/libwebrtc/experiments/field_trials.py
index 567cafc058..4aa9bcbe4b 100755
--- a/third_party/libwebrtc/experiments/field_trials.py
+++ b/third_party/libwebrtc/experiments/field_trials.py
@@ -50,6 +50,9 @@ ACTIVE_FIELD_TRIALS: FrozenSet[FieldTrial] = frozenset([
FieldTrial('WebRTC-Audio-OpusSetSignalVoiceWithDtx',
'webrtc:4559',
date(2024, 4, 1)),
+ FieldTrial('WebRTC-AV1-OverridePriorityBitrate',
+ 'webrtc:15763',
+ date(2024, 4, 1)),
FieldTrial('WebRTC-Av1-GetEncoderInfoOverride',
'webrtc:14931',
date(2024, 4, 1)),
@@ -125,6 +128,9 @@ ACTIVE_FIELD_TRIALS: FrozenSet[FieldTrial] = frozenset([
FieldTrial('WebRTC-VideoEncoderSettings',
'chromium:1406331',
date(2024, 4, 1)),
+ FieldTrial('WebRTC-ZeroHertzQueueOverload',
+ 'webrtc:332381',
+ date(2024, 7, 1)),
# keep-sorted end
]) # yapf: disable
@@ -565,9 +571,6 @@ POLICY_EXEMPT_FIELD_TRIALS: FrozenSet[FieldTrial] = frozenset([
FieldTrial('WebRTC-DependencyDescriptorAdvertised',
'webrtc:10342',
date(2024, 4, 1)),
- FieldTrial('WebRTC-DisablePacerEmergencyStop',
- '',
- date(2024, 4, 1)),
FieldTrial('WebRTC-DisableUlpFecExperiment',
'',
date(2024, 4, 1)),
diff --git a/third_party/libwebrtc/infra/OWNERS b/third_party/libwebrtc/infra/OWNERS
index eae8171db5..4a9e6f5856 100644
--- a/third_party/libwebrtc/infra/OWNERS
+++ b/third_party/libwebrtc/infra/OWNERS
@@ -3,4 +3,3 @@ jleconte@webrtc.org
titovartem@webrtc.org
jansson@webrtc.org
terelius@webrtc.org
-landrey@webrtc.org
diff --git a/third_party/libwebrtc/infra/config/config.star b/third_party/libwebrtc/infra/config/config.star
index eecac11c94..94d2d9ccc6 100755
--- a/third_party/libwebrtc/infra/config/config.star
+++ b/third_party/libwebrtc/infra/config/config.star
@@ -800,6 +800,8 @@ ci_builder("Win64 ASan", "Win Clang|x64|asan")
try_builder("win_asan")
ci_builder("Win (more configs)", "Win Clang|x86|more")
try_builder("win_x86_more_configs")
+try_builder("win11_release", cq = None)
+try_builder("win11_debug", cq = None)
chromium_try_builder("win_chromium_compile")
chromium_try_builder("win_chromium_compile_dbg")
diff --git a/third_party/libwebrtc/infra/config/cr-buildbucket.cfg b/third_party/libwebrtc/infra/config/cr-buildbucket.cfg
index cc36dc359c..039c580a34 100644
--- a/third_party/libwebrtc/infra/config/cr-buildbucket.cfg
+++ b/third_party/libwebrtc/infra/config/cr-buildbucket.cfg
@@ -4988,6 +4988,100 @@ buckets {
}
}
builders {
+ name: "win11_debug"
+ swarming_host: "chromium-swarm.appspot.com"
+ swarming_tags: "vpython:native-python-wrapper"
+ dimensions: "builderless:1"
+ dimensions: "cpu:x86-64"
+ dimensions: "os:Windows"
+ dimensions: "pool:luci.webrtc.try"
+ exe {
+ cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+ cipd_version: "refs/heads/main"
+ cmd: "luciexe"
+ }
+ properties:
+ '{'
+ ' "$build/reclient": {'
+ ' "instance": "rbe-webrtc-untrusted",'
+ ' "metrics_project": "chromium-reclient-metrics"'
+ ' },'
+ ' "$recipe_engine/resultdb/test_presentation": {'
+ ' "column_keys": [],'
+ ' "grouping_keys": ['
+ ' "status",'
+ ' "v.test_suite"'
+ ' ]'
+ ' },'
+ ' "builder_group": "tryserver.webrtc",'
+ ' "recipe": "webrtc/standalone"'
+ '}'
+ priority: 30
+ execution_timeout_secs: 7200
+ build_numbers: YES
+ service_account: "webrtc-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+ experiments {
+ key: "luci.recipes.use_python3"
+ value: 100
+ }
+ resultdb {
+ enable: true
+ bq_exports {
+ project: "webrtc-ci"
+ dataset: "resultdb"
+ table: "try_test_results"
+ test_results {}
+ }
+ }
+ }
+ builders {
+ name: "win11_release"
+ swarming_host: "chromium-swarm.appspot.com"
+ swarming_tags: "vpython:native-python-wrapper"
+ dimensions: "builderless:1"
+ dimensions: "cpu:x86-64"
+ dimensions: "os:Windows"
+ dimensions: "pool:luci.webrtc.try"
+ exe {
+ cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+ cipd_version: "refs/heads/main"
+ cmd: "luciexe"
+ }
+ properties:
+ '{'
+ ' "$build/reclient": {'
+ ' "instance": "rbe-webrtc-untrusted",'
+ ' "metrics_project": "chromium-reclient-metrics"'
+ ' },'
+ ' "$recipe_engine/resultdb/test_presentation": {'
+ ' "column_keys": [],'
+ ' "grouping_keys": ['
+ ' "status",'
+ ' "v.test_suite"'
+ ' ]'
+ ' },'
+ ' "builder_group": "tryserver.webrtc",'
+ ' "recipe": "webrtc/standalone"'
+ '}'
+ priority: 30
+ execution_timeout_secs: 7200
+ build_numbers: YES
+ service_account: "webrtc-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+ experiments {
+ key: "luci.recipes.use_python3"
+ value: 100
+ }
+ resultdb {
+ enable: true
+ bq_exports {
+ project: "webrtc-ci"
+ dataset: "resultdb"
+ table: "try_test_results"
+ test_results {}
+ }
+ }
+ }
+ builders {
name: "win_asan"
swarming_host: "chromium-swarm.appspot.com"
swarming_tags: "vpython:native-python-wrapper"
diff --git a/third_party/libwebrtc/infra/config/luci-milo.cfg b/third_party/libwebrtc/infra/config/luci-milo.cfg
index cca46cd157..3f3a18832a 100644
--- a/third_party/libwebrtc/infra/config/luci-milo.cfg
+++ b/third_party/libwebrtc/infra/config/luci-milo.cfg
@@ -594,6 +594,12 @@ consoles {
name: "buildbucket/luci.webrtc.try/win_x86_more_configs"
}
builders {
+ name: "buildbucket/luci.webrtc.try/win11_release"
+ }
+ builders {
+ name: "buildbucket/luci.webrtc.try/win11_debug"
+ }
+ builders {
name: "buildbucket/luci.webrtc.try/win_chromium_compile"
}
builders {
diff --git a/third_party/libwebrtc/infra/config/luci-notify.cfg b/third_party/libwebrtc/infra/config/luci-notify.cfg
index 8cae9b3f93..129d0e4268 100644
--- a/third_party/libwebrtc/infra/config/luci-notify.cfg
+++ b/third_party/libwebrtc/infra/config/luci-notify.cfg
@@ -2035,6 +2035,32 @@ notifiers {
}
builders {
bucket: "try"
+ name: "win11_debug"
+ }
+}
+notifiers {
+ notifications {
+ on_new_status: INFRA_FAILURE
+ email {
+ recipients: "webrtc-troopers-robots@google.com"
+ }
+ template: "infra_failure"
+ }
+ builders {
+ bucket: "try"
+ name: "win11_release"
+ }
+}
+notifiers {
+ notifications {
+ on_new_status: INFRA_FAILURE
+ email {
+ recipients: "webrtc-troopers-robots@google.com"
+ }
+ template: "infra_failure"
+ }
+ builders {
+ bucket: "try"
name: "win_asan"
}
}
diff --git a/third_party/libwebrtc/infra/specs/client.webrtc.json b/third_party/libwebrtc/infra/specs/client.webrtc.json
index 6f8bfb5ba5..4d53f6de9d 100644
--- a/third_party/libwebrtc/infra/specs/client.webrtc.json
+++ b/third_party/libwebrtc/infra/specs/client.webrtc.json
@@ -1864,7 +1864,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "audio_decoder_unittests",
@@ -1888,7 +1888,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_audio_unittests",
@@ -1912,7 +1912,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_video_unittests",
@@ -1936,7 +1936,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "dcsctp_unittests",
@@ -1960,7 +1960,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_media_unittests",
@@ -1984,7 +1984,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_pc_unittests",
@@ -2008,7 +2008,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -2033,7 +2033,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "system_wrappers_unittests",
@@ -2057,7 +2057,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -2082,7 +2082,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "voip_unittests",
@@ -2106,7 +2106,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "webrtc_nonparallel_tests",
@@ -2127,7 +2127,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -2149,7 +2149,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "audio_decoder_unittests",
@@ -2166,7 +2166,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_audio_unittests",
@@ -2183,7 +2183,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_video_unittests",
@@ -2200,7 +2200,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "dcsctp_unittests",
@@ -2217,7 +2217,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 2
},
@@ -2235,7 +2235,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -2253,7 +2253,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -2271,7 +2271,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_media_unittests",
@@ -2288,7 +2288,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_pc_unittests",
@@ -2305,7 +2305,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_stats_unittests",
@@ -2322,7 +2322,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -2340,7 +2340,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "shared_screencast_stream_test",
@@ -2357,7 +2357,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "slow_peer_connection_unittests",
@@ -2374,7 +2374,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -2392,7 +2392,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "system_wrappers_unittests",
@@ -2409,7 +2409,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "test_support_unittests",
@@ -2426,7 +2426,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "tools_unittests",
@@ -2443,7 +2443,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -2461,7 +2461,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "voip_unittests",
@@ -2478,7 +2478,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "webrtc_nonparallel_tests",
@@ -2832,7 +2832,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "audio_decoder_unittests",
@@ -2849,7 +2849,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_audio_unittests",
@@ -2866,7 +2866,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_video_unittests",
@@ -2883,7 +2883,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "dcsctp_unittests",
@@ -2900,7 +2900,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 2
},
@@ -2918,7 +2918,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -2936,7 +2936,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -2954,7 +2954,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_media_unittests",
@@ -2971,7 +2971,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_pc_unittests",
@@ -2988,7 +2988,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_stats_unittests",
@@ -3005,7 +3005,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -3023,7 +3023,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "slow_peer_connection_unittests",
@@ -3040,7 +3040,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -3058,7 +3058,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "system_wrappers_unittests",
@@ -3075,7 +3075,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "test_support_unittests",
@@ -3092,7 +3092,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "tools_unittests",
@@ -3109,7 +3109,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -3127,7 +3127,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "voip_unittests",
@@ -3144,7 +3144,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "webrtc_nonparallel_tests",
@@ -3165,7 +3165,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "audio_decoder_unittests",
@@ -3182,7 +3182,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_audio_unittests",
@@ -3199,7 +3199,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_video_unittests",
@@ -3216,7 +3216,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "dcsctp_unittests",
@@ -3233,7 +3233,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 2
},
@@ -3251,7 +3251,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -3269,7 +3269,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -3287,7 +3287,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_media_unittests",
@@ -3304,7 +3304,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_pc_unittests",
@@ -3321,7 +3321,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_stats_unittests",
@@ -3338,7 +3338,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -3356,7 +3356,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "shared_screencast_stream_test",
@@ -3373,7 +3373,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "slow_peer_connection_unittests",
@@ -3390,7 +3390,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -3408,7 +3408,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "system_wrappers_unittests",
@@ -3425,7 +3425,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "test_support_unittests",
@@ -3442,7 +3442,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "tools_unittests",
@@ -3459,7 +3459,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -3477,7 +3477,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "voip_unittests",
@@ -3494,7 +3494,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "webrtc_nonparallel_tests",
@@ -3515,7 +3515,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "audio_decoder_unittests",
@@ -3532,7 +3532,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_audio_unittests",
@@ -3549,7 +3549,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_video_unittests",
@@ -3566,7 +3566,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "dcsctp_unittests",
@@ -3583,7 +3583,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 2
},
@@ -3601,7 +3601,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -3619,7 +3619,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -3637,7 +3637,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_media_unittests",
@@ -3654,7 +3654,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_pc_unittests",
@@ -3671,7 +3671,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_stats_unittests",
@@ -3688,7 +3688,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -3706,7 +3706,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "shared_screencast_stream_test",
@@ -3723,7 +3723,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "slow_peer_connection_unittests",
@@ -3740,7 +3740,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -3758,7 +3758,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "system_wrappers_unittests",
@@ -3775,7 +3775,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "test_support_unittests",
@@ -3792,7 +3792,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "tools_unittests",
@@ -3809,7 +3809,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -3827,7 +3827,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "voip_unittests",
@@ -3844,7 +3844,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "webrtc_nonparallel_tests",
@@ -3865,7 +3865,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "audio_decoder_unittests",
@@ -3882,7 +3882,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_audio_unittests",
@@ -3899,7 +3899,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_video_unittests",
@@ -3916,7 +3916,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "dcsctp_unittests",
@@ -3933,7 +3933,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 2
},
@@ -3951,7 +3951,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -3969,7 +3969,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -3987,7 +3987,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_media_unittests",
@@ -4004,7 +4004,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_pc_unittests",
@@ -4021,7 +4021,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_stats_unittests",
@@ -4038,7 +4038,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -4056,7 +4056,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "slow_peer_connection_unittests",
@@ -4073,7 +4073,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -4091,7 +4091,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "system_wrappers_unittests",
@@ -4108,7 +4108,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "test_support_unittests",
@@ -4125,7 +4125,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "tools_unittests",
@@ -4142,7 +4142,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -4160,7 +4160,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "voip_unittests",
@@ -4177,7 +4177,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "webrtc_nonparallel_tests",
@@ -4199,7 +4199,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "audio_decoder_unittests",
@@ -4216,7 +4216,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_audio_unittests",
@@ -4233,7 +4233,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_video_unittests",
@@ -4250,7 +4250,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "dcsctp_unittests",
@@ -4267,7 +4267,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 2
},
@@ -4285,7 +4285,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -4303,7 +4303,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -4321,7 +4321,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_media_unittests",
@@ -4338,7 +4338,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_pc_unittests",
@@ -4355,7 +4355,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_stats_unittests",
@@ -4372,7 +4372,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -4390,7 +4390,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "slow_peer_connection_unittests",
@@ -4407,7 +4407,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -4425,7 +4425,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "system_wrappers_unittests",
@@ -4442,7 +4442,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "test_support_unittests",
@@ -4459,7 +4459,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "tools_unittests",
@@ -4476,7 +4476,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -4494,7 +4494,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "voip_unittests",
@@ -4511,7 +4511,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "webrtc_nonparallel_tests",
@@ -4534,7 +4534,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "audio_decoder_unittests",
@@ -4551,7 +4551,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_audio_unittests",
@@ -4568,7 +4568,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_video_unittests",
@@ -4585,7 +4585,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "dcsctp_unittests",
@@ -4602,7 +4602,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 2
},
@@ -4620,7 +4620,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -4638,7 +4638,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -4656,7 +4656,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_media_unittests",
@@ -4673,7 +4673,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_pc_unittests",
@@ -4690,7 +4690,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_stats_unittests",
@@ -4707,7 +4707,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -4725,7 +4725,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "shared_screencast_stream_test",
@@ -4742,7 +4742,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "slow_peer_connection_unittests",
@@ -4759,7 +4759,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -4777,7 +4777,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "system_wrappers_unittests",
@@ -4794,7 +4794,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "test_support_unittests",
@@ -4811,7 +4811,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "tools_unittests",
@@ -4828,7 +4828,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -4846,7 +4846,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "voip_unittests",
@@ -4863,7 +4863,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "webrtc_nonparallel_tests",
@@ -4885,7 +4885,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "audio_decoder_unittests",
@@ -4902,7 +4902,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_audio_unittests",
@@ -4919,7 +4919,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_video_unittests",
@@ -4936,7 +4936,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "dcsctp_unittests",
@@ -4953,7 +4953,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 2
},
@@ -4971,7 +4971,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -4989,7 +4989,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -5007,7 +5007,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_media_unittests",
@@ -5024,7 +5024,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_pc_unittests",
@@ -5041,7 +5041,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_stats_unittests",
@@ -5058,7 +5058,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -5076,7 +5076,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "shared_screencast_stream_test",
@@ -5093,7 +5093,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "slow_peer_connection_unittests",
@@ -5110,7 +5110,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -5128,7 +5128,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "system_wrappers_unittests",
@@ -5145,7 +5145,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "test_support_unittests",
@@ -5162,7 +5162,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "tools_unittests",
@@ -5172,24 +5172,6 @@
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "video_capture_tests",
- "resultdb": {
- "result_format": "json"
- },
- "swarming": {
- "dimensions": {
- "cpu": "x86-64",
- "os": "Ubuntu-18.04",
- "pool": "WebRTC-baremetal"
- }
- },
- "test": "video_capture_tests",
- "test_id_prefix": "ninja://modules/video_capture:video_capture_tests/"
- },
- {
- "merge": {
- "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
- },
"name": "video_engine_tests",
"resultdb": {
"result_format": "json"
@@ -5197,7 +5179,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -5215,7 +5197,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "voip_unittests",
@@ -5232,7 +5214,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "webrtc_nonparallel_tests",
@@ -6230,24 +6212,6 @@
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "video_capture_tests",
- "resultdb": {
- "result_format": "json"
- },
- "swarming": {
- "dimensions": {
- "cpu": "x86-64",
- "os": "Mac-12",
- "pool": "WebRTC-baremetal"
- }
- },
- "test": "video_capture_tests",
- "test_id_prefix": "ninja://modules/video_capture:video_capture_tests/"
- },
- {
- "merge": {
- "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
- },
"name": "video_engine_tests",
"resultdb": {
"result_format": "json"
@@ -7938,24 +7902,6 @@
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "video_capture_tests",
- "resultdb": {
- "result_format": "json"
- },
- "swarming": {
- "dimensions": {
- "cpu": "x86-64",
- "os": "Windows-10-19045",
- "pool": "WebRTC-baremetal"
- }
- },
- "test": "video_capture_tests",
- "test_id_prefix": "ninja://modules/video_capture:video_capture_tests/"
- },
- {
- "merge": {
- "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
- },
"name": "video_engine_tests",
"resultdb": {
"result_format": "json"
@@ -8011,20 +7957,20 @@
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
- "--xcode-parallelization",
+ "--xcodebuild-sim-runner",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "apprtcmobile_tests iPhone X 14.5",
+ "name": "apprtcmobile_tests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -8034,29 +7980,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "apprtcmobile_tests",
"test_id_prefix": "ninja://examples:apprtcmobile_tests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -8067,9 +8012,9 @@
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
- "--xcode-parallelization",
+ "--xcodebuild-sim-runner",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -8084,17 +8029,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -8113,18 +8057,18 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
- "--xcode-parallelization",
+ "--xcodebuild-sim-runner",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "apprtcmobile_tests iPhone X 16.2",
+ "name": "apprtcmobile_tests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -8134,46 +8078,45 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "apprtcmobile_tests",
"test_id_prefix": "ninja://examples:apprtcmobile_tests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "audio_decoder_unittests iPhone X 14.5",
+ "name": "audio_decoder_unittests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -8183,29 +8126,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "audio_decoder_unittests",
"test_id_prefix": "ninja://modules/audio_coding:audio_decoder_unittests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -8217,7 +8159,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -8232,17 +8174,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -8261,17 +8202,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "audio_decoder_unittests iPhone X 16.2",
+ "name": "audio_decoder_unittests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -8281,46 +8222,45 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "audio_decoder_unittests",
"test_id_prefix": "ninja://modules/audio_coding:audio_decoder_unittests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "common_audio_unittests iPhone X 14.5",
+ "name": "common_audio_unittests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -8330,29 +8270,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "common_audio_unittests",
"test_id_prefix": "ninja://common_audio:common_audio_unittests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -8364,7 +8303,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -8379,17 +8318,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -8408,17 +8346,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "common_audio_unittests iPhone X 16.2",
+ "name": "common_audio_unittests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -8428,46 +8366,45 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "common_audio_unittests",
"test_id_prefix": "ninja://common_audio:common_audio_unittests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "common_video_unittests iPhone X 14.5",
+ "name": "common_video_unittests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -8477,29 +8414,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "common_video_unittests",
"test_id_prefix": "ninja://common_video:common_video_unittests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -8511,7 +8447,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -8526,17 +8462,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -8555,17 +8490,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "common_video_unittests iPhone X 16.2",
+ "name": "common_video_unittests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -8575,46 +8510,45 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "common_video_unittests",
"test_id_prefix": "ninja://common_video:common_video_unittests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "dcsctp_unittests iPhone X 14.5",
+ "name": "dcsctp_unittests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -8624,29 +8558,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "dcsctp_unittests",
"test_id_prefix": "ninja://net/dcsctp:dcsctp_unittests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -8658,7 +8591,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -8673,17 +8606,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -8702,17 +8634,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "dcsctp_unittests iPhone X 16.2",
+ "name": "dcsctp_unittests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -8722,46 +8654,45 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "dcsctp_unittests",
"test_id_prefix": "ninja://net/dcsctp:dcsctp_unittests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "modules_tests iPhone X 14.5",
+ "name": "modules_tests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -8771,22 +8702,21 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -8794,7 +8724,7 @@
},
"test": "modules_tests",
"test_id_prefix": "ninja://modules:modules_tests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -8806,7 +8736,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -8821,17 +8751,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -8851,17 +8780,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "modules_tests iPhone X 16.2",
+ "name": "modules_tests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -8871,22 +8800,21 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -8894,24 +8822,24 @@
},
"test": "modules_tests",
"test_id_prefix": "ninja://modules:modules_tests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "modules_unittests iPhone X 14.5",
+ "name": "modules_unittests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -8921,23 +8849,22 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cores": "12",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -8945,7 +8872,7 @@
},
"test": "modules_unittests",
"test_id_prefix": "ninja://modules:modules_unittests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -8957,7 +8884,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -8972,18 +8899,17 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cores": "12",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -9003,17 +8929,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "modules_unittests iPhone X 16.2",
+ "name": "modules_unittests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -9023,23 +8949,22 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cores": "12",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -9047,24 +8972,24 @@
},
"test": "modules_unittests",
"test_id_prefix": "ninja://modules:modules_unittests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "rtc_media_unittests iPhone X 14.5",
+ "name": "rtc_media_unittests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -9074,29 +8999,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "rtc_media_unittests",
"test_id_prefix": "ninja://media:rtc_media_unittests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -9108,7 +9032,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -9123,17 +9047,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -9152,17 +9075,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "rtc_media_unittests iPhone X 16.2",
+ "name": "rtc_media_unittests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -9172,46 +9095,45 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "rtc_media_unittests",
"test_id_prefix": "ninja://media:rtc_media_unittests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "rtc_pc_unittests iPhone X 14.5",
+ "name": "rtc_pc_unittests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -9221,29 +9143,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "rtc_pc_unittests",
"test_id_prefix": "ninja://pc:rtc_pc_unittests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -9255,7 +9176,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -9270,17 +9191,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -9299,17 +9219,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "rtc_pc_unittests iPhone X 16.2",
+ "name": "rtc_pc_unittests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -9319,46 +9239,45 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "rtc_pc_unittests",
"test_id_prefix": "ninja://pc:rtc_pc_unittests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "rtc_stats_unittests iPhone X 14.5",
+ "name": "rtc_stats_unittests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -9368,29 +9287,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "rtc_stats_unittests",
"test_id_prefix": "ninja://stats:rtc_stats_unittests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -9402,7 +9320,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -9417,17 +9335,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -9446,17 +9363,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "rtc_stats_unittests iPhone X 16.2",
+ "name": "rtc_stats_unittests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -9466,46 +9383,45 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "rtc_stats_unittests",
"test_id_prefix": "ninja://stats:rtc_stats_unittests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "rtc_unittests iPhone X 14.5",
+ "name": "rtc_unittests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -9515,22 +9431,21 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -9538,7 +9453,7 @@
},
"test": "rtc_unittests",
"test_id_prefix": "ninja://:rtc_unittests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -9550,7 +9465,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -9565,17 +9480,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -9595,17 +9509,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "rtc_unittests iPhone X 16.2",
+ "name": "rtc_unittests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -9615,22 +9529,21 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -9638,25 +9551,25 @@
},
"test": "rtc_unittests",
"test_id_prefix": "ninja://:rtc_unittests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
- "--xcode-parallelization",
+ "--xcodebuild-sim-runner",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "sdk_framework_unittests iPhone X 14.5",
+ "name": "sdk_framework_unittests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -9666,29 +9579,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "sdk_framework_unittests",
"test_id_prefix": "ninja://sdk:sdk_framework_unittests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -9699,9 +9611,9 @@
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
- "--xcode-parallelization",
+ "--xcodebuild-sim-runner",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -9716,17 +9628,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -9745,18 +9656,18 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
- "--xcode-parallelization",
+ "--xcodebuild-sim-runner",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "sdk_framework_unittests iPhone X 16.2",
+ "name": "sdk_framework_unittests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -9766,47 +9677,46 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "sdk_framework_unittests",
"test_id_prefix": "ninja://sdk:sdk_framework_unittests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
- "--xcode-parallelization",
+ "--xcodebuild-sim-runner",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "sdk_unittests iPhone X 14.5",
+ "name": "sdk_unittests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -9816,29 +9726,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "sdk_unittests",
"test_id_prefix": "ninja://sdk:sdk_unittests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -9849,9 +9758,9 @@
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
- "--xcode-parallelization",
+ "--xcodebuild-sim-runner",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -9866,17 +9775,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -9895,18 +9803,18 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
- "--xcode-parallelization",
+ "--xcodebuild-sim-runner",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "sdk_unittests iPhone X 16.2",
+ "name": "sdk_unittests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -9916,46 +9824,45 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "sdk_unittests",
"test_id_prefix": "ninja://sdk:sdk_unittests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "svc_tests iPhone X 14.5",
+ "name": "svc_tests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -9965,23 +9872,22 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cores": "12",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -9989,7 +9895,7 @@
},
"test": "svc_tests",
"test_id_prefix": "ninja://pc:svc_tests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -10001,7 +9907,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -10016,18 +9922,17 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cores": "12",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -10047,17 +9952,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "svc_tests iPhone X 16.2",
+ "name": "svc_tests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -10067,23 +9972,22 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cores": "12",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -10091,24 +9995,24 @@
},
"test": "svc_tests",
"test_id_prefix": "ninja://pc:svc_tests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "system_wrappers_unittests iPhone X 14.5",
+ "name": "system_wrappers_unittests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -10118,29 +10022,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "system_wrappers_unittests",
"test_id_prefix": "ninja://system_wrappers:system_wrappers_unittests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -10152,7 +10055,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -10167,17 +10070,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -10196,17 +10098,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "system_wrappers_unittests iPhone X 16.2",
+ "name": "system_wrappers_unittests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -10216,46 +10118,45 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "system_wrappers_unittests",
"test_id_prefix": "ninja://system_wrappers:system_wrappers_unittests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "test_support_unittests iPhone X 14.5",
+ "name": "test_support_unittests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -10265,29 +10166,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "test_support_unittests",
"test_id_prefix": "ninja://test:test_support_unittests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -10299,7 +10199,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -10314,17 +10214,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -10343,17 +10242,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "test_support_unittests iPhone X 16.2",
+ "name": "test_support_unittests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -10363,46 +10262,45 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "test_support_unittests",
"test_id_prefix": "ninja://test:test_support_unittests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "tools_unittests iPhone X 14.5",
+ "name": "tools_unittests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -10412,29 +10310,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "tools_unittests",
"test_id_prefix": "ninja://rtc_tools:tools_unittests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -10446,7 +10343,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -10461,17 +10358,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -10490,17 +10386,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "tools_unittests iPhone X 16.2",
+ "name": "tools_unittests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -10510,46 +10406,45 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "tools_unittests",
"test_id_prefix": "ninja://rtc_tools:tools_unittests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "video_capture_tests iPhone X 14.5",
+ "name": "video_engine_tests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -10559,169 +10454,21 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
- }
- ],
- "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
- },
- "test": "video_capture_tests",
- "test_id_prefix": "ninja://modules/video_capture:video_capture_tests/",
- "variant_id": "iPhone X 14.5"
- },
- {
- "args": [
- "--platform",
- "iPhone X",
- "--version",
- "15.5",
- "--out-dir",
- "${ISOLATED_OUTDIR}",
- "--xctest",
- "--xcode-build-version",
- "14c18"
- ],
- "merge": {
- "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
- },
- "name": "video_capture_tests iPhone X 15.5",
- "resultdb": {
- "enable": true,
- "has_native_resultdb_integration": true
- },
- "swarming": {
- "cipd_packages": [
- {
- "cipd_package": "infra/tools/mac_toolchain/${platform}",
- "location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
- }
- ],
- "dimensions": {
- "caches": "xcode_ios_14c18",
- "cpu": "x86-64",
- "os": "Mac-12"
- },
- "named_caches": [
- {
- "name": "xcode_ios_14c18",
- "path": "Xcode.app"
- },
- {
- "name": "runtime_ios_15_5",
- "path": "Runtime-ios-15.5"
- }
- ],
- "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
- },
- "test": "video_capture_tests",
- "test_id_prefix": "ninja://modules/video_capture:video_capture_tests/",
- "variant_id": "iPhone X 15.5"
- },
- {
- "args": [
- "--platform",
- "iPhone X",
- "--version",
- "16.2",
- "--out-dir",
- "${ISOLATED_OUTDIR}",
- "--xctest",
- "--xcode-build-version",
- "14c18"
- ],
- "merge": {
- "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
- },
- "name": "video_capture_tests iPhone X 16.2",
- "resultdb": {
- "enable": true,
- "has_native_resultdb_integration": true
- },
- "swarming": {
- "cipd_packages": [
- {
- "cipd_package": "infra/tools/mac_toolchain/${platform}",
- "location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
- }
- ],
- "dimensions": {
- "caches": "xcode_ios_14c18",
- "cpu": "x86-64",
- "os": "Mac-12"
- },
- "named_caches": [
- {
- "name": "xcode_ios_14c18",
- "path": "Xcode.app"
- },
- {
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
- }
- ],
- "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
- },
- "test": "video_capture_tests",
- "test_id_prefix": "ninja://modules/video_capture:video_capture_tests/",
- "variant_id": "iPhone X 16.2"
- },
- {
- "args": [
- "--platform",
- "iPhone X",
- "--version",
- "14.5",
- "--out-dir",
- "${ISOLATED_OUTDIR}",
- "--xctest",
- "--xcode-build-version",
- "13c100"
- ],
- "merge": {
- "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
- },
- "name": "video_engine_tests iPhone X 14.5",
- "resultdb": {
- "enable": true,
- "has_native_resultdb_integration": true
- },
- "swarming": {
- "cipd_packages": [
- {
- "cipd_package": "infra/tools/mac_toolchain/${platform}",
- "location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
- }
- ],
- "dimensions": {
- "caches": "xcode_ios_13c100",
- "cpu": "x86-64",
- "os": "Mac-12"
- },
- "named_caches": [
- {
- "name": "xcode_ios_13c100",
- "path": "Xcode.app"
- },
- {
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -10729,7 +10476,7 @@
},
"test": "video_engine_tests",
"test_id_prefix": "ninja://:video_engine_tests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -10741,7 +10488,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -10756,17 +10503,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -10786,17 +10532,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "video_engine_tests iPhone X 16.2",
+ "name": "video_engine_tests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -10806,22 +10552,21 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -10829,24 +10574,24 @@
},
"test": "video_engine_tests",
"test_id_prefix": "ninja://:video_engine_tests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "voip_unittests iPhone X 14.5",
+ "name": "voip_unittests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -10856,29 +10601,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "voip_unittests",
"test_id_prefix": "ninja://:voip_unittests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -10890,7 +10634,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -10905,17 +10649,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -10934,17 +10677,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "voip_unittests iPhone X 16.2",
+ "name": "voip_unittests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -10954,46 +10697,45 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "voip_unittests",
"test_id_prefix": "ninja://:voip_unittests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "webrtc_nonparallel_tests iPhone X 14.5",
+ "name": "webrtc_nonparallel_tests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -11003,29 +10745,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "webrtc_nonparallel_tests",
"test_id_prefix": "ninja://:webrtc_nonparallel_tests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -11037,7 +10778,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -11052,17 +10793,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -11081,17 +10821,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "webrtc_nonparallel_tests iPhone X 16.2",
+ "name": "webrtc_nonparallel_tests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -11101,29 +10841,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "webrtc_nonparallel_tests",
"test_id_prefix": "ninja://:webrtc_nonparallel_tests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
}
]
},
diff --git a/third_party/libwebrtc/infra/specs/internal.client.webrtc.json b/third_party/libwebrtc/infra/specs/internal.client.webrtc.json
index 59547fc132..b6dbd5c2c3 100644
--- a/third_party/libwebrtc/infra/specs/internal.client.webrtc.json
+++ b/third_party/libwebrtc/infra/specs/internal.client.webrtc.json
@@ -24,7 +24,7 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
@@ -64,7 +64,7 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
@@ -105,7 +105,7 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
@@ -148,7 +148,7 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
@@ -189,7 +189,7 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
@@ -229,7 +229,7 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
@@ -269,7 +269,7 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
@@ -309,7 +309,7 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
@@ -349,7 +349,7 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
@@ -379,46 +379,6 @@
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "video_capture_tests",
- "resultdb": {
- "enable": true,
- "has_native_resultdb_integration": true
- },
- "swarming": {
- "cipd_packages": [
- {
- "cipd_package": "infra/tools/mac_toolchain/${platform}",
- "location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
- }
- ],
- "dimensions": {
- "device_status": "available",
- "os": "iOS-16.6",
- "pool": "chrome.tests"
- },
- "named_caches": [
- {
- "name": "xcode_ios_15a507",
- "path": "Xcode.app"
- }
- ],
- "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com"
- },
- "test": "video_capture_tests",
- "test_id_prefix": "ninja://modules/video_capture:video_capture_tests/"
- },
- {
- "args": [
- "--xctest",
- "--xcode-build-version",
- "15a507",
- "--out-dir",
- "${ISOLATED_OUTDIR}"
- ],
- "merge": {
- "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
- },
"name": "video_engine_tests",
"resultdb": {
"enable": true,
@@ -429,7 +389,7 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
@@ -480,7 +440,7 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
@@ -529,7 +489,7 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
@@ -569,7 +529,7 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
@@ -610,7 +570,7 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
@@ -653,7 +613,7 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
@@ -694,7 +654,7 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
@@ -734,7 +694,7 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
@@ -774,7 +734,7 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
@@ -814,7 +774,7 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
@@ -854,7 +814,7 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
@@ -884,46 +844,6 @@
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "video_capture_tests",
- "resultdb": {
- "enable": true,
- "has_native_resultdb_integration": true
- },
- "swarming": {
- "cipd_packages": [
- {
- "cipd_package": "infra/tools/mac_toolchain/${platform}",
- "location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
- }
- ],
- "dimensions": {
- "device_status": "available",
- "os": "iOS-16.6",
- "pool": "chrome.tests"
- },
- "named_caches": [
- {
- "name": "xcode_ios_15a507",
- "path": "Xcode.app"
- }
- ],
- "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com"
- },
- "test": "video_capture_tests",
- "test_id_prefix": "ninja://modules/video_capture:video_capture_tests/"
- },
- {
- "args": [
- "--xctest",
- "--xcode-build-version",
- "15a507",
- "--out-dir",
- "${ISOLATED_OUTDIR}"
- ],
- "merge": {
- "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
- },
"name": "video_engine_tests",
"resultdb": {
"enable": true,
@@ -934,7 +854,7 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
diff --git a/third_party/libwebrtc/infra/specs/mixins.pyl b/third_party/libwebrtc/infra/specs/mixins.pyl
index e436846ef0..8520002df6 100644
--- a/third_party/libwebrtc/infra/specs/mixins.pyl
+++ b/third_party/libwebrtc/infra/specs/mixins.pyl
@@ -18,20 +18,6 @@
}
}
},
- 'baremetal-pool': {
- 'swarming': {
- 'dimensions': {
- 'pool': 'WebRTC-baremetal'
- }
- }
- },
- 'baremetal-try-pool': {
- 'swarming': {
- 'dimensions': {
- 'pool': 'WebRTC-baremetal-try'
- }
- }
- },
'chrome-tester-service-account': {
'swarming': {
'service_account':
@@ -92,27 +78,27 @@
}
}
},
- 'ios_runtime_cache_14_5': {
+ 'ios_runtime_cache_15_5': {
'swarming': {
'named_caches': [{
- 'name': 'runtime_ios_14_5',
- 'path': 'Runtime-ios-14.5'
+ 'name': 'runtime_ios_15_5',
+ 'path': 'Runtime-ios-15.5'
}]
}
},
- 'ios_runtime_cache_15_5': {
+ 'ios_runtime_cache_16_4': {
'swarming': {
'named_caches': [{
- 'name': 'runtime_ios_15_5',
- 'path': 'Runtime-ios-15.5'
+ 'name': 'runtime_ios_16_4',
+ 'path': 'Runtime-ios-16.4'
}]
}
},
- 'ios_runtime_cache_16_2': {
+ 'ios_runtime_cache_17_0': {
'swarming': {
'named_caches': [{
- 'name': 'runtime_ios_16_2',
- 'path': 'Runtime-ios-16.2'
+ 'name': 'runtime_ios_17_0',
+ 'path': 'Runtime-ios-17.0'
}]
}
},
@@ -168,6 +154,14 @@
}
}
},
+ 'mac_13_x64': {
+ 'swarming': {
+ 'dimensions': {
+ 'cpu': 'x86-64',
+ 'os': 'Mac-13'
+ }
+ }
+ },
'mac_toolchain': {
'swarming': {
'cipd_packages': [{
@@ -176,7 +170,7 @@
'location':
'.',
'revision':
- 'git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce'
+ 'git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0'
}]
}
},
@@ -299,23 +293,18 @@
}
}
},
- 'x86-64': {
+ 'win11': {
'swarming': {
'dimensions': {
- 'cpu': 'x86-64'
+ 'os': 'Windows-11-22000'
}
}
},
- 'xcode_13_main': {
- 'args': ['--xcode-build-version', '13c100'],
+ 'x86-64': {
'swarming': {
'dimensions': {
- 'caches': 'xcode_ios_13c100'
- },
- 'named_caches': [{
- 'name': 'xcode_ios_13c100',
- 'path': 'Xcode.app'
- }]
+ 'cpu': 'x86-64'
+ }
}
},
'xcode_14_main': {
@@ -339,7 +328,7 @@
}]
}
},
- 'xcode_parallelization': {
- 'args': ['--xcode-parallelization']
+ 'xcodebuild_sim_runner': {
+ 'args': ['--xcodebuild-sim-runner']
}
}
diff --git a/third_party/libwebrtc/infra/specs/mixins_webrtc.pyl b/third_party/libwebrtc/infra/specs/mixins_webrtc.pyl
index 443a4450eb..cd83ad2d9b 100644
--- a/third_party/libwebrtc/infra/specs/mixins_webrtc.pyl
+++ b/third_party/libwebrtc/infra/specs/mixins_webrtc.pyl
@@ -14,20 +14,6 @@
},
},
},
- 'baremetal-pool': {
- 'swarming': {
- 'dimensions': {
- 'pool': 'WebRTC-baremetal',
- },
- },
- },
- 'baremetal-try-pool': {
- 'swarming': {
- 'dimensions': {
- 'pool': 'WebRTC-baremetal-try',
- },
- },
- },
'cores-12': {
'swarming': {
'dimensions': {
@@ -72,22 +58,6 @@
},
},
},
- 'ios_runtime_cache_14_5': {
- 'swarming': {
- 'named_caches': [{
- 'name': 'runtime_ios_14_5',
- 'path': 'Runtime-ios-14.5'
- }]
- }
- },
- 'ios_runtime_cache_16_2': {
- 'swarming': {
- 'named_caches': [{
- 'name': 'runtime_ios_16_2',
- 'path': 'Runtime-ios-16.2'
- }]
- }
- },
'limited-capacity': {
# Sometimes there are multiple tests that can be run only on one machine.
# We need to increase timeouts so the tests dont expire before the machine is freed.
@@ -221,18 +191,6 @@
'--xctest',
],
},
- 'xcode_13_main': {
- 'args': ['--xcode-build-version', '13c100'],
- 'swarming': {
- 'dimensions': {
- 'caches': 'xcode_ios_13c100',
- },
- 'named_caches': [{
- 'name': 'xcode_ios_13c100',
- 'path': 'Xcode.app'
- }]
- }
- },
'xcode_14_main': {
'args': ['--xcode-build-version', '14c18'],
'swarming': {
diff --git a/third_party/libwebrtc/infra/specs/test_suites.pyl b/third_party/libwebrtc/infra/specs/test_suites.pyl
index 570314c9bd..e6d98748c7 100644
--- a/third_party/libwebrtc/infra/specs/test_suites.pyl
+++ b/third_party/libwebrtc/infra/specs/test_suites.pyl
@@ -54,6 +54,9 @@
'webrtc_nonparallel_tests': {},
},
'android_tests_tryserver_specific': {
+ 'video_codec_perf_tests': {
+ 'mixins': ['shards-2', 'quick-perf-tests'],
+ },
'webrtc_perf_tests': {
'mixins': ['quick-perf-tests'],
}
@@ -95,9 +98,6 @@
'shared_screencast_stream_test': {},
},
'desktop_tests_try_server_specific': {
- 'video_capture_tests': {
- 'mixins': ['baremetal-try-pool'],
- },
'video_codec_perf_tests': {
'mixins': ['quick-perf-tests', 'resultdb-gtest-json-format'],
},
@@ -155,14 +155,13 @@
'system_wrappers_unittests': {},
'test_support_unittests': {},
'tools_unittests': {},
- 'video_capture_tests': {},
'video_engine_tests': {
'mixins': ['shards-4'],
},
},
'ios_simulator_tests': {
'apprtcmobile_tests': {
- 'mixins': ['xcode_parallelization']
+ 'mixins': ['xcodebuild_sim_runner']
},
'audio_decoder_unittests': {},
'common_audio_unittests': {},
@@ -181,10 +180,10 @@
'mixins': ['shards-6'],
},
'sdk_framework_unittests': {
- 'mixins': ['xcode_parallelization']
+ 'mixins': ['xcodebuild_sim_runner']
},
'sdk_unittests': {
- 'mixins': ['xcode_parallelization']
+ 'mixins': ['xcodebuild_sim_runner']
},
'svc_tests': {
'mixins': ['shards-4', 'cores-12'],
@@ -192,7 +191,6 @@
'system_wrappers_unittests': {},
'test_support_unittests': {},
'tools_unittests': {},
- 'video_capture_tests': {},
'video_engine_tests': {
'mixins': ['shards-4'],
},
@@ -231,11 +229,6 @@
],
},
},
- 'video_capture_tests': {
- 'video_capture_tests': {
- 'mixins': ['baremetal-pool'],
- }
- },
},
##############################################################################
@@ -250,20 +243,11 @@
'desktop_tests',
'desktop_tests_try_server_specific',
],
- 'desktop_tests_with_video_capture': [
- 'desktop_tests',
- 'video_capture_tests',
- ],
'linux_desktop_tests_tryserver': [
'desktop_tests',
'desktop_tests_linux_specific',
'desktop_tests_try_server_specific',
],
- 'linux_desktop_tests_with_video_capture': [
- 'desktop_tests',
- 'desktop_tests_linux_specific',
- 'video_capture_tests',
- ],
'linux_tests': [
'desktop_tests',
'desktop_tests_linux_specific',
@@ -277,9 +261,9 @@
'ios_simulator_tests_matrix': {
'ios_simulator_tests': {
'variants': [
- 'SIM_IPHONE_X_14_5',
'SIM_IPHONE_X_15_5',
- 'SIM_IPHONE_X_16_2',
+ 'SIM_IPHONE_X_16_4',
+ 'SIM_IPHONE_14_17_0',
],
},
},
diff --git a/third_party/libwebrtc/infra/specs/tryserver.webrtc.json b/third_party/libwebrtc/infra/specs/tryserver.webrtc.json
index 61e47fd0f8..3ab538b02c 100644
--- a/third_party/libwebrtc/infra/specs/tryserver.webrtc.json
+++ b/third_party/libwebrtc/infra/specs/tryserver.webrtc.json
@@ -369,6 +369,31 @@
"test_id_prefix": "ninja://rtc_tools:tools_unittests/"
},
{
+ "args": [
+ "--webrtc_quick_perf_test",
+ "--nologs"
+ ],
+ "merge": {
+ "script": "//testing/merge_scripts/standard_gtest_merge.py"
+ },
+ "name": "video_codec_perf_tests",
+ "resultdb": {
+ "enable": true,
+ "has_native_resultdb_integration": true
+ },
+ "swarming": {
+ "dimensions": {
+ "android_devices": "1",
+ "device_type": "walleye",
+ "os": "Android"
+ },
+ "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+ "shards": 2
+ },
+ "test": "video_codec_perf_tests",
+ "test_id_prefix": "ninja://modules/video_coding:video_codec_perf_tests/"
+ },
+ {
"merge": {
"script": "//testing/merge_scripts/standard_gtest_merge.py"
},
@@ -846,6 +871,31 @@
"test_id_prefix": "ninja://rtc_tools:tools_unittests/"
},
{
+ "args": [
+ "--webrtc_quick_perf_test",
+ "--nologs"
+ ],
+ "merge": {
+ "script": "//testing/merge_scripts/standard_gtest_merge.py"
+ },
+ "name": "video_codec_perf_tests",
+ "resultdb": {
+ "enable": true,
+ "has_native_resultdb_integration": true
+ },
+ "swarming": {
+ "dimensions": {
+ "android_devices": "1",
+ "device_type": "walleye",
+ "os": "Android"
+ },
+ "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+ "shards": 2
+ },
+ "test": "video_codec_perf_tests",
+ "test_id_prefix": "ninja://modules/video_coding:video_codec_perf_tests/"
+ },
+ {
"merge": {
"script": "//testing/merge_scripts/standard_gtest_merge.py"
},
@@ -1323,6 +1373,31 @@
"test_id_prefix": "ninja://rtc_tools:tools_unittests/"
},
{
+ "args": [
+ "--webrtc_quick_perf_test",
+ "--nologs"
+ ],
+ "merge": {
+ "script": "//testing/merge_scripts/standard_gtest_merge.py"
+ },
+ "name": "video_codec_perf_tests",
+ "resultdb": {
+ "enable": true,
+ "has_native_resultdb_integration": true
+ },
+ "swarming": {
+ "dimensions": {
+ "android_devices": "1",
+ "device_type": "walleye",
+ "os": "Android"
+ },
+ "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+ "shards": 2
+ },
+ "test": "video_codec_perf_tests",
+ "test_id_prefix": "ninja://modules/video_coding:video_codec_perf_tests/"
+ },
+ {
"merge": {
"script": "//testing/merge_scripts/standard_gtest_merge.py"
},
@@ -1825,6 +1900,31 @@
"test_id_prefix": "ninja://rtc_tools:tools_unittests/"
},
{
+ "args": [
+ "--webrtc_quick_perf_test",
+ "--nologs"
+ ],
+ "merge": {
+ "script": "//testing/merge_scripts/standard_gtest_merge.py"
+ },
+ "name": "video_codec_perf_tests",
+ "resultdb": {
+ "enable": true,
+ "has_native_resultdb_integration": true
+ },
+ "swarming": {
+ "dimensions": {
+ "android_devices": "1",
+ "device_type": "walleye",
+ "os": "Android"
+ },
+ "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+ "shards": 2
+ },
+ "test": "video_codec_perf_tests",
+ "test_id_prefix": "ninja://modules/video_coding:video_codec_perf_tests/"
+ },
+ {
"merge": {
"script": "//testing/merge_scripts/standard_gtest_merge.py"
},
@@ -1962,7 +2062,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "audio_decoder_unittests",
@@ -1986,7 +2086,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_audio_unittests",
@@ -2010,7 +2110,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_video_unittests",
@@ -2034,7 +2134,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "dcsctp_unittests",
@@ -2058,7 +2158,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_media_unittests",
@@ -2082,7 +2182,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_pc_unittests",
@@ -2106,7 +2206,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -2131,7 +2231,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "system_wrappers_unittests",
@@ -2155,7 +2255,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -2180,7 +2280,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "voip_unittests",
@@ -2204,7 +2304,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "webrtc_nonparallel_tests",
@@ -2219,20 +2319,20 @@
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
- "--xcode-parallelization",
+ "--xcodebuild-sim-runner",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "apprtcmobile_tests iPhone X 14.5",
+ "name": "apprtcmobile_tests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -2242,29 +2342,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "apprtcmobile_tests",
"test_id_prefix": "ninja://examples:apprtcmobile_tests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -2275,9 +2374,9 @@
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
- "--xcode-parallelization",
+ "--xcodebuild-sim-runner",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -2292,17 +2391,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -2321,18 +2419,18 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
- "--xcode-parallelization",
+ "--xcodebuild-sim-runner",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "apprtcmobile_tests iPhone X 16.2",
+ "name": "apprtcmobile_tests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -2342,46 +2440,45 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "apprtcmobile_tests",
"test_id_prefix": "ninja://examples:apprtcmobile_tests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "audio_decoder_unittests iPhone X 14.5",
+ "name": "audio_decoder_unittests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -2391,29 +2488,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "audio_decoder_unittests",
"test_id_prefix": "ninja://modules/audio_coding:audio_decoder_unittests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -2425,7 +2521,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -2440,17 +2536,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -2469,17 +2564,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "audio_decoder_unittests iPhone X 16.2",
+ "name": "audio_decoder_unittests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -2489,46 +2584,45 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "audio_decoder_unittests",
"test_id_prefix": "ninja://modules/audio_coding:audio_decoder_unittests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "common_audio_unittests iPhone X 14.5",
+ "name": "common_audio_unittests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -2538,29 +2632,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "common_audio_unittests",
"test_id_prefix": "ninja://common_audio:common_audio_unittests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -2572,7 +2665,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -2587,17 +2680,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -2616,17 +2708,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "common_audio_unittests iPhone X 16.2",
+ "name": "common_audio_unittests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -2636,46 +2728,45 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "common_audio_unittests",
"test_id_prefix": "ninja://common_audio:common_audio_unittests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "common_video_unittests iPhone X 14.5",
+ "name": "common_video_unittests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -2685,29 +2776,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "common_video_unittests",
"test_id_prefix": "ninja://common_video:common_video_unittests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -2719,7 +2809,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -2734,17 +2824,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -2763,17 +2852,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "common_video_unittests iPhone X 16.2",
+ "name": "common_video_unittests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -2783,46 +2872,45 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "common_video_unittests",
"test_id_prefix": "ninja://common_video:common_video_unittests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "dcsctp_unittests iPhone X 14.5",
+ "name": "dcsctp_unittests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -2832,29 +2920,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "dcsctp_unittests",
"test_id_prefix": "ninja://net/dcsctp:dcsctp_unittests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -2866,7 +2953,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -2881,17 +2968,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -2910,17 +2996,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "dcsctp_unittests iPhone X 16.2",
+ "name": "dcsctp_unittests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -2930,46 +3016,45 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "dcsctp_unittests",
"test_id_prefix": "ninja://net/dcsctp:dcsctp_unittests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "modules_tests iPhone X 14.5",
+ "name": "modules_tests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -2979,22 +3064,21 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -3002,7 +3086,7 @@
},
"test": "modules_tests",
"test_id_prefix": "ninja://modules:modules_tests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -3014,7 +3098,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -3029,17 +3113,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -3059,17 +3142,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "modules_tests iPhone X 16.2",
+ "name": "modules_tests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -3079,22 +3162,21 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -3102,24 +3184,24 @@
},
"test": "modules_tests",
"test_id_prefix": "ninja://modules:modules_tests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "modules_unittests iPhone X 14.5",
+ "name": "modules_unittests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -3129,23 +3211,22 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cores": "12",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -3153,7 +3234,7 @@
},
"test": "modules_unittests",
"test_id_prefix": "ninja://modules:modules_unittests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -3165,7 +3246,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -3180,18 +3261,17 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cores": "12",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -3211,17 +3291,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "modules_unittests iPhone X 16.2",
+ "name": "modules_unittests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -3231,23 +3311,22 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cores": "12",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -3255,24 +3334,24 @@
},
"test": "modules_unittests",
"test_id_prefix": "ninja://modules:modules_unittests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "rtc_media_unittests iPhone X 14.5",
+ "name": "rtc_media_unittests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -3282,29 +3361,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "rtc_media_unittests",
"test_id_prefix": "ninja://media:rtc_media_unittests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -3316,7 +3394,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -3331,17 +3409,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -3360,17 +3437,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "rtc_media_unittests iPhone X 16.2",
+ "name": "rtc_media_unittests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -3380,46 +3457,45 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "rtc_media_unittests",
"test_id_prefix": "ninja://media:rtc_media_unittests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "rtc_pc_unittests iPhone X 14.5",
+ "name": "rtc_pc_unittests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -3429,29 +3505,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "rtc_pc_unittests",
"test_id_prefix": "ninja://pc:rtc_pc_unittests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -3463,7 +3538,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -3478,17 +3553,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -3507,17 +3581,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "rtc_pc_unittests iPhone X 16.2",
+ "name": "rtc_pc_unittests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -3527,46 +3601,45 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "rtc_pc_unittests",
"test_id_prefix": "ninja://pc:rtc_pc_unittests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "rtc_stats_unittests iPhone X 14.5",
+ "name": "rtc_stats_unittests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -3576,29 +3649,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "rtc_stats_unittests",
"test_id_prefix": "ninja://stats:rtc_stats_unittests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -3610,7 +3682,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -3625,17 +3697,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -3654,17 +3725,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "rtc_stats_unittests iPhone X 16.2",
+ "name": "rtc_stats_unittests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -3674,46 +3745,45 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "rtc_stats_unittests",
"test_id_prefix": "ninja://stats:rtc_stats_unittests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "rtc_unittests iPhone X 14.5",
+ "name": "rtc_unittests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -3723,22 +3793,21 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -3746,7 +3815,7 @@
},
"test": "rtc_unittests",
"test_id_prefix": "ninja://:rtc_unittests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -3758,7 +3827,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -3773,17 +3842,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -3803,17 +3871,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "rtc_unittests iPhone X 16.2",
+ "name": "rtc_unittests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -3823,22 +3891,21 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -3846,25 +3913,25 @@
},
"test": "rtc_unittests",
"test_id_prefix": "ninja://:rtc_unittests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
- "--xcode-parallelization",
+ "--xcodebuild-sim-runner",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "sdk_framework_unittests iPhone X 14.5",
+ "name": "sdk_framework_unittests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -3874,29 +3941,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "sdk_framework_unittests",
"test_id_prefix": "ninja://sdk:sdk_framework_unittests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -3907,9 +3973,9 @@
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
- "--xcode-parallelization",
+ "--xcodebuild-sim-runner",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -3924,17 +3990,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -3953,18 +4018,18 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
- "--xcode-parallelization",
+ "--xcodebuild-sim-runner",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "sdk_framework_unittests iPhone X 16.2",
+ "name": "sdk_framework_unittests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -3974,47 +4039,46 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "sdk_framework_unittests",
"test_id_prefix": "ninja://sdk:sdk_framework_unittests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
- "--xcode-parallelization",
+ "--xcodebuild-sim-runner",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "sdk_unittests iPhone X 14.5",
+ "name": "sdk_unittests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -4024,29 +4088,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "sdk_unittests",
"test_id_prefix": "ninja://sdk:sdk_unittests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -4057,9 +4120,9 @@
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
- "--xcode-parallelization",
+ "--xcodebuild-sim-runner",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -4074,17 +4137,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -4103,18 +4165,18 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
- "--xcode-parallelization",
+ "--xcodebuild-sim-runner",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "sdk_unittests iPhone X 16.2",
+ "name": "sdk_unittests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -4124,46 +4186,45 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "sdk_unittests",
"test_id_prefix": "ninja://sdk:sdk_unittests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "svc_tests iPhone X 14.5",
+ "name": "svc_tests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -4173,23 +4234,22 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cores": "12",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -4197,7 +4257,7 @@
},
"test": "svc_tests",
"test_id_prefix": "ninja://pc:svc_tests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -4209,7 +4269,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -4224,18 +4284,17 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cores": "12",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -4255,17 +4314,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "svc_tests iPhone X 16.2",
+ "name": "svc_tests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -4275,23 +4334,22 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cores": "12",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -4299,24 +4357,24 @@
},
"test": "svc_tests",
"test_id_prefix": "ninja://pc:svc_tests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "system_wrappers_unittests iPhone X 14.5",
+ "name": "system_wrappers_unittests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -4326,29 +4384,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "system_wrappers_unittests",
"test_id_prefix": "ninja://system_wrappers:system_wrappers_unittests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -4360,7 +4417,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -4375,17 +4432,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -4404,17 +4460,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "system_wrappers_unittests iPhone X 16.2",
+ "name": "system_wrappers_unittests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -4424,46 +4480,45 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "system_wrappers_unittests",
"test_id_prefix": "ninja://system_wrappers:system_wrappers_unittests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "test_support_unittests iPhone X 14.5",
+ "name": "test_support_unittests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -4473,29 +4528,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "test_support_unittests",
"test_id_prefix": "ninja://test:test_support_unittests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -4507,7 +4561,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -4522,17 +4576,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -4551,17 +4604,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "test_support_unittests iPhone X 16.2",
+ "name": "test_support_unittests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -4571,46 +4624,45 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "test_support_unittests",
"test_id_prefix": "ninja://test:test_support_unittests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "tools_unittests iPhone X 14.5",
+ "name": "tools_unittests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -4620,29 +4672,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "tools_unittests",
"test_id_prefix": "ninja://rtc_tools:tools_unittests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -4654,7 +4705,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -4669,17 +4720,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -4698,17 +4748,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "tools_unittests iPhone X 16.2",
+ "name": "tools_unittests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -4718,144 +4768,45 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "tools_unittests",
"test_id_prefix": "ninja://rtc_tools:tools_unittests/",
- "variant_id": "iPhone X 16.2"
- },
- {
- "args": [
- "--platform",
- "iPhone X",
- "--version",
- "14.5",
- "--out-dir",
- "${ISOLATED_OUTDIR}",
- "--xctest",
- "--xcode-build-version",
- "13c100"
- ],
- "merge": {
- "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
- },
- "name": "video_capture_tests iPhone X 14.5",
- "resultdb": {
- "enable": true,
- "has_native_resultdb_integration": true
- },
- "swarming": {
- "cipd_packages": [
- {
- "cipd_package": "infra/tools/mac_toolchain/${platform}",
- "location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
- }
- ],
- "dimensions": {
- "caches": "xcode_ios_13c100",
- "cpu": "x86-64",
- "os": "Mac-12"
- },
- "named_caches": [
- {
- "name": "xcode_ios_13c100",
- "path": "Xcode.app"
- },
- {
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
- }
- ],
- "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
- },
- "test": "video_capture_tests",
- "test_id_prefix": "ninja://modules/video_capture:video_capture_tests/",
- "variant_id": "iPhone X 14.5"
- },
- {
- "args": [
- "--platform",
- "iPhone X",
- "--version",
- "15.5",
- "--out-dir",
- "${ISOLATED_OUTDIR}",
- "--xctest",
- "--xcode-build-version",
- "14c18"
- ],
- "merge": {
- "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
- },
- "name": "video_capture_tests iPhone X 15.5",
- "resultdb": {
- "enable": true,
- "has_native_resultdb_integration": true
- },
- "swarming": {
- "cipd_packages": [
- {
- "cipd_package": "infra/tools/mac_toolchain/${platform}",
- "location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
- }
- ],
- "dimensions": {
- "caches": "xcode_ios_14c18",
- "cpu": "x86-64",
- "os": "Mac-12"
- },
- "named_caches": [
- {
- "name": "xcode_ios_14c18",
- "path": "Xcode.app"
- },
- {
- "name": "runtime_ios_15_5",
- "path": "Runtime-ios-15.5"
- }
- ],
- "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
- },
- "test": "video_capture_tests",
- "test_id_prefix": "ninja://modules/video_capture:video_capture_tests/",
- "variant_id": "iPhone X 15.5"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "16.2",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "video_capture_tests iPhone X 16.2",
+ "name": "video_engine_tests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -4865,71 +4816,21 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
- }
- ],
- "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
- },
- "test": "video_capture_tests",
- "test_id_prefix": "ninja://modules/video_capture:video_capture_tests/",
- "variant_id": "iPhone X 16.2"
- },
- {
- "args": [
- "--platform",
- "iPhone X",
- "--version",
- "14.5",
- "--out-dir",
- "${ISOLATED_OUTDIR}",
- "--xctest",
- "--xcode-build-version",
- "13c100"
- ],
- "merge": {
- "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
- },
- "name": "video_engine_tests iPhone X 14.5",
- "resultdb": {
- "enable": true,
- "has_native_resultdb_integration": true
- },
- "swarming": {
- "cipd_packages": [
- {
- "cipd_package": "infra/tools/mac_toolchain/${platform}",
- "location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
- }
- ],
- "dimensions": {
- "caches": "xcode_ios_13c100",
- "cpu": "x86-64",
- "os": "Mac-12"
- },
- "named_caches": [
- {
- "name": "xcode_ios_13c100",
- "path": "Xcode.app"
- },
- {
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -4937,7 +4838,7 @@
},
"test": "video_engine_tests",
"test_id_prefix": "ninja://:video_engine_tests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -4949,7 +4850,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -4964,17 +4865,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -4994,17 +4894,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "video_engine_tests iPhone X 16.2",
+ "name": "video_engine_tests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -5014,22 +4914,21 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -5037,24 +4936,24 @@
},
"test": "video_engine_tests",
"test_id_prefix": "ninja://:video_engine_tests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "voip_unittests iPhone X 14.5",
+ "name": "voip_unittests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -5064,29 +4963,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "voip_unittests",
"test_id_prefix": "ninja://:voip_unittests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -5098,7 +4996,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -5113,17 +5011,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -5142,17 +5039,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "voip_unittests iPhone X 16.2",
+ "name": "voip_unittests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -5162,46 +5059,45 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "voip_unittests",
"test_id_prefix": "ninja://:voip_unittests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
},
{
"args": [
"--platform",
- "iPhone X",
+ "iPhone 14",
"--version",
- "14.5",
+ "17.0",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "13c100"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "webrtc_nonparallel_tests iPhone X 14.5",
+ "name": "webrtc_nonparallel_tests iPhone 14 17.0",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -5211,29 +5107,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_13c100",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_13c100",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_14_5",
- "path": "Runtime-ios-14.5"
+ "name": "runtime_ios_17_0",
+ "path": "Runtime-ios-17.0"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "webrtc_nonparallel_tests",
"test_id_prefix": "ninja://:webrtc_nonparallel_tests/",
- "variant_id": "iPhone X 14.5"
+ "variant_id": "iPhone 14 17.0"
},
{
"args": [
@@ -5245,7 +5140,7 @@
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -5260,17 +5155,16 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
@@ -5289,17 +5183,17 @@
"--platform",
"iPhone X",
"--version",
- "16.2",
+ "16.4",
"--out-dir",
"${ISOLATED_OUTDIR}",
"--xctest",
"--xcode-build-version",
- "14c18"
+ "15a507"
],
"merge": {
"script": "//testing/merge_scripts/standard_isolated_script_merge.py"
},
- "name": "webrtc_nonparallel_tests iPhone X 16.2",
+ "name": "webrtc_nonparallel_tests iPhone X 16.4",
"resultdb": {
"enable": true,
"has_native_resultdb_integration": true
@@ -5309,29 +5203,28 @@
{
"cipd_package": "infra/tools/mac_toolchain/${platform}",
"location": ".",
- "revision": "git_revision:32d81d877ee07af07bf03b7f70ce597e323b80ce"
+ "revision": "git_revision:b28cf90d462a7bbd45c28f2d931960c2b9404cb0"
}
],
"dimensions": {
- "caches": "xcode_ios_14c18",
"cpu": "x86-64",
- "os": "Mac-12"
+ "os": "Mac-13"
},
"named_caches": [
{
- "name": "xcode_ios_14c18",
+ "name": "xcode_ios_15a507",
"path": "Xcode.app"
},
{
- "name": "runtime_ios_16_2",
- "path": "Runtime-ios-16.2"
+ "name": "runtime_ios_16_4",
+ "path": "Runtime-ios-16.4"
}
],
"service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
},
"test": "webrtc_nonparallel_tests",
"test_id_prefix": "ninja://:webrtc_nonparallel_tests/",
- "variant_id": "iPhone X 16.2"
+ "variant_id": "iPhone X 16.4"
}
]
},
@@ -5348,7 +5241,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "audio_decoder_unittests",
@@ -5365,7 +5258,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_audio_unittests",
@@ -5382,7 +5275,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_video_unittests",
@@ -5399,7 +5292,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "dcsctp_unittests",
@@ -5416,7 +5309,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 2
},
@@ -5434,7 +5327,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -5452,7 +5345,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -5470,7 +5363,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_media_unittests",
@@ -5487,7 +5380,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_pc_unittests",
@@ -5504,7 +5397,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_stats_unittests",
@@ -5521,7 +5414,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -5539,7 +5432,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "shared_screencast_stream_test",
@@ -5556,7 +5449,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "slow_peer_connection_unittests",
@@ -5573,7 +5466,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -5591,7 +5484,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "system_wrappers_unittests",
@@ -5608,7 +5501,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "test_support_unittests",
@@ -5625,7 +5518,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "tools_unittests",
@@ -5642,7 +5535,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -5660,7 +5553,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "voip_unittests",
@@ -5677,7 +5570,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "webrtc_nonparallel_tests",
@@ -5705,7 +5598,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "audio_decoder_unittests",
@@ -5723,7 +5616,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_audio_unittests",
@@ -5741,7 +5634,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_video_unittests",
@@ -5759,7 +5652,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "dcsctp_unittests",
@@ -5777,7 +5670,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 2
},
@@ -5796,7 +5689,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -5815,7 +5708,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -5834,7 +5727,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_media_unittests",
@@ -5852,7 +5745,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_pc_unittests",
@@ -5870,7 +5763,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_stats_unittests",
@@ -5888,7 +5781,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -5907,7 +5800,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "shared_screencast_stream_test",
@@ -5925,7 +5818,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "slow_peer_connection_unittests",
@@ -5943,7 +5836,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -5962,7 +5855,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "system_wrappers_unittests",
@@ -5980,7 +5873,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "test_support_unittests",
@@ -5998,32 +5891,13 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "tools_unittests",
"test_id_prefix": "ninja://rtc_tools:tools_unittests/"
},
{
- "isolate_profile_data": true,
- "merge": {
- "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
- },
- "name": "video_capture_tests",
- "resultdb": {
- "result_format": "json"
- },
- "swarming": {
- "dimensions": {
- "cpu": "x86-64",
- "os": "Ubuntu-18.04",
- "pool": "WebRTC-baremetal-try"
- }
- },
- "test": "video_capture_tests",
- "test_id_prefix": "ninja://modules/video_capture:video_capture_tests/"
- },
- {
"args": [
"--webrtc_quick_perf_test",
"--nologs",
@@ -6041,7 +5915,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "video_codec_perf_tests",
@@ -6059,7 +5933,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -6078,7 +5952,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "voip_unittests",
@@ -6096,7 +5970,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "webrtc_nonparallel_tests",
@@ -6120,7 +5994,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "webrtc_perf_tests",
@@ -6141,7 +6015,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "audio_decoder_unittests",
@@ -6158,7 +6032,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_audio_unittests",
@@ -6175,7 +6049,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_video_unittests",
@@ -6192,7 +6066,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "dcsctp_unittests",
@@ -6209,7 +6083,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 2
},
@@ -6227,7 +6101,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -6245,7 +6119,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -6263,7 +6137,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_media_unittests",
@@ -6280,7 +6154,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_pc_unittests",
@@ -6297,7 +6171,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_stats_unittests",
@@ -6314,7 +6188,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -6332,7 +6206,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "shared_screencast_stream_test",
@@ -6349,7 +6223,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "slow_peer_connection_unittests",
@@ -6366,7 +6240,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -6384,7 +6258,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "system_wrappers_unittests",
@@ -6401,7 +6275,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "test_support_unittests",
@@ -6418,7 +6292,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "tools_unittests",
@@ -6435,7 +6309,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -6453,7 +6327,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "voip_unittests",
@@ -6470,7 +6344,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "webrtc_nonparallel_tests",
@@ -6492,7 +6366,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "audio_decoder_unittests",
@@ -6509,7 +6383,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_audio_unittests",
@@ -6526,7 +6400,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_video_unittests",
@@ -6543,7 +6417,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "dcsctp_unittests",
@@ -6560,7 +6434,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 2
},
@@ -6578,7 +6452,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -6596,7 +6470,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -6614,7 +6488,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_media_unittests",
@@ -6631,7 +6505,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_pc_unittests",
@@ -6648,7 +6522,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_stats_unittests",
@@ -6665,7 +6539,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -6683,7 +6557,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "shared_screencast_stream_test",
@@ -6700,7 +6574,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "slow_peer_connection_unittests",
@@ -6717,7 +6591,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -6735,7 +6609,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "system_wrappers_unittests",
@@ -6752,7 +6626,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "test_support_unittests",
@@ -6769,7 +6643,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "tools_unittests",
@@ -6786,7 +6660,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -6804,7 +6678,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "voip_unittests",
@@ -6821,7 +6695,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "webrtc_nonparallel_tests",
@@ -6842,7 +6716,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -7197,7 +7071,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "audio_decoder_unittests",
@@ -7214,7 +7088,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_audio_unittests",
@@ -7231,7 +7105,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_video_unittests",
@@ -7248,7 +7122,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "dcsctp_unittests",
@@ -7265,7 +7139,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 2
},
@@ -7283,7 +7157,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -7301,7 +7175,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -7319,7 +7193,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_media_unittests",
@@ -7336,7 +7210,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_pc_unittests",
@@ -7353,7 +7227,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_stats_unittests",
@@ -7370,7 +7244,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -7388,7 +7262,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "shared_screencast_stream_test",
@@ -7405,7 +7279,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "slow_peer_connection_unittests",
@@ -7422,7 +7296,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -7440,7 +7314,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "system_wrappers_unittests",
@@ -7457,7 +7331,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "test_support_unittests",
@@ -7474,31 +7348,13 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "tools_unittests",
"test_id_prefix": "ninja://rtc_tools:tools_unittests/"
},
{
- "merge": {
- "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
- },
- "name": "video_capture_tests",
- "resultdb": {
- "result_format": "json"
- },
- "swarming": {
- "dimensions": {
- "cpu": "x86-64",
- "os": "Ubuntu-18.04",
- "pool": "WebRTC-baremetal-try"
- }
- },
- "test": "video_capture_tests",
- "test_id_prefix": "ninja://modules/video_capture:video_capture_tests/"
- },
- {
"args": [
"--webrtc_quick_perf_test",
"--nologs",
@@ -7515,7 +7371,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "video_codec_perf_tests",
@@ -7532,7 +7388,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -7550,7 +7406,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "voip_unittests",
@@ -7567,7 +7423,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "webrtc_nonparallel_tests",
@@ -7590,7 +7446,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "webrtc_perf_tests",
@@ -7611,7 +7467,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "audio_decoder_unittests",
@@ -7628,7 +7484,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_audio_unittests",
@@ -7645,7 +7501,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_video_unittests",
@@ -7662,7 +7518,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "dcsctp_unittests",
@@ -7679,7 +7535,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 2
},
@@ -7697,7 +7553,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -7715,7 +7571,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -7733,7 +7589,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_media_unittests",
@@ -7750,7 +7606,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_pc_unittests",
@@ -7767,7 +7623,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_stats_unittests",
@@ -7784,7 +7640,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -7802,7 +7658,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "slow_peer_connection_unittests",
@@ -7819,7 +7675,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -7837,7 +7693,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "system_wrappers_unittests",
@@ -7854,7 +7710,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "test_support_unittests",
@@ -7871,7 +7727,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "tools_unittests",
@@ -7888,7 +7744,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -7906,7 +7762,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "voip_unittests",
@@ -7923,7 +7779,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "webrtc_nonparallel_tests",
@@ -7944,7 +7800,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "audio_decoder_unittests",
@@ -7961,7 +7817,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_audio_unittests",
@@ -7978,7 +7834,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_video_unittests",
@@ -7995,7 +7851,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "dcsctp_unittests",
@@ -8012,7 +7868,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 2
},
@@ -8030,7 +7886,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -8048,7 +7904,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -8066,7 +7922,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_media_unittests",
@@ -8083,7 +7939,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_pc_unittests",
@@ -8100,7 +7956,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_stats_unittests",
@@ -8117,7 +7973,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -8135,7 +7991,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "shared_screencast_stream_test",
@@ -8152,7 +8008,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "slow_peer_connection_unittests",
@@ -8169,7 +8025,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -8187,7 +8043,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "system_wrappers_unittests",
@@ -8204,7 +8060,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "test_support_unittests",
@@ -8221,7 +8077,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "tools_unittests",
@@ -8238,7 +8094,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -8256,7 +8112,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "voip_unittests",
@@ -8273,7 +8129,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "webrtc_nonparallel_tests",
@@ -8294,7 +8150,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "audio_decoder_unittests",
@@ -8311,7 +8167,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_audio_unittests",
@@ -8328,7 +8184,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_video_unittests",
@@ -8345,7 +8201,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "dcsctp_unittests",
@@ -8362,7 +8218,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 2
},
@@ -8380,7 +8236,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -8398,7 +8254,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -8416,7 +8272,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_media_unittests",
@@ -8433,7 +8289,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_pc_unittests",
@@ -8450,7 +8306,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_stats_unittests",
@@ -8467,7 +8323,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -8485,7 +8341,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "shared_screencast_stream_test",
@@ -8502,7 +8358,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "slow_peer_connection_unittests",
@@ -8519,7 +8375,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -8537,7 +8393,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "system_wrappers_unittests",
@@ -8554,7 +8410,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "test_support_unittests",
@@ -8571,7 +8427,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "tools_unittests",
@@ -8588,7 +8444,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -8606,7 +8462,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "voip_unittests",
@@ -8623,7 +8479,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "webrtc_nonparallel_tests",
@@ -8644,7 +8500,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "audio_decoder_unittests",
@@ -8661,7 +8517,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_audio_unittests",
@@ -8678,7 +8534,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_video_unittests",
@@ -8695,7 +8551,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "dcsctp_unittests",
@@ -8712,7 +8568,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 2
},
@@ -8730,7 +8586,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -8748,7 +8604,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -8766,7 +8622,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_media_unittests",
@@ -8783,7 +8639,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_pc_unittests",
@@ -8800,7 +8656,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_stats_unittests",
@@ -8817,7 +8673,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -8835,7 +8691,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "slow_peer_connection_unittests",
@@ -8852,7 +8708,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -8870,7 +8726,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "system_wrappers_unittests",
@@ -8887,7 +8743,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "test_support_unittests",
@@ -8904,7 +8760,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "tools_unittests",
@@ -8921,7 +8777,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -8939,7 +8795,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "voip_unittests",
@@ -8956,7 +8812,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "webrtc_nonparallel_tests",
@@ -8977,7 +8833,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "audio_decoder_unittests",
@@ -8994,7 +8850,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_audio_unittests",
@@ -9011,7 +8867,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "common_video_unittests",
@@ -9028,7 +8884,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "dcsctp_unittests",
@@ -9045,7 +8901,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 2
},
@@ -9063,7 +8919,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -9081,7 +8937,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -9099,7 +8955,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_media_unittests",
@@ -9116,7 +8972,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_pc_unittests",
@@ -9133,7 +8989,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "rtc_stats_unittests",
@@ -9150,7 +9006,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 6
},
@@ -9168,7 +9024,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "slow_peer_connection_unittests",
@@ -9185,7 +9041,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -9203,7 +9059,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "system_wrappers_unittests",
@@ -9220,7 +9076,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "test_support_unittests",
@@ -9237,7 +9093,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "tools_unittests",
@@ -9254,7 +9110,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
},
"shards": 4
},
@@ -9272,7 +9128,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "voip_unittests",
@@ -9289,7 +9145,7 @@
"swarming": {
"dimensions": {
"cpu": "x86-64",
- "os": "Ubuntu-18.04"
+ "os": "Ubuntu-20.04"
}
},
"test": "webrtc_nonparallel_tests",
@@ -10616,24 +10472,6 @@
"test_id_prefix": "ninja://rtc_tools:tools_unittests/"
},
{
- "merge": {
- "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
- },
- "name": "video_capture_tests",
- "resultdb": {
- "result_format": "json"
- },
- "swarming": {
- "dimensions": {
- "cpu": "x86-64",
- "os": "Mac-12",
- "pool": "WebRTC-baremetal-try"
- }
- },
- "test": "video_capture_tests",
- "test_id_prefix": "ninja://modules/video_capture:video_capture_tests/"
- },
- {
"args": [
"--webrtc_quick_perf_test",
"--nologs",
@@ -11066,6 +10904,718 @@
}
]
},
+ "win11_debug": {
+ "isolated_scripts": [
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "audio_decoder_unittests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ }
+ },
+ "test": "audio_decoder_unittests",
+ "test_id_prefix": "ninja://modules/audio_coding:audio_decoder_unittests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "common_audio_unittests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ }
+ },
+ "test": "common_audio_unittests",
+ "test_id_prefix": "ninja://common_audio:common_audio_unittests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "common_video_unittests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ }
+ },
+ "test": "common_video_unittests",
+ "test_id_prefix": "ninja://common_video:common_video_unittests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "dcsctp_unittests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ }
+ },
+ "test": "dcsctp_unittests",
+ "test_id_prefix": "ninja://net/dcsctp:dcsctp_unittests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "modules_tests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ },
+ "shards": 2
+ },
+ "test": "modules_tests",
+ "test_id_prefix": "ninja://modules:modules_tests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "modules_unittests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ },
+ "shards": 6
+ },
+ "test": "modules_unittests",
+ "test_id_prefix": "ninja://modules:modules_unittests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "peerconnection_unittests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ },
+ "shards": 4
+ },
+ "test": "peerconnection_unittests",
+ "test_id_prefix": "ninja://pc:peerconnection_unittests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "rtc_media_unittests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ }
+ },
+ "test": "rtc_media_unittests",
+ "test_id_prefix": "ninja://media:rtc_media_unittests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "rtc_pc_unittests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ }
+ },
+ "test": "rtc_pc_unittests",
+ "test_id_prefix": "ninja://pc:rtc_pc_unittests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "rtc_stats_unittests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ }
+ },
+ "test": "rtc_stats_unittests",
+ "test_id_prefix": "ninja://stats:rtc_stats_unittests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "rtc_unittests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ },
+ "shards": 6
+ },
+ "test": "rtc_unittests",
+ "test_id_prefix": "ninja://:rtc_unittests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "slow_peer_connection_unittests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ }
+ },
+ "test": "slow_peer_connection_unittests",
+ "test_id_prefix": "ninja://pc:slow_peer_connection_unittests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "svc_tests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ },
+ "shards": 4
+ },
+ "test": "svc_tests",
+ "test_id_prefix": "ninja://pc:svc_tests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "system_wrappers_unittests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ }
+ },
+ "test": "system_wrappers_unittests",
+ "test_id_prefix": "ninja://system_wrappers:system_wrappers_unittests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "test_support_unittests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ }
+ },
+ "test": "test_support_unittests",
+ "test_id_prefix": "ninja://test:test_support_unittests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "tools_unittests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ }
+ },
+ "test": "tools_unittests",
+ "test_id_prefix": "ninja://rtc_tools:tools_unittests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "video_engine_tests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ },
+ "shards": 4
+ },
+ "test": "video_engine_tests",
+ "test_id_prefix": "ninja://:video_engine_tests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "voip_unittests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ }
+ },
+ "test": "voip_unittests",
+ "test_id_prefix": "ninja://:voip_unittests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "webrtc_nonparallel_tests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ }
+ },
+ "test": "webrtc_nonparallel_tests",
+ "test_id_prefix": "ninja://:webrtc_nonparallel_tests/"
+ }
+ ]
+ },
+ "win11_release": {
+ "isolated_scripts": [
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "audio_decoder_unittests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ }
+ },
+ "test": "audio_decoder_unittests",
+ "test_id_prefix": "ninja://modules/audio_coding:audio_decoder_unittests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "common_audio_unittests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ }
+ },
+ "test": "common_audio_unittests",
+ "test_id_prefix": "ninja://common_audio:common_audio_unittests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "common_video_unittests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ }
+ },
+ "test": "common_video_unittests",
+ "test_id_prefix": "ninja://common_video:common_video_unittests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "dcsctp_unittests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ }
+ },
+ "test": "dcsctp_unittests",
+ "test_id_prefix": "ninja://net/dcsctp:dcsctp_unittests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "modules_tests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ },
+ "shards": 2
+ },
+ "test": "modules_tests",
+ "test_id_prefix": "ninja://modules:modules_tests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "modules_unittests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ },
+ "shards": 6
+ },
+ "test": "modules_unittests",
+ "test_id_prefix": "ninja://modules:modules_unittests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "peerconnection_unittests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ },
+ "shards": 4
+ },
+ "test": "peerconnection_unittests",
+ "test_id_prefix": "ninja://pc:peerconnection_unittests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "rtc_media_unittests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ }
+ },
+ "test": "rtc_media_unittests",
+ "test_id_prefix": "ninja://media:rtc_media_unittests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "rtc_pc_unittests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ }
+ },
+ "test": "rtc_pc_unittests",
+ "test_id_prefix": "ninja://pc:rtc_pc_unittests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "rtc_stats_unittests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ }
+ },
+ "test": "rtc_stats_unittests",
+ "test_id_prefix": "ninja://stats:rtc_stats_unittests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "rtc_unittests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ },
+ "shards": 6
+ },
+ "test": "rtc_unittests",
+ "test_id_prefix": "ninja://:rtc_unittests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "slow_peer_connection_unittests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ }
+ },
+ "test": "slow_peer_connection_unittests",
+ "test_id_prefix": "ninja://pc:slow_peer_connection_unittests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "svc_tests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ },
+ "shards": 4
+ },
+ "test": "svc_tests",
+ "test_id_prefix": "ninja://pc:svc_tests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "system_wrappers_unittests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ }
+ },
+ "test": "system_wrappers_unittests",
+ "test_id_prefix": "ninja://system_wrappers:system_wrappers_unittests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "test_support_unittests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ }
+ },
+ "test": "test_support_unittests",
+ "test_id_prefix": "ninja://test:test_support_unittests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "tools_unittests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ }
+ },
+ "test": "tools_unittests",
+ "test_id_prefix": "ninja://rtc_tools:tools_unittests/"
+ },
+ {
+ "args": [
+ "--webrtc_quick_perf_test",
+ "--nologs",
+ "--gtest_output=json:${ISOLATED_OUTDIR}/gtest_output.json"
+ ],
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "video_codec_perf_tests",
+ "resultdb": {
+ "result_file": "${ISOLATED_OUTDIR}/gtest_output.json",
+ "result_format": "gtest_json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ }
+ },
+ "test": "video_codec_perf_tests",
+ "test_id_prefix": "ninja://modules/video_coding:video_codec_perf_tests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "video_engine_tests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ },
+ "shards": 4
+ },
+ "test": "video_engine_tests",
+ "test_id_prefix": "ninja://:video_engine_tests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "voip_unittests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ }
+ },
+ "test": "voip_unittests",
+ "test_id_prefix": "ninja://:voip_unittests/"
+ },
+ {
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "webrtc_nonparallel_tests",
+ "resultdb": {
+ "result_format": "json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ }
+ },
+ "test": "webrtc_nonparallel_tests",
+ "test_id_prefix": "ninja://:webrtc_nonparallel_tests/"
+ },
+ {
+ "args": [
+ "--webrtc_quick_perf_test",
+ "--nologs",
+ "--gtest_output=json:${ISOLATED_OUTDIR}/gtest_output.json"
+ ],
+ "merge": {
+ "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+ },
+ "name": "webrtc_perf_tests",
+ "resultdb": {
+ "result_file": "${ISOLATED_OUTDIR}/gtest_output.json",
+ "result_format": "gtest_json"
+ },
+ "swarming": {
+ "dimensions": {
+ "cpu": "x86-64",
+ "os": "Windows-11-22000"
+ }
+ },
+ "test": "webrtc_perf_tests",
+ "test_id_prefix": "ninja://:webrtc_perf_tests/"
+ }
+ ]
+ },
"win_asan": {
"isolated_scripts": [
{
@@ -12682,24 +13232,6 @@
"test_id_prefix": "ninja://rtc_tools:tools_unittests/"
},
{
- "merge": {
- "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
- },
- "name": "video_capture_tests",
- "resultdb": {
- "result_format": "json"
- },
- "swarming": {
- "dimensions": {
- "cpu": "x86-64",
- "os": "Windows-10-19045",
- "pool": "WebRTC-baremetal-try"
- }
- },
- "test": "video_capture_tests",
- "test_id_prefix": "ninja://modules/video_capture:video_capture_tests/"
- },
- {
"args": [
"--webrtc_quick_perf_test",
"--nologs",
diff --git a/third_party/libwebrtc/infra/specs/variants.pyl b/third_party/libwebrtc/infra/specs/variants.pyl
index e764f698e3..c97bdc5468 100644
--- a/third_party/libwebrtc/infra/specs/variants.pyl
+++ b/third_party/libwebrtc/infra/specs/variants.pyl
@@ -7,34 +7,34 @@
# be found in the AUTHORS file in the root of the source tree.
{
- 'SIM_IPHONE_X_14_5': {
+ 'SIM_IPHONE_X_15_5': {
'args': [
'--platform',
'iPhone X',
'--version',
- '14.5',
+ '15.5',
],
- 'identifier': 'iPhone X 14.5',
- 'mixins': ['xcode_13_main', 'ios_runtime_cache_14_5'],
+ 'identifier': 'iPhone X 15.5',
+ 'mixins': ['xcode_15_main', 'ios_runtime_cache_15_5'],
},
- 'SIM_IPHONE_X_15_5': {
+ 'SIM_IPHONE_X_16_4': {
'args': [
'--platform',
'iPhone X',
'--version',
- '15.5',
+ '16.4',
],
- 'identifier': 'iPhone X 15.5',
- 'mixins': ['xcode_14_main', 'ios_runtime_cache_15_5'],
+ 'identifier': 'iPhone X 16.4',
+ 'mixins': ['xcode_15_main', 'ios_runtime_cache_16_4'],
},
- 'SIM_IPHONE_X_16_2': {
+ 'SIM_IPHONE_14_17_0': {
'args': [
'--platform',
- 'iPhone X',
+ 'iPhone 14',
'--version',
- '16.2',
+ '17.0',
],
- 'identifier': 'iPhone X 16.2',
- 'mixins': ['xcode_14_main', 'ios_runtime_cache_16_2'],
+ 'identifier': 'iPhone 14 17.0',
+ 'mixins': ['xcode_15_main', 'ios_runtime_cache_17_0'],
},
}
diff --git a/third_party/libwebrtc/infra/specs/waterfalls.pyl b/third_party/libwebrtc/infra/specs/waterfalls.pyl
index 76c1760d1d..3521a9fee3 100644
--- a/third_party/libwebrtc/infra/specs/waterfalls.pyl
+++ b/third_party/libwebrtc/infra/specs/waterfalls.pyl
@@ -70,7 +70,7 @@
'os_type':
'linux',
'mixins': [
- 'linux-bionic', 'x86-64', 'fuchsia-gtest-output',
+ 'linux-focal', 'x86-64', 'fuchsia-gtest-output',
'resultdb-gtest-json-format'
],
'test_suites': {
@@ -79,14 +79,14 @@
},
'Linux (more configs)': {
'os_type': 'linux',
- 'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
+ 'mixins': ['linux-focal', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'more_configs_tests',
},
},
'Linux Asan': {
'os_type': 'linux',
- 'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
+ 'mixins': ['linux-focal', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'linux_tests',
},
@@ -103,7 +103,7 @@
},
'Linux Tsan v2': {
'os_type': 'linux',
- 'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
+ 'mixins': ['linux-focal', 'x86-64', 'resultdb-json-format'],
'test_suites': {
# TODO(crbug.com/webrtc/14568): Using 'linux_tests'
# fails on "ThreadSanitizer: data race on vptr (ctor/dtor vs
@@ -113,21 +113,21 @@
},
'Linux UBSan': {
'os_type': 'linux',
- 'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
+ 'mixins': ['linux-focal', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'linux_tests',
},
},
'Linux UBSan vptr': {
'os_type': 'linux',
- 'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
+ 'mixins': ['linux-focal', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'linux_tests',
},
},
'Linux32 Debug': {
'os_type': 'linux',
- 'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
+ 'mixins': ['linux-focal', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'desktop_tests',
},
@@ -135,7 +135,7 @@
'Linux32 Debug (ARM)': {},
'Linux32 Release': {
'os_type': 'linux',
- 'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
+ 'mixins': ['linux-focal', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'desktop_tests',
},
@@ -144,7 +144,7 @@
'Linux64 Builder': {},
'Linux64 Debug': {
'os_type': 'linux',
- 'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
+ 'mixins': ['linux-focal', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'linux_tests',
},
@@ -152,9 +152,9 @@
'Linux64 Debug (ARM)': {},
'Linux64 Release': {
'os_type': 'linux',
- 'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
+ 'mixins': ['linux-focal', 'x86-64', 'resultdb-json-format'],
'test_suites': {
- 'isolated_scripts': 'linux_desktop_tests_with_video_capture',
+ 'isolated_scripts': 'linux_tests',
},
},
'Linux64 Release (ARM)': {},
@@ -178,14 +178,12 @@
'os_type': 'mac',
'mixins': ['mac_12_x64', 'resultdb-json-format'],
'test_suites': {
- 'isolated_scripts': 'desktop_tests_with_video_capture',
+ 'isolated_scripts': 'desktop_tests',
},
},
'MacARM64 M1 Release': {
'os_type': 'mac',
'mixins': ['mac_12_arm64', 'mac-m1-cpu', 'resultdb-json-format'],
- # TODO(b/228171565): Replace desktop_tests by desktop_tests_with_video_capture when
- # there is a camera available for the baremetal m1 machines.
'test_suites': {
'isolated_scripts': 'desktop_tests',
},
@@ -225,12 +223,12 @@
'os_type': 'win',
'mixins': ['win10', 'x86-64', 'resultdb-json-format'],
'test_suites': {
- 'isolated_scripts': 'desktop_tests_with_video_capture',
+ 'isolated_scripts': 'desktop_tests',
},
},
'iOS Debug (simulator)': {
'mixins': [
- 'mac_12_x64', 'chromium-tester-service-account', 'mac_toolchain',
+ 'mac_13_x64', 'chromium-tester-service-account', 'mac_toolchain',
'has_native_resultdb_integration', 'out_dir_arg', 'webrtc-xctest'
],
'test_suites': {
@@ -422,7 +420,7 @@
'os_type':
'linux',
'mixins': [
- 'linux-bionic', 'x86-64', 'fuchsia-gtest-output',
+ 'linux-focal', 'x86-64', 'fuchsia-gtest-output',
'resultdb-gtest-json-format'
],
'test_suites': {
@@ -433,7 +431,7 @@
'ios_compile_arm64_rel': {},
'ios_dbg_simulator': {
'mixins': [
- 'mac_12_x64', 'chromium-tester-service-account', 'mac_toolchain',
+ 'mac_13_x64', 'chromium-tester-service-account', 'mac_toolchain',
'has_native_resultdb_integration', 'out_dir_arg', 'webrtc-xctest'
],
'test_suites': {
@@ -442,7 +440,7 @@
},
'linux_asan': {
'os_type': 'linux',
- 'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
+ 'mixins': ['linux-focal', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'linux_tests',
},
@@ -457,7 +455,7 @@
'os_type':
'linux',
'mixins': [
- 'linux-bionic', 'x86-64', 'resultdb-json-format',
+ 'linux-focal', 'x86-64', 'resultdb-json-format',
'isolate_profile_data'
],
'test_suites': {
@@ -466,7 +464,7 @@
},
'linux_dbg': {
'os_type': 'linux',
- 'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
+ 'mixins': ['linux-focal', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'linux_tests',
},
@@ -474,14 +472,14 @@
'linux_libfuzzer_rel': {},
'linux_memcheck': {
'os_type': 'linux',
- 'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
+ 'mixins': ['linux-focal', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'linux_tests',
},
},
'linux_more_configs': {
'os_type': 'linux',
- 'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
+ 'mixins': ['linux-focal', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'more_configs_tests',
},
@@ -498,14 +496,14 @@
},
'linux_rel': {
'os_type': 'linux',
- 'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
+ 'mixins': ['linux-focal', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'linux_desktop_tests_tryserver',
},
},
'linux_tsan2': {
'os_type': 'linux',
- 'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
+ 'mixins': ['linux-focal', 'x86-64', 'resultdb-json-format'],
'test_suites': {
# TODO(crbug.com/webrtc/14568): Using 'linux_tests'
# fails on "ThreadSanitizer: data race on vptr (ctor/dtor vs
@@ -515,28 +513,28 @@
},
'linux_ubsan': {
'os_type': 'linux',
- 'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
+ 'mixins': ['linux-focal', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'linux_tests',
},
},
'linux_ubsan_vptr': {
'os_type': 'linux',
- 'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
+ 'mixins': ['linux-focal', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'linux_tests',
},
},
'linux_x86_dbg': {
'os_type': 'linux',
- 'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
+ 'mixins': ['linux-focal', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'desktop_tests',
},
},
'linux_x86_rel': {
'os_type': 'linux',
- 'mixins': ['linux-bionic', 'x86-64', 'resultdb-json-format'],
+ 'mixins': ['linux-focal', 'x86-64', 'resultdb-json-format'],
'test_suites': {
'isolated_scripts': 'desktop_tests',
},
@@ -574,12 +572,24 @@
'mac_rel_m1': {
'os_type': 'mac',
'mixins': ['mac_12_arm64', 'mac-m1-cpu', 'resultdb-json-format'],
- # TODO(b/228171565): Replace desktop_tests by desktop_tests_tryserver when
- # there is a camera available for the baremetal-try m1 machines.
'test_suites': {
'isolated_scripts': 'desktop_tests',
},
},
+ 'win11_debug': {
+ 'os_type': 'win',
+ 'mixins': ['win11', 'x86-64', 'resultdb-json-format'],
+ 'test_suites': {
+ 'isolated_scripts': 'desktop_tests',
+ },
+ },
+ 'win11_release': {
+ 'os_type': 'win',
+ 'mixins': ['win11', 'x86-64', 'resultdb-json-format'],
+ 'test_suites': {
+ 'isolated_scripts': 'desktop_tests_tryserver',
+ },
+ },
'win_asan': {
'os_type': 'win',
'mixins': ['win10', 'x86-64', 'resultdb-json-format'],
diff --git a/third_party/libwebrtc/logging/BUILD.gn b/third_party/libwebrtc/logging/BUILD.gn
index 92f55edfa0..91890553a3 100644
--- a/third_party/libwebrtc/logging/BUILD.gn
+++ b/third_party/libwebrtc/logging/BUILD.gn
@@ -82,6 +82,7 @@ rtc_library("rtc_stream_config") {
}
rtc_library("rtc_event_pacing") {
+ visibility = [ "*" ]
sources = [
"rtc_event_log/events/rtc_event_alr_state.cc",
"rtc_event_log/events/rtc_event_alr_state.h",
@@ -99,6 +100,7 @@ rtc_library("rtc_event_pacing") {
}
rtc_library("rtc_event_audio") {
+ visibility = [ "*" ]
sources = [
"rtc_event_log/events/rtc_event_audio_network_adaptation.cc",
"rtc_event_log/events/rtc_event_audio_network_adaptation.h",
@@ -143,6 +145,7 @@ rtc_library("rtc_event_begin_end") {
}
rtc_library("rtc_event_bwe") {
+ visibility = [ "*" ]
sources = [
"rtc_event_log/events/rtc_event_bwe_update_delay_based.cc",
"rtc_event_log/events/rtc_event_bwe_update_delay_based.h",
@@ -174,6 +177,7 @@ rtc_library("rtc_event_bwe") {
}
rtc_library("rtc_event_frame_events") {
+ visibility = [ "*" ]
sources = [
"rtc_event_log/events/rtc_event_frame_decoded.cc",
"rtc_event_log/events/rtc_event_frame_decoded.h",
@@ -216,6 +220,7 @@ rtc_library("rtc_event_generic_packet_events") {
}
rtc_library("rtc_event_rtp_rtcp") {
+ visibility = [ "*" ]
sources = [
"rtc_event_log/events/logged_rtp_rtcp.h",
"rtc_event_log/events/rtc_event_rtcp_packet_incoming.cc",
@@ -245,6 +250,7 @@ rtc_library("rtc_event_rtp_rtcp") {
}
rtc_library("rtc_event_video") {
+ visibility = [ "*" ]
sources = [
"rtc_event_log/events/rtc_event_video_receive_stream_config.cc",
"rtc_event_log/events/rtc_event_video_receive_stream_config.h",
@@ -450,8 +456,10 @@ if (rtc_enable_protobuf) {
":ice_log",
":rtc_event_log_api",
":rtc_event_log_impl_encoder",
+ "../api:field_trials_view",
"../api:libjingle_logging_api",
"../api:sequence_checker",
+ "../api/environment",
"../api/rtc_event_log",
"../api/task_queue",
"../api/units:time_delta",
@@ -459,7 +467,6 @@ if (rtc_enable_protobuf) {
"../rtc_base:logging",
"../rtc_base:macromagic",
"../rtc_base:rtc_event",
- "../rtc_base:rtc_task_queue",
"../rtc_base:safe_conversions",
"../rtc_base:safe_minmax",
"../rtc_base:timeutils",
@@ -666,6 +673,7 @@ if (rtc_enable_protobuf) {
}
rtc_library("ice_log") {
+ visibility = [ "*" ]
sources = [
"rtc_event_log/events/rtc_event_dtls_transport_state.cc",
"rtc_event_log/events/rtc_event_dtls_transport_state.h",
diff --git a/third_party/libwebrtc/logging/rtc_event_log/rtc_event_log_impl.cc b/third_party/libwebrtc/logging/rtc_event_log/rtc_event_log_impl.cc
index f2b3f22d6a..419afd330a 100644
--- a/third_party/libwebrtc/logging/rtc_event_log/rtc_event_log_impl.cc
+++ b/third_party/libwebrtc/logging/rtc_event_log/rtc_event_log_impl.cc
@@ -17,6 +17,8 @@
#include <vector>
#include "absl/strings/string_view.h"
+#include "api/environment/environment.h"
+#include "api/field_trials_view.h"
#include "api/task_queue/task_queue_base.h"
#include "api/units/time_delta.h"
#include "logging/rtc_event_log/encoder/rtc_event_log_encoder_legacy.h"
@@ -29,24 +31,23 @@
#include "rtc_base/time_utils.h"
namespace webrtc {
+namespace {
-std::unique_ptr<RtcEventLogEncoder> RtcEventLogImpl::CreateEncoder(
- RtcEventLog::EncodingType type) {
- switch (type) {
- case RtcEventLog::EncodingType::Legacy:
- RTC_DLOG(LS_INFO) << "Creating legacy encoder for RTC event log.";
- return std::make_unique<RtcEventLogEncoderLegacy>();
- case RtcEventLog::EncodingType::NewFormat:
- RTC_DLOG(LS_INFO) << "Creating new format encoder for RTC event log.";
- return std::make_unique<RtcEventLogEncoderNewFormat>();
- default:
- RTC_LOG(LS_ERROR) << "Unknown RtcEventLog encoder type (" << int(type)
- << ")";
- RTC_DCHECK_NOTREACHED();
- return std::unique_ptr<RtcEventLogEncoder>(nullptr);
+std::unique_ptr<RtcEventLogEncoder> CreateEncoder(const Environment& env) {
+ if (env.field_trials().IsDisabled("WebRTC-RtcEventLogNewFormat")) {
+ RTC_DLOG(LS_INFO) << "Creating legacy encoder for RTC event log.";
+ return std::make_unique<RtcEventLogEncoderLegacy>();
+ } else {
+ RTC_DLOG(LS_INFO) << "Creating new format encoder for RTC event log.";
+ return std::make_unique<RtcEventLogEncoderNewFormat>();
}
}
+} // namespace
+
+RtcEventLogImpl::RtcEventLogImpl(const Environment& env)
+ : RtcEventLogImpl(CreateEncoder(env), &env.task_queue_factory()) {}
+
RtcEventLogImpl::RtcEventLogImpl(std::unique_ptr<RtcEventLogEncoder> encoder,
TaskQueueFactory* task_queue_factory,
size_t max_events_in_history,
@@ -55,10 +56,9 @@ RtcEventLogImpl::RtcEventLogImpl(std::unique_ptr<RtcEventLogEncoder> encoder,
max_config_events_in_history_(max_config_events_in_history),
event_encoder_(std::move(encoder)),
last_output_ms_(rtc::TimeMillis()),
- task_queue_(
- std::make_unique<rtc::TaskQueue>(task_queue_factory->CreateTaskQueue(
- "rtc_event_log",
- TaskQueueFactory::Priority::NORMAL))) {}
+ task_queue_(task_queue_factory->CreateTaskQueue(
+ "rtc_event_log",
+ TaskQueueFactory::Priority::NORMAL)) {}
RtcEventLogImpl::~RtcEventLogImpl() {
// If we're logging to the output, this will stop that. Blocking function.
@@ -71,10 +71,12 @@ RtcEventLogImpl::~RtcEventLogImpl() {
StopLogging();
}
- // We want to block on any executing task by invoking ~TaskQueue() before
+ // Since we are posting tasks bound to `this`, it is critical that the event
+ // log and its members outlive `task_queue_`. Destruct `task_queue_` first
+ // to ensure tasks living on the queue can access other members.
+ // We want to block on any executing task by deleting TaskQueue before
// we set unique_ptr's internal pointer to null.
- rtc::TaskQueue* tq = task_queue_.get();
- delete tq;
+ task_queue_.get_deleter()(task_queue_.get());
task_queue_.release();
}
diff --git a/third_party/libwebrtc/logging/rtc_event_log/rtc_event_log_impl.h b/third_party/libwebrtc/logging/rtc_event_log/rtc_event_log_impl.h
index 3187a7fe87..2c4ef8d7ed 100644
--- a/third_party/libwebrtc/logging/rtc_event_log/rtc_event_log_impl.h
+++ b/third_party/libwebrtc/logging/rtc_event_log/rtc_event_log_impl.h
@@ -18,14 +18,15 @@
#include <string>
#include "absl/strings/string_view.h"
+#include "api/environment/environment.h"
#include "api/rtc_event_log/rtc_event.h"
#include "api/rtc_event_log/rtc_event_log.h"
#include "api/rtc_event_log_output.h"
#include "api/sequence_checker.h"
+#include "api/task_queue/task_queue_base.h"
#include "api/task_queue/task_queue_factory.h"
#include "logging/rtc_event_log/encoder/rtc_event_log_encoder.h"
#include "rtc_base/system/no_unique_address.h"
-#include "rtc_base/task_queue.h"
#include "rtc_base/thread_annotations.h"
namespace webrtc {
@@ -39,6 +40,7 @@ class RtcEventLogImpl final : public RtcEventLog {
// bound to prevent an attack via unreasonable memory use.
static constexpr size_t kMaxEventsInConfigHistory = 1000;
+ explicit RtcEventLogImpl(const Environment& env);
RtcEventLogImpl(
std::unique_ptr<RtcEventLogEncoder> encoder,
TaskQueueFactory* task_queue_factory,
@@ -49,9 +51,6 @@ class RtcEventLogImpl final : public RtcEventLog {
~RtcEventLogImpl() override;
- static std::unique_ptr<RtcEventLogEncoder> CreateEncoder(
- EncodingType encoding_type);
-
// TODO(eladalon): We should change these name to reflect that what we're
// actually starting/stopping is the output of the log, not the log itself.
bool StartLogging(std::unique_ptr<RtcEventLogOutput> output,
@@ -114,11 +113,7 @@ class RtcEventLogImpl final : public RtcEventLog {
bool immediately_output_mode_ RTC_GUARDED_BY(mutex_) = false;
bool need_schedule_output_ RTC_GUARDED_BY(mutex_) = false;
- // Since we are posting tasks bound to `this`, it is critical that the event
- // log and its members outlive `task_queue_`. Keep the `task_queue_`
- // last to ensure it destructs first, or else tasks living on the queue might
- // access other members after they've been torn down.
- std::unique_ptr<rtc::TaskQueue> task_queue_;
+ std::unique_ptr<TaskQueueBase, TaskQueueDeleter> task_queue_;
Mutex mutex_;
};
diff --git a/third_party/libwebrtc/media/BUILD.gn b/third_party/libwebrtc/media/BUILD.gn
index 055bf75a19..44638d562e 100644
--- a/third_party/libwebrtc/media/BUILD.gn
+++ b/third_party/libwebrtc/media/BUILD.gn
@@ -549,7 +549,6 @@ rtc_library("rtc_audio_video") {
"../rtc_base:macromagic",
"../rtc_base:network_route",
"../rtc_base:race_checker",
- "../rtc_base:rtc_task_queue",
"../rtc_base:safe_conversions",
"../rtc_base:socket",
"../rtc_base:ssl",
diff --git a/third_party/libwebrtc/media/base/codec.cc b/third_party/libwebrtc/media/base/codec.cc
index c4e1c6f1f3..d18baf7132 100644
--- a/third_party/libwebrtc/media/base/codec.cc
+++ b/third_party/libwebrtc/media/base/codec.cc
@@ -15,6 +15,9 @@
#include "api/audio_codecs/audio_format.h"
#include "api/video_codecs/av1_profile.h"
#include "api/video_codecs/h264_profile_level_id.h"
+#ifdef RTC_ENABLE_H265
+#include "api/video_codecs/h265_profile_tier_level.h"
+#endif
#include "api/video_codecs/vp9_profile.h"
#include "media/base/media_constants.h"
#include "rtc_base/checks.h"
@@ -25,7 +28,8 @@
namespace cricket {
namespace {
-std::string GetH264PacketizationModeOrDefault(const CodecParameterMap& params) {
+std::string GetH264PacketizationModeOrDefault(
+ const webrtc::CodecParameterMap& params) {
auto it = params.find(kH264FmtpPacketizationMode);
if (it != params.end()) {
return it->second;
@@ -35,18 +39,36 @@ std::string GetH264PacketizationModeOrDefault(const CodecParameterMap& params) {
return "0";
}
-bool IsSameH264PacketizationMode(const CodecParameterMap& left,
- const CodecParameterMap& right) {
+bool IsSameH264PacketizationMode(const webrtc::CodecParameterMap& left,
+ const webrtc::CodecParameterMap& right) {
return GetH264PacketizationModeOrDefault(left) ==
GetH264PacketizationModeOrDefault(right);
}
+#ifdef RTC_ENABLE_H265
+std::string GetH265TxModeOrDefault(const webrtc::CodecParameterMap& params) {
+ auto it = params.find(kH265FmtpTxMode);
+ if (it != params.end()) {
+ return it->second;
+ }
+ // If TxMode is not present, a value of "SRST" must be inferred.
+ // https://tools.ietf.org/html/rfc7798@section-7.1
+ return "SRST";
+}
+
+bool IsSameH265TxMode(const webrtc::CodecParameterMap& left,
+ const webrtc::CodecParameterMap& right) {
+ return absl::EqualsIgnoreCase(GetH265TxModeOrDefault(left),
+ GetH265TxModeOrDefault(right));
+}
+#endif
+
// Some (video) codecs are actually families of codecs and rely on parameters
// to distinguish different incompatible family members.
bool IsSameCodecSpecific(const std::string& name1,
- const CodecParameterMap& params1,
+ const webrtc::CodecParameterMap& params1,
const std::string& name2,
- const CodecParameterMap& params2) {
+ const webrtc::CodecParameterMap& params2) {
// The names might not necessarily match, so check both.
auto either_name_matches = [&](const std::string name) {
return absl::EqualsIgnoreCase(name, name1) ||
@@ -59,6 +81,12 @@ bool IsSameCodecSpecific(const std::string& name1,
return webrtc::VP9IsSameProfile(params1, params2);
if (either_name_matches(kAv1CodecName))
return webrtc::AV1IsSameProfile(params1, params2);
+#ifdef RTC_ENABLE_H265
+ if (either_name_matches(kH265CodecName)) {
+ return webrtc::H265IsSameProfileTierLevel(params1, params2) &&
+ IsSameH265TxMode(params1, params2);
+ }
+#endif
return true;
}
@@ -222,7 +250,7 @@ bool Codec::MatchesRtpCodec(const webrtc::RtpCodec& codec_capability) const {
}
bool Codec::GetParam(const std::string& name, std::string* out) const {
- CodecParameterMap::const_iterator iter = params.find(name);
+ webrtc::CodecParameterMap::const_iterator iter = params.find(name);
if (iter == params.end())
return false;
*out = iter->second;
@@ -230,7 +258,7 @@ bool Codec::GetParam(const std::string& name, std::string* out) const {
}
bool Codec::GetParam(const std::string& name, int* out) const {
- CodecParameterMap::const_iterator iter = params.find(name);
+ webrtc::CodecParameterMap::const_iterator iter = params.find(name);
if (iter == params.end())
return false;
return rtc::FromString(iter->second, out);
@@ -283,7 +311,8 @@ webrtc::RtpCodecParameters Codec::ToCodecParameters() const {
}
bool Codec::IsMediaCodec() const {
- return !IsResiliencyCodec();
+ return !IsResiliencyCodec() &&
+ !absl::EqualsIgnoreCase(name, kComfortNoiseCodecName);
}
bool Codec::IsResiliencyCodec() const {
diff --git a/third_party/libwebrtc/media/base/codec.h b/third_party/libwebrtc/media/base/codec.h
index bd4239b251..f586f78973 100644
--- a/third_party/libwebrtc/media/base/codec.h
+++ b/third_party/libwebrtc/media/base/codec.h
@@ -27,8 +27,6 @@
namespace cricket {
-using CodecParameterMap = std::map<std::string, std::string>;
-
class FeedbackParam {
public:
FeedbackParam() = default;
@@ -98,9 +96,12 @@ struct RTC_EXPORT Codec {
absl::InlinedVector<webrtc::ScalabilityMode, webrtc::kScalabilityModeCount>
scalability_modes;
+ // H.265 only
+ absl::optional<std::string> tx_mode;
+
// Non key-value parameters such as the telephone-event "0‐15" are
// represented using an empty string as key, i.e. {"": "0-15"}.
- CodecParameterMap params;
+ webrtc::CodecParameterMap params;
FeedbackParams feedback_params;
Codec(const Codec& c);
@@ -110,7 +111,9 @@ struct RTC_EXPORT Codec {
// Indicates if this codec is compatible with the specified codec by
// checking the assigned id and profile values for the relevant video codecs.
- // H264 levels are not compared.
+ // For H.264, packetization modes will be compared; If H.265 is enabled,
+ // TxModes will be compared.
+ // H.264(and H.265, if enabled) levels are not compared.
bool Matches(const Codec& codec) const;
bool MatchesRtpCodec(const webrtc::RtpCodec& capability) const;
diff --git a/third_party/libwebrtc/media/base/codec_unittest.cc b/third_party/libwebrtc/media/base/codec_unittest.cc
index eb34530c38..4dc3b18c21 100644
--- a/third_party/libwebrtc/media/base/codec_unittest.cc
+++ b/third_party/libwebrtc/media/base/codec_unittest.cc
@@ -342,6 +342,67 @@ TEST(CodecTest, TestH264CodecMatches) {
}
}
+#ifdef RTC_ENABLE_H265
+// Matching H.265 codecs should have matching profile/tier/level and tx-mode.
+TEST(CodecTest, TestH265CodecMatches) {
+ constexpr char kProfile1[] = "1";
+ constexpr char kTier1[] = "1";
+ constexpr char kLevel3_1[] = "93";
+ constexpr char kLevel4[] = "120";
+ constexpr char kTxMrst[] = "MRST";
+
+ VideoCodec c_ptl_blank =
+ cricket::CreateVideoCodec(95, cricket::kH265CodecName);
+
+ {
+ VideoCodec c_profile_1 =
+ cricket::CreateVideoCodec(95, cricket::kH265CodecName);
+ c_profile_1.params[cricket::kH265FmtpProfileId] = kProfile1;
+
+ // Matches since profile-id unspecified defaults to "1".
+ EXPECT_TRUE(c_ptl_blank.Matches(c_profile_1));
+ }
+
+ {
+ VideoCodec c_tier_flag_1 =
+ cricket::CreateVideoCodec(95, cricket::kH265CodecName);
+ c_tier_flag_1.params[cricket::kH265FmtpTierFlag] = kTier1;
+
+ // Does not match since profile-space unspecified defaults to "0".
+ EXPECT_FALSE(c_ptl_blank.Matches(c_tier_flag_1));
+ }
+
+ {
+ VideoCodec c_level_id_3_1 =
+ cricket::CreateVideoCodec(95, cricket::kH265CodecName);
+ c_level_id_3_1.params[cricket::kH265FmtpLevelId] = kLevel3_1;
+
+ // Matches since level-id unspecified defautls to "93".
+ EXPECT_TRUE(c_ptl_blank.Matches(c_level_id_3_1));
+ }
+
+ {
+ VideoCodec c_level_id_4 =
+ cricket::CreateVideoCodec(95, cricket::kH265CodecName);
+ c_level_id_4.params[cricket::kH265FmtpLevelId] = kLevel4;
+
+ // Does not match since different level-ids are specified.
+ EXPECT_FALSE(c_ptl_blank.Matches(c_level_id_4));
+ }
+
+ {
+ VideoCodec c_tx_mode_mrst =
+ cricket::CreateVideoCodec(95, cricket::kH265CodecName);
+ c_tx_mode_mrst.params[cricket::kH265FmtpTxMode] = kTxMrst;
+
+ // Does not match since tx-mode implies to "SRST" and must be not specified
+ // when it is the only mode supported:
+ // https://datatracker.ietf.org/doc/html/draft-ietf-avtcore-hevc-webrtc
+ EXPECT_FALSE(c_ptl_blank.Matches(c_tx_mode_mrst));
+ }
+}
+#endif
+
TEST(CodecTest, TestSetParamGetParamAndRemoveParam) {
AudioCodec codec = cricket::CreateAudioCodec(0, "foo", 22222, 2);
codec.SetParam("a", "1");
diff --git a/third_party/libwebrtc/media/base/media_channel_impl.cc b/third_party/libwebrtc/media/base/media_channel_impl.cc
index 5b41a9ccda..ff69ea62dc 100644
--- a/third_party/libwebrtc/media/base/media_channel_impl.cc
+++ b/third_party/libwebrtc/media/base/media_channel_impl.cc
@@ -58,18 +58,6 @@ int MediaChannelUtil::GetRtpSendTimeExtnId() const {
return -1;
}
-void MediaChannelUtil::SetFrameEncryptor(
- uint32_t ssrc,
- rtc::scoped_refptr<FrameEncryptorInterface> frame_encryptor) {
- // Placeholder should be pure virtual once internal supports it.
-}
-
-void MediaChannelUtil::SetFrameDecryptor(
- uint32_t ssrc,
- rtc::scoped_refptr<FrameDecryptorInterface> frame_decryptor) {
- // Placeholder should be pure virtual once internal supports it.
-}
-
bool MediaChannelUtil::SendPacket(rtc::CopyOnWriteBuffer* packet,
const rtc::PacketOptions& options) {
return transport_.DoSendPacket(packet, false, options);
@@ -102,14 +90,6 @@ bool MediaChannelUtil::HasNetworkInterface() const {
return transport_.HasNetworkInterface();
}
-void MediaChannelUtil::SetEncoderToPacketizerFrameTransformer(
- uint32_t ssrc,
- rtc::scoped_refptr<FrameTransformerInterface> frame_transformer) {}
-
-void MediaChannelUtil::SetDepacketizerToDecoderFrameTransformer(
- uint32_t ssrc,
- rtc::scoped_refptr<FrameTransformerInterface> frame_transformer) {}
-
bool MediaChannelUtil::DscpEnabled() const {
return transport_.DscpEnabled();
}
diff --git a/third_party/libwebrtc/media/base/media_channel_impl.h b/third_party/libwebrtc/media/base/media_channel_impl.h
index f8c8174efa..eda47af568 100644
--- a/third_party/libwebrtc/media/base/media_channel_impl.h
+++ b/third_party/libwebrtc/media/base/media_channel_impl.h
@@ -106,20 +106,6 @@ class MediaChannelUtil {
// Must be called on the network thread.
bool HasNetworkInterface() const;
- void SetFrameEncryptor(
- uint32_t ssrc,
- rtc::scoped_refptr<webrtc::FrameEncryptorInterface> frame_encryptor);
- void SetFrameDecryptor(
- uint32_t ssrc,
- rtc::scoped_refptr<webrtc::FrameDecryptorInterface> frame_decryptor);
-
- void SetEncoderToPacketizerFrameTransformer(
- uint32_t ssrc,
- rtc::scoped_refptr<webrtc::FrameTransformerInterface> frame_transformer);
- void SetDepacketizerToDecoderFrameTransformer(
- uint32_t ssrc,
- rtc::scoped_refptr<webrtc::FrameTransformerInterface> frame_transformer);
-
protected:
bool DscpEnabled() const;
diff --git a/third_party/libwebrtc/media/base/sdp_video_format_utils.cc b/third_party/libwebrtc/media/base/sdp_video_format_utils.cc
index a156afdc02..2b756ba734 100644
--- a/third_party/libwebrtc/media/base/sdp_video_format_utils.cc
+++ b/third_party/libwebrtc/media/base/sdp_video_format_utils.cc
@@ -15,6 +15,9 @@
#include <utility>
#include "api/video_codecs/h264_profile_level_id.h"
+#ifdef RTC_ENABLE_H265
+#include "api/video_codecs/h265_profile_tier_level.h"
+#endif
#include "rtc_base/checks.h"
#include "rtc_base/string_to_number.h"
@@ -27,8 +30,13 @@ const char kVPxFmtpMaxFrameRate[] = "max-fr";
// Max frame size for VP8 and VP9 video.
const char kVPxFmtpMaxFrameSize[] = "max-fs";
const int kVPxFmtpFrameSizeSubBlockPixels = 256;
+#ifdef RTC_ENABLE_H265
+constexpr char kH265ProfileId[] = "profile-id";
+constexpr char kH265TierFlag[] = "tier-flag";
+constexpr char kH265LevelId[] = "level-id";
+#endif
-bool IsH264LevelAsymmetryAllowed(const SdpVideoFormat::Parameters& params) {
+bool IsH264LevelAsymmetryAllowed(const CodecParameterMap& params) {
const auto it = params.find(kH264LevelAsymmetryAllowed);
return it != params.end() && strcmp(it->second.c_str(), "1") == 0;
}
@@ -47,7 +55,7 @@ H264Level H264LevelMin(H264Level a, H264Level b) {
}
absl::optional<int> ParsePositiveNumberFromParams(
- const SdpVideoFormat::Parameters& params,
+ const CodecParameterMap& params,
const char* parameter_name) {
const auto max_frame_rate_it = params.find(parameter_name);
if (max_frame_rate_it == params.end())
@@ -60,13 +68,64 @@ absl::optional<int> ParsePositiveNumberFromParams(
return i;
}
+#ifdef RTC_ENABLE_H265
+// Compares two H265Level and return the smaller.
+H265Level H265LevelMin(H265Level a, H265Level b) {
+ return a <= b ? a : b;
+}
+
+// Returns true if none of profile-id/tier-flag/level-id is specified
+// explicitly in the param.
+bool IsDefaultH265PTL(const CodecParameterMap& params) {
+ return !params.count(kH265ProfileId) && !params.count(kH265TierFlag) &&
+ !params.count(kH265LevelId);
+}
+#endif
+
} // namespace
+#ifdef RTC_ENABLE_H265
+// Set level according to https://tools.ietf.org/html/rfc7798#section-7.1
+void H265GenerateProfileTierLevelForAnswer(
+ const CodecParameterMap& local_supported_params,
+ const CodecParameterMap& remote_offered_params,
+ CodecParameterMap* answer_params) {
+ // If local and remote haven't set profile-id/tier-flag/level-id, they
+ // are both using the default PTL In this case, don't set PTL in answer
+ // either.
+ if (IsDefaultH265PTL(local_supported_params) &&
+ IsDefaultH265PTL(remote_offered_params)) {
+ return;
+ }
+
+ // Parse profile-tier-level.
+ const absl::optional<H265ProfileTierLevel> local_profile_tier_level =
+ ParseSdpForH265ProfileTierLevel(local_supported_params);
+ const absl::optional<H265ProfileTierLevel> remote_profile_tier_level =
+ ParseSdpForH265ProfileTierLevel(remote_offered_params);
+ // Profile and tier for local and remote codec must be valid and equal.
+ RTC_DCHECK(local_profile_tier_level);
+ RTC_DCHECK(remote_profile_tier_level);
+ RTC_DCHECK_EQ(local_profile_tier_level->profile,
+ remote_profile_tier_level->profile);
+ RTC_DCHECK_EQ(local_profile_tier_level->tier,
+ remote_profile_tier_level->tier);
+
+ const H265Level answer_level = H265LevelMin(local_profile_tier_level->level,
+ remote_profile_tier_level->level);
+
+ // Level-id in answer is changable as long as the highest level indicated by
+ // the answer is not higher than that indicated by the offer. See
+ // https://tools.ietf.org/html/rfc7798#section-7.2.2, sub-clause 2.
+ (*answer_params)[kH265LevelId] = H265LevelToString(answer_level);
+}
+#endif
+
// Set level according to https://tools.ietf.org/html/rfc6184#section-8.2.2.
void H264GenerateProfileLevelIdForAnswer(
- const SdpVideoFormat::Parameters& local_supported_params,
- const SdpVideoFormat::Parameters& remote_offered_params,
- SdpVideoFormat::Parameters* answer_params) {
+ const CodecParameterMap& local_supported_params,
+ const CodecParameterMap& remote_offered_params,
+ CodecParameterMap* answer_params) {
// If both local and remote haven't set profile-level-id, they are both using
// the default profile. In this case, don't set profile-level-id in answer
// either.
@@ -106,12 +165,12 @@ void H264GenerateProfileLevelIdForAnswer(
}
absl::optional<int> ParseSdpForVPxMaxFrameRate(
- const SdpVideoFormat::Parameters& params) {
+ const CodecParameterMap& params) {
return ParsePositiveNumberFromParams(params, kVPxFmtpMaxFrameRate);
}
absl::optional<int> ParseSdpForVPxMaxFrameSize(
- const SdpVideoFormat::Parameters& params) {
+ const CodecParameterMap& params) {
const absl::optional<int> i =
ParsePositiveNumberFromParams(params, kVPxFmtpMaxFrameSize);
return i ? absl::make_optional(i.value() * kVPxFmtpFrameSizeSubBlockPixels)
diff --git a/third_party/libwebrtc/media/base/sdp_video_format_utils.h b/third_party/libwebrtc/media/base/sdp_video_format_utils.h
index 80c1e4d501..931caa8a29 100644
--- a/third_party/libwebrtc/media/base/sdp_video_format_utils.h
+++ b/third_party/libwebrtc/media/base/sdp_video_format_utils.h
@@ -32,20 +32,30 @@ namespace webrtc {
// parameters that are used when negotiating are the level part of
// profile-level-id and level-asymmetry-allowed.
void H264GenerateProfileLevelIdForAnswer(
- const SdpVideoFormat::Parameters& local_supported_params,
- const SdpVideoFormat::Parameters& remote_offered_params,
- SdpVideoFormat::Parameters* answer_params);
+ const CodecParameterMap& local_supported_params,
+ const CodecParameterMap& remote_offered_params,
+ CodecParameterMap* answer_params);
+
+#ifdef RTC_ENABLE_H265
+// Works similarly as H264GenerateProfileLevelIdForAnswer, but generates codec
+// parameters that will be used as answer for H.265.
+// Media configuration parameters, except level-id, must be used symmetrically.
+// For level-id, the highest level indicated by the answer must not be higher
+// than that indicated by the offer.
+void H265GenerateProfileTierLevelForAnswer(
+ const CodecParameterMap& local_supported_params,
+ const CodecParameterMap& remote_offered_params,
+ CodecParameterMap* answer_params);
+#endif
// Parse max frame rate from SDP FMTP line. absl::nullopt is returned if the
// field is missing or not a number.
-absl::optional<int> ParseSdpForVPxMaxFrameRate(
- const SdpVideoFormat::Parameters& params);
+absl::optional<int> ParseSdpForVPxMaxFrameRate(const CodecParameterMap& params);
// Parse max frame size from SDP FMTP line. absl::nullopt is returned if the
// field is missing or not a number. Please note that the value is stored in sub
// blocks but the returned value is in total number of pixels.
-absl::optional<int> ParseSdpForVPxMaxFrameSize(
- const SdpVideoFormat::Parameters& params);
+absl::optional<int> ParseSdpForVPxMaxFrameSize(const CodecParameterMap& params);
} // namespace webrtc
diff --git a/third_party/libwebrtc/media/base/sdp_video_format_utils_unittest.cc b/third_party/libwebrtc/media/base/sdp_video_format_utils_unittest.cc
index d8ef9ab827..9f505c19d7 100644
--- a/third_party/libwebrtc/media/base/sdp_video_format_utils_unittest.cc
+++ b/third_party/libwebrtc/media/base/sdp_video_format_utils_unittest.cc
@@ -27,29 +27,28 @@ const char kVPxFmtpMaxFrameSize[] = "max-fs";
} // namespace
TEST(SdpVideoFormatUtilsTest, TestH264GenerateProfileLevelIdForAnswerEmpty) {
- SdpVideoFormat::Parameters answer_params;
- H264GenerateProfileLevelIdForAnswer(SdpVideoFormat::Parameters(),
- SdpVideoFormat::Parameters(),
+ CodecParameterMap answer_params;
+ H264GenerateProfileLevelIdForAnswer(CodecParameterMap(), CodecParameterMap(),
&answer_params);
EXPECT_TRUE(answer_params.empty());
}
TEST(SdpVideoFormatUtilsTest,
TestH264GenerateProfileLevelIdForAnswerLevelSymmetryCapped) {
- SdpVideoFormat::Parameters low_level;
+ CodecParameterMap low_level;
low_level["profile-level-id"] = "42e015";
- SdpVideoFormat::Parameters high_level;
+ CodecParameterMap high_level;
high_level["profile-level-id"] = "42e01f";
// Level asymmetry is not allowed; test that answer level is the lower of the
// local and remote levels.
- SdpVideoFormat::Parameters answer_params;
+ CodecParameterMap answer_params;
H264GenerateProfileLevelIdForAnswer(low_level /* local_supported */,
high_level /* remote_offered */,
&answer_params);
EXPECT_EQ("42e015", answer_params["profile-level-id"]);
- SdpVideoFormat::Parameters answer_params2;
+ CodecParameterMap answer_params2;
H264GenerateProfileLevelIdForAnswer(high_level /* local_supported */,
low_level /* remote_offered */,
&answer_params2);
@@ -58,13 +57,13 @@ TEST(SdpVideoFormatUtilsTest,
TEST(SdpVideoFormatUtilsTest,
TestH264GenerateProfileLevelIdForAnswerConstrainedBaselineLevelAsymmetry) {
- SdpVideoFormat::Parameters local_params;
+ CodecParameterMap local_params;
local_params["profile-level-id"] = "42e01f";
local_params["level-asymmetry-allowed"] = "1";
- SdpVideoFormat::Parameters remote_params;
+ CodecParameterMap remote_params;
remote_params["profile-level-id"] = "42e015";
remote_params["level-asymmetry-allowed"] = "1";
- SdpVideoFormat::Parameters answer_params;
+ CodecParameterMap answer_params;
H264GenerateProfileLevelIdForAnswer(local_params, remote_params,
&answer_params);
// When level asymmetry is allowed, we can answer a higher level than what was
@@ -72,8 +71,38 @@ TEST(SdpVideoFormatUtilsTest,
EXPECT_EQ("42e01f", answer_params["profile-level-id"]);
}
+#ifdef RTC_ENABLE_H265
+// Answer should not include explicit PTL info if neither local nor remote set
+// any of them.
+TEST(SdpVideoFormatUtilsTest, H265GenerateProfileTierLevelEmpty) {
+ CodecParameterMap answer_params;
+ H265GenerateProfileTierLevelForAnswer(CodecParameterMap(),
+ CodecParameterMap(), &answer_params);
+ EXPECT_TRUE(answer_params.empty());
+}
+
+// Answer must use the minimum level as supported by both local and remote.
+TEST(SdpVideoFormatUtilsTest, H265GenerateProfileTierLevelNoEmpty) {
+ constexpr char kLocallySupportedLevelId[] = "93";
+ constexpr char kRemoteOfferedLevelId[] = "120";
+
+ CodecParameterMap local_params;
+ local_params["profile-id"] = "1";
+ local_params["tier-flag"] = "0";
+ local_params["level-id"] = kLocallySupportedLevelId;
+ CodecParameterMap remote_params;
+ remote_params["profile-id"] = "1";
+ remote_params["tier-flag"] = "0";
+ remote_params["level-id"] = kRemoteOfferedLevelId;
+ CodecParameterMap answer_params;
+ H265GenerateProfileTierLevelForAnswer(local_params, remote_params,
+ &answer_params);
+ EXPECT_EQ(kLocallySupportedLevelId, answer_params["level-id"]);
+}
+#endif
+
TEST(SdpVideoFormatUtilsTest, MaxFrameRateIsMissingOrInvalid) {
- SdpVideoFormat::Parameters params;
+ CodecParameterMap params;
absl::optional<int> empty = ParseSdpForVPxMaxFrameRate(params);
EXPECT_FALSE(empty);
params[kVPxFmtpMaxFrameRate] = "-1";
@@ -85,7 +114,7 @@ TEST(SdpVideoFormatUtilsTest, MaxFrameRateIsMissingOrInvalid) {
}
TEST(SdpVideoFormatUtilsTest, MaxFrameRateIsSpecified) {
- SdpVideoFormat::Parameters params;
+ CodecParameterMap params;
params[kVPxFmtpMaxFrameRate] = "30";
EXPECT_EQ(ParseSdpForVPxMaxFrameRate(params), 30);
params[kVPxFmtpMaxFrameRate] = "60";
@@ -93,7 +122,7 @@ TEST(SdpVideoFormatUtilsTest, MaxFrameRateIsSpecified) {
}
TEST(SdpVideoFormatUtilsTest, MaxFrameSizeIsMissingOrInvalid) {
- SdpVideoFormat::Parameters params;
+ CodecParameterMap params;
absl::optional<int> empty = ParseSdpForVPxMaxFrameSize(params);
EXPECT_FALSE(empty);
params[kVPxFmtpMaxFrameSize] = "-1";
@@ -105,7 +134,7 @@ TEST(SdpVideoFormatUtilsTest, MaxFrameSizeIsMissingOrInvalid) {
}
TEST(SdpVideoFormatUtilsTest, MaxFrameSizeIsSpecified) {
- SdpVideoFormat::Parameters params;
+ CodecParameterMap params;
params[kVPxFmtpMaxFrameSize] = "8100"; // 1920 x 1080 / (16^2)
EXPECT_EQ(ParseSdpForVPxMaxFrameSize(params), 1920 * 1080);
params[kVPxFmtpMaxFrameSize] = "32400"; // 3840 x 2160 / (16^2)
diff --git a/third_party/libwebrtc/media/engine/internal_decoder_factory_unittest.cc b/third_party/libwebrtc/media/engine/internal_decoder_factory_unittest.cc
index bb2e24d5d8..51d6a94dd6 100644
--- a/third_party/libwebrtc/media/engine/internal_decoder_factory_unittest.cc
+++ b/third_party/libwebrtc/media/engine/internal_decoder_factory_unittest.cc
@@ -43,6 +43,8 @@ constexpr bool kDav1dIsIncluded = true;
#else
constexpr bool kDav1dIsIncluded = false;
#endif
+constexpr bool kH265Enabled = false;
+
constexpr VideoDecoderFactory::CodecSupport kSupported = {
/*is_supported=*/true, /*is_power_efficient=*/false};
constexpr VideoDecoderFactory::CodecSupport kUnsupported = {
@@ -99,6 +101,14 @@ TEST(InternalDecoderFactoryTest, Av1Profile0) {
}
}
+// At current stage since internal H.265 decoder is not implemented,
+TEST(InternalDecoderFactoryTest, H265IsNotEnabled) {
+ InternalDecoderFactory factory;
+ std::unique_ptr<VideoDecoder> decoder =
+ factory.CreateVideoDecoder(SdpVideoFormat(cricket::kH265CodecName));
+ EXPECT_EQ(static_cast<bool>(decoder), kH265Enabled);
+}
+
#if defined(RTC_DAV1D_IN_INTERNAL_DECODER_FACTORY)
TEST(InternalDecoderFactoryTest, Av1) {
InternalDecoderFactory factory;
diff --git a/third_party/libwebrtc/media/engine/internal_encoder_factory_unittest.cc b/third_party/libwebrtc/media/engine/internal_encoder_factory_unittest.cc
index a1c90b8cf4..b9ca6d88c2 100644
--- a/third_party/libwebrtc/media/engine/internal_encoder_factory_unittest.cc
+++ b/third_party/libwebrtc/media/engine/internal_encoder_factory_unittest.cc
@@ -33,6 +33,8 @@ constexpr bool kH264Enabled = true;
#else
constexpr bool kH264Enabled = false;
#endif
+constexpr bool kH265Enabled = false;
+
constexpr VideoEncoderFactory::CodecSupport kSupported = {
/*is_supported=*/true, /*is_power_efficient=*/false};
constexpr VideoEncoderFactory::CodecSupport kUnsupported = {
@@ -78,6 +80,17 @@ TEST(InternalEncoderFactoryTest, H264) {
}
}
+// At current stage H.265 is not supported by internal encoder factory.
+TEST(InternalEncoderFactoryTest, H265IsNotEnabled) {
+ InternalEncoderFactory factory;
+ std::unique_ptr<VideoEncoder> encoder =
+ factory.CreateVideoEncoder(SdpVideoFormat(cricket::kH265CodecName));
+ EXPECT_EQ(static_cast<bool>(encoder), kH265Enabled);
+ EXPECT_THAT(
+ factory.GetSupportedFormats(),
+ Not(Contains(Field(&SdpVideoFormat::name, cricket::kH265CodecName))));
+}
+
TEST(InternalEncoderFactoryTest, QueryCodecSupportWithScalabilityMode) {
InternalEncoderFactory factory;
// VP8 and VP9 supported for singles spatial layers.
diff --git a/third_party/libwebrtc/media/engine/simulcast_encoder_adapter_unittest.cc b/third_party/libwebrtc/media/engine/simulcast_encoder_adapter_unittest.cc
index e2ac5ea390..3ee3465e13 100644
--- a/third_party/libwebrtc/media/engine/simulcast_encoder_adapter_unittest.cc
+++ b/third_party/libwebrtc/media/engine/simulcast_encoder_adapter_unittest.cc
@@ -586,7 +586,7 @@ class TestSimulcastEncoderAdapterFake : public ::testing::Test,
absl::optional<int> last_encoded_image_simulcast_index_;
std::unique_ptr<SimulcastRateAllocator> rate_allocator_;
bool use_fallback_factory_;
- SdpVideoFormat::Parameters sdp_video_parameters_;
+ CodecParameterMap sdp_video_parameters_;
test::ScopedKeyValueConfig field_trials_;
};
diff --git a/third_party/libwebrtc/media/engine/webrtc_media_engine.cc b/third_party/libwebrtc/media/engine/webrtc_media_engine.cc
index 463ed29109..31769e05de 100644
--- a/third_party/libwebrtc/media/engine/webrtc_media_engine.cc
+++ b/third_party/libwebrtc/media/engine/webrtc_media_engine.cc
@@ -12,53 +12,16 @@
#include <algorithm>
#include <map>
-#include <memory>
#include <string>
#include <utility>
#include "absl/algorithm/container.h"
#include "absl/strings/match.h"
-#include "api/transport/field_trial_based_config.h"
#include "media/base/media_constants.h"
-#include "media/engine/webrtc_voice_engine.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
-#ifdef HAVE_WEBRTC_VIDEO
-#include "media/engine/webrtc_video_engine.h"
-#else
-#include "media/engine/null_webrtc_video_engine.h"
-#endif
-
namespace cricket {
-
-std::unique_ptr<MediaEngineInterface> CreateMediaEngine(
- MediaEngineDependencies dependencies) {
- // TODO(sprang): Make populating `dependencies.trials` mandatory and remove
- // these fallbacks.
- std::unique_ptr<webrtc::FieldTrialsView> fallback_trials(
- dependencies.trials ? nullptr : new webrtc::FieldTrialBasedConfig());
- const webrtc::FieldTrialsView& trials =
- dependencies.trials ? *dependencies.trials : *fallback_trials;
- auto audio_engine = std::make_unique<WebRtcVoiceEngine>(
- dependencies.task_queue_factory, dependencies.adm.get(),
- std::move(dependencies.audio_encoder_factory),
- std::move(dependencies.audio_decoder_factory),
- std::move(dependencies.audio_mixer),
- std::move(dependencies.audio_processing),
- std::move(dependencies.owned_audio_frame_processor), trials);
-#ifdef HAVE_WEBRTC_VIDEO
- auto video_engine = std::make_unique<WebRtcVideoEngine>(
- std::move(dependencies.video_encoder_factory),
- std::move(dependencies.video_decoder_factory), trials);
-#else
- auto video_engine = std::make_unique<NullWebRtcVideoEngine>();
-#endif
- return std::make_unique<CompositeMediaEngine>(std::move(fallback_trials),
- std::move(audio_engine),
- std::move(video_engine));
-}
-
namespace {
// Remove mutually exclusive extensions with lower priority.
void DiscardRedundantExtensions(
diff --git a/third_party/libwebrtc/media/engine/webrtc_media_engine.h b/third_party/libwebrtc/media/engine/webrtc_media_engine.h
index 863db9f278..5bd5a8bce5 100644
--- a/third_party/libwebrtc/media/engine/webrtc_media_engine.h
+++ b/third_party/libwebrtc/media/engine/webrtc_media_engine.h
@@ -11,59 +11,17 @@
#ifndef MEDIA_ENGINE_WEBRTC_MEDIA_ENGINE_H_
#define MEDIA_ENGINE_WEBRTC_MEDIA_ENGINE_H_
-#include <memory>
#include <vector>
#include "absl/strings/string_view.h"
#include "api/array_view.h"
-#include "api/audio/audio_frame_processor.h"
-#include "api/audio/audio_mixer.h"
-#include "api/audio_codecs/audio_decoder_factory.h"
-#include "api/audio_codecs/audio_encoder_factory.h"
#include "api/field_trials_view.h"
#include "api/rtp_parameters.h"
-#include "api/scoped_refptr.h"
-#include "api/task_queue/task_queue_factory.h"
#include "api/transport/bitrate_settings.h"
-#include "api/video_codecs/video_decoder_factory.h"
-#include "api/video_codecs/video_encoder_factory.h"
#include "media/base/codec.h"
-#include "media/base/media_engine.h"
-#include "modules/audio_device/include/audio_device.h"
-#include "modules/audio_processing/include/audio_processing.h"
-#include "rtc_base/system/rtc_export.h"
namespace cricket {
-struct MediaEngineDependencies {
- MediaEngineDependencies() = default;
- MediaEngineDependencies(const MediaEngineDependencies&) = delete;
- MediaEngineDependencies(MediaEngineDependencies&&) = default;
- MediaEngineDependencies& operator=(const MediaEngineDependencies&) = delete;
- MediaEngineDependencies& operator=(MediaEngineDependencies&&) = default;
- ~MediaEngineDependencies() = default;
-
- webrtc::TaskQueueFactory* task_queue_factory = nullptr;
- rtc::scoped_refptr<webrtc::AudioDeviceModule> adm;
- rtc::scoped_refptr<webrtc::AudioEncoderFactory> audio_encoder_factory;
- rtc::scoped_refptr<webrtc::AudioDecoderFactory> audio_decoder_factory;
- rtc::scoped_refptr<webrtc::AudioMixer> audio_mixer;
- rtc::scoped_refptr<webrtc::AudioProcessing> audio_processing;
- std::unique_ptr<webrtc::AudioFrameProcessor> owned_audio_frame_processor;
-
- std::unique_ptr<webrtc::VideoEncoderFactory> video_encoder_factory;
- std::unique_ptr<webrtc::VideoDecoderFactory> video_decoder_factory;
-
- const webrtc::FieldTrialsView* trials = nullptr;
-};
-
-// CreateMediaEngine may be called on any thread, though the engine is
-// only expected to be used on one thread, internally called the "worker
-// thread". This is the thread Init must be called on.
-[[deprecated("bugs.webrtc.org/15574")]] //
-RTC_EXPORT std::unique_ptr<MediaEngineInterface>
-CreateMediaEngine(MediaEngineDependencies dependencies);
-
// Verify that extension IDs are within 1-byte extension range and are not
// overlapping, and that they form a legal change from previously registerd
// extensions (if any).
diff --git a/third_party/libwebrtc/media/engine/webrtc_video_engine.cc b/third_party/libwebrtc/media/engine/webrtc_video_engine.cc
index 8a9d6ff95c..a5b46d3344 100644
--- a/third_party/libwebrtc/media/engine/webrtc_video_engine.cc
+++ b/third_party/libwebrtc/media/engine/webrtc_video_engine.cc
@@ -1038,13 +1038,19 @@ bool WebRtcVideoSendChannel::GetChangedSenderParameters(
return false;
}
+ std::vector<VideoCodecSettings> mapped_codecs = MapCodecs(params.codecs);
+ if (mapped_codecs.empty()) {
+ // This suggests a failure in MapCodecs, e.g. inconsistent RTX codecs.
+ return false;
+ }
+
std::vector<VideoCodecSettings> negotiated_codecs =
- SelectSendVideoCodecs(MapCodecs(params.codecs));
+ SelectSendVideoCodecs(mapped_codecs);
- // We should only fail here if send direction is enabled.
if (params.is_stream_active && negotiated_codecs.empty()) {
- RTC_LOG(LS_ERROR) << "No video codecs supported.";
- return false;
+ // This is not a failure but will lead to the answer being rejected.
+ RTC_LOG(LS_ERROR) << "No video codecs in common.";
+ return true;
}
// Never enable sending FlexFEC, unless we are in the experiment.
diff --git a/third_party/libwebrtc/media/engine/webrtc_video_engine_unittest.cc b/third_party/libwebrtc/media/engine/webrtc_video_engine_unittest.cc
index f5736679be..148223f497 100644
--- a/third_party/libwebrtc/media/engine/webrtc_video_engine_unittest.cc
+++ b/third_party/libwebrtc/media/engine/webrtc_video_engine_unittest.cc
@@ -3781,7 +3781,7 @@ class Vp9SettingsTest : public WebRtcVideoChannelTest {
TEST_F(Vp9SettingsTest, VerifyVp9SpecificSettings) {
encoder_factory_->AddSupportedVideoCodec(
- webrtc::SdpVideoFormat("VP9", webrtc::SdpVideoFormat::Parameters(),
+ webrtc::SdpVideoFormat("VP9", webrtc::CodecParameterMap(),
{ScalabilityMode::kL1T1, ScalabilityMode::kL2T1}));
cricket::VideoSenderParameters parameters;
@@ -8545,7 +8545,7 @@ TEST_F(WebRtcVideoChannelTest, FallbackForUnsetOrUnsupportedScalabilityMode) {
ScalabilityMode::kL1T3};
encoder_factory_->AddSupportedVideoCodec(webrtc::SdpVideoFormat(
- "VP8", webrtc::SdpVideoFormat::Parameters(), kSupportedModes));
+ "VP8", webrtc::CodecParameterMap(), kSupportedModes));
FakeVideoSendStream* stream = SetUpSimulcast(true, /*with_rtx=*/false);
@@ -8615,9 +8615,9 @@ TEST_F(WebRtcVideoChannelTest,
kVp9SupportedModes = {ScalabilityMode::kL3T3};
encoder_factory_->AddSupportedVideoCodec(webrtc::SdpVideoFormat(
- "VP8", webrtc::SdpVideoFormat::Parameters(), {ScalabilityMode::kL1T1}));
+ "VP8", webrtc::CodecParameterMap(), {ScalabilityMode::kL1T1}));
encoder_factory_->AddSupportedVideoCodec(webrtc::SdpVideoFormat(
- "VP9", webrtc::SdpVideoFormat::Parameters(), {ScalabilityMode::kL3T3}));
+ "VP9", webrtc::CodecParameterMap(), {ScalabilityMode::kL3T3}));
cricket::VideoSenderParameters send_parameters;
send_parameters.codecs.push_back(GetEngineCodec("VP9"));
diff --git a/third_party/libwebrtc/media/engine/webrtc_voice_engine.cc b/third_party/libwebrtc/media/engine/webrtc_voice_engine.cc
index adf662074d..fcc2703f98 100644
--- a/third_party/libwebrtc/media/engine/webrtc_voice_engine.cc
+++ b/third_party/libwebrtc/media/engine/webrtc_voice_engine.cc
@@ -377,9 +377,8 @@ void WebRtcVoiceEngine::Init() {
// TaskQueue expects to be created/destroyed on the same thread.
RTC_DCHECK(!low_priority_worker_queue_);
- low_priority_worker_queue_.reset(
- new rtc::TaskQueue(task_queue_factory_->CreateTaskQueue(
- "rtc-low-prio", webrtc::TaskQueueFactory::Priority::LOW)));
+ low_priority_worker_queue_ = task_queue_factory_->CreateTaskQueue(
+ "rtc-low-prio", webrtc::TaskQueueFactory::Priority::LOW);
// Load our audio codec lists.
RTC_LOG(LS_VERBOSE) << "Supported send codecs in order of preference:";
@@ -761,9 +760,9 @@ std::vector<AudioCodec> WebRtcVoiceEngine::CollectCodecs(
out.push_back(codec);
if (codec.name == kOpusCodecName) {
- std::string redFmtp =
+ std::string red_fmtp =
rtc::ToString(codec.id) + "/" + rtc::ToString(codec.id);
- map_format({kRedCodecName, 48000, 2, {{"", redFmtp}}}, &out);
+ map_format({kRedCodecName, 48000, 2, {{"", red_fmtp}}}, &out);
}
}
}
@@ -1318,7 +1317,7 @@ bool WebRtcVoiceSendChannel::SetSenderParameters(
}
}
- if (!SetMaxSendBitrate(params.max_bandwidth_bps)) {
+ if (send_codec_spec_ && !SetMaxSendBitrate(params.max_bandwidth_bps)) {
return false;
}
return SetOptions(params.options);
@@ -1402,7 +1401,8 @@ bool WebRtcVoiceSendChannel::SetSendCodecs(
}
if (!send_codec_spec) {
- return false;
+ // No codecs in common, bail out early.
+ return true;
}
RTC_DCHECK(voice_codec_info);
diff --git a/third_party/libwebrtc/media/engine/webrtc_voice_engine.h b/third_party/libwebrtc/media/engine/webrtc_voice_engine.h
index ed71667525..b28b9652bb 100644
--- a/third_party/libwebrtc/media/engine/webrtc_voice_engine.h
+++ b/third_party/libwebrtc/media/engine/webrtc_voice_engine.h
@@ -66,7 +66,6 @@
#include "rtc_base/network/sent_packet.h"
#include "rtc_base/network_route.h"
#include "rtc_base/system/file_wrapper.h"
-#include "rtc_base/task_queue.h"
namespace webrtc {
class AudioFrameProcessor;
@@ -141,7 +140,8 @@ class WebRtcVoiceEngine final : public VoiceEngineInterface {
void ApplyOptions(const AudioOptions& options);
webrtc::TaskQueueFactory* const task_queue_factory_;
- std::unique_ptr<rtc::TaskQueue> low_priority_worker_queue_;
+ std::unique_ptr<webrtc::TaskQueueBase, webrtc::TaskQueueDeleter>
+ low_priority_worker_queue_;
webrtc::AudioDeviceModule* adm();
webrtc::AudioProcessing* apm() const;
diff --git a/third_party/libwebrtc/media/engine/webrtc_voice_engine_unittest.cc b/third_party/libwebrtc/media/engine/webrtc_voice_engine_unittest.cc
index 4d6580631d..8ae441bc69 100644
--- a/third_party/libwebrtc/media/engine/webrtc_voice_engine_unittest.cc
+++ b/third_party/libwebrtc/media/engine/webrtc_voice_engine_unittest.cc
@@ -1702,27 +1702,29 @@ TEST_P(WebRtcVoiceEngineTestFake, DontRecreateSendStream) {
// TODO(ossu): Revisit if these tests need to be here, now that these kinds of
// tests should be available in AudioEncoderOpusTest.
-// Test that if clockrate is not 48000 for opus, we fail.
+// Test that if clockrate is not 48000 for opus, we do not have a send codec.
TEST_P(WebRtcVoiceEngineTestFake, SetSendCodecOpusBadClockrate) {
EXPECT_TRUE(SetupSendStream());
cricket::AudioSenderParameter parameters;
parameters.codecs.push_back(kOpusCodec);
parameters.codecs[0].bitrate = 0;
parameters.codecs[0].clockrate = 50000;
- EXPECT_FALSE(send_channel_->SetSenderParameters(parameters));
+ EXPECT_TRUE(send_channel_->SetSenderParameters(parameters));
+ EXPECT_EQ(send_channel_->GetSendCodec(), absl::nullopt);
}
-// Test that if channels=0 for opus, we fail.
+// Test that if channels=0 for opus, we do not have a send codec.
TEST_P(WebRtcVoiceEngineTestFake, SetSendCodecOpusBad0ChannelsNoStereo) {
EXPECT_TRUE(SetupSendStream());
cricket::AudioSenderParameter parameters;
parameters.codecs.push_back(kOpusCodec);
parameters.codecs[0].bitrate = 0;
parameters.codecs[0].channels = 0;
- EXPECT_FALSE(send_channel_->SetSenderParameters(parameters));
+ EXPECT_TRUE(send_channel_->SetSenderParameters(parameters));
+ EXPECT_EQ(send_channel_->GetSendCodec(), absl::nullopt);
}
-// Test that if channels=0 for opus, we fail.
+// Test that if channels=0 for opus, we do not have a send codec.
TEST_P(WebRtcVoiceEngineTestFake, SetSendCodecOpusBad0Channels1Stereo) {
EXPECT_TRUE(SetupSendStream());
cricket::AudioSenderParameter parameters;
@@ -1730,20 +1732,23 @@ TEST_P(WebRtcVoiceEngineTestFake, SetSendCodecOpusBad0Channels1Stereo) {
parameters.codecs[0].bitrate = 0;
parameters.codecs[0].channels = 0;
parameters.codecs[0].params["stereo"] = "1";
- EXPECT_FALSE(send_channel_->SetSenderParameters(parameters));
+ EXPECT_TRUE(send_channel_->SetSenderParameters(parameters));
+ EXPECT_EQ(send_channel_->GetSendCodec(), absl::nullopt);
}
-// Test that if channel is 1 for opus and there's no stereo, we fail.
+// Test that if channel is 1 for opus and there's no stereo, we do not have a
+// send codec.
TEST_P(WebRtcVoiceEngineTestFake, SetSendCodecOpus1ChannelNoStereo) {
EXPECT_TRUE(SetupSendStream());
cricket::AudioSenderParameter parameters;
parameters.codecs.push_back(kOpusCodec);
parameters.codecs[0].bitrate = 0;
parameters.codecs[0].channels = 1;
- EXPECT_FALSE(send_channel_->SetSenderParameters(parameters));
+ EXPECT_TRUE(send_channel_->SetSenderParameters(parameters));
+ EXPECT_EQ(send_channel_->GetSendCodec(), absl::nullopt);
}
-// Test that if channel is 1 for opus and stereo=0, we fail.
+// Test that if channel is 1 for opus and stereo=0, we do not have a send codec.
TEST_P(WebRtcVoiceEngineTestFake, SetSendCodecOpusBad1Channel0Stereo) {
EXPECT_TRUE(SetupSendStream());
cricket::AudioSenderParameter parameters;
@@ -1751,10 +1756,11 @@ TEST_P(WebRtcVoiceEngineTestFake, SetSendCodecOpusBad1Channel0Stereo) {
parameters.codecs[0].bitrate = 0;
parameters.codecs[0].channels = 1;
parameters.codecs[0].params["stereo"] = "0";
- EXPECT_FALSE(send_channel_->SetSenderParameters(parameters));
+ EXPECT_TRUE(send_channel_->SetSenderParameters(parameters));
+ EXPECT_EQ(send_channel_->GetSendCodec(), absl::nullopt);
}
-// Test that if channel is 1 for opus and stereo=1, we fail.
+// Test that if channel is 1 for opus and stereo=1, we do not have a send codec.
TEST_P(WebRtcVoiceEngineTestFake, SetSendCodecOpusBad1Channel1Stereo) {
EXPECT_TRUE(SetupSendStream());
cricket::AudioSenderParameter parameters;
@@ -1762,7 +1768,8 @@ TEST_P(WebRtcVoiceEngineTestFake, SetSendCodecOpusBad1Channel1Stereo) {
parameters.codecs[0].bitrate = 0;
parameters.codecs[0].channels = 1;
parameters.codecs[0].params["stereo"] = "1";
- EXPECT_FALSE(send_channel_->SetSenderParameters(parameters));
+ EXPECT_TRUE(send_channel_->SetSenderParameters(parameters));
+ EXPECT_EQ(send_channel_->GetSendCodec(), absl::nullopt);
}
// Test that with bitrate=0 and no stereo, bitrate is 32000.
@@ -2035,11 +2042,12 @@ TEST_P(WebRtcVoiceEngineTestFake, SetSendCodecsBitrate) {
}
}
-// Test that we fail if no codecs are specified.
+// Test that we do not fail if no codecs are specified.
TEST_P(WebRtcVoiceEngineTestFake, SetSendCodecsNoCodecs) {
EXPECT_TRUE(SetupSendStream());
cricket::AudioSenderParameter parameters;
- EXPECT_FALSE(send_channel_->SetSenderParameters(parameters));
+ EXPECT_TRUE(send_channel_->SetSenderParameters(parameters));
+ EXPECT_EQ(send_channel_->GetSendCodec(), absl::nullopt);
}
// Test that we can set send codecs even with telephone-event codec as the first
diff --git a/third_party/libwebrtc/modules/audio_coding/codecs/builtin_audio_decoder_factory_unittest.cc b/third_party/libwebrtc/modules/audio_coding/codecs/builtin_audio_decoder_factory_unittest.cc
index bd8d1cc341..998e78e1d3 100644
--- a/third_party/libwebrtc/modules/audio_coding/codecs/builtin_audio_decoder_factory_unittest.cc
+++ b/third_party/libwebrtc/modules/audio_coding/codecs/builtin_audio_decoder_factory_unittest.cc
@@ -146,7 +146,7 @@ TEST(AudioDecoderFactoryTest, CreateOpus) {
for (int hz : {8000, 16000, 32000, 48000}) {
for (int channels : {0, 1, 2, 3}) {
for (std::string stereo : {"XX", "0", "1", "2"}) {
- SdpAudioFormat::Parameters params;
+ CodecParameterMap params;
if (stereo != "XX") {
params["stereo"] = stereo;
}
diff --git a/third_party/libwebrtc/modules/audio_coding/codecs/opus/audio_encoder_opus_unittest.cc b/third_party/libwebrtc/modules/audio_coding/codecs/opus/audio_encoder_opus_unittest.cc
index a2ebe43bbe..f82ef965db 100644
--- a/third_party/libwebrtc/modules/audio_coding/codecs/opus/audio_encoder_opus_unittest.cc
+++ b/third_party/libwebrtc/modules/audio_coding/codecs/opus/audio_encoder_opus_unittest.cc
@@ -39,7 +39,7 @@ constexpr int kDefaultOpusPacSize = 960;
constexpr int64_t kInitialTimeUs = 12345678;
AudioEncoderOpusConfig CreateConfigWithParameters(
- const SdpAudioFormat::Parameters& params) {
+ const CodecParameterMap& params) {
const SdpAudioFormat format("opus", 48000, 2, params);
return *AudioEncoderOpus::SdpToConfig(format);
}
diff --git a/third_party/libwebrtc/modules/audio_coding/neteq/test/neteq_opus_quality_test.cc b/third_party/libwebrtc/modules/audio_coding/neteq/test/neteq_opus_quality_test.cc
index 5a2df24ef6..eddacd3680 100644
--- a/third_party/libwebrtc/modules/audio_coding/neteq/test/neteq_opus_quality_test.cc
+++ b/third_party/libwebrtc/modules/audio_coding/neteq/test/neteq_opus_quality_test.cc
@@ -106,8 +106,8 @@ NetEqOpusQualityTest::NetEqOpusQualityTest()
// Redefine decoder type if input is stereo.
if (channels_ > 1) {
- audio_format_ = SdpAudioFormat("opus", 48000, 2,
- SdpAudioFormat::Parameters{{"stereo", "1"}});
+ audio_format_ =
+ SdpAudioFormat("opus", 48000, 2, CodecParameterMap{{"stereo", "1"}});
}
application_ = absl::GetFlag(FLAGS_application);
}
diff --git a/third_party/libwebrtc/modules/audio_coding/test/TestStereo.cc b/third_party/libwebrtc/modules/audio_coding/test/TestStereo.cc
index 94a1576026..1e65e4a219 100644
--- a/third_party/libwebrtc/modules/audio_coding/test/TestStereo.cc
+++ b/third_party/libwebrtc/modules/audio_coding/test/TestStereo.cc
@@ -469,7 +469,7 @@ void TestStereo::RegisterSendCodec(char side,
: sampling_freq_hz;
const std::string ptime = rtc::ToString(rtc::CheckedDivExact(
pack_size, rtc::CheckedDivExact(sampling_freq_hz, 1000)));
- SdpAudioFormat::Parameters params = {{"ptime", ptime}};
+ CodecParameterMap params = {{"ptime", ptime}};
RTC_CHECK(channels == 1 || channels == 2);
if (absl::EqualsIgnoreCase(codec_name, "opus")) {
if (channels == 2) {
diff --git a/third_party/libwebrtc/modules/audio_device/BUILD.gn b/third_party/libwebrtc/modules/audio_device/BUILD.gn
index a135f042db..1672be3f95 100644
--- a/third_party/libwebrtc/modules/audio_device/BUILD.gn
+++ b/third_party/libwebrtc/modules/audio_device/BUILD.gn
@@ -91,6 +91,7 @@ if (!build_with_mozilla) { # See Bug 1820869.
"../../system_wrappers",
"../../system_wrappers:metrics",
]
+ absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
}
}
@@ -213,7 +214,6 @@ if (!build_with_chromium) {
"../../rtc_base:platform_thread",
"../../rtc_base:random",
"../../rtc_base:rtc_event",
- "../../rtc_base:rtc_task_queue",
"../../rtc_base:safe_conversions",
"../../rtc_base:timeutils",
"../../rtc_base/synchronization:mutex",
@@ -461,6 +461,7 @@ rtc_source_set("mock_audio_device") {
"../../api:make_ref_counted",
"../../test:test_support",
]
+ absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
}
}
diff --git a/third_party/libwebrtc/modules/audio_device/fine_audio_buffer.cc b/third_party/libwebrtc/modules/audio_device/fine_audio_buffer.cc
index 86240da196..f483b8dc79 100644
--- a/third_party/libwebrtc/modules/audio_device/fine_audio_buffer.cc
+++ b/third_party/libwebrtc/modules/audio_device/fine_audio_buffer.cc
@@ -13,6 +13,7 @@
#include <cstdint>
#include <cstring>
+#include "api/array_view.h"
#include "modules/audio_device/audio_device_buffer.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
@@ -107,7 +108,8 @@ void FineAudioBuffer::GetPlayoutData(rtc::ArrayView<int16_t> audio_buffer,
void FineAudioBuffer::DeliverRecordedData(
rtc::ArrayView<const int16_t> audio_buffer,
- int record_delay_ms) {
+ int record_delay_ms,
+ absl::optional<int64_t> capture_time_ns) {
RTC_DCHECK(IsReadyForRecord());
// Always append new data and grow the buffer when needed.
record_buffer_.AppendData(audio_buffer.data(), audio_buffer.size());
@@ -118,7 +120,8 @@ void FineAudioBuffer::DeliverRecordedData(
record_channels_ * record_samples_per_channel_10ms_;
while (record_buffer_.size() >= num_elements_10ms) {
audio_device_buffer_->SetRecordedBuffer(record_buffer_.data(),
- record_samples_per_channel_10ms_);
+ record_samples_per_channel_10ms_,
+ capture_time_ns);
audio_device_buffer_->SetVQEData(playout_delay_ms_, record_delay_ms);
audio_device_buffer_->DeliverRecordedData();
memmove(record_buffer_.data(), record_buffer_.data() + num_elements_10ms,
diff --git a/third_party/libwebrtc/modules/audio_device/fine_audio_buffer.h b/third_party/libwebrtc/modules/audio_device/fine_audio_buffer.h
index a6c3042bb2..7af41d3b21 100644
--- a/third_party/libwebrtc/modules/audio_device/fine_audio_buffer.h
+++ b/third_party/libwebrtc/modules/audio_device/fine_audio_buffer.h
@@ -11,6 +11,10 @@
#ifndef MODULES_AUDIO_DEVICE_FINE_AUDIO_BUFFER_H_
#define MODULES_AUDIO_DEVICE_FINE_AUDIO_BUFFER_H_
+#include <cstddef>
+#include <cstdint>
+
+#include "absl/types/optional.h"
#include "api/array_view.h"
#include "rtc_base/buffer.h"
@@ -61,7 +65,12 @@ class FineAudioBuffer {
// 5ms of data and sends a total of 10ms to WebRTC and clears the internal
// cache. Call #3 restarts the scheme above.
void DeliverRecordedData(rtc::ArrayView<const int16_t> audio_buffer,
- int record_delay_ms);
+ int record_delay_ms) {
+ DeliverRecordedData(audio_buffer, record_delay_ms, absl::nullopt);
+ }
+ void DeliverRecordedData(rtc::ArrayView<const int16_t> audio_buffer,
+ int record_delay_ms,
+ absl::optional<int64_t> capture_time_ns);
private:
// Device buffer that works with 10ms chunks of data both for playout and
diff --git a/third_party/libwebrtc/modules/audio_device/fine_audio_buffer_unittest.cc b/third_party/libwebrtc/modules/audio_device/fine_audio_buffer_unittest.cc
index 36ea85f7dd..bb9fe63922 100644
--- a/third_party/libwebrtc/modules/audio_device/fine_audio_buffer_unittest.cc
+++ b/third_party/libwebrtc/modules/audio_device/fine_audio_buffer_unittest.cc
@@ -113,7 +113,7 @@ void RunFineBufferTest(int frame_size_in_samples) {
{
InSequence s;
for (int j = 0; j < kNumberOfUpdateBufferCalls - 1; ++j) {
- EXPECT_CALL(audio_device_buffer, SetRecordedBuffer(_, kSamplesPer10Ms))
+ EXPECT_CALL(audio_device_buffer, SetRecordedBuffer(_, kSamplesPer10Ms, _))
.WillOnce(VerifyInputBuffer(j, kChannels * kSamplesPer10Ms))
.RetiresOnSaturation();
}
diff --git a/third_party/libwebrtc/modules/audio_device/include/test_audio_device.cc b/third_party/libwebrtc/modules/audio_device/include/test_audio_device.cc
index 4c29c98f2c..b3923ae67d 100644
--- a/third_party/libwebrtc/modules/audio_device/include/test_audio_device.cc
+++ b/third_party/libwebrtc/modules/audio_device/include/test_audio_device.cc
@@ -21,6 +21,7 @@
#include "absl/strings/string_view.h"
#include "api/array_view.h"
#include "api/make_ref_counted.h"
+#include "api/task_queue/task_queue_factory.h"
#include "common_audio/wav_file.h"
#include "modules/audio_device/audio_device_impl.h"
#include "modules/audio_device/include/audio_device_default.h"
@@ -33,7 +34,6 @@
#include "rtc_base/platform_thread.h"
#include "rtc_base/random.h"
#include "rtc_base/synchronization/mutex.h"
-#include "rtc_base/task_queue.h"
#include "rtc_base/task_utils/repeating_task.h"
#include "rtc_base/thread_annotations.h"
#include "rtc_base/time_utils.h"
diff --git a/third_party/libwebrtc/modules/audio_device/mock_audio_device_buffer.h b/third_party/libwebrtc/modules/audio_device/mock_audio_device_buffer.h
index b0f54c20ff..0b276185da 100644
--- a/third_party/libwebrtc/modules/audio_device/mock_audio_device_buffer.h
+++ b/third_party/libwebrtc/modules/audio_device/mock_audio_device_buffer.h
@@ -11,6 +11,7 @@
#ifndef MODULES_AUDIO_DEVICE_MOCK_AUDIO_DEVICE_BUFFER_H_
#define MODULES_AUDIO_DEVICE_MOCK_AUDIO_DEVICE_BUFFER_H_
+#include "absl/types/optional.h"
#include "modules/audio_device/audio_device_buffer.h"
#include "test/gmock.h"
@@ -24,7 +25,9 @@ class MockAudioDeviceBuffer : public AudioDeviceBuffer {
MOCK_METHOD(int32_t, GetPlayoutData, (void* audioBuffer), (override));
MOCK_METHOD(int32_t,
SetRecordedBuffer,
- (const void* audioBuffer, size_t nSamples),
+ (const void* audioBuffer,
+ size_t nSamples,
+ absl::optional<int64_t> capture_time_ns),
(override));
MOCK_METHOD(void, SetVQEData, (int playDelayMS, int recDelayMS), (override));
MOCK_METHOD(int32_t, DeliverRecordedData, (), (override));
diff --git a/third_party/libwebrtc/modules/audio_device/test_audio_device_impl.cc b/third_party/libwebrtc/modules/audio_device/test_audio_device_impl.cc
index 627e68b36f..a3742ea581 100644
--- a/third_party/libwebrtc/modules/audio_device/test_audio_device_impl.cc
+++ b/third_party/libwebrtc/modules/audio_device/test_audio_device_impl.cc
@@ -19,7 +19,6 @@
#include "modules/audio_device/include/test_audio_device.h"
#include "rtc_base/checks.h"
#include "rtc_base/synchronization/mutex.h"
-#include "rtc_base/task_queue.h"
#include "rtc_base/task_utils/repeating_task.h"
namespace webrtc {
@@ -59,11 +58,10 @@ TestAudioDevice::TestAudioDevice(
}
AudioDeviceGeneric::InitStatus TestAudioDevice::Init() {
- task_queue_ =
- std::make_unique<rtc::TaskQueue>(task_queue_factory_->CreateTaskQueue(
- "TestAudioDeviceModuleImpl", TaskQueueFactory::Priority::NORMAL));
+ task_queue_ = task_queue_factory_->CreateTaskQueue(
+ "TestAudioDeviceModuleImpl", TaskQueueFactory::Priority::NORMAL);
- RepeatingTaskHandle::Start(task_queue_->Get(), [this]() {
+ RepeatingTaskHandle::Start(task_queue_.get(), [this]() {
ProcessAudio();
return TimeDelta::Micros(process_interval_us_);
});
diff --git a/third_party/libwebrtc/modules/audio_device/test_audio_device_impl.h b/third_party/libwebrtc/modules/audio_device/test_audio_device_impl.h
index 36192b7f7f..84b48948ba 100644
--- a/third_party/libwebrtc/modules/audio_device/test_audio_device_impl.h
+++ b/third_party/libwebrtc/modules/audio_device/test_audio_device_impl.h
@@ -14,6 +14,7 @@
#include <memory>
#include <vector>
+#include "api/task_queue/task_queue_base.h"
#include "api/task_queue/task_queue_factory.h"
#include "modules/audio_device/audio_device_buffer.h"
#include "modules/audio_device/audio_device_generic.h"
@@ -22,7 +23,6 @@
#include "modules/audio_device/include/test_audio_device.h"
#include "rtc_base/buffer.h"
#include "rtc_base/synchronization/mutex.h"
-#include "rtc_base/task_queue.h"
namespace webrtc {
@@ -190,7 +190,7 @@ class TestAudioDevice : public AudioDeviceGeneric {
std::vector<int16_t> playout_buffer_ RTC_GUARDED_BY(lock_);
rtc::BufferT<int16_t> recording_buffer_ RTC_GUARDED_BY(lock_);
- std::unique_ptr<rtc::TaskQueue> task_queue_;
+ std::unique_ptr<TaskQueueBase, TaskQueueDeleter> task_queue_;
};
} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/audio_processing/BUILD.gn b/third_party/libwebrtc/modules/audio_processing/BUILD.gn
index 6aca7dee46..817a3515b0 100644
--- a/third_party/libwebrtc/modules/audio_processing/BUILD.gn
+++ b/third_party/libwebrtc/modules/audio_processing/BUILD.gn
@@ -34,6 +34,7 @@ rtc_library("api") {
"../../api/audio:aec3_config",
"../../api/audio:audio_frame_api",
"../../api/audio:echo_control",
+ "../../api/task_queue",
"../../rtc_base:macromagic",
"../../rtc_base:refcount",
"../../rtc_base:stringutils",
@@ -43,6 +44,7 @@ rtc_library("api") {
"agc:gain_control_interface",
]
absl_deps = [
+ "//third_party/abseil-cpp/absl/base:nullability",
"//third_party/abseil-cpp/absl/strings",
"//third_party/abseil-cpp/absl/types:optional",
]
@@ -185,6 +187,7 @@ rtc_library("audio_processing") {
"../../api/audio:aec3_config",
"../../api/audio:audio_frame_api",
"../../api/audio:echo_control",
+ "../../api/task_queue",
"../../audio/utility:audio_frame_operations",
"../../common_audio:common_audio_c",
"../../common_audio/third_party/ooura:fft_size_256",
@@ -217,6 +220,7 @@ rtc_library("audio_processing") {
"vad",
]
absl_deps = [
+ "//third_party/abseil-cpp/absl/base:nullability",
"//third_party/abseil-cpp/absl/strings",
"//third_party/abseil-cpp/absl/types:optional",
]
@@ -337,9 +341,13 @@ if (rtc_include_tests) {
":audio_buffer",
":audio_processing",
":audio_processing_statistics",
+ "../../api/task_queue",
"../../test:test_support",
]
- absl_deps = [ "//third_party/abseil-cpp/absl/strings" ]
+ absl_deps = [
+ "//third_party/abseil-cpp/absl/base:nullability",
+ "//third_party/abseil-cpp/absl/strings",
+ ]
}
if (!build_with_chromium) {
diff --git a/third_party/libwebrtc/modules/audio_processing/aec_dump/BUILD.gn b/third_party/libwebrtc/modules/audio_processing/aec_dump/BUILD.gn
index 78bae56835..5193e28dff 100644
--- a/third_party/libwebrtc/modules/audio_processing/aec_dump/BUILD.gn
+++ b/third_party/libwebrtc/modules/audio_processing/aec_dump/BUILD.gn
@@ -14,10 +14,14 @@ rtc_source_set("aec_dump") {
deps = [
"..:aec_dump_interface",
+ "../../../api/task_queue",
"../../../rtc_base/system:file_wrapper",
"../../../rtc_base/system:rtc_export",
]
- absl_deps = [ "//third_party/abseil-cpp/absl/strings" ]
+ absl_deps = [
+ "//third_party/abseil-cpp/absl/base:nullability",
+ "//third_party/abseil-cpp/absl/strings",
+ ]
}
if (rtc_include_tests) {
@@ -71,11 +75,13 @@ if (rtc_enable_protobuf) {
"../../../rtc_base:protobuf_utils",
"../../../rtc_base:race_checker",
"../../../rtc_base:rtc_event",
- "../../../rtc_base:rtc_task_queue",
"../../../rtc_base/system:file_wrapper",
"../../../system_wrappers",
]
- absl_deps = [ "//third_party/abseil-cpp/absl/strings" ]
+ absl_deps = [
+ "//third_party/abseil-cpp/absl/base:nullability",
+ "//third_party/abseil-cpp/absl/strings",
+ ]
deps += [ "../:audioproc_debug_proto" ]
}
@@ -106,6 +112,10 @@ rtc_library("null_aec_dump_factory") {
deps = [
":aec_dump",
"..:aec_dump_interface",
+ "../../../api/task_queue",
+ ]
+ absl_deps = [
+ "//third_party/abseil-cpp/absl/base:nullability",
+ "//third_party/abseil-cpp/absl/strings",
]
- absl_deps = [ "//third_party/abseil-cpp/absl/strings" ]
}
diff --git a/third_party/libwebrtc/modules/audio_processing/aec_dump/aec_dump_factory.h b/third_party/libwebrtc/modules/audio_processing/aec_dump/aec_dump_factory.h
index 20718c3d7f..0d258a9ebc 100644
--- a/third_party/libwebrtc/modules/audio_processing/aec_dump/aec_dump_factory.h
+++ b/third_party/libwebrtc/modules/audio_processing/aec_dump/aec_dump_factory.h
@@ -13,34 +13,34 @@
#include <memory>
+#include "absl/base/nullability.h"
#include "absl/strings/string_view.h"
+#include "api/task_queue/task_queue_base.h"
#include "modules/audio_processing/include/aec_dump.h"
#include "rtc_base/system/file_wrapper.h"
#include "rtc_base/system/rtc_export.h"
-namespace rtc {
-class TaskQueue;
-} // namespace rtc
-
namespace webrtc {
class RTC_EXPORT AecDumpFactory {
public:
- // The `worker_queue` may not be null and must outlive the created
- // AecDump instance. `max_log_size_bytes == -1` means the log size
- // will be unlimited. `handle` may not be null. The AecDump takes
- // responsibility for `handle` and closes it in the destructor. A
- // non-null return value indicates that the file has been
+ // The `worker_queue` must outlive the created AecDump instance.
+ // `max_log_size_bytes == -1` means the log size will be unlimited.
+ // The AecDump takes responsibility for `handle` and closes it in the
+ // destructor. A non-null return value indicates that the file has been
// sucessfully opened.
- static std::unique_ptr<AecDump> Create(webrtc::FileWrapper file,
- int64_t max_log_size_bytes,
- rtc::TaskQueue* worker_queue);
- static std::unique_ptr<AecDump> Create(absl::string_view file_name,
- int64_t max_log_size_bytes,
- rtc::TaskQueue* worker_queue);
- static std::unique_ptr<AecDump> Create(FILE* handle,
- int64_t max_log_size_bytes,
- rtc::TaskQueue* worker_queue);
+ static absl::Nullable<std::unique_ptr<AecDump>> Create(
+ FileWrapper file,
+ int64_t max_log_size_bytes,
+ absl::Nonnull<TaskQueueBase*> worker_queue);
+ static absl::Nullable<std::unique_ptr<AecDump>> Create(
+ absl::string_view file_name,
+ int64_t max_log_size_bytes,
+ absl::Nonnull<TaskQueueBase*> worker_queue);
+ static absl::Nullable<std::unique_ptr<AecDump>> Create(
+ absl::Nonnull<FILE*> handle,
+ int64_t max_log_size_bytes,
+ absl::Nonnull<TaskQueueBase*> worker_queue);
};
} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/audio_processing/aec_dump/aec_dump_impl.cc b/third_party/libwebrtc/modules/audio_processing/aec_dump/aec_dump_impl.cc
index 94c24048e0..8484fcc6e1 100644
--- a/third_party/libwebrtc/modules/audio_processing/aec_dump/aec_dump_impl.cc
+++ b/third_party/libwebrtc/modules/audio_processing/aec_dump/aec_dump_impl.cc
@@ -13,11 +13,12 @@
#include <memory>
#include <utility>
+#include "absl/base/nullability.h"
#include "absl/strings/string_view.h"
+#include "api/task_queue/task_queue_base.h"
#include "modules/audio_processing/aec_dump/aec_dump_factory.h"
#include "rtc_base/checks.h"
#include "rtc_base/event.h"
-#include "rtc_base/task_queue.h"
namespace webrtc {
@@ -59,7 +60,7 @@ void CopyFromConfigToEvent(const webrtc::InternalAPMConfig& config,
AecDumpImpl::AecDumpImpl(FileWrapper debug_file,
int64_t max_log_size_bytes,
- rtc::TaskQueue* worker_queue)
+ absl::Nonnull<TaskQueueBase*> worker_queue)
: debug_file_(std::move(debug_file)),
num_bytes_left_for_log_(max_log_size_bytes),
worker_queue_(worker_queue) {}
@@ -254,9 +255,10 @@ void AecDumpImpl::PostWriteToFileTask(std::unique_ptr<audioproc::Event> event) {
});
}
-std::unique_ptr<AecDump> AecDumpFactory::Create(webrtc::FileWrapper file,
- int64_t max_log_size_bytes,
- rtc::TaskQueue* worker_queue) {
+absl::Nullable<std::unique_ptr<AecDump>> AecDumpFactory::Create(
+ FileWrapper file,
+ int64_t max_log_size_bytes,
+ absl::Nonnull<TaskQueueBase*> worker_queue) {
RTC_DCHECK(worker_queue);
if (!file.is_open())
return nullptr;
@@ -265,16 +267,18 @@ std::unique_ptr<AecDump> AecDumpFactory::Create(webrtc::FileWrapper file,
worker_queue);
}
-std::unique_ptr<AecDump> AecDumpFactory::Create(absl::string_view file_name,
- int64_t max_log_size_bytes,
- rtc::TaskQueue* worker_queue) {
+absl::Nullable<std::unique_ptr<AecDump>> AecDumpFactory::Create(
+ absl::string_view file_name,
+ int64_t max_log_size_bytes,
+ absl::Nonnull<TaskQueueBase*> worker_queue) {
return Create(FileWrapper::OpenWriteOnly(file_name), max_log_size_bytes,
worker_queue);
}
-std::unique_ptr<AecDump> AecDumpFactory::Create(FILE* handle,
- int64_t max_log_size_bytes,
- rtc::TaskQueue* worker_queue) {
+absl::Nullable<std::unique_ptr<AecDump>> AecDumpFactory::Create(
+ absl::Nonnull<FILE*> handle,
+ int64_t max_log_size_bytes,
+ absl::Nonnull<TaskQueueBase*> worker_queue) {
return Create(FileWrapper(handle), max_log_size_bytes, worker_queue);
}
diff --git a/third_party/libwebrtc/modules/audio_processing/aec_dump/aec_dump_impl.h b/third_party/libwebrtc/modules/audio_processing/aec_dump/aec_dump_impl.h
index 429808f9af..d5af31b01e 100644
--- a/third_party/libwebrtc/modules/audio_processing/aec_dump/aec_dump_impl.h
+++ b/third_party/libwebrtc/modules/audio_processing/aec_dump/aec_dump_impl.h
@@ -15,11 +15,11 @@
#include <string>
#include <vector>
+#include "api/task_queue/task_queue_base.h"
#include "modules/audio_processing/aec_dump/capture_stream_info.h"
#include "modules/audio_processing/include/aec_dump.h"
#include "rtc_base/race_checker.h"
#include "rtc_base/system/file_wrapper.h"
-#include "rtc_base/task_queue.h"
#include "rtc_base/thread_annotations.h"
// Files generated at build-time by the protobuf compiler.
@@ -39,7 +39,7 @@ class AecDumpImpl : public AecDump {
// `max_log_size_bytes == -1` means the log size will be unlimited.
AecDumpImpl(FileWrapper debug_file,
int64_t max_log_size_bytes,
- rtc::TaskQueue* worker_queue);
+ absl::Nonnull<TaskQueueBase*> worker_queue);
AecDumpImpl(const AecDumpImpl&) = delete;
AecDumpImpl& operator=(const AecDumpImpl&) = delete;
~AecDumpImpl() override;
@@ -74,7 +74,7 @@ class AecDumpImpl : public AecDump {
FileWrapper debug_file_;
int64_t num_bytes_left_for_log_ = 0;
rtc::RaceChecker race_checker_;
- rtc::TaskQueue* worker_queue_;
+ absl::Nonnull<TaskQueueBase*> worker_queue_;
CaptureStreamInfo capture_stream_info_;
};
} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/audio_processing/aec_dump/aec_dump_unittest.cc b/third_party/libwebrtc/modules/audio_processing/aec_dump/aec_dump_unittest.cc
index 62f896fe14..2a8110c4fc 100644
--- a/third_party/libwebrtc/modules/audio_processing/aec_dump/aec_dump_unittest.cc
+++ b/third_party/libwebrtc/modules/audio_processing/aec_dump/aec_dump_unittest.cc
@@ -28,7 +28,7 @@ TEST(AecDumper, APICallsDoNotCrash) {
{
std::unique_ptr<webrtc::AecDump> aec_dump =
- webrtc::AecDumpFactory::Create(filename, -1, &file_writer_queue);
+ webrtc::AecDumpFactory::Create(filename, -1, file_writer_queue.Get());
constexpr int kNumChannels = 1;
constexpr int kNumSamplesPerChannel = 160;
@@ -63,7 +63,7 @@ TEST(AecDumper, WriteToFile) {
{
std::unique_ptr<webrtc::AecDump> aec_dump =
- webrtc::AecDumpFactory::Create(filename, -1, &file_writer_queue);
+ webrtc::AecDumpFactory::Create(filename, -1, file_writer_queue.Get());
constexpr int kNumChannels = 1;
constexpr int kNumSamplesPerChannel = 160;
diff --git a/third_party/libwebrtc/modules/audio_processing/aec_dump/null_aec_dump_factory.cc b/third_party/libwebrtc/modules/audio_processing/aec_dump/null_aec_dump_factory.cc
index 9bd9745069..63929afac4 100644
--- a/third_party/libwebrtc/modules/audio_processing/aec_dump/null_aec_dump_factory.cc
+++ b/third_party/libwebrtc/modules/audio_processing/aec_dump/null_aec_dump_factory.cc
@@ -8,27 +8,32 @@
* be found in the AUTHORS file in the root of the source tree.
*/
+#include "absl/base/nullability.h"
#include "absl/strings/string_view.h"
+#include "api/task_queue/task_queue_base.h"
#include "modules/audio_processing/aec_dump/aec_dump_factory.h"
#include "modules/audio_processing/include/aec_dump.h"
namespace webrtc {
-std::unique_ptr<AecDump> AecDumpFactory::Create(webrtc::FileWrapper file,
- int64_t max_log_size_bytes,
- rtc::TaskQueue* worker_queue) {
+absl::Nullable<std::unique_ptr<AecDump>> AecDumpFactory::Create(
+ FileWrapper file,
+ int64_t max_log_size_bytes,
+ absl::Nonnull<TaskQueueBase*> worker_queue) {
return nullptr;
}
-std::unique_ptr<AecDump> AecDumpFactory::Create(absl::string_view file_name,
- int64_t max_log_size_bytes,
- rtc::TaskQueue* worker_queue) {
+absl::Nullable<std::unique_ptr<AecDump>> AecDumpFactory::Create(
+ absl::string_view file_name,
+ int64_t max_log_size_bytes,
+ absl::Nonnull<TaskQueueBase*> worker_queue) {
return nullptr;
}
-std::unique_ptr<AecDump> AecDumpFactory::Create(FILE* handle,
- int64_t max_log_size_bytes,
- rtc::TaskQueue* worker_queue) {
+absl::Nullable<std::unique_ptr<AecDump>> AecDumpFactory::Create(
+ absl::Nonnull<FILE*> handle,
+ int64_t max_log_size_bytes,
+ absl::Nonnull<TaskQueueBase*> worker_queue) {
return nullptr;
}
} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/audio_processing/audio_processing_impl.cc b/third_party/libwebrtc/modules/audio_processing/audio_processing_impl.cc
index c304453388..4ac074526c 100644
--- a/third_party/libwebrtc/modules/audio_processing/audio_processing_impl.cc
+++ b/third_party/libwebrtc/modules/audio_processing/audio_processing_impl.cc
@@ -18,11 +18,13 @@
#include <type_traits>
#include <utility>
+#include "absl/base/nullability.h"
#include "absl/strings/match.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "api/array_view.h"
#include "api/audio/audio_frame.h"
+#include "api/task_queue/task_queue_base.h"
#include "common_audio/audio_converter.h"
#include "common_audio/include/audio_util.h"
#include "modules/audio_processing/aec_dump/aec_dump_factory.h"
@@ -2083,9 +2085,10 @@ void AudioProcessingImpl::UpdateRecommendedInputVolumeLocked() {
capture_.recommended_input_volume = capture_.applied_input_volume;
}
-bool AudioProcessingImpl::CreateAndAttachAecDump(absl::string_view file_name,
- int64_t max_log_size_bytes,
- rtc::TaskQueue* worker_queue) {
+bool AudioProcessingImpl::CreateAndAttachAecDump(
+ absl::string_view file_name,
+ int64_t max_log_size_bytes,
+ absl::Nonnull<TaskQueueBase*> worker_queue) {
std::unique_ptr<AecDump> aec_dump =
AecDumpFactory::Create(file_name, max_log_size_bytes, worker_queue);
if (!aec_dump) {
@@ -2096,9 +2099,10 @@ bool AudioProcessingImpl::CreateAndAttachAecDump(absl::string_view file_name,
return true;
}
-bool AudioProcessingImpl::CreateAndAttachAecDump(FILE* handle,
- int64_t max_log_size_bytes,
- rtc::TaskQueue* worker_queue) {
+bool AudioProcessingImpl::CreateAndAttachAecDump(
+ FILE* handle,
+ int64_t max_log_size_bytes,
+ absl::Nonnull<TaskQueueBase*> worker_queue) {
std::unique_ptr<AecDump> aec_dump =
AecDumpFactory::Create(handle, max_log_size_bytes, worker_queue);
if (!aec_dump) {
diff --git a/third_party/libwebrtc/modules/audio_processing/audio_processing_impl.h b/third_party/libwebrtc/modules/audio_processing/audio_processing_impl.h
index 1e058b5a32..2c0ab198db 100644
--- a/third_party/libwebrtc/modules/audio_processing/audio_processing_impl.h
+++ b/third_party/libwebrtc/modules/audio_processing/audio_processing_impl.h
@@ -19,10 +19,12 @@
#include <string>
#include <vector>
+#include "absl/base/nullability.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "api/array_view.h"
#include "api/function_view.h"
+#include "api/task_queue/task_queue_base.h"
#include "modules/audio_processing/aec3/echo_canceller3.h"
#include "modules/audio_processing/agc/agc_manager_direct.h"
#include "modules/audio_processing/agc/gain_control.h"
@@ -71,12 +73,14 @@ class AudioProcessingImpl : public AudioProcessing {
int Initialize() override;
int Initialize(const ProcessingConfig& processing_config) override;
void ApplyConfig(const AudioProcessing::Config& config) override;
- bool CreateAndAttachAecDump(absl::string_view file_name,
- int64_t max_log_size_bytes,
- rtc::TaskQueue* worker_queue) override;
- bool CreateAndAttachAecDump(FILE* handle,
- int64_t max_log_size_bytes,
- rtc::TaskQueue* worker_queue) override;
+ bool CreateAndAttachAecDump(
+ absl::string_view file_name,
+ int64_t max_log_size_bytes,
+ absl::Nonnull<TaskQueueBase*> worker_queue) override;
+ bool CreateAndAttachAecDump(
+ FILE* handle,
+ int64_t max_log_size_bytes,
+ absl::Nonnull<TaskQueueBase*> worker_queue) override;
// TODO(webrtc:5298) Deprecated variant.
void AttachAecDump(std::unique_ptr<AecDump> aec_dump) override;
void DetachAecDump() override;
diff --git a/third_party/libwebrtc/modules/audio_processing/audio_processing_unittest.cc b/third_party/libwebrtc/modules/audio_processing/audio_processing_unittest.cc
index c2bedb2da4..2d3684e9b5 100644
--- a/third_party/libwebrtc/modules/audio_processing/audio_processing_unittest.cc
+++ b/third_party/libwebrtc/modules/audio_processing/audio_processing_unittest.cc
@@ -1485,8 +1485,8 @@ void ApmTest::ProcessDebugDump(absl::string_view in_filename,
if (first_init) {
// AttachAecDump() writes an additional init message. Don't start
// recording until after the first init to avoid the extra message.
- auto aec_dump =
- AecDumpFactory::Create(out_filename, max_size_bytes, &worker_queue);
+ auto aec_dump = AecDumpFactory::Create(out_filename, max_size_bytes,
+ worker_queue.Get());
EXPECT_TRUE(aec_dump);
apm_->AttachAecDump(std::move(aec_dump));
first_init = false;
@@ -1632,7 +1632,7 @@ TEST_F(ApmTest, DebugDump) {
const std::string filename =
test::TempFilename(test::OutputPath(), "debug_aec");
{
- auto aec_dump = AecDumpFactory::Create("", -1, &worker_queue);
+ auto aec_dump = AecDumpFactory::Create("", -1, worker_queue.Get());
EXPECT_FALSE(aec_dump);
}
@@ -1640,7 +1640,7 @@ TEST_F(ApmTest, DebugDump) {
// Stopping without having started should be OK.
apm_->DetachAecDump();
- auto aec_dump = AecDumpFactory::Create(filename, -1, &worker_queue);
+ auto aec_dump = AecDumpFactory::Create(filename, -1, worker_queue.Get());
EXPECT_TRUE(aec_dump);
apm_->AttachAecDump(std::move(aec_dump));
EXPECT_EQ(apm_->kNoError,
@@ -1683,7 +1683,7 @@ TEST_F(ApmTest, DebugDumpFromFileHandle) {
// Stopping without having started should be OK.
apm_->DetachAecDump();
- auto aec_dump = AecDumpFactory::Create(std::move(f), -1, &worker_queue);
+ auto aec_dump = AecDumpFactory::Create(std::move(f), -1, worker_queue.Get());
EXPECT_TRUE(aec_dump);
apm_->AttachAecDump(std::move(aec_dump));
EXPECT_EQ(apm_->kNoError,
diff --git a/third_party/libwebrtc/modules/audio_processing/include/audio_processing.h b/third_party/libwebrtc/modules/audio_processing/include/audio_processing.h
index e3223513af..dd484be4f1 100644
--- a/third_party/libwebrtc/modules/audio_processing/include/audio_processing.h
+++ b/third_party/libwebrtc/modules/audio_processing/include/audio_processing.h
@@ -23,6 +23,7 @@
#include <vector>
+#include "absl/base/nullability.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "api/array_view.h"
@@ -30,15 +31,12 @@
#include "api/audio/echo_control.h"
#include "api/ref_count.h"
#include "api/scoped_refptr.h"
+#include "api/task_queue/task_queue_base.h"
#include "modules/audio_processing/include/audio_processing_statistics.h"
#include "rtc_base/arraysize.h"
#include "rtc_base/system/file_wrapper.h"
#include "rtc_base/system/rtc_export.h"
-namespace rtc {
-class TaskQueue;
-} // namespace rtc
-
namespace webrtc {
class AecDump;
@@ -632,12 +630,14 @@ class RTC_EXPORT AudioProcessing : public RefCountInterface {
// return value of true indicates that the file has been
// sucessfully opened, while a value of false indicates that
// opening the file failed.
- virtual bool CreateAndAttachAecDump(absl::string_view file_name,
- int64_t max_log_size_bytes,
- rtc::TaskQueue* worker_queue) = 0;
- virtual bool CreateAndAttachAecDump(FILE* handle,
- int64_t max_log_size_bytes,
- rtc::TaskQueue* worker_queue) = 0;
+ virtual bool CreateAndAttachAecDump(
+ absl::string_view file_name,
+ int64_t max_log_size_bytes,
+ absl::Nonnull<TaskQueueBase*> worker_queue) = 0;
+ virtual bool CreateAndAttachAecDump(
+ absl::Nonnull<FILE*> handle,
+ int64_t max_log_size_bytes,
+ absl::Nonnull<TaskQueueBase*> worker_queue) = 0;
// TODO(webrtc:5298) Deprecated variant.
// Attaches provided webrtc::AecDump for recording debugging
diff --git a/third_party/libwebrtc/modules/audio_processing/include/mock_audio_processing.h b/third_party/libwebrtc/modules/audio_processing/include/mock_audio_processing.h
index 2ea1a865c3..dfe7d84e07 100644
--- a/third_party/libwebrtc/modules/audio_processing/include/mock_audio_processing.h
+++ b/third_party/libwebrtc/modules/audio_processing/include/mock_audio_processing.h
@@ -13,7 +13,9 @@
#include <memory>
+#include "absl/base/nullability.h"
#include "absl/strings/string_view.h"
+#include "api/task_queue/task_queue_base.h"
#include "modules/audio_processing/include/aec_dump.h"
#include "modules/audio_processing/include/audio_processing.h"
#include "modules/audio_processing/include/audio_processing_statistics.h"
@@ -155,13 +157,13 @@ class MockAudioProcessing : public AudioProcessing {
CreateAndAttachAecDump,
(absl::string_view file_name,
int64_t max_log_size_bytes,
- rtc::TaskQueue* worker_queue),
+ absl::Nonnull<TaskQueueBase*> worker_queue),
(override));
MOCK_METHOD(bool,
CreateAndAttachAecDump,
(FILE * handle,
int64_t max_log_size_bytes,
- rtc::TaskQueue* worker_queue),
+ absl::Nonnull<TaskQueueBase*> worker_queue),
(override));
MOCK_METHOD(void, AttachAecDump, (std::unique_ptr<AecDump>), (override));
MOCK_METHOD(void, DetachAecDump, (), (override));
diff --git a/third_party/libwebrtc/modules/audio_processing/test/audio_processing_simulator.cc b/third_party/libwebrtc/modules/audio_processing/test/audio_processing_simulator.cc
index 7bd6da0133..500005f26a 100644
--- a/third_party/libwebrtc/modules/audio_processing/test/audio_processing_simulator.cc
+++ b/third_party/libwebrtc/modules/audio_processing/test/audio_processing_simulator.cc
@@ -622,7 +622,7 @@ void AudioProcessingSimulator::ConfigureAudioProcessor() {
if (settings_.aec_dump_output_filename) {
ap_->AttachAecDump(AecDumpFactory::Create(
- *settings_.aec_dump_output_filename, -1, &worker_queue_));
+ *settings_.aec_dump_output_filename, -1, worker_queue_.Get()));
}
}
diff --git a/third_party/libwebrtc/modules/audio_processing/test/debug_dump_test.cc b/third_party/libwebrtc/modules/audio_processing/test/debug_dump_test.cc
index cded5de217..0d3eefa94a 100644
--- a/third_party/libwebrtc/modules/audio_processing/test/debug_dump_test.cc
+++ b/third_party/libwebrtc/modules/audio_processing/test/debug_dump_test.cc
@@ -197,7 +197,7 @@ void DebugDumpGenerator::SetOutputChannels(int channels) {
void DebugDumpGenerator::StartRecording() {
apm_->AttachAecDump(
- AecDumpFactory::Create(dump_file_name_.c_str(), -1, &worker_queue_));
+ AecDumpFactory::Create(dump_file_name_.c_str(), -1, worker_queue_.Get()));
}
void DebugDumpGenerator::Process(size_t num_blocks) {
diff --git a/third_party/libwebrtc/modules/congestion_controller/goog_cc/delay_based_bwe_unittest_helper.cc b/third_party/libwebrtc/modules/congestion_controller/goog_cc/delay_based_bwe_unittest_helper.cc
index 16bd4153a6..e56aa4a09c 100644
--- a/third_party/libwebrtc/modules/congestion_controller/goog_cc/delay_based_bwe_unittest_helper.cc
+++ b/third_party/libwebrtc/modules/congestion_controller/goog_cc/delay_based_bwe_unittest_helper.cc
@@ -26,6 +26,7 @@
#include "modules/congestion_controller/goog_cc/probe_bitrate_estimator.h"
#include "rtc_base/checks.h"
#include "test/field_trial.h"
+#include "test/gtest.h"
namespace webrtc {
constexpr size_t kMtu = 1200;
@@ -52,6 +53,7 @@ RtpStream::RtpStream(int fps, int bitrate_bps)
// previous frame, no frame will be generated. The frame is split into
// packets.
int64_t RtpStream::GenerateFrame(int64_t time_now_us,
+ int64_t* next_sequence_number,
std::vector<PacketResult>* packets) {
if (time_now_us < next_rtp_time_) {
return next_rtp_time_;
@@ -66,6 +68,7 @@ int64_t RtpStream::GenerateFrame(int64_t time_now_us,
packet.sent_packet.send_time =
Timestamp::Micros(time_now_us + kSendSideOffsetUs);
packet.sent_packet.size = DataSize::Bytes(payload_size);
+ packet.sent_packet.sequence_number = (*next_sequence_number)++;
packets->push_back(packet);
}
next_rtp_time_ = time_now_us + (1000000 + fps_ / 2) / fps_;
@@ -131,14 +134,15 @@ void StreamGenerator::SetBitrateBps(int bitrate_bps) {
// TODO(holmer): Break out the channel simulation part from this class to make
// it possible to simulate different types of channels.
-int64_t StreamGenerator::GenerateFrame(std::vector<PacketResult>* packets,
- int64_t time_now_us) {
+int64_t StreamGenerator::GenerateFrame(int64_t time_now_us,
+ int64_t* next_sequence_number,
+ std::vector<PacketResult>* packets) {
RTC_CHECK(packets != NULL);
RTC_CHECK(packets->empty());
RTC_CHECK_GT(capacity_, 0);
auto it =
std::min_element(streams_.begin(), streams_.end(), RtpStream::Compare);
- (*it)->GenerateFrame(time_now_us, packets);
+ (*it)->GenerateFrame(time_now_us, next_sequence_number, packets);
for (PacketResult& packet : *packets) {
int capacity_bpus = capacity_ / 1000;
int64_t required_network_time_us =
@@ -233,8 +237,8 @@ bool DelayBasedBweTest::GenerateAndProcessFrame(uint32_t ssrc,
stream_generator_->SetBitrateBps(bitrate_bps);
std::vector<PacketResult> packets;
- int64_t next_time_us =
- stream_generator_->GenerateFrame(&packets, clock_.TimeInMicroseconds());
+ int64_t next_time_us = stream_generator_->GenerateFrame(
+ clock_.TimeInMicroseconds(), &next_sequence_number_, &packets);
if (packets.empty())
return false;
diff --git a/third_party/libwebrtc/modules/congestion_controller/goog_cc/delay_based_bwe_unittest_helper.h b/third_party/libwebrtc/modules/congestion_controller/goog_cc/delay_based_bwe_unittest_helper.h
index 89eb1a353f..634e6a4d82 100644
--- a/third_party/libwebrtc/modules/congestion_controller/goog_cc/delay_based_bwe_unittest_helper.h
+++ b/third_party/libwebrtc/modules/congestion_controller/goog_cc/delay_based_bwe_unittest_helper.h
@@ -15,12 +15,11 @@
#include <stdint.h>
#include <memory>
-#include <string>
#include <vector>
-#include "absl/strings/string_view.h"
#include "api/transport/field_trial_based_config.h"
#include "api/transport/network_types.h"
+#include "api/units/timestamp.h"
#include "modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator.h"
#include "modules/congestion_controller/goog_cc/delay_based_bwe.h"
#include "system_wrappers/include/clock.h"
@@ -61,6 +60,7 @@ class RtpStream {
// previous frame, no frame will be generated. The frame is split into
// packets.
int64_t GenerateFrame(int64_t time_now_us,
+ int64_t* next_sequence_number,
std::vector<PacketResult>* packets);
// The send-side time when the next frame can be generated.
@@ -102,8 +102,9 @@ class StreamGenerator {
// TODO(holmer): Break out the channel simulation part from this class to make
// it possible to simulate different types of channels.
- int64_t GenerateFrame(std::vector<PacketResult>* packets,
- int64_t time_now_us);
+ int64_t GenerateFrame(int64_t time_now_us,
+ int64_t* next_sequence_number,
+ std::vector<PacketResult>* packets);
private:
// Capacity of the simulated channel in bits per second.
diff --git a/third_party/libwebrtc/modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.cc b/third_party/libwebrtc/modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.cc
index 22693d67e9..211d86c95d 100644
--- a/third_party/libwebrtc/modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.cc
+++ b/third_party/libwebrtc/modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.cc
@@ -281,7 +281,8 @@ void SendSideBandwidthEstimation::OnRouteChange() {
uma_update_state_ = kNoUpdate;
uma_rtt_state_ = kNoUpdate;
last_rtc_event_log_ = Timestamp::MinusInfinity();
- if (loss_based_bandwidth_estimator_v2_->UseInStartPhase()) {
+ if (LossBasedBandwidthEstimatorV2Enabled() &&
+ loss_based_bandwidth_estimator_v2_->UseInStartPhase()) {
loss_based_bandwidth_estimator_v2_.reset(
new LossBasedBweV2(key_value_config_));
}
diff --git a/third_party/libwebrtc/modules/congestion_controller/rtp/BUILD.gn b/third_party/libwebrtc/modules/congestion_controller/rtp/BUILD.gn
index cd13332b7f..8ec755e1aa 100644
--- a/third_party/libwebrtc/modules/congestion_controller/rtp/BUILD.gn
+++ b/third_party/libwebrtc/modules/congestion_controller/rtp/BUILD.gn
@@ -34,7 +34,6 @@ rtc_library("control_handler") {
"../../../rtc_base:safe_conversions",
"../../../rtc_base:safe_minmax",
"../../../rtc_base/system:no_unique_address",
- "../../../system_wrappers:field_trial",
"../../pacing",
]
absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
diff --git a/third_party/libwebrtc/modules/congestion_controller/rtp/control_handler.cc b/third_party/libwebrtc/modules/congestion_controller/rtp/control_handler.cc
index da6451c97e..357a0f0798 100644
--- a/third_party/libwebrtc/modules/congestion_controller/rtp/control_handler.cc
+++ b/third_party/libwebrtc/modules/congestion_controller/rtp/control_handler.cc
@@ -18,21 +18,8 @@
#include "rtc_base/logging.h"
#include "rtc_base/numerics/safe_conversions.h"
#include "rtc_base/numerics/safe_minmax.h"
-#include "system_wrappers/include/field_trial.h"
namespace webrtc {
-namespace {
-
-// By default, pacer emergency stops encoder when buffer reaches a high level.
-bool IsPacerEmergencyStopDisabled() {
- return field_trial::IsEnabled("WebRTC-DisablePacerEmergencyStop");
-}
-
-} // namespace
-CongestionControlHandler::CongestionControlHandler()
- : disable_pacer_emergency_stop_(IsPacerEmergencyStopDisabled()) {}
-
-CongestionControlHandler::~CongestionControlHandler() {}
void CongestionControlHandler::SetTargetRate(
TargetTransferRate new_target_rate) {
@@ -60,9 +47,8 @@ absl::optional<TargetTransferRate> CongestionControlHandler::GetUpdate() {
bool pause_encoding = false;
if (!network_available_) {
pause_encoding = true;
- } else if (!disable_pacer_emergency_stop_ &&
- pacer_expected_queue_ms_ >
- PacingController::kMaxExpectedQueueLength.ms()) {
+ } else if (pacer_expected_queue_ms_ >
+ PacingController::kMaxExpectedQueueLength.ms()) {
pause_encoding = true;
}
if (pause_encoding)
diff --git a/third_party/libwebrtc/modules/congestion_controller/rtp/control_handler.h b/third_party/libwebrtc/modules/congestion_controller/rtp/control_handler.h
index d8e7263a02..649d2f911e 100644
--- a/third_party/libwebrtc/modules/congestion_controller/rtp/control_handler.h
+++ b/third_party/libwebrtc/modules/congestion_controller/rtp/control_handler.h
@@ -28,12 +28,13 @@ namespace webrtc {
// destruction unless members are properly ordered.
class CongestionControlHandler {
public:
- CongestionControlHandler();
- ~CongestionControlHandler();
+ CongestionControlHandler() = default;
CongestionControlHandler(const CongestionControlHandler&) = delete;
CongestionControlHandler& operator=(const CongestionControlHandler&) = delete;
+ ~CongestionControlHandler() = default;
+
void SetTargetRate(TargetTransferRate new_target_rate);
void SetNetworkAvailability(bool network_available);
void SetPacerQueue(TimeDelta expected_queue_time);
@@ -45,7 +46,6 @@ class CongestionControlHandler {
bool network_available_ = true;
bool encoder_paused_in_last_report_ = false;
- const bool disable_pacer_emergency_stop_;
int64_t pacer_expected_queue_ms_ = 0;
RTC_NO_UNIQUE_ADDRESS SequenceChecker sequenced_checker_;
diff --git a/third_party/libwebrtc/modules/pacing/BUILD.gn b/third_party/libwebrtc/modules/pacing/BUILD.gn
index ea80c8c819..87498817b6 100644
--- a/third_party/libwebrtc/modules/pacing/BUILD.gn
+++ b/third_party/libwebrtc/modules/pacing/BUILD.gn
@@ -63,6 +63,7 @@ rtc_library("pacing") {
]
absl_deps = [
"//third_party/abseil-cpp/absl/cleanup",
+ "//third_party/abseil-cpp/absl/container:inlined_vector",
"//third_party/abseil-cpp/absl/memory",
"//third_party/abseil-cpp/absl/strings",
"//third_party/abseil-cpp/absl/types:optional",
diff --git a/third_party/libwebrtc/modules/pacing/pacing_controller.cc b/third_party/libwebrtc/modules/pacing/pacing_controller.cc
index 5b81207d56..41f97a37fb 100644
--- a/third_party/libwebrtc/modules/pacing/pacing_controller.cc
+++ b/third_party/libwebrtc/modules/pacing/pacing_controller.cc
@@ -19,11 +19,11 @@
#include "absl/strings/match.h"
#include "api/units/data_size.h"
#include "api/units/time_delta.h"
+#include "api/units/timestamp.h"
#include "modules/pacing/bitrate_prober.h"
-#include "modules/pacing/interval_budget.h"
+#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
-#include "rtc_base/time_utils.h"
#include "system_wrappers/include/clock.h"
namespace webrtc {
@@ -44,8 +44,6 @@ bool IsEnabled(const FieldTrialsView& field_trials, absl::string_view key) {
} // namespace
-const TimeDelta PacingController::kMaxExpectedQueueLength =
- TimeDelta::Millis(2000);
const TimeDelta PacingController::kPausedProcessInterval =
kCongestedPacketInterval;
const TimeDelta PacingController::kMinSleepTime = TimeDelta::Millis(1);
@@ -57,11 +55,13 @@ const TimeDelta PacingController::kMaxEarlyProbeProcessing =
PacingController::PacingController(Clock* clock,
PacketSender* packet_sender,
- const FieldTrialsView& field_trials)
+ const FieldTrialsView& field_trials,
+ Configuration configuration)
: clock_(clock),
packet_sender_(packet_sender),
field_trials_(field_trials),
drain_large_queues_(
+ configuration.drain_large_queues &&
!IsDisabled(field_trials_, "WebRTC-Pacer-DrainQueue")),
send_padding_if_silent_(
IsEnabled(field_trials_, "WebRTC-Pacer-PadInSilence")),
@@ -71,9 +71,10 @@ PacingController::PacingController(Clock* clock,
fast_retransmissions_(
IsEnabled(field_trials_, "WebRTC-Pacer-FastRetransmissions")),
keyframe_flushing_(
+ configuration.keyframe_flushing ||
IsEnabled(field_trials_, "WebRTC-Pacer-KeyframeFlushing")),
transport_overhead_per_packet_(DataSize::Zero()),
- send_burst_interval_(kDefaultBurstInterval),
+ send_burst_interval_(configuration.send_burst_interval),
last_timestamp_(clock_->CurrentTime()),
paused_(false),
media_debt_(DataSize::Zero()),
@@ -86,9 +87,11 @@ PacingController::PacingController(Clock* clock,
last_process_time_(clock->CurrentTime()),
last_send_time_(last_process_time_),
seen_first_packet_(false),
- packet_queue_(/*creation_time=*/last_process_time_),
+ packet_queue_(/*creation_time=*/last_process_time_,
+ configuration.prioritize_audio_retransmission,
+ configuration.packet_queue_ttl),
congested_(false),
- queue_time_limit_(kMaxExpectedQueueLength),
+ queue_time_limit_(configuration.queue_time_limit),
account_for_audio_(false),
include_overhead_(false),
circuit_breaker_threshold_(1 << 16) {
@@ -710,8 +713,7 @@ Timestamp PacingController::NextUnpacedSendTime() const {
}
if (fast_retransmissions_) {
Timestamp leading_retransmission_send_time =
- packet_queue_.LeadingPacketEnqueueTime(
- RtpPacketMediaType::kRetransmission);
+ packet_queue_.LeadingPacketEnqueueTimeForRetransmission();
if (leading_retransmission_send_time.IsFinite()) {
return leading_retransmission_send_time;
}
diff --git a/third_party/libwebrtc/modules/pacing/pacing_controller.h b/third_party/libwebrtc/modules/pacing/pacing_controller.h
index 04e0a820f9..fe6ee737a9 100644
--- a/third_party/libwebrtc/modules/pacing/pacing_controller.h
+++ b/third_party/libwebrtc/modules/pacing/pacing_controller.h
@@ -67,11 +67,6 @@ class PacingController {
}
};
- // Expected max pacer delay. If ExpectedQueueTime() is higher than
- // this value, the packet producers should wait (eg drop frames rather than
- // encoding them). Bitrate sent may temporarily exceed target set by
- // UpdateBitrate() so that this limit will be upheld.
- static const TimeDelta kMaxExpectedQueueLength;
// If no media or paused, wake up at least every `kPausedProcessIntervalMs` in
// order to send a keep-alive packet so we don't get stuck in a bad state due
// to lack of feedback.
@@ -93,14 +88,45 @@ class PacingController {
// the send burst interval.
// Ex: max send burst interval = 63Kb / 10Mbit/s = 50ms.
static constexpr DataSize kMaxBurstSize = DataSize::Bytes(63 * 1000);
- // The pacer is allowed to send enqued packets in bursts and can build up a
- // packet "debt" that correspond to approximately the send rate during
- // the burst interval.
+
+ // Configuration default values.
static constexpr TimeDelta kDefaultBurstInterval = TimeDelta::Millis(40);
+ static constexpr TimeDelta kMaxExpectedQueueLength = TimeDelta::Millis(2000);
+
+ struct Configuration {
+ // If the pacer queue grows longer than the configured max queue limit,
+ // pacer sends at the minimum rate needed to keep the max queue limit and
+ // ignore the current bandwidth estimate.
+ bool drain_large_queues = true;
+ // Expected max pacer delay. If ExpectedQueueTime() is higher than
+ // this value, the packet producers should wait (eg drop frames rather than
+ // encoding them). Bitrate sent may temporarily exceed target set by
+ // SetPacingRates() so that this limit will be upheld if
+ // `drain_large_queues` is set.
+ TimeDelta queue_time_limit = kMaxExpectedQueueLength;
+ // If the first packet of a keyframe is enqueued on a RTP stream, pacer
+ // skips forward to that packet and drops other enqueued packets on that
+ // stream, unless a keyframe is already being paced.
+ bool keyframe_flushing = false;
+ // Audio retransmission is prioritized before video retransmission packets.
+ bool prioritize_audio_retransmission = false;
+ // Configure separate timeouts per priority. After a timeout, a packet of
+ // that sort will not be paced and instead dropped.
+ // Note: to set TTL on audio retransmission,
+ // `prioritize_audio_retransmission` must be true.
+ PacketQueueTTL packet_queue_ttl;
+ // The pacer is allowed to send enqueued packets in bursts and can build up
+ // a packet "debt" that correspond to approximately the send rate during the
+ // burst interval.
+ TimeDelta send_burst_interval = kDefaultBurstInterval;
+ };
+
+ static Configuration DefaultConfiguration() { return Configuration{}; }
PacingController(Clock* clock,
PacketSender* packet_sender,
- const FieldTrialsView& field_trials);
+ const FieldTrialsView& field_trials,
+ Configuration configuration = DefaultConfiguration());
~PacingController();
diff --git a/third_party/libwebrtc/modules/pacing/pacing_controller_unittest.cc b/third_party/libwebrtc/modules/pacing/pacing_controller_unittest.cc
index 9e6ede6dc0..2c3a71b369 100644
--- a/third_party/libwebrtc/modules/pacing/pacing_controller_unittest.cc
+++ b/third_party/libwebrtc/modules/pacing/pacing_controller_unittest.cc
@@ -2348,5 +2348,43 @@ TEST_F(PacingControllerTest, FlushesPacketsOnKeyFrames) {
pacer->ProcessPackets();
}
+TEST_F(PacingControllerTest, CanControlQueueSizeUsingTtl) {
+ const uint32_t kSsrc = 12345;
+ const uint32_t kAudioSsrc = 2345;
+ uint16_t sequence_number = 1234;
+
+ PacingController::Configuration config;
+ config.drain_large_queues = false;
+ config.packet_queue_ttl.video = TimeDelta::Millis(500);
+ auto pacer =
+ std::make_unique<PacingController>(&clock_, &callback_, trials_, config);
+ pacer->SetPacingRates(DataRate::BitsPerSec(100'000), DataRate::Zero());
+
+ Timestamp send_time = Timestamp::Zero();
+ for (int i = 0; i < 100; ++i) {
+ // Enqueue a new audio and video frame every 33ms.
+ if (clock_.CurrentTime() - send_time > TimeDelta::Millis(33)) {
+ for (int j = 0; j < 3; ++j) {
+ auto packet = BuildPacket(RtpPacketMediaType::kVideo, kSsrc,
+ /*sequence_number=*/++sequence_number,
+ /*capture_time_ms=*/2,
+ /*size_bytes=*/1000);
+ pacer->EnqueuePacket(std::move(packet));
+ }
+ auto packet = BuildPacket(RtpPacketMediaType::kAudio, kAudioSsrc,
+ /*sequence_number=*/++sequence_number,
+ /*capture_time_ms=*/2,
+ /*size_bytes=*/100);
+ pacer->EnqueuePacket(std::move(packet));
+ send_time = clock_.CurrentTime();
+ }
+
+ EXPECT_LE(clock_.CurrentTime() - pacer->OldestPacketEnqueueTime(),
+ TimeDelta::Millis(500));
+ clock_.AdvanceTime(pacer->NextSendTime() - clock_.CurrentTime());
+ pacer->ProcessPackets();
+ }
+}
+
} // namespace
} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/pacing/prioritized_packet_queue.cc b/third_party/libwebrtc/modules/pacing/prioritized_packet_queue.cc
index ea211ea683..2d0d829648 100644
--- a/third_party/libwebrtc/modules/pacing/prioritized_packet_queue.cc
+++ b/third_party/libwebrtc/modules/pacing/prioritized_packet_queue.cc
@@ -10,41 +10,70 @@
#include "modules/pacing/prioritized_packet_queue.h"
+#include <algorithm>
+#include <array>
#include <utility>
+#include "absl/container/inlined_vector.h"
+#include "absl/types/optional.h"
+#include "api/units/time_delta.h"
+#include "api/units/timestamp.h"
#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
+#include "modules/rtp_rtcp/source/rtp_packet_to_send.h"
#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
namespace webrtc {
namespace {
constexpr int kAudioPrioLevel = 0;
-int GetPriorityForType(RtpPacketMediaType type) {
+int GetPriorityForType(
+ RtpPacketMediaType type,
+ absl::optional<RtpPacketToSend::OriginalType> original_type) {
// Lower number takes priority over higher.
switch (type) {
case RtpPacketMediaType::kAudio:
// Audio is always prioritized over other packet types.
return kAudioPrioLevel;
case RtpPacketMediaType::kRetransmission:
- // Send retransmissions before new media.
+ // Send retransmissions before new media. If original_type is set, audio
+ // retransmission is prioritized more than video retransmission.
+ if (original_type == RtpPacketToSend::OriginalType::kVideo) {
+ return kAudioPrioLevel + 2;
+ }
return kAudioPrioLevel + 1;
case RtpPacketMediaType::kVideo:
case RtpPacketMediaType::kForwardErrorCorrection:
// Video has "normal" priority, in the old speak.
// Send redundancy concurrently to video. If it is delayed it might have a
// lower chance of being useful.
- return kAudioPrioLevel + 2;
+ return kAudioPrioLevel + 3;
case RtpPacketMediaType::kPadding:
// Packets that are in themselves likely useless, only sent to keep the
// BWE high.
- return kAudioPrioLevel + 3;
+ return kAudioPrioLevel + 4;
}
RTC_CHECK_NOTREACHED();
}
} // namespace
+absl::InlinedVector<TimeDelta, PrioritizedPacketQueue::kNumPriorityLevels>
+PrioritizedPacketQueue::ToTtlPerPrio(PacketQueueTTL packet_queue_ttl) {
+ absl::InlinedVector<TimeDelta, PrioritizedPacketQueue::kNumPriorityLevels>
+ ttl_per_prio(kNumPriorityLevels, TimeDelta::PlusInfinity());
+ ttl_per_prio[GetPriorityForType(RtpPacketMediaType::kRetransmission,
+ RtpPacketToSend::OriginalType::kAudio)] =
+ packet_queue_ttl.audio_retransmission;
+ ttl_per_prio[GetPriorityForType(RtpPacketMediaType::kRetransmission,
+ RtpPacketToSend::OriginalType::kVideo)] =
+ packet_queue_ttl.video_retransmission;
+ ttl_per_prio[GetPriorityForType(RtpPacketMediaType::kVideo, absl::nullopt)] =
+ packet_queue_ttl.video;
+ return ttl_per_prio;
+}
+
DataSize PrioritizedPacketQueue::QueuedPacket::PacketSize() const {
return DataSize::Bytes(packet->payload_size() + packet->padding_size());
}
@@ -109,8 +138,13 @@ PrioritizedPacketQueue::StreamQueue::DequeueAll() {
return packets_by_prio;
}
-PrioritizedPacketQueue::PrioritizedPacketQueue(Timestamp creation_time)
- : queue_time_sum_(TimeDelta::Zero()),
+PrioritizedPacketQueue::PrioritizedPacketQueue(
+ Timestamp creation_time,
+ bool prioritize_audio_retransmission,
+ PacketQueueTTL packet_queue_ttl)
+ : prioritize_audio_retransmission_(prioritize_audio_retransmission),
+ time_to_live_per_prio_(ToTtlPerPrio(packet_queue_ttl)),
+ queue_time_sum_(TimeDelta::Zero()),
pause_time_sum_(TimeDelta::Zero()),
size_packets_(0),
size_packets_per_media_type_({}),
@@ -133,7 +167,11 @@ void PrioritizedPacketQueue::Push(Timestamp enqueue_time,
enqueue_times_.insert(enqueue_times_.end(), enqueue_time);
RTC_DCHECK(packet->packet_type().has_value());
RtpPacketMediaType packet_type = packet->packet_type().value();
- int prio_level = GetPriorityForType(packet_type);
+ int prio_level =
+ GetPriorityForType(packet_type, prioritize_audio_retransmission_
+ ? packet->original_packet_type()
+ : absl::nullopt);
+ PurgeOldPacketsAtPriorityLevel(prio_level, enqueue_time);
RTC_DCHECK_GE(prio_level, 0);
RTC_DCHECK_LT(prio_level, kNumPriorityLevels);
QueuedPacket queued_packed = {.packet = std::move(packet),
@@ -214,7 +252,8 @@ PrioritizedPacketQueue::SizeInPacketsPerRtpPacketMediaType() const {
Timestamp PrioritizedPacketQueue::LeadingPacketEnqueueTime(
RtpPacketMediaType type) const {
- const int priority_level = GetPriorityForType(type);
+ RTC_DCHECK(type != RtpPacketMediaType::kRetransmission);
+ const int priority_level = GetPriorityForType(type, absl::nullopt);
if (streams_by_prio_[priority_level].empty()) {
return Timestamp::MinusInfinity();
}
@@ -222,6 +261,39 @@ Timestamp PrioritizedPacketQueue::LeadingPacketEnqueueTime(
priority_level);
}
+Timestamp PrioritizedPacketQueue::LeadingPacketEnqueueTimeForRetransmission()
+ const {
+ if (!prioritize_audio_retransmission_) {
+ const int priority_level =
+ GetPriorityForType(RtpPacketMediaType::kRetransmission, absl::nullopt);
+ if (streams_by_prio_[priority_level].empty()) {
+ return Timestamp::PlusInfinity();
+ }
+ return streams_by_prio_[priority_level].front()->LeadingPacketEnqueueTime(
+ priority_level);
+ }
+ const int audio_priority_level =
+ GetPriorityForType(RtpPacketMediaType::kRetransmission,
+ RtpPacketToSend::OriginalType::kAudio);
+ const int video_priority_level =
+ GetPriorityForType(RtpPacketMediaType::kRetransmission,
+ RtpPacketToSend::OriginalType::kVideo);
+
+ Timestamp next_audio =
+ streams_by_prio_[audio_priority_level].empty()
+ ? Timestamp::PlusInfinity()
+ : streams_by_prio_[audio_priority_level]
+ .front()
+ ->LeadingPacketEnqueueTime(audio_priority_level);
+ Timestamp next_video =
+ streams_by_prio_[video_priority_level].empty()
+ ? Timestamp::PlusInfinity()
+ : streams_by_prio_[video_priority_level]
+ .front()
+ ->LeadingPacketEnqueueTime(video_priority_level);
+ return std::min(next_audio, next_video);
+}
+
Timestamp PrioritizedPacketQueue::OldestEnqueueTime() const {
return enqueue_times_.empty() ? Timestamp::MinusInfinity()
: enqueue_times_.front();
@@ -283,9 +355,6 @@ void PrioritizedPacketQueue::RemovePacketsForSsrc(uint32_t ssrc) {
// Update the global top prio level if neccessary.
RTC_DCHECK(streams_by_prio_[i].front() == &queue);
streams_by_prio_[i].pop_front();
- if (i == top_active_prio_level_) {
- MaybeUpdateTopPrioLevel();
- }
} else {
// More than stream had packets at this prio level, filter this one out.
std::deque<StreamQueue*> filtered_queue;
@@ -298,6 +367,7 @@ void PrioritizedPacketQueue::RemovePacketsForSsrc(uint32_t ssrc) {
}
}
}
+ MaybeUpdateTopPrioLevel();
}
bool PrioritizedPacketQueue::HasKeyframePackets(uint32_t ssrc) const {
@@ -340,18 +410,53 @@ void PrioritizedPacketQueue::DequeuePacketInternal(QueuedPacket& packet) {
}
void PrioritizedPacketQueue::MaybeUpdateTopPrioLevel() {
- if (streams_by_prio_[top_active_prio_level_].empty()) {
- // No stream queues have packets at this prio level, find top priority
- // that is not empty.
- if (size_packets_ == 0) {
- top_active_prio_level_ = -1;
+ if (top_active_prio_level_ != -1 &&
+ !streams_by_prio_[top_active_prio_level_].empty()) {
+ return;
+ }
+ // No stream queues have packets at top_active_prio_level_, find top priority
+ // that is not empty.
+ for (int i = 0; i < kNumPriorityLevels; ++i) {
+ PurgeOldPacketsAtPriorityLevel(i, last_update_time_);
+ if (!streams_by_prio_[i].empty()) {
+ top_active_prio_level_ = i;
+ break;
+ }
+ }
+ if (size_packets_ == 0) {
+ // There are no packets left to send. Last packet may have been purged. Prio
+ // will change when a new packet is pushed.
+ top_active_prio_level_ = -1;
+ }
+}
+
+void PrioritizedPacketQueue::PurgeOldPacketsAtPriorityLevel(int prio_level,
+ Timestamp now) {
+ RTC_DCHECK(prio_level >= 0 && prio_level < kNumPriorityLevels);
+ TimeDelta time_to_live = time_to_live_per_prio_[prio_level];
+ if (time_to_live.IsInfinite()) {
+ return;
+ }
+
+ std::deque<StreamQueue*>& queues = streams_by_prio_[prio_level];
+ auto iter = queues.begin();
+ while (iter != queues.end()) {
+ StreamQueue* queue_ptr = *iter;
+ while (queue_ptr->HasPacketsAtPrio(prio_level) &&
+ (now - queue_ptr->LeadingPacketEnqueueTime(prio_level)) >
+ time_to_live) {
+ QueuedPacket packet = queue_ptr->DequeuePacket(prio_level);
+ RTC_LOG(LS_INFO) << "Dropping old packet on SSRC: "
+ << packet.packet->Ssrc()
+ << " seq:" << packet.packet->SequenceNumber()
+ << " time in queue:" << (now - packet.enqueue_time).ms()
+ << " ms";
+ DequeuePacketInternal(packet);
+ }
+ if (!queue_ptr->HasPacketsAtPrio(prio_level)) {
+ iter = queues.erase(iter);
} else {
- for (int i = 0; i < kNumPriorityLevels; ++i) {
- if (!streams_by_prio_[i].empty()) {
- top_active_prio_level_ = i;
- break;
- }
- }
+ ++iter;
}
}
}
diff --git a/third_party/libwebrtc/modules/pacing/prioritized_packet_queue.h b/third_party/libwebrtc/modules/pacing/prioritized_packet_queue.h
index 935c530027..179ef104fe 100644
--- a/third_party/libwebrtc/modules/pacing/prioritized_packet_queue.h
+++ b/third_party/libwebrtc/modules/pacing/prioritized_packet_queue.h
@@ -18,8 +18,8 @@
#include <list>
#include <memory>
#include <unordered_map>
-#include <vector>
+#include "absl/container/inlined_vector.h"
#include "api/units/data_size.h"
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
@@ -27,9 +27,19 @@
namespace webrtc {
+// Describes how long time a packet may stay in the queue before being dropped.
+struct PacketQueueTTL {
+ TimeDelta audio_retransmission = TimeDelta::PlusInfinity();
+ TimeDelta video_retransmission = TimeDelta::PlusInfinity();
+ TimeDelta video = TimeDelta::PlusInfinity();
+};
+
class PrioritizedPacketQueue {
public:
- explicit PrioritizedPacketQueue(Timestamp creation_time);
+ explicit PrioritizedPacketQueue(
+ Timestamp creation_time,
+ bool prioritize_audio_retransmission = false,
+ PacketQueueTTL packet_queue_ttl = PacketQueueTTL());
PrioritizedPacketQueue(const PrioritizedPacketQueue&) = delete;
PrioritizedPacketQueue& operator=(const PrioritizedPacketQueue&) = delete;
@@ -63,6 +73,7 @@ class PrioritizedPacketQueue {
// method, for the given packet type. If queue has no packets, of that type,
// returns Timestamp::MinusInfinity().
Timestamp LeadingPacketEnqueueTime(RtpPacketMediaType type) const;
+ Timestamp LeadingPacketEnqueueTimeForRetransmission() const;
// Enqueue time of the oldest packet in the queue,
// Timestamp::MinusInfinity() if queue is empty.
@@ -90,7 +101,7 @@ class PrioritizedPacketQueue {
bool HasKeyframePackets(uint32_t ssrc) const;
private:
- static constexpr int kNumPriorityLevels = 4;
+ static constexpr int kNumPriorityLevels = 5;
class QueuedPacket {
public:
@@ -139,6 +150,15 @@ class PrioritizedPacketQueue {
// if so move it to the lowest non-empty index.
void MaybeUpdateTopPrioLevel();
+ void PurgeOldPacketsAtPriorityLevel(int prio_level, Timestamp now);
+
+ static absl::InlinedVector<TimeDelta, kNumPriorityLevels> ToTtlPerPrio(
+ PacketQueueTTL);
+
+ const bool prioritize_audio_retransmission_;
+ const absl::InlinedVector<TimeDelta, kNumPriorityLevels>
+ time_to_live_per_prio_;
+
// Cumulative sum, over all packets, of time spent in the queue.
TimeDelta queue_time_sum_;
// Cumulative sum of time the queue has spent in a paused state.
diff --git a/third_party/libwebrtc/modules/pacing/prioritized_packet_queue_unittest.cc b/third_party/libwebrtc/modules/pacing/prioritized_packet_queue_unittest.cc
index 9ed19642c7..76c31036b3 100644
--- a/third_party/libwebrtc/modules/pacing/prioritized_packet_queue_unittest.cc
+++ b/third_party/libwebrtc/modules/pacing/prioritized_packet_queue_unittest.cc
@@ -10,6 +10,7 @@
#include "modules/pacing/prioritized_packet_queue.h"
+#include <memory>
#include <utility>
#include "api/units/time_delta.h"
@@ -26,18 +27,39 @@ constexpr uint32_t kDefaultSsrc = 123;
constexpr int kDefaultPayloadSize = 789;
std::unique_ptr<RtpPacketToSend> CreatePacket(RtpPacketMediaType type,
- uint16_t sequence_number,
+ uint16_t seq,
uint32_t ssrc = kDefaultSsrc,
bool is_key_frame = false) {
auto packet = std::make_unique<RtpPacketToSend>(/*extensions=*/nullptr);
packet->set_packet_type(type);
packet->SetSsrc(ssrc);
- packet->SetSequenceNumber(sequence_number);
+ packet->SetSequenceNumber(seq);
packet->SetPayloadSize(kDefaultPayloadSize);
packet->set_is_key_frame(is_key_frame);
return packet;
}
+std::unique_ptr<RtpPacketToSend> CreateRetransmissionPacket(
+ RtpPacketMediaType original_type,
+ uint16_t seq,
+ uint32_t ssrc = kDefaultSsrc) {
+ auto packet = std::make_unique<RtpPacketToSend>(/*extensions=*/nullptr);
+ packet->set_packet_type(original_type);
+ packet->set_packet_type(RtpPacketMediaType::kRetransmission);
+ RTC_DCHECK(packet->packet_type() == RtpPacketMediaType::kRetransmission);
+ if (original_type == RtpPacketMediaType::kVideo) {
+ RTC_DCHECK(packet->original_packet_type() ==
+ RtpPacketToSend::OriginalType::kVideo);
+ } else {
+ RTC_DCHECK(packet->original_packet_type() ==
+ RtpPacketToSend::OriginalType::kAudio);
+ }
+ packet->SetSsrc(ssrc);
+ packet->SetSequenceNumber(seq);
+ packet->SetPayloadSize(kDefaultPayloadSize);
+ return packet;
+}
+
} // namespace
TEST(PrioritizedPacketQueue, ReturnsPacketsInPrioritizedOrder) {
@@ -49,18 +71,42 @@ TEST(PrioritizedPacketQueue, ReturnsPacketsInPrioritizedOrder) {
queue.Push(now, CreatePacket(RtpPacketMediaType::kVideo, /*seq=*/2));
queue.Push(now, CreatePacket(RtpPacketMediaType::kForwardErrorCorrection,
/*seq=*/3));
- queue.Push(now, CreatePacket(RtpPacketMediaType::kRetransmission, /*seq=*/4));
- queue.Push(now, CreatePacket(RtpPacketMediaType::kAudio, /*seq=*/5));
+ queue.Push(now,
+ CreateRetransmissionPacket(RtpPacketMediaType::kVideo, /*seq=*/4));
+ queue.Push(now,
+ CreateRetransmissionPacket(RtpPacketMediaType::kAudio, /*seq=*/5));
+ queue.Push(now, CreatePacket(RtpPacketMediaType::kAudio, /*seq=*/6));
// Packets should be returned in high to low order.
- EXPECT_EQ(queue.Pop()->SequenceNumber(), 5);
+ EXPECT_EQ(queue.Pop()->SequenceNumber(), 6);
+ // Audio and video retransmission has same prio, but video was enqueued first.
EXPECT_EQ(queue.Pop()->SequenceNumber(), 4);
+ EXPECT_EQ(queue.Pop()->SequenceNumber(), 5);
// Video and FEC prioritized equally - but video was enqueued first.
EXPECT_EQ(queue.Pop()->SequenceNumber(), 2);
EXPECT_EQ(queue.Pop()->SequenceNumber(), 3);
EXPECT_EQ(queue.Pop()->SequenceNumber(), 1);
}
+TEST(PrioritizedPacketQueue,
+ PrioritizeAudioRetransmissionBeforeVideoRetransmissionIfConfigured) {
+ Timestamp now = Timestamp::Zero();
+ PrioritizedPacketQueue queue(now, /*prioritize_audio_retransmission=*/true);
+
+ // Add packets in low to high packet order.
+ queue.Push(now, CreatePacket(RtpPacketMediaType::kVideo, /*seq=*/3));
+ queue.Push(now,
+ CreateRetransmissionPacket(RtpPacketMediaType::kVideo, /*seq=*/4));
+ queue.Push(now,
+ CreateRetransmissionPacket(RtpPacketMediaType::kAudio, /*seq=*/5));
+ queue.Push(now, CreatePacket(RtpPacketMediaType::kAudio, /*seq=*/6));
+
+ // Packets should be returned in high to low order.
+ EXPECT_EQ(queue.Pop()->SequenceNumber(), 6);
+ EXPECT_EQ(queue.Pop()->SequenceNumber(), 5);
+ EXPECT_EQ(queue.Pop()->SequenceNumber(), 4);
+}
+
TEST(PrioritizedPacketQueue, ReturnsEqualPrioPacketsInRoundRobinOrder) {
Timestamp now = Timestamp::Zero();
PrioritizedPacketQueue queue(now);
@@ -251,6 +297,26 @@ TEST(PrioritizedPacketQueue, ReportsLeadingPacketEnqueueTime) {
Timestamp::MinusInfinity());
}
+TEST(PrioritizedPacketQueue, ReportsLeadingPacketEnqueueTimeForRetransmission) {
+ PrioritizedPacketQueue queue(/*creation_time=*/Timestamp::Zero(),
+ /*prioritize_audio_retransmission=*/true);
+ EXPECT_EQ(queue.LeadingPacketEnqueueTimeForRetransmission(),
+ Timestamp::PlusInfinity());
+
+ queue.Push(Timestamp::Millis(10),
+ CreateRetransmissionPacket(RtpPacketMediaType::kVideo, /*seq=*/1));
+ queue.Push(Timestamp::Millis(11),
+ CreateRetransmissionPacket(RtpPacketMediaType::kAudio, /*seq=*/2));
+ EXPECT_EQ(queue.LeadingPacketEnqueueTimeForRetransmission(),
+ Timestamp::Millis(10));
+ queue.Pop(); // Pop audio retransmission since it has higher prio.
+ EXPECT_EQ(queue.LeadingPacketEnqueueTimeForRetransmission(),
+ Timestamp::Millis(10));
+ queue.Pop(); // Pop video retransmission.
+ EXPECT_EQ(queue.LeadingPacketEnqueueTimeForRetransmission(),
+ Timestamp::PlusInfinity());
+}
+
TEST(PrioritizedPacketQueue,
PushAndPopUpdatesSizeInPacketsPerRtpPacketMediaType) {
Timestamp now = Timestamp::Zero();
@@ -272,7 +338,7 @@ TEST(PrioritizedPacketQueue,
RtpPacketMediaType::kVideo)],
1);
- queue.Push(now, CreatePacket(RtpPacketMediaType::kRetransmission, 3));
+ queue.Push(now, CreateRetransmissionPacket(RtpPacketMediaType::kVideo, 3));
EXPECT_EQ(queue.SizeInPacketsPerRtpPacketMediaType()[static_cast<size_t>(
RtpPacketMediaType::kRetransmission)],
1);
@@ -326,6 +392,8 @@ TEST(PrioritizedPacketQueue, ClearsPackets) {
// Remove all of them.
queue.RemovePacketsForSsrc(kSsrc);
EXPECT_TRUE(queue.Empty());
+ queue.RemovePacketsForSsrc(kSsrc);
+ EXPECT_TRUE(queue.Empty());
}
TEST(PrioritizedPacketQueue, ClearPacketsAffectsOnlySpecifiedSsrc) {
@@ -338,16 +406,16 @@ TEST(PrioritizedPacketQueue, ClearPacketsAffectsOnlySpecifiedSsrc) {
// ensuring they are first in line.
queue.Push(
now, CreatePacket(RtpPacketMediaType::kAudio, /*seq=*/1, kRemovingSsrc));
- queue.Push(now, CreatePacket(RtpPacketMediaType::kRetransmission, /*seq=*/2,
- kRemovingSsrc));
+ queue.Push(now, CreateRetransmissionPacket(RtpPacketMediaType::kVideo,
+ /*seq=*/2, kRemovingSsrc));
// Add a video packet and a retransmission for the SSRC that will remain.
// The retransmission packets now both have pointers to their respective qeues
// from the same prio level.
queue.Push(now,
CreatePacket(RtpPacketMediaType::kVideo, /*seq=*/3, kStayingSsrc));
- queue.Push(now, CreatePacket(RtpPacketMediaType::kRetransmission, /*seq=*/4,
- kStayingSsrc));
+ queue.Push(now, CreateRetransmissionPacket(RtpPacketMediaType::kVideo,
+ /*seq=*/4, kStayingSsrc));
EXPECT_EQ(queue.SizeInPackets(), 4);
@@ -413,4 +481,87 @@ TEST(PrioritizedPacketQueue, ReportsKeyframePackets) {
EXPECT_FALSE(queue.HasKeyframePackets(kVideoSsrc2));
}
+TEST(PrioritizedPacketQueue, PacketsDroppedIfNotPulledWithinTttl) {
+ Timestamp now = Timestamp::Zero();
+ PacketQueueTTL ttls;
+ ttls.audio_retransmission = TimeDelta::Millis(200);
+ PrioritizedPacketQueue queue(now, /*prioritize_audio_retransmission=*/true,
+ ttls);
+
+ queue.Push(now,
+ CreateRetransmissionPacket(RtpPacketMediaType::kAudio, /*seq=*/1));
+ now += ttls.audio_retransmission + TimeDelta::Millis(1);
+ EXPECT_EQ(queue.SizeInPackets(), 1);
+ queue.Push(now,
+ CreateRetransmissionPacket(RtpPacketMediaType::kAudio, /*seq=*/2));
+ EXPECT_EQ(queue.SizeInPackets(), 1);
+ EXPECT_EQ(queue.Pop()->SequenceNumber(), 2);
+}
+
+TEST(PrioritizedPacketQueue, DontSendPacketsAfterTttl) {
+ Timestamp now = Timestamp::Zero();
+ PacketQueueTTL ttls;
+ ttls.audio_retransmission = TimeDelta::Millis(200);
+ PrioritizedPacketQueue queue(now, /*prioritize_audio_retransmission=*/true,
+ ttls);
+
+ queue.Push(now,
+ CreateRetransmissionPacket(RtpPacketMediaType::kAudio, /*seq=*/1));
+ now += ttls.audio_retransmission + TimeDelta::Millis(1);
+ EXPECT_EQ(queue.SizeInPackets(), 1);
+ queue.Push(now, CreatePacket(RtpPacketMediaType::kVideo, /*seq=*/2));
+ queue.Push(now, CreatePacket(RtpPacketMediaType::kAudio, /*seq=*/3));
+ // Expect the old packet to have been removed since it was not popped in time.
+ EXPECT_EQ(queue.SizeInPackets(), 3);
+ EXPECT_EQ(queue.Pop()->SequenceNumber(), 3);
+ EXPECT_EQ(queue.SizeInPackets(), 1);
+ EXPECT_EQ(queue.Pop()->SequenceNumber(), 2);
+ EXPECT_EQ(queue.SizeInPackets(), 0);
+}
+
+TEST(PrioritizedPacketQueue, SendsNewVideoPacketAfterPurgingLastOldRtxPacket) {
+ Timestamp now = Timestamp::Zero();
+ PacketQueueTTL ttls;
+ ttls.video_retransmission = TimeDelta::Millis(400);
+ PrioritizedPacketQueue queue(now, /*prioritize_audio_retransmission=*/true,
+ ttls);
+
+ queue.Push(now,
+ CreateRetransmissionPacket(RtpPacketMediaType::kVideo, /*seq=*/1));
+ now += ttls.video_retransmission + TimeDelta::Millis(1);
+ queue.Push(now, CreatePacket(RtpPacketMediaType::kAudio, /*seq=*/2));
+ EXPECT_EQ(queue.SizeInPackets(), 2);
+ // Expect the audio packet to be send and the video retransmission packet to
+ // be dropped since it is old.
+ EXPECT_EQ(queue.Pop()->SequenceNumber(), 2);
+ EXPECT_EQ(queue.SizeInPackets(), 0);
+
+ queue.Push(now, CreatePacket(RtpPacketMediaType::kVideo, /*seq=*/3));
+ EXPECT_EQ(queue.SizeInPackets(), 1);
+ EXPECT_EQ(queue.Pop()->SequenceNumber(), 3);
+ EXPECT_EQ(queue.SizeInPackets(), 0);
+}
+
+TEST(PrioritizedPacketQueue,
+ SendsPacketsAfterTttlIfPrioHigherThanPushedPackets) {
+ Timestamp now = Timestamp::Zero();
+ PacketQueueTTL ttls;
+ ttls.audio_retransmission = TimeDelta::Millis(200);
+ PrioritizedPacketQueue queue(now, /*prioritize_audio_retransmission=*/true,
+ ttls);
+
+ queue.Push(now,
+ CreateRetransmissionPacket(RtpPacketMediaType::kAudio, /*seq=*/1));
+ now += ttls.audio_retransmission + TimeDelta::Millis(1);
+ EXPECT_EQ(queue.SizeInPackets(), 1);
+ queue.Push(now, CreatePacket(RtpPacketMediaType::kVideo, /*seq=*/2));
+
+ // This test just show that TTL is not enforced strictly. If a new audio
+ // packet had been queued before a packet was popped, the audio retransmission
+ // packet would have been dropped.
+ EXPECT_EQ(queue.SizeInPackets(), 2);
+ EXPECT_EQ(queue.Pop()->SequenceNumber(), 1);
+ EXPECT_EQ(queue.SizeInPackets(), 1);
+}
+
} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/remote_bitrate_estimator/remote_estimator_proxy.cc b/third_party/libwebrtc/modules/remote_bitrate_estimator/remote_estimator_proxy.cc
index e100995b8e..6953ec8400 100644
--- a/third_party/libwebrtc/modules/remote_bitrate_estimator/remote_estimator_proxy.cc
+++ b/third_party/libwebrtc/modules/remote_bitrate_estimator/remote_estimator_proxy.cc
@@ -260,8 +260,11 @@ void RemoteEstimatorProxy::SendFeedbackOnRequest(
feedback_request.include_timestamps, first_sequence_number,
sequence_number + 1, /*is_periodic_update=*/false);
- // This is called when a packet has just been added.
- RTC_DCHECK(feedback_packet != nullptr);
+ // Even though this is called when a packet has just been added,
+ // no feedback may be produced when that new packet is too old.
+ if (feedback_packet == nullptr) {
+ return;
+ }
// Clear up to the first packet that is included in this feedback packet.
packet_arrival_times_.EraseTo(first_sequence_number);
diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/receive_statistics_impl.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/receive_statistics_impl.cc
index 0e5e40f502..1dc56bb96f 100644
--- a/third_party/libwebrtc/modules/rtp_rtcp/source/receive_statistics_impl.cc
+++ b/third_party/libwebrtc/modules/rtp_rtcp/source/receive_statistics_impl.cc
@@ -18,6 +18,7 @@
#include "api/units/time_delta.h"
#include "modules/remote_bitrate_estimator/test/bwe_test_logging.h"
+#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
#include "modules/rtp_rtcp/source/rtcp_packet/report_block.h"
#include "modules/rtp_rtcp/source/rtp_packet_received.h"
#include "modules/rtp_rtcp/source/rtp_rtcp_config.h"
@@ -159,16 +160,15 @@ void StreamStatisticianImpl::UpdateJitter(const RtpPacketReceived& packet,
int32_t time_diff_samples =
receive_diff_rtp - (packet.Timestamp() - last_received_timestamp_);
- time_diff_samples = std::abs(time_diff_samples);
-
ReviseFrequencyAndJitter(packet.payload_type_frequency());
// lib_jingle sometimes deliver crazy jumps in TS for the same stream.
// If this happens, don't update jitter value. Use 5 secs video frequency
// as the threshold.
- if (time_diff_samples < 450000) {
+ if (time_diff_samples < 5 * kVideoPayloadTypeFrequency &&
+ time_diff_samples > -5 * kVideoPayloadTypeFrequency) {
// Note we calculate in Q4 to avoid using float.
- int32_t jitter_diff_q4 = (time_diff_samples << 4) - jitter_q4_;
+ int32_t jitter_diff_q4 = (std::abs(time_diff_samples) << 4) - jitter_q4_;
jitter_q4_ += ((jitter_diff_q4 + 8) >> 4);
}
}
diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/receive_statistics_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/receive_statistics_unittest.cc
index a2558545f0..8b31912f0f 100644
--- a/third_party/libwebrtc/modules/rtp_rtcp/source/receive_statistics_unittest.cc
+++ b/third_party/libwebrtc/modules/rtp_rtcp/source/receive_statistics_unittest.cc
@@ -898,5 +898,22 @@ TEST(ReviseJitterTest,
EXPECT_EQ(GetJitter(*statistics), 172U);
}
+TEST(ReviseJitterTest, TwoPacketsWithMaximumRtpTimestampDifference) {
+ SimulatedClock clock(0);
+ std::unique_ptr<ReceiveStatistics> statistics =
+ ReceiveStatistics::Create(&clock);
+ RtpPacketReceived packet1 = MakeRtpPacket(/*payload_type_frequency=*/90'000,
+ /*timestamp=*/0x01234567);
+ RtpPacketReceived packet2 =
+ MakeNextRtpPacket(packet1,
+ /*payload_type_frequency=*/90'000,
+ /*timestamp=*/0x81234567);
+ statistics->OnRtpPacket(packet1);
+ statistics->OnRtpPacket(packet2);
+
+ // Expect large jump in RTP timestamp is ignored for jitter calculation.
+ EXPECT_EQ(GetJitter(*statistics), 0U);
+}
+
} // namespace
} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_receiver.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_receiver.cc
index bda6ad9a52..756136866d 100644
--- a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_receiver.cc
+++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_receiver.cc
@@ -1190,11 +1190,11 @@ std::vector<rtcp::TmmbItem> RTCPReceiver::TmmbrReceived() {
MutexLock lock(&rtcp_receiver_lock_);
std::vector<rtcp::TmmbItem> candidates;
- Timestamp timeout = clock_->CurrentTime() - kTmmbrTimeoutInterval;
+ Timestamp now = clock_->CurrentTime();
for (auto& kv : tmmbr_infos_) {
for (auto it = kv.second.tmmbr.begin(); it != kv.second.tmmbr.end();) {
- if (it->second.last_updated < timeout) {
+ if (now - it->second.last_updated > kTmmbrTimeoutInterval) {
// Erase timeout entries.
it = kv.second.tmmbr.erase(it);
} else {
diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_extension.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_extension.cc
index fd42b798d4..27b0420926 100644
--- a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_extension.cc
+++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_extension.cc
@@ -52,4 +52,17 @@ bool RtpDependencyDescriptorExtension::Write(
return writer.Write();
}
+bool RtpDependencyDescriptorExtensionMandatory::Parse(
+ rtc::ArrayView<const uint8_t> data,
+ DependencyDescriptorMandatory* descriptor) {
+ if (data.size() < 3) {
+ return false;
+ }
+ descriptor->set_first_packet_in_frame(data[0] & 0b1000'0000);
+ descriptor->set_last_packet_in_frame(data[0] & 0b0100'0000);
+ descriptor->set_template_id(data[0] & 0b0011'1111);
+ descriptor->set_frame_number((uint16_t{data[1]} << 8) | data[2]);
+ return true;
+}
+
} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_extension.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_extension.h
index 8d6e4b8d37..a3e415917c 100644
--- a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_extension.h
+++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_extension.h
@@ -54,6 +54,16 @@ class RtpDependencyDescriptorExtension {
static constexpr std::bitset<32> kAllChainsAreActive = ~uint32_t{0};
};
+// Trait to only read the mandatory part of the descriptor.
+class RtpDependencyDescriptorExtensionMandatory {
+ public:
+ static constexpr webrtc::RTPExtensionType kId =
+ webrtc::RtpDependencyDescriptorExtension::kId;
+
+ static bool Parse(rtc::ArrayView<const uint8_t> data,
+ DependencyDescriptorMandatory* descriptor);
+};
+
} // namespace webrtc
#endif // MODULES_RTP_RTCP_SOURCE_RTP_DEPENDENCY_DESCRIPTOR_EXTENSION_H_
diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_h264.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_h264.cc
index d066bafb90..9c1dc4edb8 100644
--- a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_h264.cc
+++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_h264.cc
@@ -19,6 +19,7 @@
#include <utility>
#include <vector>
+#include "absl/algorithm/container.h"
#include "absl/types/optional.h"
#include "absl/types/variant.h"
#include "common_video/h264/h264_common.h"
@@ -52,11 +53,14 @@ RtpPacketizerH264::RtpPacketizerH264(rtc::ArrayView<const uint8_t> payload,
input_fragments_.push_back(
payload.subview(nalu.payload_start_offset, nalu.payload_size));
}
-
- if (!GeneratePackets(packetization_mode)) {
- // If failed to generate all the packets, discard already generated
- // packets in case the caller would ignore return value and still try to
- // call NextPacket().
+ bool has_empty_fragments = absl::c_any_of(
+ input_fragments_, [](const rtc::ArrayView<const uint8_t> fragment) {
+ return fragment.empty();
+ });
+ if (has_empty_fragments || !GeneratePackets(packetization_mode)) {
+ // If empty fragments were found or we failed to generate all the packets,
+ // discard already generated packets in case the caller would ignore the
+ // return value and still try to call NextPacket().
num_packets_left_ = 0;
while (!packets_.empty()) {
packets_.pop();
@@ -73,6 +77,7 @@ size_t RtpPacketizerH264::NumPackets() const {
bool RtpPacketizerH264::GeneratePackets(
H264PacketizationMode packetization_mode) {
for (size_t i = 0; i < input_fragments_.size();) {
+ RTC_DCHECK(!input_fragments_[i].empty());
switch (packetization_mode) {
case H264PacketizationMode::SingleNalUnit:
if (!PacketizeSingleNalu(i))
@@ -173,11 +178,11 @@ size_t RtpPacketizerH264::PacketizeStapA(size_t fragment_index) {
return fragment_size;
}
};
-
while (payload_size_left >= payload_size_needed()) {
RTC_CHECK_GT(fragment.size(), 0);
- packets_.push(PacketUnit(fragment, aggregated_fragments == 0, false, true,
- fragment[0]));
+
+ packets_.push(PacketUnit(fragment, /*first=*/aggregated_fragments == 0,
+ /*last=*/false, /*aggregated=*/true, fragment[0]));
payload_size_left -= fragment.size();
payload_size_left -= fragment_headers_length;
@@ -218,9 +223,9 @@ bool RtpPacketizerH264::PacketizeSingleNalu(size_t fragment_index) {
<< limits_.max_payload_len;
return false;
}
- RTC_CHECK_GT(fragment.size(), 0u);
- packets_.push(PacketUnit(fragment, true /* first */, true /* last */,
- false /* aggregated */, fragment[0]));
+ RTC_CHECK(!fragment.empty());
+ packets_.push(PacketUnit(fragment, /*first=*/true, /*last=*/true,
+ /*aggregated=*/false, fragment[0]));
++num_packets_left_;
return true;
}
diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_h264_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_h264_unittest.cc
index 18311c6e8c..3920c4acd5 100644
--- a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_h264_unittest.cc
+++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_h264_unittest.cc
@@ -116,6 +116,7 @@ std::vector<RtpPacketToSend> FetchAllPackets(RtpPacketizerH264* packetizer) {
RtpPacketToSend packet(kNoExtensions);
while (packetizer->NextPacket(&packet)) {
result.push_back(packet);
+ packet.Clear();
}
EXPECT_THAT(result, SizeIs(num_packets));
return result;
@@ -527,5 +528,190 @@ TEST(RtpPacketizerH264Test, RejectsOverlongDataInPacketizationMode0) {
EXPECT_THAT(packets, IsEmpty());
}
+
+TEST(RtpPacketizerH264Test, DoesNotPacketizeWithEmptyNalUnit) {
+ RtpPacketizer::PayloadSizeLimits limits;
+ limits.max_payload_len = kMaxPayloadSize;
+
+ uint8_t empty_nal_input[] = {0x00, 0x00, 0x01, /* empty NAL unit data */
+ 0x00, 0x00, 0x01, 0x01};
+ RtpPacketizerH264 packetizer(empty_nal_input, limits,
+ H264PacketizationMode::NonInterleaved);
+ EXPECT_EQ(packetizer.NumPackets(), 0u);
+}
+
+TEST(RtpPacketizerH264Test, MultipleStapA) {
+ RtpPacketizer::PayloadSizeLimits limits;
+ limits.max_payload_len = kMaxPayloadSize;
+ limits.first_packet_reduction_len = 0;
+ limits.last_packet_reduction_len = 0;
+ limits.single_packet_reduction_len = 0;
+ // A lot of small NAL units that will result in two STAP-A being generated.
+ // Input data must exceed the size of a single RTP packet.
+ uint8_t long_input[] = {
+ 0x19, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01,
+ 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x04,
+ 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01,
+ 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00,
+ 0x00, 0x01, 0x00, 0xaf, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01,
+ 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00,
+ 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x00, 0xaf, 0x01,
+ 0x04, 0x00, 0x19, 0x00, 0x00, 0x01, 0xf9, 0x01, 0x00, 0x00, 0x00, 0x01,
+ 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xf7, 0x01,
+ 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x00, 0x0b, 0x01, 0x04, 0x00,
+ 0x19, 0x00, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x04, 0x00, 0x00, 0x01, 0x00, 0xaf, 0x01, 0x04, 0x00, 0x19, 0x00,
+ 0x00, 0x01, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0xaf, 0x01, 0x04, 0x00, 0x19, 0x00, 0x00,
+ 0x01, 0x01, 0x02, 0x00, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00, 0x00,
+ 0x19, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01,
+ 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x04,
+ 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x19,
+ 0x7a, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01,
+ 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x04,
+ 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x01, 0x00,
+ 0xaf, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01,
+ 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x04,
+ 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0xaf, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01, 0x00,
+ 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00,
+ 0x01, 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00,
+ 0x00, 0x00, 0x11, 0xd4, 0x00, 0x19, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01,
+ 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x00, 0xaf, 0x00, 0x00, 0x01,
+ 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01,
+ 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00,
+ 0x00, 0x01, 0x00, 0xaf, 0x01, 0x04, 0x00, 0x19, 0x00, 0x01, 0x00, 0xaf,
+ 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x04, 0x00,
+ 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x10, 0x00, 0x00,
+ 0x01, 0x04, 0x00, 0x00, 0x01, 0x00, 0xaf, 0x01, 0x04, 0x00, 0x19, 0x00,
+ 0x00, 0x01, 0xf9, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00,
+ 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
+ 0x08, 0xfe, 0xfb, 0xff, 0xff, 0xf4, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x00, 0x04, 0x00,
+ 0x00, 0x01, 0x00, 0xaf, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01,
+ 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00,
+ 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0xaf, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x01, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x11, 0xd4, 0x00, 0x19, 0x00, 0x00,
+ 0x01, 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x01,
+ 0x04, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01,
+ 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x00,
+ 0xaf, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x04,
+ 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e,
+ 0x00, 0x00, 0x00, 0x00, 0x11, 0xd4, 0x00, 0x19, 0x00, 0x00, 0x01, 0x04,
+ 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x01, 0x04, 0x00,
+ 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x00, 0xaf, 0x00,
+ 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x04, 0x00, 0x00, 0x01, 0x00, 0xaf, 0x01, 0x04, 0x00, 0x19, 0x00, 0x00,
+ 0x01, 0xf9, 0x01, 0x00, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0xf7, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01,
+ 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00,
+ 0x00, 0x01, 0x00, 0xaf, 0x01, 0x04, 0x00, 0x19, 0x00, 0x00, 0x01, 0x01,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0xaf, 0x01, 0x04, 0x00, 0x19, 0x00, 0x00, 0x01, 0x01, 0x02,
+ 0x00, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0xf7, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01,
+ 0x00, 0xf7, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x26, 0x00, 0x00, 0x01, 0x00,
+ 0xaf, 0x01, 0x04, 0x00, 0x19, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00,
+ 0x01, 0x00, 0xaf, 0x01, 0x04, 0x00, 0x19, 0x00, 0x00, 0x01, 0x00, 0x01,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x00,
+ 0xaf, 0x01, 0x04, 0x00, 0x19, 0x00, 0x00, 0x01, 0xf9, 0x01, 0x00, 0x00,
+ 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+ 0xf7, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x00, 0x0b, 0x2c,
+ 0x01, 0x04, 0x00, 0x19, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x04, 0x00, 0x00, 0x01, 0x00, 0xaf, 0x01, 0x04, 0x00, 0x19, 0x00,
+ 0x00, 0x01, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0xaf, 0x01, 0x04, 0x00, 0x19, 0x00, 0x00,
+ 0x01, 0x01, 0x02, 0x00, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00, 0x00,
+ 0x19, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01,
+ 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x04,
+ 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x19, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01,
+ 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04,
+ 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x00, 0xaf,
+ 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x04, 0x00,
+ 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x10, 0x00, 0x00,
+ 0x01, 0x04, 0x00, 0x00, 0x01, 0x00, 0xaf, 0x01, 0x04, 0x00, 0x19, 0x00,
+ 0x00, 0x01, 0xf9, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00,
+ 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
+ 0x08, 0xfe, 0xfb, 0xff, 0xff, 0xf4, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x00, 0x04, 0x00,
+ 0x00, 0x01, 0x00, 0xaf, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01,
+ 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00,
+ 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0xaf, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x04, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01,
+ 0x8e, 0x00, 0x04, 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x01, 0x00,
+ 0xaf, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x11, 0xd4, 0x00,
+ 0x19, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01,
+ 0x8e, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01,
+ 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00,
+ 0x00, 0x01, 0x00, 0xaf, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01,
+ 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00,
+ 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x00, 0xaf, 0x01,
+ 0x04, 0x00, 0x19, 0x00, 0x00, 0x01, 0xf9, 0x01, 0x00, 0x00, 0x00, 0x01,
+ 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xf7, 0x01,
+ 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x00, 0xaf, 0x01, 0x04, 0x00,
+ 0x19, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xaf, 0x01, 0x04, 0x00, 0x19,
+ 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xf7, 0x01, 0x04, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0xf7, 0x01, 0x04, 0x00, 0x00, 0x01,
+ 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04,
+ 0x26, 0x00, 0x00, 0x01, 0x00, 0xaf, 0x01, 0x04, 0x00, 0x19, 0x00, 0x00,
+ 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x00, 0xaf, 0x01, 0x04, 0x00, 0x19,
+ 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x04, 0x00, 0x00, 0xaf, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01,
+ 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00,
+ 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00,
+ 0x00, 0x00, 0x00, 0x11, 0xd4, 0x00, 0x19, 0x00, 0x00, 0x01, 0x04, 0x00,
+ 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x01, 0x04, 0x00, 0x00,
+ 0x01, 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x00, 0xaf, 0x00, 0x00,
+ 0x01, 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01,
+ 0x01, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01,
+ 0x00, 0xaf, 0x01, 0x04, 0x00, 0x19, 0x00, 0x00, 0x01, 0xf9, 0x01, 0x00,
+ 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0xf7, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e,
+ 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x8e,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x01, 0x04, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01};
+ RtpPacketizerH264 packetizer(long_input, limits,
+ H264PacketizationMode::NonInterleaved);
+ EXPECT_EQ(packetizer.NumPackets(), 2u);
+ EXPECT_THAT(FetchAllPackets(&packetizer), SizeIs(2));
+}
+
} // namespace
} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8.cc
index ae5f4e50a4..34b3fd9d2f 100644
--- a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8.cc
+++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8.cc
@@ -63,7 +63,9 @@ RtpPacketizerVp8::RtpPacketizerVp8(rtc::ArrayView<const uint8_t> payload,
const RTPVideoHeaderVP8& hdr_info)
: hdr_(BuildHeader(hdr_info)), remaining_payload_(payload) {
limits.max_payload_len -= hdr_.size();
- payload_sizes_ = SplitAboutEqually(payload.size(), limits);
+ if (!payload.empty()) {
+ payload_sizes_ = SplitAboutEqually(payload.size(), limits);
+ }
current_packet_ = payload_sizes_.begin();
}
diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8_unittest.cc
index 7934ff8ea9..cab7fc4422 100644
--- a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8_unittest.cc
+++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8_unittest.cc
@@ -21,6 +21,18 @@ namespace {
constexpr RtpPacketizer::PayloadSizeLimits kNoSizeLimits;
+TEST(RtpPacketizerVp8Test, EmptyPayload) {
+ RTPVideoHeaderVP8 hdr_info;
+ hdr_info.InitRTPVideoHeaderVP8();
+ hdr_info.pictureId = 200;
+ RtpFormatVp8TestHelper helper(&hdr_info, /*payload_len=*/30);
+
+ RtpPacketizer::PayloadSizeLimits limits;
+ limits.max_payload_len = 12; // Small enough to produce 4 packets.
+ RtpPacketizerVp8 packetizer({}, limits, hdr_info);
+ EXPECT_EQ(packetizer.NumPackets(), 0u);
+}
+
TEST(RtpPacketizerVp8Test, ResultPacketsAreAlmostEqualSize) {
RTPVideoHeaderVP8 hdr_info;
hdr_info.InitRTPVideoHeaderVP8();
diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp9.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp9.cc
index 9ad4aa97c3..5e037859ce 100644
--- a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp9.cc
+++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp9.cc
@@ -321,8 +321,9 @@ RtpPacketizerVp9::RtpPacketizerVp9(rtc::ArrayView<const uint8_t> payload,
limits.max_payload_len -= header_size_;
limits.first_packet_reduction_len += first_packet_extra_header_size_;
limits.single_packet_reduction_len += first_packet_extra_header_size_;
-
- payload_sizes_ = SplitAboutEqually(payload.size(), limits);
+ if (!payload.empty()) {
+ payload_sizes_ = SplitAboutEqually(payload.size(), limits);
+ }
current_packet_ = payload_sizes_.begin();
}
diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp9_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp9_unittest.cc
index e18b8a803f..948bcf3bdb 100644
--- a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp9_unittest.cc
+++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp9_unittest.cc
@@ -186,6 +186,11 @@ class RtpPacketizerVp9Test : public ::testing::Test {
}
};
+TEST_F(RtpPacketizerVp9Test, EmptyPayload) {
+ RTPVideoHeader video_header;
+ VideoRtpDepacketizerVp9::ParseRtpPayload({}, &video_header);
+}
+
TEST_F(RtpPacketizerVp9Test, TestEqualSizedMode_OnePacket) {
const size_t kFrameSize = 25;
const size_t kPacketSize = 26;
diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet.h
index e91ec6368b..c002e51de6 100644
--- a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet.h
+++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet.h
@@ -11,6 +11,7 @@
#define MODULES_RTP_RTCP_SOURCE_RTP_PACKET_H_
#include <string>
+#include <utility>
#include <vector>
#include "absl/types/optional.h"
@@ -127,7 +128,7 @@ class RtpPacket {
bool IsRegistered() const;
template <typename Extension, typename FirstValue, typename... Values>
- bool GetExtension(FirstValue, Values...) const;
+ bool GetExtension(FirstValue&&, Values&&...) const;
template <typename Extension>
absl::optional<typename Extension::value_type> GetExtension() const;
@@ -231,11 +232,12 @@ bool RtpPacket::IsRegistered() const {
}
template <typename Extension, typename FirstValue, typename... Values>
-bool RtpPacket::GetExtension(FirstValue first, Values... values) const {
+bool RtpPacket::GetExtension(FirstValue&& first, Values&&... values) const {
auto raw = FindExtension(Extension::kId);
if (raw.empty())
return false;
- return Extension::Parse(raw, first, values...);
+ return Extension::Parse(raw, std::forward<FirstValue>(first),
+ std::forward<Values>(values)...);
}
template <typename Extension>
diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_to_send.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_to_send.cc
index b55e74aaf0..691a243c5f 100644
--- a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_to_send.cc
+++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_to_send.cc
@@ -12,6 +12,8 @@
#include <cstdint>
+#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
+
namespace webrtc {
RtpPacketToSend::RtpPacketToSend(const ExtensionManager* extensions)
@@ -28,4 +30,13 @@ RtpPacketToSend& RtpPacketToSend::operator=(RtpPacketToSend&& packet) = default;
RtpPacketToSend::~RtpPacketToSend() = default;
+void RtpPacketToSend::set_packet_type(RtpPacketMediaType type) {
+ if (packet_type_ == RtpPacketMediaType::kAudio) {
+ original_packet_type_ = OriginalType::kAudio;
+ } else if (packet_type_ == RtpPacketMediaType::kVideo) {
+ original_packet_type_ = OriginalType::kVideo;
+ }
+ packet_type_ = type;
+}
+
} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_to_send.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_to_send.h
index 438ca354ed..64f9ee1ab1 100644
--- a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_to_send.h
+++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_to_send.h
@@ -49,11 +49,18 @@ class RtpPacketToSend : public RtpPacket {
webrtc::Timestamp capture_time() const { return capture_time_; }
void set_capture_time(webrtc::Timestamp time) { capture_time_ = time; }
- void set_packet_type(RtpPacketMediaType type) { packet_type_ = type; }
+ void set_packet_type(RtpPacketMediaType type);
+
absl::optional<RtpPacketMediaType> packet_type() const {
return packet_type_;
}
+ enum class OriginalType { kAudio, kVideo };
+ // Original type does not change if packet type is changed to kRetransmission.
+ absl::optional<OriginalType> original_packet_type() const {
+ return original_packet_type_;
+ }
+
// If this is a retransmission, indicates the sequence number of the original
// media packet that this packet represents. If RTX is used this will likely
// be different from SequenceNumber().
@@ -133,6 +140,7 @@ class RtpPacketToSend : public RtpPacket {
private:
webrtc::Timestamp capture_time_ = webrtc::Timestamp::Zero();
absl::optional<RtpPacketMediaType> packet_type_;
+ absl::optional<OriginalType> original_packet_type_;
bool allow_retransmission_ = false;
absl::optional<uint16_t> retransmitted_sequence_number_;
rtc::scoped_refptr<rtc::RefCountedBase> additional_data_;
diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_unittest.cc
index b3a9452df9..44f1a9e742 100644
--- a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_unittest.cc
+++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_unittest.cc
@@ -933,6 +933,41 @@ TEST(RtpPacketTest, GetUncopyableExtension) {
EXPECT_TRUE(rtp_packet.GetExtension<UncopyableExtension>(&value2));
}
+struct ParseByReferenceExtension {
+ static constexpr RTPExtensionType kId = kRtpExtensionDependencyDescriptor;
+ static constexpr absl::string_view Uri() { return "uri"; }
+
+ static size_t ValueSize(uint8_t value1, uint8_t value2) { return 2; }
+ static bool Write(rtc::ArrayView<uint8_t> data,
+ uint8_t value1,
+ uint8_t value2) {
+ data[0] = value1;
+ data[1] = value2;
+ return true;
+ }
+ static bool Parse(rtc::ArrayView<const uint8_t> data,
+ uint8_t& value1,
+ uint8_t& value2) {
+ value1 = data[0];
+ value2 = data[1];
+ return true;
+ }
+};
+
+TEST(RtpPacketTest, GetExtensionByReference) {
+ RtpHeaderExtensionMap extensions;
+ extensions.Register<ParseByReferenceExtension>(1);
+ RtpPacket rtp_packet(&extensions);
+ rtp_packet.SetExtension<ParseByReferenceExtension>(13, 42);
+
+ uint8_t value1 = 1;
+ uint8_t value2 = 1;
+ EXPECT_TRUE(
+ rtp_packet.GetExtension<ParseByReferenceExtension>(value1, value2));
+ EXPECT_EQ(int{value1}, 13);
+ EXPECT_EQ(int{value2}, 42);
+}
+
TEST(RtpPacketTest, CreateAndParseTimingFrameExtension) {
// Create a packet with video frame timing extension populated.
RtpPacketToSend::ExtensionManager send_extensions;
diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1_unittest.cc
index 2151a59295..83a2be24ea 100644
--- a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1_unittest.cc
+++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1_unittest.cc
@@ -99,6 +99,12 @@ Av1Frame ReassembleFrame(rtc::ArrayView<const RtpPayload> rtp_payloads) {
return Av1Frame(VideoRtpDepacketizerAv1().AssembleFrame(payloads));
}
+TEST(RtpPacketizerAv1Test, EmptyPayload) {
+ RtpPacketizer::PayloadSizeLimits limits;
+ RtpPacketizerAv1 packetizer({}, limits, VideoFrameType::kVideoFrameKey, true);
+ EXPECT_EQ(packetizer.NumPackets(), 0u);
+}
+
TEST(RtpPacketizerAv1Test, PacketizeOneObuWithoutSizeAndExtension) {
auto kFrame = BuildAv1Frame({Av1Obu(kAv1ObuTypeFrame)
.WithoutSize()
diff --git a/third_party/libwebrtc/modules/video_capture/BUILD.gn b/third_party/libwebrtc/modules/video_capture/BUILD.gn
index 45a0272eee..3132e452ba 100644
--- a/third_party/libwebrtc/modules/video_capture/BUILD.gn
+++ b/third_party/libwebrtc/modules/video_capture/BUILD.gn
@@ -173,6 +173,7 @@ if (!build_with_chromium || is_linux || is_chromeos) {
"../../api/video:video_frame",
"../../api/video:video_rtp_headers",
"../../common_video",
+ "../../rtc_base:gunit_helpers",
"../../rtc_base:timeutils",
"../../rtc_base/synchronization:mutex",
"../../system_wrappers",
diff --git a/third_party/libwebrtc/modules/video_capture/test/video_capture_unittest.cc b/third_party/libwebrtc/modules/video_capture/test/video_capture_unittest.cc
index c8af222b57..dec8de70cb 100644
--- a/third_party/libwebrtc/modules/video_capture/test/video_capture_unittest.cc
+++ b/third_party/libwebrtc/modules/video_capture/test/video_capture_unittest.cc
@@ -22,35 +22,16 @@
#include "api/video/video_frame.h"
#include "common_video/libyuv/include/webrtc_libyuv.h"
#include "modules/video_capture/video_capture_factory.h"
+#include "rtc_base/gunit.h"
#include "rtc_base/synchronization/mutex.h"
#include "rtc_base/time_utils.h"
-#include "system_wrappers/include/sleep.h"
#include "test/frame_utils.h"
#include "test/gtest.h"
-using webrtc::SleepMs;
using webrtc::VideoCaptureCapability;
using webrtc::VideoCaptureFactory;
using webrtc::VideoCaptureModule;
-#define WAIT_(ex, timeout, res) \
- do { \
- res = (ex); \
- int64_t start = rtc::TimeMillis(); \
- while (!res && rtc::TimeMillis() < start + timeout) { \
- SleepMs(5); \
- res = (ex); \
- } \
- } while (0)
-
-#define EXPECT_TRUE_WAIT(ex, timeout) \
- do { \
- bool res; \
- WAIT_(ex, timeout, res); \
- if (!res) \
- EXPECT_TRUE(ex); \
- } while (0)
-
static const int kTimeOut = 5000;
#ifdef WEBRTC_MAC
static const int kTestHeight = 288;
diff --git a/third_party/libwebrtc/modules/video_coding/BUILD.gn b/third_party/libwebrtc/modules/video_coding/BUILD.gn
index d9e614ff81..0457b818c3 100644
--- a/third_party/libwebrtc/modules/video_coding/BUILD.gn
+++ b/third_party/libwebrtc/modules/video_coding/BUILD.gn
@@ -218,6 +218,7 @@ rtc_library("video_coding") {
"../../api:rtp_packet_info",
"../../api:scoped_refptr",
"../../api:sequence_checker",
+ "../../api/environment",
"../../api/task_queue",
"../../api/units:data_rate",
"../../api/units:data_size",
@@ -258,7 +259,6 @@ rtc_library("video_coding") {
"../../rtc_base/task_utils:repeating_task",
"../../rtc_base/third_party/base64",
"../../system_wrappers",
- "../../system_wrappers:field_trial",
"../../system_wrappers:metrics",
"../../video/config:encoder_config",
"../rtp_rtcp",
@@ -1217,6 +1217,7 @@ if (rtc_include_tests) {
"../../api:scoped_refptr",
"../../api:simulcast_test_fixture_api",
"../../api:videocodec_test_fixture_api",
+ "../../api/environment:environment_factory",
"../../api/task_queue",
"../../api/task_queue:default_task_queue_factory",
"../../api/test/video:function_video_factory",
diff --git a/third_party/libwebrtc/modules/video_coding/codecs/av1/dav1d_decoder.cc b/third_party/libwebrtc/modules/video_coding/codecs/av1/dav1d_decoder.cc
index 6a787ff935..d658e401e8 100644
--- a/third_party/libwebrtc/modules/video_coding/codecs/av1/dav1d_decoder.cc
+++ b/third_party/libwebrtc/modules/video_coding/codecs/av1/dav1d_decoder.cc
@@ -87,6 +87,8 @@ bool Dav1dDecoder::Configure(const Settings& settings) {
s.n_threads = std::max(2, settings.number_of_cores());
s.max_frame_delay = 1; // For low latency decoding.
s.all_layers = 0; // Don't output a frame for every spatial layer.
+ // Limit max frame size to avoid OOM'ing fuzzers. crbug.com/325284120.
+ s.frame_size_limit = 16384 * 16384;
s.operating_point = 31; // Decode all operating points.
return dav1d_open(&context_, &s) == 0;
diff --git a/third_party/libwebrtc/modules/video_coding/codecs/av1/libaom_av1_unittest.cc b/third_party/libwebrtc/modules/video_coding/codecs/av1/libaom_av1_unittest.cc
index 766b7660e4..d486c1d062 100644
--- a/third_party/libwebrtc/modules/video_coding/codecs/av1/libaom_av1_unittest.cc
+++ b/third_party/libwebrtc/modules/video_coding/codecs/av1/libaom_av1_unittest.cc
@@ -350,7 +350,8 @@ INSTANTIATE_TEST_SUITE_P(
SvcTestParam{"L3T1", /*num_frames_to_generate=*/3},
SvcTestParam{"L3T3", /*num_frames_to_generate=*/8},
SvcTestParam{"S2T1", /*num_frames_to_generate=*/3},
- SvcTestParam{"S3T3", /*num_frames_to_generate=*/8},
+ // TODO: bugs.webrtc.org/15715 - Re-enable once AV1 is fixed.
+ // SvcTestParam{"S3T3", /*num_frames_to_generate=*/8},
SvcTestParam{"L2T2", /*num_frames_to_generate=*/4},
SvcTestParam{"L2T2_KEY", /*num_frames_to_generate=*/4},
SvcTestParam{"L2T2_KEY_SHIFT",
diff --git a/third_party/libwebrtc/modules/video_coding/codecs/h264/h264_decoder_impl.cc b/third_party/libwebrtc/modules/video_coding/codecs/h264/h264_decoder_impl.cc
index a9e9926c4f..c6446c25ce 100644
--- a/third_party/libwebrtc/modules/video_coding/codecs/h264/h264_decoder_impl.cc
+++ b/third_party/libwebrtc/modules/video_coding/codecs/h264/h264_decoder_impl.cc
@@ -80,7 +80,11 @@ int H264DecoderImpl::AVGetBuffer2(AVCodecContext* context,
kPixelFormatsSupported.begin(), kPixelFormatsSupported.end(),
[context](AVPixelFormat format) { return context->pix_fmt == format; });
- RTC_CHECK(pixelFormatSupported != kPixelFormatsSupported.end());
+ if (pixelFormatSupported == kPixelFormatsSupported.end()) {
+ RTC_LOG(LS_ERROR) << "Unsupported pixel format: " << context->pix_fmt;
+ decoder->ReportError();
+ return -1;
+ }
// `av_frame->width` and `av_frame->height` are set by FFmpeg. These are the
// actual image's dimensions and may be different from `context->width` and
diff --git a/third_party/libwebrtc/modules/video_coding/codecs/test/video_codec_test.cc b/third_party/libwebrtc/modules/video_coding/codecs/test/video_codec_test.cc
index 60c2fcbb6e..2ab1106a59 100644
--- a/third_party/libwebrtc/modules/video_coding/codecs/test/video_codec_test.cc
+++ b/third_party/libwebrtc/modules/video_coding/codecs/test/video_codec_test.cc
@@ -178,7 +178,8 @@ std::string TestOutputPath() {
} // namespace
std::unique_ptr<VideoCodecStats> RunEncodeDecodeTest(
- std::string codec_impl,
+ std::string encoder_impl,
+ std::string decoder_impl,
const VideoInfo& video_info,
const std::map<uint32_t, EncodingSettings>& encoding_settings) {
VideoSourceSettings source_settings{
@@ -190,28 +191,34 @@ std::unique_ptr<VideoCodecStats> RunEncodeDecodeTest(
encoding_settings.begin()->second.sdp_video_format;
std::unique_ptr<VideoEncoderFactory> encoder_factory =
- CreateEncoderFactory(codec_impl);
+ CreateEncoderFactory(encoder_impl);
if (!encoder_factory
->QueryCodecSupport(sdp_video_format,
/*scalability_mode=*/absl::nullopt)
.is_supported) {
- RTC_LOG(LS_WARNING) << "No encoder for video format "
+ RTC_LOG(LS_WARNING) << "No " << encoder_impl << " encoder for video format "
<< sdp_video_format.ToString();
return nullptr;
}
std::unique_ptr<VideoDecoderFactory> decoder_factory =
- CreateDecoderFactory(codec_impl);
+ CreateDecoderFactory(decoder_impl);
if (!decoder_factory
->QueryCodecSupport(sdp_video_format,
/*reference_scaling=*/false)
.is_supported) {
+ RTC_LOG(LS_WARNING) << "No " << decoder_impl << " decoder for video format "
+ << sdp_video_format.ToString()
+ << ". Trying built-in decoder.";
+ // TODO(ssilkin): No H264 support in ffmpeg on ARM. Consider trying HW
+ // decoder.
decoder_factory = CreateDecoderFactory("builtin");
if (!decoder_factory
->QueryCodecSupport(sdp_video_format,
/*reference_scaling=*/false)
.is_supported) {
- RTC_LOG(LS_WARNING) << "No decoder for video format "
+ RTC_LOG(LS_WARNING) << "No " << decoder_impl
+ << " decoder for video format "
<< sdp_video_format.ToString();
return nullptr;
}
@@ -221,7 +228,7 @@ std::unique_ptr<VideoCodecStats> RunEncodeDecodeTest(
VideoCodecTester::EncoderSettings encoder_settings;
encoder_settings.pacing_settings.mode =
- codec_impl == "builtin" ? PacingMode::kNoPacing : PacingMode::kRealTime;
+ encoder_impl == "builtin" ? PacingMode::kNoPacing : PacingMode::kRealTime;
if (absl::GetFlag(FLAGS_dump_encoder_input)) {
encoder_settings.encoder_input_base_path = output_path + "_enc_input";
}
@@ -231,7 +238,7 @@ std::unique_ptr<VideoCodecStats> RunEncodeDecodeTest(
VideoCodecTester::DecoderSettings decoder_settings;
decoder_settings.pacing_settings.mode =
- codec_impl == "builtin" ? PacingMode::kNoPacing : PacingMode::kRealTime;
+ decoder_impl == "builtin" ? PacingMode::kNoPacing : PacingMode::kRealTime;
if (absl::GetFlag(FLAGS_dump_decoder_input)) {
decoder_settings.decoder_input_base_path = output_path + "_dec_input";
}
@@ -318,7 +325,7 @@ TEST_P(SpatialQualityTest, SpatialQuality) {
{bitrate_kbps}, framerate_fps, num_frames);
std::unique_ptr<VideoCodecStats> stats =
- RunEncodeDecodeTest(codec_impl, video_info, frames_settings);
+ RunEncodeDecodeTest(codec_impl, codec_impl, video_info, frames_settings);
VideoCodecStats::Stream stream;
if (stats != nullptr) {
@@ -348,15 +355,15 @@ INSTANTIATE_TEST_SUITE_P(
Values("builtin"),
#endif
Values(kRawVideos.at("FourPeople_1280x720_30")),
- Values(std::make_tuple(320, 180, 30, 32, 28),
- std::make_tuple(320, 180, 30, 64, 30),
- std::make_tuple(320, 180, 30, 128, 33),
+ Values(std::make_tuple(320, 180, 30, 32, 26),
+ std::make_tuple(320, 180, 30, 64, 29),
+ std::make_tuple(320, 180, 30, 128, 32),
std::make_tuple(320, 180, 30, 256, 36),
- std::make_tuple(640, 360, 30, 128, 31),
+ std::make_tuple(640, 360, 30, 128, 29),
std::make_tuple(640, 360, 30, 256, 33),
std::make_tuple(640, 360, 30, 384, 35),
std::make_tuple(640, 360, 30, 512, 36),
- std::make_tuple(1280, 720, 30, 256, 32),
+ std::make_tuple(1280, 720, 30, 256, 30),
std::make_tuple(1280, 720, 30, 512, 34),
std::make_tuple(1280, 720, 30, 1024, 37),
std::make_tuple(1280, 720, 30, 2048, 39))),
@@ -538,6 +545,7 @@ TEST(VideoCodecTest, DISABLED_EncodeDecode) {
// Sync with changes in Stream::LogMetrics (see TODOs there).
std::unique_ptr<VideoCodecStats> stats = RunEncodeDecodeTest(
CodecNameToCodecImpl(absl::GetFlag(FLAGS_encoder)),
+ CodecNameToCodecImpl(absl::GetFlag(FLAGS_decoder)),
kRawVideos.at(absl::GetFlag(FLAGS_video_name)), frames_settings);
ASSERT_NE(nullptr, stats);
diff --git a/third_party/libwebrtc/modules/video_coding/codecs/test/videocodec_test_fixture_impl.cc b/third_party/libwebrtc/modules/video_coding/codecs/test/videocodec_test_fixture_impl.cc
index eb264e5285..35355d4387 100644
--- a/third_party/libwebrtc/modules/video_coding/codecs/test/videocodec_test_fixture_impl.cc
+++ b/third_party/libwebrtc/modules/video_coding/codecs/test/videocodec_test_fixture_impl.cc
@@ -167,7 +167,7 @@ SdpVideoFormat CreateSdpVideoFormat(
H264PacketizationMode::NonInterleaved
? "1"
: "0";
- SdpVideoFormat::Parameters codec_params = {
+ CodecParameterMap codec_params = {
{cricket::kH264FmtpProfileLevelId,
*H264ProfileLevelIdToString(H264ProfileLevelId(
config.h264_codec_settings.profile, H264Level::kLevel3_1))},
diff --git a/third_party/libwebrtc/modules/video_coding/fec_controller_default.cc b/third_party/libwebrtc/modules/video_coding/fec_controller_default.cc
index f204b01c7c..d548d6580c 100644
--- a/third_party/libwebrtc/modules/video_coding/fec_controller_default.cc
+++ b/third_party/libwebrtc/modules/video_coding/fec_controller_default.cc
@@ -15,30 +15,28 @@
#include <algorithm>
#include <string>
+#include "api/environment/environment.h"
+#include "api/field_trials_view.h"
#include "modules/include/module_fec_types.h"
#include "rtc_base/logging.h"
-#include "system_wrappers/include/field_trial.h"
+#include "system_wrappers/include/clock.h"
namespace webrtc {
const float kProtectionOverheadRateThreshold = 0.5;
FecControllerDefault::FecControllerDefault(
- Clock* clock,
+ const Environment& env,
VCMProtectionCallback* protection_callback)
- : clock_(clock),
+ : env_(env),
protection_callback_(protection_callback),
loss_prot_logic_(new media_optimization::VCMLossProtectionLogic(
- clock_->TimeInMilliseconds())),
+ env_.clock().TimeInMilliseconds())),
max_payload_size_(1460),
overhead_threshold_(GetProtectionOverheadRateThreshold()) {}
-FecControllerDefault::FecControllerDefault(Clock* clock)
- : clock_(clock),
- loss_prot_logic_(new media_optimization::VCMLossProtectionLogic(
- clock_->TimeInMilliseconds())),
- max_payload_size_(1460),
- overhead_threshold_(GetProtectionOverheadRateThreshold()) {}
+FecControllerDefault::FecControllerDefault(const Environment& env)
+ : FecControllerDefault(env, nullptr) {}
FecControllerDefault::~FecControllerDefault(void) {
loss_prot_logic_->Release();
@@ -61,8 +59,8 @@ void FecControllerDefault::SetEncodingData(size_t width,
float FecControllerDefault::GetProtectionOverheadRateThreshold() {
float overhead_threshold =
- strtof(webrtc::field_trial::FindFullName(
- "WebRTC-ProtectionOverheadRateThreshold")
+ strtof(env_.field_trials()
+ .Lookup("WebRTC-ProtectionOverheadRateThreshold")
.c_str(),
nullptr);
if (overhead_threshold > 0 && overhead_threshold <= 1) {
@@ -107,7 +105,7 @@ uint32_t FecControllerDefault::UpdateFecRates(
media_optimization::FilterPacketLossMode filter_mode =
media_optimization::kMaxFilter;
uint8_t packet_loss_enc = loss_prot_logic_->FilteredLoss(
- clock_->TimeInMilliseconds(), filter_mode, fraction_lost);
+ env_.clock().TimeInMilliseconds(), filter_mode, fraction_lost);
// For now use the filtered loss for computing the robustness settings.
loss_prot_logic_->UpdateFilteredLossPr(packet_loss_enc);
if (loss_prot_logic_->SelectedType() == media_optimization::kNone) {
@@ -191,11 +189,11 @@ void FecControllerDefault::UpdateWithEncodedData(
const float min_packets_per_frame =
encoded_length / static_cast<float>(max_payload_size_);
if (delta_frame) {
- loss_prot_logic_->UpdatePacketsPerFrame(min_packets_per_frame,
- clock_->TimeInMilliseconds());
+ loss_prot_logic_->UpdatePacketsPerFrame(
+ min_packets_per_frame, env_.clock().TimeInMilliseconds());
} else {
loss_prot_logic_->UpdatePacketsPerFrameKey(
- min_packets_per_frame, clock_->TimeInMilliseconds());
+ min_packets_per_frame, env_.clock().TimeInMilliseconds());
}
}
if (!delta_frame && encoded_length > 0) {
diff --git a/third_party/libwebrtc/modules/video_coding/fec_controller_default.h b/third_party/libwebrtc/modules/video_coding/fec_controller_default.h
index a97dea011b..f60302b2d8 100644
--- a/third_party/libwebrtc/modules/video_coding/fec_controller_default.h
+++ b/third_party/libwebrtc/modules/video_coding/fec_controller_default.h
@@ -17,24 +17,25 @@
#include <memory>
#include <vector>
+#include "api/environment/environment.h"
#include "api/fec_controller.h"
#include "modules/video_coding/media_opt_util.h"
#include "rtc_base/synchronization/mutex.h"
#include "rtc_base/thread_annotations.h"
-#include "system_wrappers/include/clock.h"
namespace webrtc {
class FecControllerDefault : public FecController {
public:
- FecControllerDefault(Clock* clock,
+ FecControllerDefault(const Environment& env,
VCMProtectionCallback* protection_callback);
- explicit FecControllerDefault(Clock* clock);
- ~FecControllerDefault() override;
+ explicit FecControllerDefault(const Environment& env);
FecControllerDefault(const FecControllerDefault&) = delete;
FecControllerDefault& operator=(const FecControllerDefault&) = delete;
+ ~FecControllerDefault() override;
+
void SetProtectionCallback(
VCMProtectionCallback* protection_callback) override;
void SetProtectionMethod(bool enable_fec, bool enable_nack) override;
@@ -54,7 +55,7 @@ class FecControllerDefault : public FecController {
private:
enum { kBitrateAverageWinMs = 1000 };
- Clock* const clock_;
+ const Environment env_;
VCMProtectionCallback* protection_callback_;
Mutex mutex_;
std::unique_ptr<media_optimization::VCMLossProtectionLogic> loss_prot_logic_
diff --git a/third_party/libwebrtc/modules/video_coding/fec_controller_unittest.cc b/third_party/libwebrtc/modules/video_coding/fec_controller_unittest.cc
index fa3cc7a93b..c4a0a08fd1 100644
--- a/third_party/libwebrtc/modules/video_coding/fec_controller_unittest.cc
+++ b/third_party/libwebrtc/modules/video_coding/fec_controller_unittest.cc
@@ -14,6 +14,7 @@
#include <vector>
+#include "api/environment/environment_factory.h"
#include "modules/include/module_fec_types.h"
#include "modules/video_coding/fec_controller_default.h"
#include "system_wrappers/include/clock.h"
@@ -50,7 +51,8 @@ class ProtectionBitrateCalculatorTest : public ::testing::Test {
// Note: simulated clock starts at 1 seconds, since parts of webrtc use 0 as
// a special case (e.g. frame rate in media optimization).
ProtectionBitrateCalculatorTest()
- : clock_(1000), fec_controller_(&clock_, &protection_callback_) {}
+ : clock_(1000),
+ fec_controller_(CreateEnvironment(&clock_), &protection_callback_) {}
SimulatedClock clock_;
ProtectionCallback protection_callback_;
diff --git a/third_party/libwebrtc/modules/video_coding/nack_requester.cc b/third_party/libwebrtc/modules/video_coding/nack_requester.cc
index 008420f4da..b3e928d05e 100644
--- a/third_party/libwebrtc/modules/video_coding/nack_requester.cc
+++ b/third_party/libwebrtc/modules/video_coding/nack_requester.cc
@@ -141,13 +141,12 @@ void NackRequester::ProcessNacks() {
}
}
-int NackRequester::OnReceivedPacket(uint16_t seq_num, bool is_keyframe) {
+int NackRequester::OnReceivedPacket(uint16_t seq_num) {
RTC_DCHECK_RUN_ON(worker_thread_);
- return OnReceivedPacket(seq_num, is_keyframe, false);
+ return OnReceivedPacket(seq_num, false);
}
int NackRequester::OnReceivedPacket(uint16_t seq_num,
- bool is_keyframe,
bool is_recovered) {
RTC_DCHECK_RUN_ON(worker_thread_);
// TODO(philipel): When the packet includes information whether it is
@@ -158,8 +157,6 @@ int NackRequester::OnReceivedPacket(uint16_t seq_num,
if (!initialized_) {
newest_seq_num_ = seq_num;
- if (is_keyframe)
- keyframe_list_.insert(seq_num);
initialized_ = true;
return 0;
}
@@ -182,15 +179,6 @@ int NackRequester::OnReceivedPacket(uint16_t seq_num,
return nacks_sent_for_packet;
}
- // Keep track of new keyframes.
- if (is_keyframe)
- keyframe_list_.insert(seq_num);
-
- // And remove old ones so we don't accumulate keyframes.
- auto it = keyframe_list_.lower_bound(seq_num - kMaxPacketAge);
- if (it != keyframe_list_.begin())
- keyframe_list_.erase(keyframe_list_.begin(), it);
-
if (is_recovered) {
recovered_list_.insert(seq_num);
@@ -225,8 +213,6 @@ void NackRequester::ClearUpTo(uint16_t seq_num) {
// thread.
RTC_DCHECK_RUN_ON(worker_thread_);
nack_list_.erase(nack_list_.begin(), nack_list_.lower_bound(seq_num));
- keyframe_list_.erase(keyframe_list_.begin(),
- keyframe_list_.lower_bound(seq_num));
recovered_list_.erase(recovered_list_.begin(),
recovered_list_.lower_bound(seq_num));
}
@@ -236,25 +222,6 @@ void NackRequester::UpdateRtt(int64_t rtt_ms) {
rtt_ = TimeDelta::Millis(rtt_ms);
}
-bool NackRequester::RemovePacketsUntilKeyFrame() {
- // Called on worker_thread_.
- while (!keyframe_list_.empty()) {
- auto it = nack_list_.lower_bound(*keyframe_list_.begin());
-
- if (it != nack_list_.begin()) {
- // We have found a keyframe that actually is newer than at least one
- // packet in the nack list.
- nack_list_.erase(nack_list_.begin(), it);
- return true;
- }
-
- // If this keyframe is so old it does not remove any packets from the list,
- // remove it from the list of keyframes and try the next keyframe.
- keyframe_list_.erase(keyframe_list_.begin());
- }
- return false;
-}
-
void NackRequester::AddPacketsToNack(uint16_t seq_num_start,
uint16_t seq_num_end) {
// Called on worker_thread_.
@@ -262,22 +229,13 @@ void NackRequester::AddPacketsToNack(uint16_t seq_num_start,
auto it = nack_list_.lower_bound(seq_num_end - kMaxPacketAge);
nack_list_.erase(nack_list_.begin(), it);
- // If the nack list is too large, remove packets from the nack list until
- // the latest first packet of a keyframe. If the list is still too large,
- // clear it and request a keyframe.
uint16_t num_new_nacks = ForwardDiff(seq_num_start, seq_num_end);
if (nack_list_.size() + num_new_nacks > kMaxNackPackets) {
- while (RemovePacketsUntilKeyFrame() &&
- nack_list_.size() + num_new_nacks > kMaxNackPackets) {
- }
-
- if (nack_list_.size() + num_new_nacks > kMaxNackPackets) {
- nack_list_.clear();
- RTC_LOG(LS_WARNING) << "NACK list full, clearing NACK"
- " list and requesting keyframe.";
- keyframe_request_sender_->RequestKeyFrame();
- return;
- }
+ nack_list_.clear();
+ RTC_LOG(LS_WARNING) << "NACK list full, clearing NACK"
+ " list and requesting keyframe.";
+ keyframe_request_sender_->RequestKeyFrame();
+ return;
}
for (uint16_t seq_num = seq_num_start; seq_num != seq_num_end; ++seq_num) {
diff --git a/third_party/libwebrtc/modules/video_coding/nack_requester.h b/third_party/libwebrtc/modules/video_coding/nack_requester.h
index c860787dcf..b5ef30f01b 100644
--- a/third_party/libwebrtc/modules/video_coding/nack_requester.h
+++ b/third_party/libwebrtc/modules/video_coding/nack_requester.h
@@ -78,8 +78,8 @@ class NackRequester final : public NackRequesterBase {
void ProcessNacks() override;
- int OnReceivedPacket(uint16_t seq_num, bool is_keyframe);
- int OnReceivedPacket(uint16_t seq_num, bool is_keyframe, bool is_recovered);
+ int OnReceivedPacket(uint16_t seq_num);
+ int OnReceivedPacket(uint16_t seq_num, bool is_recovered);
void ClearUpTo(uint16_t seq_num);
void UpdateRtt(int64_t rtt_ms);
@@ -108,10 +108,6 @@ class NackRequester final : public NackRequesterBase {
void AddPacketsToNack(uint16_t seq_num_start, uint16_t seq_num_end)
RTC_EXCLUSIVE_LOCKS_REQUIRED(worker_thread_);
- // Removes packets from the nack list until the next keyframe. Returns true
- // if packets were removed.
- bool RemovePacketsUntilKeyFrame()
- RTC_EXCLUSIVE_LOCKS_REQUIRED(worker_thread_);
std::vector<uint16_t> GetNackBatch(NackFilterOptions options)
RTC_EXCLUSIVE_LOCKS_REQUIRED(worker_thread_);
@@ -134,8 +130,6 @@ class NackRequester final : public NackRequesterBase {
// synchronized access.
std::map<uint16_t, NackInfo, DescendingSeqNumComp<uint16_t>> nack_list_
RTC_GUARDED_BY(worker_thread_);
- std::set<uint16_t, DescendingSeqNumComp<uint16_t>> keyframe_list_
- RTC_GUARDED_BY(worker_thread_);
std::set<uint16_t, DescendingSeqNumComp<uint16_t>> recovered_list_
RTC_GUARDED_BY(worker_thread_);
video_coding::Histogram reordering_histogram_ RTC_GUARDED_BY(worker_thread_);
diff --git a/third_party/libwebrtc/modules/video_coding/nack_requester_unittest.cc b/third_party/libwebrtc/modules/video_coding/nack_requester_unittest.cc
index 6f11cb6e91..2f24df2aef 100644
--- a/third_party/libwebrtc/modules/video_coding/nack_requester_unittest.cc
+++ b/third_party/libwebrtc/modules/video_coding/nack_requester_unittest.cc
@@ -102,90 +102,25 @@ class TestNackRequester : public ::testing::Test,
TEST_F(TestNackRequester, NackOnePacket) {
NackRequester& nack_module = CreateNackModule();
- nack_module.OnReceivedPacket(1, false, false);
- nack_module.OnReceivedPacket(3, false, false);
+ nack_module.OnReceivedPacket(1);
+ nack_module.OnReceivedPacket(3);
ASSERT_EQ(1u, sent_nacks_.size());
EXPECT_EQ(2, sent_nacks_[0]);
}
TEST_F(TestNackRequester, WrappingSeqNum) {
NackRequester& nack_module = CreateNackModule();
- nack_module.OnReceivedPacket(0xfffe, false, false);
- nack_module.OnReceivedPacket(1, false, false);
+ nack_module.OnReceivedPacket(0xfffe);
+ nack_module.OnReceivedPacket(1);
ASSERT_EQ(2u, sent_nacks_.size());
EXPECT_EQ(0xffff, sent_nacks_[0]);
EXPECT_EQ(0, sent_nacks_[1]);
}
-TEST_F(TestNackRequester, WrappingSeqNumClearToKeyframe) {
- NackRequester& nack_module = CreateNackModule(TimeDelta::Millis(10));
- nack_module.OnReceivedPacket(0xfffe, false, false);
- nack_module.OnReceivedPacket(1, false, false);
- ASSERT_EQ(2u, sent_nacks_.size());
- EXPECT_EQ(0xffff, sent_nacks_[0]);
- EXPECT_EQ(0, sent_nacks_[1]);
-
- sent_nacks_.clear();
- nack_module.OnReceivedPacket(2, true, false);
- ASSERT_EQ(0u, sent_nacks_.size());
-
- nack_module.OnReceivedPacket(501, true, false);
- ASSERT_EQ(498u, sent_nacks_.size());
- for (int seq_num = 3; seq_num < 501; ++seq_num)
- EXPECT_EQ(seq_num, sent_nacks_[seq_num - 3]);
-
- sent_nacks_.clear();
- nack_module.OnReceivedPacket(1001, false, false);
- EXPECT_EQ(499u, sent_nacks_.size());
- for (int seq_num = 502; seq_num < 1001; ++seq_num)
- EXPECT_EQ(seq_num, sent_nacks_[seq_num - 502]);
-
- sent_nacks_.clear();
- clock_->AdvanceTimeMilliseconds(100);
- ASSERT_TRUE(WaitForSendNack());
- ASSERT_EQ(999u, sent_nacks_.size());
- EXPECT_EQ(0xffff, sent_nacks_[0]);
- EXPECT_EQ(0, sent_nacks_[1]);
- for (int seq_num = 3; seq_num < 501; ++seq_num)
- EXPECT_EQ(seq_num, sent_nacks_[seq_num - 1]);
- for (int seq_num = 502; seq_num < 1001; ++seq_num)
- EXPECT_EQ(seq_num, sent_nacks_[seq_num - 2]);
-
- // Adding packet 1004 will cause the nack list to reach it's max limit.
- // It will then clear all nacks up to the next keyframe (seq num 2),
- // thus removing 0xffff and 0 from the nack list.
- sent_nacks_.clear();
- nack_module.OnReceivedPacket(1004, false, false);
- ASSERT_EQ(2u, sent_nacks_.size());
- EXPECT_EQ(1002, sent_nacks_[0]);
- EXPECT_EQ(1003, sent_nacks_[1]);
-
- sent_nacks_.clear();
- clock_->AdvanceTimeMilliseconds(100);
- ASSERT_TRUE(WaitForSendNack());
- ASSERT_EQ(999u, sent_nacks_.size());
- for (int seq_num = 3; seq_num < 501; ++seq_num)
- EXPECT_EQ(seq_num, sent_nacks_[seq_num - 3]);
- for (int seq_num = 502; seq_num < 1001; ++seq_num)
- EXPECT_EQ(seq_num, sent_nacks_[seq_num - 4]);
-
- // Adding packet 1007 will cause the nack module to overflow again, thus
- // clearing everything up to 501 which is the next keyframe.
- nack_module.OnReceivedPacket(1007, false, false);
- sent_nacks_.clear();
- clock_->AdvanceTimeMilliseconds(100);
- ASSERT_TRUE(WaitForSendNack());
- ASSERT_EQ(503u, sent_nacks_.size());
- for (int seq_num = 502; seq_num < 1001; ++seq_num)
- EXPECT_EQ(seq_num, sent_nacks_[seq_num - 502]);
- EXPECT_EQ(1005, sent_nacks_[501]);
- EXPECT_EQ(1006, sent_nacks_[502]);
-}
-
TEST_F(TestNackRequester, ResendNack) {
NackRequester& nack_module = CreateNackModule(TimeDelta::Millis(1));
- nack_module.OnReceivedPacket(1, false, false);
- nack_module.OnReceivedPacket(3, false, false);
+ nack_module.OnReceivedPacket(1);
+ nack_module.OnReceivedPacket(3);
size_t expected_nacks_sent = 1;
ASSERT_EQ(expected_nacks_sent, sent_nacks_.size());
EXPECT_EQ(2, sent_nacks_[0]);
@@ -225,8 +160,8 @@ TEST_F(TestNackRequester, ResendNack) {
TEST_F(TestNackRequester, ResendPacketMaxRetries) {
NackRequester& nack_module = CreateNackModule(TimeDelta::Millis(1));
- nack_module.OnReceivedPacket(1, false, false);
- nack_module.OnReceivedPacket(3, false, false);
+ nack_module.OnReceivedPacket(1);
+ nack_module.OnReceivedPacket(3);
ASSERT_EQ(1u, sent_nacks_.size());
EXPECT_EQ(2, sent_nacks_[0]);
@@ -246,37 +181,22 @@ TEST_F(TestNackRequester, ResendPacketMaxRetries) {
TEST_F(TestNackRequester, TooLargeNackList) {
NackRequester& nack_module = CreateNackModule();
- nack_module.OnReceivedPacket(0, false, false);
- nack_module.OnReceivedPacket(1001, false, false);
+ nack_module.OnReceivedPacket(0);
+ nack_module.OnReceivedPacket(1001);
EXPECT_EQ(1000u, sent_nacks_.size());
EXPECT_EQ(0, keyframes_requested_);
- nack_module.OnReceivedPacket(1003, false, false);
+ nack_module.OnReceivedPacket(1003);
EXPECT_EQ(1000u, sent_nacks_.size());
EXPECT_EQ(1, keyframes_requested_);
- nack_module.OnReceivedPacket(1004, false, false);
- EXPECT_EQ(1000u, sent_nacks_.size());
- EXPECT_EQ(1, keyframes_requested_);
-}
-
-TEST_F(TestNackRequester, TooLargeNackListWithKeyFrame) {
- NackRequester& nack_module = CreateNackModule();
- nack_module.OnReceivedPacket(0, false, false);
- nack_module.OnReceivedPacket(1, true, false);
- nack_module.OnReceivedPacket(1001, false, false);
- EXPECT_EQ(999u, sent_nacks_.size());
- EXPECT_EQ(0, keyframes_requested_);
- nack_module.OnReceivedPacket(1003, false, false);
- EXPECT_EQ(1000u, sent_nacks_.size());
- EXPECT_EQ(0, keyframes_requested_);
- nack_module.OnReceivedPacket(1005, false, false);
+ nack_module.OnReceivedPacket(1004);
EXPECT_EQ(1000u, sent_nacks_.size());
EXPECT_EQ(1, keyframes_requested_);
}
TEST_F(TestNackRequester, ClearUpTo) {
NackRequester& nack_module = CreateNackModule(TimeDelta::Millis(1));
- nack_module.OnReceivedPacket(0, false, false);
- nack_module.OnReceivedPacket(100, false, false);
+ nack_module.OnReceivedPacket(0);
+ nack_module.OnReceivedPacket(100);
EXPECT_EQ(99u, sent_nacks_.size());
sent_nacks_.clear();
@@ -289,8 +209,8 @@ TEST_F(TestNackRequester, ClearUpTo) {
TEST_F(TestNackRequester, ClearUpToWrap) {
NackRequester& nack_module = CreateNackModule();
- nack_module.OnReceivedPacket(0xfff0, false, false);
- nack_module.OnReceivedPacket(0xf, false, false);
+ nack_module.OnReceivedPacket(0xfff0);
+ nack_module.OnReceivedPacket(0xf);
EXPECT_EQ(30u, sent_nacks_.size());
sent_nacks_.clear();
@@ -303,13 +223,13 @@ TEST_F(TestNackRequester, ClearUpToWrap) {
TEST_F(TestNackRequester, PacketNackCount) {
NackRequester& nack_module = CreateNackModule(TimeDelta::Millis(1));
- EXPECT_EQ(0, nack_module.OnReceivedPacket(0, false, false));
- EXPECT_EQ(0, nack_module.OnReceivedPacket(2, false, false));
- EXPECT_EQ(1, nack_module.OnReceivedPacket(1, false, false));
+ EXPECT_EQ(0, nack_module.OnReceivedPacket(0));
+ EXPECT_EQ(0, nack_module.OnReceivedPacket(2));
+ EXPECT_EQ(1, nack_module.OnReceivedPacket(1));
sent_nacks_.clear();
nack_module.UpdateRtt(100);
- EXPECT_EQ(0, nack_module.OnReceivedPacket(5, false, false));
+ EXPECT_EQ(0, nack_module.OnReceivedPacket(5));
clock_->AdvanceTimeMilliseconds(100);
WaitForSendNack();
EXPECT_EQ(4u, sent_nacks_.size());
@@ -319,40 +239,24 @@ TEST_F(TestNackRequester, PacketNackCount) {
EXPECT_EQ(6u, sent_nacks_.size());
- EXPECT_EQ(3, nack_module.OnReceivedPacket(3, false, false));
- EXPECT_EQ(3, nack_module.OnReceivedPacket(4, false, false));
- EXPECT_EQ(0, nack_module.OnReceivedPacket(4, false, false));
-}
-
-TEST_F(TestNackRequester, NackListFullAndNoOverlapWithKeyframes) {
- NackRequester& nack_module = CreateNackModule();
- const int kMaxNackPackets = 1000;
- const unsigned int kFirstGap = kMaxNackPackets - 20;
- const unsigned int kSecondGap = 200;
- uint16_t seq_num = 0;
- nack_module.OnReceivedPacket(seq_num++, true, false);
- seq_num += kFirstGap;
- nack_module.OnReceivedPacket(seq_num++, true, false);
- EXPECT_EQ(kFirstGap, sent_nacks_.size());
- sent_nacks_.clear();
- seq_num += kSecondGap;
- nack_module.OnReceivedPacket(seq_num, true, false);
- EXPECT_EQ(kSecondGap, sent_nacks_.size());
+ EXPECT_EQ(3, nack_module.OnReceivedPacket(3));
+ EXPECT_EQ(3, nack_module.OnReceivedPacket(4));
+ EXPECT_EQ(0, nack_module.OnReceivedPacket(4));
}
TEST_F(TestNackRequester, HandleFecRecoveredPacket) {
NackRequester& nack_module = CreateNackModule();
- nack_module.OnReceivedPacket(1, false, false);
- nack_module.OnReceivedPacket(4, false, true);
+ nack_module.OnReceivedPacket(1);
+ nack_module.OnReceivedPacket(4, /*is_recovered=*/true);
EXPECT_EQ(0u, sent_nacks_.size());
- nack_module.OnReceivedPacket(5, false, false);
+ nack_module.OnReceivedPacket(5);
EXPECT_EQ(2u, sent_nacks_.size());
}
TEST_F(TestNackRequester, SendNackWithoutDelay) {
NackRequester& nack_module = CreateNackModule();
- nack_module.OnReceivedPacket(0, false, false);
- nack_module.OnReceivedPacket(100, false, false);
+ nack_module.OnReceivedPacket(0);
+ nack_module.OnReceivedPacket(100);
EXPECT_EQ(99u, sent_nacks_.size());
}
@@ -389,14 +293,14 @@ class TestNackRequesterWithFieldTrial : public ::testing::Test,
};
TEST_F(TestNackRequesterWithFieldTrial, SendNackWithDelay) {
- nack_module_.OnReceivedPacket(0, false, false);
- nack_module_.OnReceivedPacket(100, false, false);
+ nack_module_.OnReceivedPacket(0);
+ nack_module_.OnReceivedPacket(100);
EXPECT_EQ(0u, sent_nacks_.size());
clock_->AdvanceTimeMilliseconds(10);
- nack_module_.OnReceivedPacket(106, false, false);
+ nack_module_.OnReceivedPacket(106);
EXPECT_EQ(99u, sent_nacks_.size());
clock_->AdvanceTimeMilliseconds(10);
- nack_module_.OnReceivedPacket(109, false, false);
+ nack_module_.OnReceivedPacket(109);
EXPECT_EQ(104u, sent_nacks_.size());
}
} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/video_coding/utility/qp_parser.cc b/third_party/libwebrtc/modules/video_coding/utility/qp_parser.cc
index 3b9aaa377e..a531b54a7e 100644
--- a/third_party/libwebrtc/modules/video_coding/utility/qp_parser.cc
+++ b/third_party/libwebrtc/modules/video_coding/utility/qp_parser.cc
@@ -37,7 +37,10 @@ absl::optional<uint32_t> QpParser::Parse(VideoCodecType codec_type,
} else if (codec_type == kVideoCodecH264) {
return h264_parsers_[spatial_idx].Parse(frame_data, frame_size);
} else if (codec_type == kVideoCodecH265) {
- // TODO(bugs.webrtc.org/13485)
+ // H.265 bitstream parser is conditionally built.
+#ifdef RTC_ENABLE_H265
+ return h265_parsers_[spatial_idx].Parse(frame_data, frame_size);
+#endif
}
return absl::nullopt;
@@ -52,4 +55,15 @@ absl::optional<uint32_t> QpParser::H264QpParser::Parse(
return bitstream_parser_.GetLastSliceQp();
}
+#ifdef RTC_ENABLE_H265
+absl::optional<uint32_t> QpParser::H265QpParser::Parse(
+ const uint8_t* frame_data,
+ size_t frame_size) {
+ MutexLock lock(&mutex_);
+ bitstream_parser_.ParseBitstream(
+ rtc::ArrayView<const uint8_t>(frame_data, frame_size));
+ return bitstream_parser_.GetLastSliceQp();
+}
+#endif
+
} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/video_coding/utility/qp_parser.h b/third_party/libwebrtc/modules/video_coding/utility/qp_parser.h
index f132ff9337..210fe02bc3 100644
--- a/third_party/libwebrtc/modules/video_coding/utility/qp_parser.h
+++ b/third_party/libwebrtc/modules/video_coding/utility/qp_parser.h
@@ -15,6 +15,9 @@
#include "api/video/video_codec_constants.h"
#include "api/video/video_codec_type.h"
#include "common_video/h264/h264_bitstream_parser.h"
+#ifdef RTC_ENABLE_H265
+#include "common_video/h265/h265_bitstream_parser.h"
+#endif
#include "rtc_base/synchronization/mutex.h"
namespace webrtc {
@@ -38,6 +41,21 @@ class QpParser {
};
H264QpParser h264_parsers_[kMaxSimulcastStreams];
+
+#ifdef RTC_ENABLE_H265
+ // A thread safe wrapper for H.265 bitstream parser.
+ class H265QpParser {
+ public:
+ absl::optional<uint32_t> Parse(const uint8_t* frame_data,
+ size_t frame_size);
+
+ private:
+ Mutex mutex_;
+ H265BitstreamParser bitstream_parser_ RTC_GUARDED_BY(mutex_);
+ };
+
+ H265QpParser h265_parsers_[kMaxSimulcastStreams];
+#endif
};
} // namespace webrtc
diff --git a/third_party/libwebrtc/moz-patch-stack/0001.patch b/third_party/libwebrtc/moz-patch-stack/0001.patch
index 6547c47b40..e238c8c130 100644
--- a/third_party/libwebrtc/moz-patch-stack/0001.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0001.patch
@@ -399,10 +399,10 @@ index 5d4d4190d5..9868365f24 100644
struct RTC_EXPORT RTPHeader {
RTPHeader();
diff --git a/api/rtp_parameters.cc b/api/rtp_parameters.cc
-index 54132bcdbb..cf8b3ad3dc 100644
+index 3ff4b58a2e..ad0f3c9396 100644
--- a/api/rtp_parameters.cc
+++ b/api/rtp_parameters.cc
-@@ -151,7 +151,8 @@ bool RtpExtension::IsSupportedForAudio(absl::string_view uri) {
+@@ -160,7 +160,8 @@ bool RtpExtension::IsSupportedForAudio(absl::string_view uri) {
uri == webrtc::RtpExtension::kTransportSequenceNumberV2Uri ||
uri == webrtc::RtpExtension::kMidUri ||
uri == webrtc::RtpExtension::kRidUri ||
@@ -413,7 +413,7 @@ index 54132bcdbb..cf8b3ad3dc 100644
bool RtpExtension::IsSupportedForVideo(absl::string_view uri) {
diff --git a/call/BUILD.gn b/call/BUILD.gn
-index 9a7be88892..66c8b2011a 100644
+index 32ebc2e9cf..41ed587950 100644
--- a/call/BUILD.gn
+++ b/call/BUILD.gn
@@ -20,6 +20,7 @@ rtc_library("call_interfaces") {
@@ -878,7 +878,7 @@ index 2a95a3a816..b152cdbd9e 100644
}
}
diff --git a/modules/rtp_rtcp/source/rtp_packet_unittest.cc b/modules/rtp_rtcp/source/rtp_packet_unittest.cc
-index a1d1c9d4df..b3a9452df9 100644
+index ac464493b8..44f1a9e742 100644
--- a/modules/rtp_rtcp/source/rtp_packet_unittest.cc
+++ b/modules/rtp_rtcp/source/rtp_packet_unittest.cc
@@ -123,6 +123,18 @@ constexpr uint8_t kPacketWithMid[] = {
@@ -1560,7 +1560,7 @@ index 0474e7bc17..1953923f81 100644
std::unique_ptr<ScalableVideoController> svc_controller_;
absl::optional<ScalabilityMode> scalability_mode_;
diff --git a/rtc_base/BUILD.gn b/rtc_base/BUILD.gn
-index 3011d6a797..9f5f0aad56 100644
+index ac30d8708b..84dc1718b4 100644
--- a/rtc_base/BUILD.gn
+++ b/rtc_base/BUILD.gn
@@ -463,6 +463,12 @@ rtc_library("logging") {
diff --git a/third_party/libwebrtc/moz-patch-stack/0006.patch b/third_party/libwebrtc/moz-patch-stack/0006.patch
index 7decaa705c..1b0eca5f02 100644
--- a/third_party/libwebrtc/moz-patch-stack/0006.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0006.patch
@@ -30,7 +30,7 @@ index 542067d30c..500ca1447f 100644
// single 'timing frame'.
absl::optional<webrtc::TimingFrameInfo> timing_frame_info;
diff --git a/modules/rtp_rtcp/source/rtcp_receiver.cc b/modules/rtp_rtcp/source/rtcp_receiver.cc
-index ba5b951f4d..61b88c89b8 100644
+index ee31db2d36..982cda19da 100644
--- a/modules/rtp_rtcp/source/rtcp_receiver.cc
+++ b/modules/rtp_rtcp/source/rtcp_receiver.cc
@@ -361,6 +361,13 @@ RTCPReceiver::ConsumeReceivedXrReferenceTimeInfo() {
diff --git a/third_party/libwebrtc/moz-patch-stack/0007.patch b/third_party/libwebrtc/moz-patch-stack/0007.patch
index 052a4431bf..81646cfcfa 100644
--- a/third_party/libwebrtc/moz-patch-stack/0007.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0007.patch
@@ -33,7 +33,7 @@ index 500ca1447f..95f1a47f4e 100644
// Timing frame info: all important timestamps for a full lifetime of a
// single 'timing frame'.
diff --git a/modules/rtp_rtcp/source/rtcp_receiver.cc b/modules/rtp_rtcp/source/rtcp_receiver.cc
-index 61b88c89b8..a98b200c05 100644
+index 982cda19da..a596ef5fd4 100644
--- a/modules/rtp_rtcp/source/rtcp_receiver.cc
+++ b/modules/rtp_rtcp/source/rtcp_receiver.cc
@@ -362,10 +362,12 @@ RTCPReceiver::ConsumeReceivedXrReferenceTimeInfo() {
diff --git a/third_party/libwebrtc/moz-patch-stack/0008.patch b/third_party/libwebrtc/moz-patch-stack/0008.patch
index 4b7bb94bd6..b300b7ac54 100644
--- a/third_party/libwebrtc/moz-patch-stack/0008.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0008.patch
@@ -32,7 +32,7 @@ diff --git a/modules/desktop_capture/mac/screen_capturer_mac.mm b/modules/deskto
index 60089fd0f2..a2370ed695 100644
--- a/modules/desktop_capture/mac/screen_capturer_mac.mm
+++ b/modules/desktop_capture/mac/screen_capturer_mac.mm
-@@ -182,6 +182,7 @@ DesktopRect GetExcludedWindowPixelBounds(CGWindowID window, float dip_to_pixel_s
+@@ -182,6 +182,7 @@ void ScreenCapturerMac::Start(Callback* callback) {
"webrtc", "ScreenCapturermac::Start", "target display id ", current_display_);
callback_ = callback;
@@ -40,7 +40,7 @@ index 60089fd0f2..a2370ed695 100644
// Start and operate CGDisplayStream handler all from capture thread.
if (!RegisterRefreshAndMoveHandlers()) {
RTC_LOG(LS_ERROR) << "Failed to register refresh and move handlers.";
-@@ -202,7 +203,8 @@ DesktopRect GetExcludedWindowPixelBounds(CGWindowID window, float dip_to_pixel_s
+@@ -202,7 +203,8 @@ void ScreenCapturerMac::CaptureFrame() {
}
MacDesktopConfiguration new_config = desktop_config_monitor_->desktop_configuration();
@@ -54,7 +54,7 @@ diff --git a/modules/desktop_capture/mouse_cursor_monitor_mac.mm b/modules/deskt
index 3db4332cd1..512103ab5e 100644
--- a/modules/desktop_capture/mouse_cursor_monitor_mac.mm
+++ b/modules/desktop_capture/mouse_cursor_monitor_mac.mm
-@@ -133,7 +133,7 @@ void DisplaysReconfigured(CGDirectDisplayID display,
+@@ -133,7 +133,7 @@ void MouseCursorMonitorMac::CaptureImage(float scale) {
NSSize nssize = [nsimage size]; // DIP size
// No need to caputre cursor image if it's unchanged since last capture.
diff --git a/third_party/libwebrtc/moz-patch-stack/0009.patch b/third_party/libwebrtc/moz-patch-stack/0009.patch
index 58b2ab9994..f724dadc84 100644
--- a/third_party/libwebrtc/moz-patch-stack/0009.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0009.patch
@@ -19,7 +19,7 @@ diff --git a/modules/desktop_capture/mac/screen_capturer_mac.mm b/modules/deskto
index a2370ed695..785a15dfa4 100644
--- a/modules/desktop_capture/mac/screen_capturer_mac.mm
+++ b/modules/desktop_capture/mac/screen_capturer_mac.mm
-@@ -276,7 +276,8 @@ DesktopRect GetExcludedWindowPixelBounds(CGWindowID window, float dip_to_pixel_s
+@@ -276,7 +276,8 @@ bool ScreenCapturerMac::GetSourceList(SourceList* screens) {
for (MacDisplayConfigurations::iterator it = desktop_config_.displays.begin();
it != desktop_config_.displays.end();
++it) {
diff --git a/third_party/libwebrtc/moz-patch-stack/0030.patch b/third_party/libwebrtc/moz-patch-stack/0030.patch
index 8c6652af46..9e8bd7b2fd 100644
--- a/third_party/libwebrtc/moz-patch-stack/0030.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0030.patch
@@ -63,6 +63,10 @@ Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/136b3fc0377be6dca
Bug 1876843 - (fix-b29ff000da) remove mozilla dependency on api:enable_media
Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/7f403ee038e9797a1aff6161fc70a2d92769851f
+
+Bug 1883116 - (fix-3d9c3687a4) Supporting change of call_factory.cc to create_call.cc.
+
+Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/b86cb7278bc4e557104cec0313d83511b9c8f40d
---
.gn | 2 +
BUILD.gn | 46 ++++++++++++++++++-
@@ -202,10 +206,10 @@ index 571049f3e4..f393179bbb 100644
} else {
deps += [
diff --git a/api/BUILD.gn b/api/BUILD.gn
-index ee162577c8..10a4c8c95f 100644
+index 6af4fa5517..1628660c3c 100644
--- a/api/BUILD.gn
+++ b/api/BUILD.gn
-@@ -49,6 +49,9 @@ rtc_source_set("enable_media") {
+@@ -40,6 +40,9 @@ rtc_source_set("enable_media") {
"../rtc_base/system:rtc_export",
"environment",
]
@@ -215,7 +219,7 @@ index ee162577c8..10a4c8c95f 100644
}
rtc_source_set("enable_media_with_defaults") {
-@@ -75,7 +78,7 @@ rtc_source_set("enable_media_with_defaults") {
+@@ -66,7 +69,7 @@ rtc_source_set("enable_media_with_defaults") {
]
}
@@ -224,7 +228,7 @@ index ee162577c8..10a4c8c95f 100644
rtc_library("create_peerconnection_factory") {
visibility = [ "*" ]
allow_poison = [ "environment_construction" ]
-@@ -228,6 +231,10 @@ rtc_source_set("ice_transport_interface") {
+@@ -219,6 +222,10 @@ rtc_source_set("ice_transport_interface") {
}
rtc_library("dtls_transport_interface") {
@@ -235,7 +239,7 @@ index ee162577c8..10a4c8c95f 100644
visibility = [ "*" ]
sources = [
-@@ -244,6 +251,7 @@ rtc_library("dtls_transport_interface") {
+@@ -235,6 +242,7 @@ rtc_library("dtls_transport_interface") {
]
absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
}
@@ -243,7 +247,7 @@ index ee162577c8..10a4c8c95f 100644
rtc_library("dtmf_sender_interface") {
visibility = [ "*" ]
-@@ -256,6 +264,10 @@ rtc_library("dtmf_sender_interface") {
+@@ -247,6 +255,10 @@ rtc_library("dtmf_sender_interface") {
}
rtc_library("rtp_sender_interface") {
@@ -254,7 +258,7 @@ index ee162577c8..10a4c8c95f 100644
visibility = [ "*" ]
sources = [
-@@ -270,16 +282,31 @@ rtc_library("rtp_sender_interface") {
+@@ -261,16 +273,31 @@ rtc_library("rtp_sender_interface") {
":ref_count",
":rtc_error",
":rtp_parameters",
@@ -286,7 +290,7 @@ index ee162577c8..10a4c8c95f 100644
visibility = [ "*" ]
cflags = []
sources = [
-@@ -396,6 +423,7 @@ rtc_library("libjingle_peerconnection_api") {
+@@ -387,6 +414,7 @@ rtc_library("libjingle_peerconnection_api") {
"//third_party/abseil-cpp/absl/types:optional",
]
}
@@ -294,7 +298,7 @@ index ee162577c8..10a4c8c95f 100644
rtc_source_set("frame_transformer_interface") {
visibility = [ "*" ]
-@@ -568,6 +596,7 @@ rtc_source_set("peer_network_dependencies") {
+@@ -560,6 +588,7 @@ rtc_source_set("peer_network_dependencies") {
}
rtc_source_set("peer_connection_quality_test_fixture_api") {
@@ -302,7 +306,7 @@ index ee162577c8..10a4c8c95f 100644
visibility = [ "*" ]
testonly = true
sources = [ "test/peerconnection_quality_test_fixture.h" ]
-@@ -618,6 +647,7 @@ rtc_source_set("peer_connection_quality_test_fixture_api") {
+@@ -609,6 +638,7 @@ rtc_source_set("peer_connection_quality_test_fixture_api") {
"//third_party/abseil-cpp/absl/types:optional",
]
}
@@ -310,7 +314,7 @@ index ee162577c8..10a4c8c95f 100644
rtc_source_set("frame_generator_api") {
visibility = [ "*" ]
-@@ -736,6 +766,7 @@ rtc_library("create_frame_generator") {
+@@ -728,6 +758,7 @@ rtc_library("create_frame_generator") {
absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
}
@@ -318,7 +322,7 @@ index ee162577c8..10a4c8c95f 100644
rtc_library("create_peer_connection_quality_test_frame_generator") {
visibility = [ "*" ]
testonly = true
-@@ -752,6 +783,7 @@ rtc_library("create_peer_connection_quality_test_frame_generator") {
+@@ -744,6 +775,7 @@ rtc_library("create_peer_connection_quality_test_frame_generator") {
]
absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
}
@@ -326,7 +330,7 @@ index ee162577c8..10a4c8c95f 100644
rtc_source_set("libjingle_logging_api") {
visibility = [ "*" ]
-@@ -926,6 +958,7 @@ rtc_source_set("refcountedbase") {
+@@ -924,6 +956,7 @@ rtc_source_set("refcountedbase") {
]
}
@@ -334,7 +338,7 @@ index ee162577c8..10a4c8c95f 100644
rtc_library("ice_transport_factory") {
visibility = [ "*" ]
sources = [
-@@ -944,6 +977,7 @@ rtc_library("ice_transport_factory") {
+@@ -942,6 +975,7 @@ rtc_library("ice_transport_factory") {
"rtc_event_log:rtc_event_log",
]
}
@@ -426,7 +430,7 @@ index 0000000000..45194f5ace
+
+#endif // API_RTP_SENDER_SETPARAMETERS_CALLBACK_H_
diff --git a/api/task_queue/BUILD.gn b/api/task_queue/BUILD.gn
-index 246164c68b..d557d8f100 100644
+index 1b9425d719..afdf47dfc9 100644
--- a/api/task_queue/BUILD.gn
+++ b/api/task_queue/BUILD.gn
@@ -31,6 +31,7 @@ rtc_library("task_queue") {
@@ -466,11 +470,11 @@ index 84a0968ee9..c0209bf0d0 100644
if (rtc_include_tests) {
rtc_source_set("test_feedback_generator_interface") {
diff --git a/call/BUILD.gn b/call/BUILD.gn
-index 66c8b2011a..fa733a67b9 100644
+index 41ed587950..cca88ea7bb 100644
--- a/call/BUILD.gn
+++ b/call/BUILD.gn
-@@ -46,7 +46,7 @@ rtc_library("call_interfaces") {
- "../api:rtc_error",
+@@ -44,7 +44,7 @@ rtc_library("call_interfaces") {
+ "../api:network_state_predictor_api",
"../api:rtp_headers",
"../api:rtp_parameters",
- "../api:rtp_sender_interface",
@@ -478,13 +482,13 @@ index 66c8b2011a..fa733a67b9 100644
"../api:scoped_refptr",
"../api:transport_api",
"../api/adaptation:resource_adaptation_api",
-@@ -347,6 +347,16 @@ rtc_library("call") {
+@@ -345,6 +345,16 @@ rtc_library("call") {
"//third_party/abseil-cpp/absl/strings",
"//third_party/abseil-cpp/absl/types:optional",
]
+ if (build_with_mozilla) { # See Bug 1820869.
+ sources -= [
-+ "call_factory.cc",
++ "create_call.cc",
+ "degraded_call.cc",
+ ]
+ deps -= [
@@ -495,7 +499,7 @@ index 66c8b2011a..fa733a67b9 100644
}
rtc_source_set("receive_stream_interface") {
-@@ -374,7 +384,7 @@ rtc_library("video_stream_api") {
+@@ -372,7 +382,7 @@ rtc_library("video_stream_api") {
"../api:frame_transformer_interface",
"../api:rtp_headers",
"../api:rtp_parameters",
@@ -577,7 +581,7 @@ index 0000000000..f6ff7f218f
+ #endif
+#endif
diff --git a/media/BUILD.gn b/media/BUILD.gn
-index 295a748802..055bf75a19 100644
+index 2a9cbcbff4..44638d562e 100644
--- a/media/BUILD.gn
+++ b/media/BUILD.gn
@@ -64,7 +64,7 @@ rtc_library("rtc_media_base") {
@@ -714,7 +718,7 @@ index f941823323..1fe86f9588 100644
namespace cricket {
diff --git a/media/base/media_channel_impl.cc b/media/base/media_channel_impl.cc
-index e7e84c781c..5b41a9ccda 100644
+index 1c08382969..ff69ea62dc 100644
--- a/media/base/media_channel_impl.cc
+++ b/media/base/media_channel_impl.cc
@@ -31,19 +31,6 @@
@@ -751,7 +755,7 @@ index 5de99efa45..ddd1fd2656 100644
}
diff --git a/modules/audio_device/BUILD.gn b/modules/audio_device/BUILD.gn
-index 6f52cf8af1..a135f042db 100644
+index 2088e74dcd..1672be3f95 100644
--- a/modules/audio_device/BUILD.gn
+++ b/modules/audio_device/BUILD.gn
@@ -30,6 +30,7 @@ rtc_source_set("audio_device_default") {
@@ -778,9 +782,9 @@ index 6f52cf8af1..a135f042db 100644
sources = [
"audio_device_buffer.cc",
"audio_device_buffer.h",
-@@ -89,6 +92,7 @@ rtc_library("audio_device_buffer") {
- "../../system_wrappers:metrics",
+@@ -90,6 +93,7 @@ rtc_library("audio_device_buffer") {
]
+ absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
}
+}
@@ -823,9 +827,9 @@ index 6f52cf8af1..a135f042db 100644
rtc_source_set("mock_audio_device") {
visibility = [ "*" ]
testonly = true
-@@ -455,8 +462,10 @@ rtc_source_set("mock_audio_device") {
- "../../test:test_support",
+@@ -456,8 +463,10 @@ rtc_source_set("mock_audio_device") {
]
+ absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
}
+}
@@ -1001,7 +1005,7 @@ index 8cefe5653c..b8d75865f7 100644
}
}
diff --git a/modules/video_capture/BUILD.gn b/modules/video_capture/BUILD.gn
-index 730ec9bfdd..d473dbb74c 100644
+index b583814ebe..aabf545728 100644
--- a/modules/video_capture/BUILD.gn
+++ b/modules/video_capture/BUILD.gn
@@ -125,21 +125,12 @@ if (!build_with_chromium || is_linux || is_chromeos) {
@@ -1028,7 +1032,7 @@ index 730ec9bfdd..d473dbb74c 100644
"/config/external/nspr",
"/nsprpub/lib/ds",
diff --git a/rtc_base/BUILD.gn b/rtc_base/BUILD.gn
-index 9f5f0aad56..089b9971a3 100644
+index 84dc1718b4..7372b539c4 100644
--- a/rtc_base/BUILD.gn
+++ b/rtc_base/BUILD.gn
@@ -327,6 +327,7 @@ rtc_library("sample_counter") {
@@ -1057,7 +1061,7 @@ index 9f5f0aad56..089b9971a3 100644
if (rtc_build_json) {
deps += [ "//third_party/jsoncpp" ]
} else {
-@@ -1223,6 +1227,7 @@ if (!build_with_chromium) {
+@@ -1227,6 +1231,7 @@ if (!build_with_chromium) {
}
rtc_library("network") {
@@ -1065,7 +1069,7 @@ index 9f5f0aad56..089b9971a3 100644
visibility = [ "*" ]
sources = [
"network.cc",
-@@ -1261,16 +1266,20 @@ rtc_library("network") {
+@@ -1265,16 +1270,20 @@ rtc_library("network") {
deps += [ ":win32" ]
}
}
@@ -1086,7 +1090,7 @@ index 9f5f0aad56..089b9971a3 100644
visibility = [ "*" ]
sources = [
"net_helper.cc",
-@@ -1279,8 +1288,10 @@ rtc_library("net_helper") {
+@@ -1283,8 +1292,10 @@ rtc_library("net_helper") {
absl_deps = [ "//third_party/abseil-cpp/absl/strings" ]
deps = [ "system:rtc_export" ]
}
@@ -1097,7 +1101,7 @@ index 9f5f0aad56..089b9971a3 100644
visibility = [ "*" ]
sources = [
"socket_adapters.cc",
-@@ -1300,6 +1311,7 @@ rtc_library("socket_adapters") {
+@@ -1304,6 +1315,7 @@ rtc_library("socket_adapters") {
]
absl_deps = [ "//third_party/abseil-cpp/absl/strings" ]
}
@@ -1105,7 +1109,7 @@ index 9f5f0aad56..089b9971a3 100644
rtc_library("network_route") {
sources = [
-@@ -1314,6 +1326,7 @@ rtc_library("network_route") {
+@@ -1318,6 +1330,7 @@ rtc_library("network_route") {
}
rtc_library("async_tcp_socket") {
@@ -1113,7 +1117,7 @@ index 9f5f0aad56..089b9971a3 100644
sources = [
"async_tcp_socket.cc",
"async_tcp_socket.h",
-@@ -1331,8 +1344,10 @@ rtc_library("async_tcp_socket") {
+@@ -1335,8 +1348,10 @@ rtc_library("async_tcp_socket") {
"network:sent_packet",
]
}
@@ -1124,7 +1128,7 @@ index 9f5f0aad56..089b9971a3 100644
visibility = [ "*" ]
sources = [
"async_udp_socket.cc",
-@@ -1354,8 +1369,10 @@ rtc_library("async_udp_socket") {
+@@ -1360,8 +1375,10 @@ rtc_library("async_udp_socket") {
]
absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
}
@@ -1135,7 +1139,7 @@ index 9f5f0aad56..089b9971a3 100644
visibility = [ "*" ]
sources = [
"async_packet_socket.cc",
-@@ -1375,6 +1392,7 @@ rtc_library("async_packet_socket") {
+@@ -1381,6 +1398,7 @@ rtc_library("async_packet_socket") {
"third_party/sigslot",
]
}
@@ -1143,7 +1147,7 @@ index 9f5f0aad56..089b9971a3 100644
if (rtc_include_tests) {
rtc_library("async_packet_socket_unittest") {
-@@ -1402,6 +1420,7 @@ rtc_library("dscp") {
+@@ -1408,6 +1426,7 @@ rtc_library("dscp") {
}
rtc_library("proxy_info") {
@@ -1151,7 +1155,7 @@ index 9f5f0aad56..089b9971a3 100644
visibility = [ "*" ]
sources = [
"proxy_info.cc",
-@@ -1412,6 +1431,7 @@ rtc_library("proxy_info") {
+@@ -1418,6 +1437,7 @@ rtc_library("proxy_info") {
":socket_address",
]
}
@@ -1159,7 +1163,7 @@ index 9f5f0aad56..089b9971a3 100644
rtc_library("file_rotating_stream") {
sources = [
-@@ -1440,6 +1460,7 @@ rtc_library("data_rate_limiter") {
+@@ -1446,6 +1466,7 @@ rtc_library("data_rate_limiter") {
}
rtc_library("unique_id_generator") {
@@ -1167,7 +1171,7 @@ index 9f5f0aad56..089b9971a3 100644
sources = [
"unique_id_generator.cc",
"unique_id_generator.h",
-@@ -1454,6 +1475,7 @@ rtc_library("unique_id_generator") {
+@@ -1460,6 +1481,7 @@ rtc_library("unique_id_generator") {
]
absl_deps = [ "//third_party/abseil-cpp/absl/strings" ]
}
@@ -1175,7 +1179,7 @@ index 9f5f0aad56..089b9971a3 100644
rtc_library("crc32") {
sources = [
-@@ -1481,6 +1503,7 @@ rtc_library("stream") {
+@@ -1487,6 +1509,7 @@ rtc_library("stream") {
}
rtc_library("rtc_certificate_generator") {
@@ -1183,7 +1187,7 @@ index 9f5f0aad56..089b9971a3 100644
visibility = [ "*" ]
sources = [
"rtc_certificate_generator.cc",
-@@ -1498,8 +1521,10 @@ rtc_library("rtc_certificate_generator") {
+@@ -1504,8 +1527,10 @@ rtc_library("rtc_certificate_generator") {
"//third_party/abseil-cpp/absl/types:optional",
]
}
@@ -1194,7 +1198,7 @@ index 9f5f0aad56..089b9971a3 100644
visibility = [ "*" ]
sources = [
"helpers.cc",
-@@ -1600,6 +1625,7 @@ rtc_library("ssl") {
+@@ -1606,6 +1631,7 @@ rtc_library("ssl") {
deps += [ ":win32" ]
}
}
@@ -1202,7 +1206,7 @@ index 9f5f0aad56..089b9971a3 100644
rtc_library("crypt_string") {
sources = [
-@@ -1609,6 +1635,7 @@ rtc_library("crypt_string") {
+@@ -1615,6 +1641,7 @@ rtc_library("crypt_string") {
}
rtc_library("http_common") {
@@ -1210,7 +1214,7 @@ index 9f5f0aad56..089b9971a3 100644
sources = [
"http_common.cc",
"http_common.h",
-@@ -1625,6 +1652,7 @@ rtc_library("http_common") {
+@@ -1631,6 +1658,7 @@ rtc_library("http_common") {
absl_deps = [ "//third_party/abseil-cpp/absl/strings" ]
}
@@ -1218,7 +1222,7 @@ index 9f5f0aad56..089b9971a3 100644
rtc_source_set("gtest_prod") {
sources = [ "gtest_prod_util.h" ]
-@@ -2189,7 +2217,7 @@ if (rtc_include_tests) {
+@@ -2191,7 +2219,7 @@ if (rtc_include_tests) {
}
}
@@ -1241,10 +1245,10 @@ index 77f5139a2f..486b37590c 100644
deps += [
"..:logging",
diff --git a/test/BUILD.gn b/test/BUILD.gn
-index d313c6d82d..854530c01e 100644
+index 2a37b78c7c..75d8d9f3a8 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
-@@ -241,6 +241,7 @@ rtc_library("audio_test_common") {
+@@ -239,6 +239,7 @@ rtc_library("audio_test_common") {
absl_deps = [ "//third_party/abseil-cpp/absl/memory" ]
}
@@ -1252,7 +1256,7 @@ index d313c6d82d..854530c01e 100644
if (!build_with_chromium) {
if (is_mac || is_ios) {
rtc_library("video_test_mac") {
-@@ -294,8 +295,12 @@ if (!build_with_chromium) {
+@@ -292,8 +293,12 @@ if (!build_with_chromium) {
}
}
}
@@ -1265,7 +1269,7 @@ index d313c6d82d..854530c01e 100644
testonly = true
sources = [
"rtcp_packet_parser.cc",
-@@ -305,6 +310,7 @@ rtc_library("rtp_test_utils") {
+@@ -303,6 +308,7 @@ rtc_library("rtp_test_utils") {
"rtp_file_writer.cc",
"rtp_file_writer.h",
]
@@ -1273,7 +1277,7 @@ index d313c6d82d..854530c01e 100644
deps = [
"../api:array_view",
-@@ -560,7 +566,9 @@ rtc_library("video_test_support") {
+@@ -558,7 +564,9 @@ rtc_library("video_test_support") {
absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
if (!is_ios) {
@@ -1300,7 +1304,7 @@ index d313c6d82d..854530c01e 100644
rtc_library("call_config_utils") {
testonly = true
diff --git a/video/BUILD.gn b/video/BUILD.gn
-index 204c6b66f4..0a930053c0 100644
+index 0891a31f7b..2d6d8ab10c 100644
--- a/video/BUILD.gn
+++ b/video/BUILD.gn
@@ -17,7 +17,7 @@ rtc_library("video_stream_encoder_interface") {
@@ -1312,7 +1316,7 @@ index 204c6b66f4..0a930053c0 100644
"../api:scoped_refptr",
"../api/adaptation:resource_adaptation_api",
"../api/units:data_rate",
-@@ -404,7 +404,7 @@ rtc_library("video_stream_encoder_impl") {
+@@ -409,7 +409,7 @@ rtc_library("video_stream_encoder_impl") {
":video_stream_encoder_interface",
"../api:field_trials_view",
"../api:rtp_parameters",
diff --git a/third_party/libwebrtc/moz-patch-stack/0031.patch b/third_party/libwebrtc/moz-patch-stack/0031.patch
index 0b1372a1dd..36c96225d0 100644
--- a/third_party/libwebrtc/moz-patch-stack/0031.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0031.patch
@@ -9,10 +9,10 @@ Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/7163801a480d60700
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/audio/channel_send.cc b/audio/channel_send.cc
-index ecf2cb5175..310e0517cf 100644
+index 68cd099a23..ab1b72a2f3 100644
--- a/audio/channel_send.cc
+++ b/audio/channel_send.cc
-@@ -639,9 +639,9 @@ void ChannelSend::SetSendAudioLevelIndicationStatus(bool enable, int id) {
+@@ -643,9 +643,9 @@ void ChannelSend::SetSendAudioLevelIndicationStatus(bool enable, int id) {
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
include_audio_level_indication_.store(enable);
if (enable) {
diff --git a/third_party/libwebrtc/moz-patch-stack/0033.patch b/third_party/libwebrtc/moz-patch-stack/0033.patch
index 5c69ef0bce..62695eeb23 100644
--- a/third_party/libwebrtc/moz-patch-stack/0033.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0033.patch
@@ -15,10 +15,10 @@ Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/d380a43d59f4f7cbc
4 files changed, 35 insertions(+)
diff --git a/audio/audio_send_stream.cc b/audio/audio_send_stream.cc
-index c9dc42c04e..e7ebb2bf4e 100644
+index 3701eafd6f..8dc78b18fa 100644
--- a/audio/audio_send_stream.cc
+++ b/audio/audio_send_stream.cc
-@@ -431,6 +431,7 @@ webrtc::AudioSendStream::Stats AudioSendStream::GetStats(
+@@ -432,6 +432,7 @@ webrtc::AudioSendStream::Stats AudioSendStream::GetStats(
stats.target_bitrate_bps = channel_send_->GetTargetBitrate();
webrtc::CallSendStatistics call_stats = channel_send_->GetRTCPStatistics();
@@ -27,7 +27,7 @@ index c9dc42c04e..e7ebb2bf4e 100644
stats.header_and_padding_bytes_sent =
call_stats.header_and_padding_bytes_sent;
diff --git a/audio/channel_send.cc b/audio/channel_send.cc
-index 310e0517cf..549e65a59c 100644
+index ab1b72a2f3..db632b3aa8 100644
--- a/audio/channel_send.cc
+++ b/audio/channel_send.cc
@@ -56,6 +56,31 @@ constexpr int64_t kMinRetransmissionWindowMs = 30;
@@ -64,14 +64,14 @@ index 310e0517cf..549e65a59c 100644
// packets from the ACM
@@ -210,6 +235,8 @@ class ChannelSend : public ChannelSendInterface,
bool input_mute_ RTC_GUARDED_BY(volume_settings_mutex_) = false;
- bool previous_frame_muted_ RTC_GUARDED_BY(encoder_queue_) = false;
+ bool previous_frame_muted_ RTC_GUARDED_BY(encoder_queue_checker_) = false;
+ const std::unique_ptr<RtcpCounterObserver> rtcp_counter_observer_;
+
PacketRouter* packet_router_ RTC_GUARDED_BY(&worker_thread_checker_) =
nullptr;
const std::unique_ptr<RtpPacketSenderProxy> rtp_packet_pacer_proxy_;
-@@ -398,6 +425,7 @@ ChannelSend::ChannelSend(
+@@ -397,6 +424,7 @@ ChannelSend::ChannelSend(
const FieldTrialsView& field_trials)
: ssrc_(ssrc),
event_log_(rtc_event_log),
@@ -88,7 +88,7 @@ index 310e0517cf..549e65a59c 100644
if (field_trials.IsDisabled("WebRTC-DisableRtxRateLimiter")) {
configuration.retransmission_rate_limiter =
retransmission_rate_limiter_.get();
-@@ -687,6 +717,7 @@ CallSendStatistics ChannelSend::GetRTCPStatistics() const {
+@@ -691,6 +721,7 @@ CallSendStatistics ChannelSend::GetRTCPStatistics() const {
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
CallSendStatistics stats = {0};
stats.rttMs = GetRTT();
diff --git a/third_party/libwebrtc/moz-patch-stack/0034.patch b/third_party/libwebrtc/moz-patch-stack/0034.patch
index 979f7f8528..3dfd2e8007 100644
--- a/third_party/libwebrtc/moz-patch-stack/0034.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0034.patch
@@ -25,7 +25,7 @@ index 95f1a47f4e..a1fc204e7c 100644
// See LntfConfig for description.
diff --git a/video/rtp_video_stream_receiver2.cc b/video/rtp_video_stream_receiver2.cc
-index 9a38097a93..d12e833cab 100644
+index badb942cd4..1f4cc5b6be 100644
--- a/video/rtp_video_stream_receiver2.cc
+++ b/video/rtp_video_stream_receiver2.cc
@@ -296,9 +296,8 @@ RtpVideoStreamReceiver2::RtpVideoStreamReceiver2(
diff --git a/third_party/libwebrtc/moz-patch-stack/0042.patch b/third_party/libwebrtc/moz-patch-stack/0042.patch
index 42bc15e1f6..7ffec6a12e 100644
--- a/third_party/libwebrtc/moz-patch-stack/0042.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0042.patch
@@ -20,7 +20,7 @@ Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/d0b311007c033e838
11 files changed, 57 insertions(+), 10 deletions(-)
diff --git a/audio/audio_receive_stream.cc b/audio/audio_receive_stream.cc
-index 978bbb25b2..655b2761ac 100644
+index 415ad0640a..1e8cff5441 100644
--- a/audio/audio_receive_stream.cc
+++ b/audio/audio_receive_stream.cc
@@ -39,6 +39,8 @@ std::string AudioReceiveStreamInterface::Config::Rtp::ToString() const {
@@ -189,7 +189,7 @@ index 249cf835ba..de85abd4ae 100644
static constexpr size_t kNumMediaTypes = 5;
enum class RtpPacketMediaType : size_t {
diff --git a/modules/rtp_rtcp/source/rtcp_receiver.cc b/modules/rtp_rtcp/source/rtcp_receiver.cc
-index a98b200c05..e2ad674012 100644
+index a596ef5fd4..9c6ceb2403 100644
--- a/modules/rtp_rtcp/source/rtcp_receiver.cc
+++ b/modules/rtp_rtcp/source/rtcp_receiver.cc
@@ -144,6 +144,7 @@ RTCPReceiver::RTCPReceiver(const RtpRtcpInterface::Configuration& config,
@@ -269,7 +269,7 @@ index 0bdd389795..2c56dccd2a 100644
TransportFeedbackObserver* transport_feedback_callback = nullptr;
VideoBitrateAllocationObserver* bitrate_allocation_observer = nullptr;
diff --git a/video/rtp_video_stream_receiver2.cc b/video/rtp_video_stream_receiver2.cc
-index d12e833cab..2ea8ce8c62 100644
+index 1f4cc5b6be..9694f1d0c1 100644
--- a/video/rtp_video_stream_receiver2.cc
+++ b/video/rtp_video_stream_receiver2.cc
@@ -83,7 +83,8 @@ std::unique_ptr<ModuleRtpRtcpImpl2> CreateRtpRtcpModule(
diff --git a/third_party/libwebrtc/moz-patch-stack/0044.patch b/third_party/libwebrtc/moz-patch-stack/0044.patch
index 7fdc82d1da..e38e7de18f 100644
--- a/third_party/libwebrtc/moz-patch-stack/0044.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0044.patch
@@ -15,10 +15,10 @@ Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/edac9d01a9ac7594f
3 files changed, 24 insertions(+)
diff --git a/video/rtp_video_stream_receiver2.cc b/video/rtp_video_stream_receiver2.cc
-index 2ea8ce8c62..0a215f79cc 100644
+index 9694f1d0c1..79ce90794e 100644
--- a/video/rtp_video_stream_receiver2.cc
+++ b/video/rtp_video_stream_receiver2.cc
-@@ -1066,6 +1066,16 @@ absl::optional<int64_t> RtpVideoStreamReceiver2::LastReceivedKeyframePacketMs()
+@@ -1062,6 +1062,16 @@ absl::optional<int64_t> RtpVideoStreamReceiver2::LastReceivedKeyframePacketMs()
return absl::nullopt;
}
@@ -36,10 +36,10 @@ index 2ea8ce8c62..0a215f79cc 100644
std::unique_ptr<RtpFrameObject> frame) {
RTC_DCHECK_RUN_ON(&packet_sequence_checker_);
diff --git a/video/rtp_video_stream_receiver2.h b/video/rtp_video_stream_receiver2.h
-index 0178355262..be8bce770f 100644
+index d436aa38a7..4a93e53356 100644
--- a/video/rtp_video_stream_receiver2.h
+++ b/video/rtp_video_stream_receiver2.h
-@@ -207,6 +207,12 @@ class RtpVideoStreamReceiver2 : public LossNotificationSender,
+@@ -209,6 +209,12 @@ class RtpVideoStreamReceiver2 : public LossNotificationSender,
absl::optional<uint32_t> LastReceivedFrameRtpTimestamp() const;
absl::optional<int64_t> LastReceivedKeyframePacketMs() const;
@@ -53,10 +53,10 @@ index 0178355262..be8bce770f 100644
// Implements RtpVideoFrameReceiver.
void ManageFrame(std::unique_ptr<RtpFrameObject> frame) override;
diff --git a/video/video_receive_stream2.cc b/video/video_receive_stream2.cc
-index af25c364de..776c49042b 100644
+index 9e095064ba..3d0534bf10 100644
--- a/video/video_receive_stream2.cc
+++ b/video/video_receive_stream2.cc
-@@ -570,6 +570,14 @@ VideoReceiveStreamInterface::Stats VideoReceiveStream2::GetStats() const {
+@@ -568,6 +568,14 @@ VideoReceiveStreamInterface::Stats VideoReceiveStream2::GetStats() const {
stats.rtx_rtp_stats = rtx_statistician->GetStats();
}
}
diff --git a/third_party/libwebrtc/moz-patch-stack/0046.patch b/third_party/libwebrtc/moz-patch-stack/0046.patch
index 77e1314bb9..dc5987aed0 100644
--- a/third_party/libwebrtc/moz-patch-stack/0046.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0046.patch
@@ -16,10 +16,10 @@ Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/c186df8a088e46285
1 file changed, 1 deletion(-)
diff --git a/audio/audio_receive_stream.cc b/audio/audio_receive_stream.cc
-index 655b2761ac..c49b83f95f 100644
+index 1e8cff5441..32a8e1c172 100644
--- a/audio/audio_receive_stream.cc
+++ b/audio/audio_receive_stream.cc
-@@ -366,7 +366,6 @@ int AudioReceiveStreamImpl::GetBaseMinimumPlayoutDelayMs() const {
+@@ -368,7 +368,6 @@ int AudioReceiveStreamImpl::GetBaseMinimumPlayoutDelayMs() const {
}
std::vector<RtpSource> AudioReceiveStreamImpl::GetSources() const {
diff --git a/third_party/libwebrtc/moz-patch-stack/0047.patch b/third_party/libwebrtc/moz-patch-stack/0047.patch
index bcaee89ce5..9fd44ff47f 100644
--- a/third_party/libwebrtc/moz-patch-stack/0047.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0047.patch
@@ -5,14 +5,14 @@ Subject: Bug 1714577 - Part 6 - Copy WebRTC's trace_event.h to Gecko,
Differential Revision: https://phabricator.services.mozilla.com/D116843
---
- rtc_base/trace_event.h | 1055 +---------------------------------------
- 1 file changed, 3 insertions(+), 1052 deletions(-)
+ rtc_base/trace_event.h | 924 +----------------------------------------
+ 1 file changed, 3 insertions(+), 921 deletions(-)
diff --git a/rtc_base/trace_event.h b/rtc_base/trace_event.h
-index 6689bc0c37..b34df0c93f 100644
+index 32ad031385..b34df0c93f 100644
--- a/rtc_base/trace_event.h
+++ b/rtc_base/trace_event.h
-@@ -1,1052 +1,3 @@
+@@ -1,921 +1,3 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file under third_party_mods/chromium or at:
@@ -445,100 +445,6 @@ index 6689bc0c37..b34df0c93f 100644
- name, id, TRACE_EVENT_FLAG_COPY, arg1_name, \
- arg1_val, arg2_name, arg2_val)
-
--// Records a single FLOW_BEGIN event called "name" immediately, with 0, 1 or 2
--// associated arguments. If the category is not enabled, then this
--// does nothing.
--// - category and name strings must have application lifetime (statics or
--// literals). They may not include " chars.
--// - `id` is used to match the FLOW_BEGIN event with the FLOW_END event. FLOW
--// events are considered to match if their category, name and id values all
--// match. `id` must either be a pointer or an integer value up to 64 bits. If
--// it's a pointer, the bits will be xored with a hash of the process ID so
--// that the same pointer on two different processes will not collide.
--// FLOW events are different from ASYNC events in how they are drawn by the
--// tracing UI. A FLOW defines asynchronous data flow, such as posting a task
--// (FLOW_BEGIN) and later executing that task (FLOW_END). Expect FLOWs to be
--// drawn as lines or arrows from FLOW_BEGIN scopes to FLOW_END scopes. Similar
--// to ASYNC, a FLOW can consist of multiple phases. The first phase is defined
--// by the FLOW_BEGIN calls. Additional phases can be defined using the FLOW_STEP
--// macros. When the operation completes, call FLOW_END. An async operation can
--// span threads and processes, but all events in that operation must use the
--// same `name` and `id`. Each event can have its own args.
--#define TRACE_EVENT_FLOW_BEGIN0(category, name, id) \
-- INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_BEGIN, category, \
-- name, id, TRACE_EVENT_FLAG_NONE)
--#define TRACE_EVENT_FLOW_BEGIN1(category, name, id, arg1_name, arg1_val) \
-- INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_BEGIN, category, \
-- name, id, TRACE_EVENT_FLAG_NONE, arg1_name, \
-- arg1_val)
--#define TRACE_EVENT_FLOW_BEGIN2(category, name, id, arg1_name, arg1_val, \
-- arg2_name, arg2_val) \
-- INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_BEGIN, category, \
-- name, id, TRACE_EVENT_FLAG_NONE, arg1_name, \
-- arg1_val, arg2_name, arg2_val)
--#define TRACE_EVENT_COPY_FLOW_BEGIN0(category, name, id) \
-- INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_BEGIN, category, \
-- name, id, TRACE_EVENT_FLAG_COPY)
--#define TRACE_EVENT_COPY_FLOW_BEGIN1(category, name, id, arg1_name, arg1_val) \
-- INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_BEGIN, category, \
-- name, id, TRACE_EVENT_FLAG_COPY, arg1_name, \
-- arg1_val)
--#define TRACE_EVENT_COPY_FLOW_BEGIN2(category, name, id, arg1_name, arg1_val, \
-- arg2_name, arg2_val) \
-- INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_BEGIN, category, \
-- name, id, TRACE_EVENT_FLAG_COPY, arg1_name, \
-- arg1_val, arg2_name, arg2_val)
--
--// Records a single FLOW_STEP event for `step` immediately. If the category
--// is not enabled, then this does nothing. The `name` and `id` must match the
--// FLOW_BEGIN event above. The `step` param identifies this step within the
--// async event. This should be called at the beginning of the next phase of an
--// asynchronous operation.
--#define TRACE_EVENT_FLOW_STEP0(category, name, id, step) \
-- INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_STEP, category, \
-- name, id, TRACE_EVENT_FLAG_NONE, "step", \
-- step)
--#define TRACE_EVENT_FLOW_STEP1(category, name, id, step, arg1_name, arg1_val) \
-- INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_STEP, category, \
-- name, id, TRACE_EVENT_FLAG_NONE, "step", \
-- step, arg1_name, arg1_val)
--#define TRACE_EVENT_COPY_FLOW_STEP0(category, name, id, step) \
-- INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_STEP, category, \
-- name, id, TRACE_EVENT_FLAG_COPY, "step", \
-- step)
--#define TRACE_EVENT_COPY_FLOW_STEP1(category, name, id, step, arg1_name, \
-- arg1_val) \
-- INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_STEP, category, \
-- name, id, TRACE_EVENT_FLAG_COPY, "step", \
-- step, arg1_name, arg1_val)
--
--// Records a single FLOW_END event for "name" immediately. If the category
--// is not enabled, then this does nothing.
--#define TRACE_EVENT_FLOW_END0(category, name, id) \
-- INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_END, category, name, \
-- id, TRACE_EVENT_FLAG_NONE)
--#define TRACE_EVENT_FLOW_END1(category, name, id, arg1_name, arg1_val) \
-- INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_END, category, name, \
-- id, TRACE_EVENT_FLAG_NONE, arg1_name, \
-- arg1_val)
--#define TRACE_EVENT_FLOW_END2(category, name, id, arg1_name, arg1_val, \
-- arg2_name, arg2_val) \
-- INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_END, category, name, \
-- id, TRACE_EVENT_FLAG_NONE, arg1_name, \
-- arg1_val, arg2_name, arg2_val)
--#define TRACE_EVENT_COPY_FLOW_END0(category, name, id) \
-- INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_END, category, name, \
-- id, TRACE_EVENT_FLAG_COPY)
--#define TRACE_EVENT_COPY_FLOW_END1(category, name, id, arg1_name, arg1_val) \
-- INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_END, category, name, \
-- id, TRACE_EVENT_FLAG_COPY, arg1_name, \
-- arg1_val)
--#define TRACE_EVENT_COPY_FLOW_END2(category, name, id, arg1_name, arg1_val, \
-- arg2_name, arg2_val) \
-- INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_END, category, name, \
-- id, TRACE_EVENT_FLAG_COPY, arg1_name, \
-- arg1_val, arg2_name, arg2_val)
--
-////////////////////////////////////////////////////////////////////////////////
-// Implementation specific tracing API definitions.
-
@@ -645,9 +551,6 @@ index 6689bc0c37..b34df0c93f 100644
-#define TRACE_EVENT_PHASE_ASYNC_BEGIN ('S')
-#define TRACE_EVENT_PHASE_ASYNC_STEP ('T')
-#define TRACE_EVENT_PHASE_ASYNC_END ('F')
--#define TRACE_EVENT_PHASE_FLOW_BEGIN ('s')
--#define TRACE_EVENT_PHASE_FLOW_STEP ('t')
--#define TRACE_EVENT_PHASE_FLOW_END ('f')
-#define TRACE_EVENT_PHASE_METADATA ('M')
-#define TRACE_EVENT_PHASE_COUNTER ('C')
-
@@ -1024,40 +927,6 @@ index 6689bc0c37..b34df0c93f 100644
- arg2_name, arg2_val) \
- RTC_NOOP()
-
--#define TRACE_EVENT_FLOW_BEGIN0(category, name, id) RTC_NOOP()
--#define TRACE_EVENT_FLOW_BEGIN1(category, name, id, arg1_name, arg1_val) \
-- RTC_NOOP()
--#define TRACE_EVENT_FLOW_BEGIN2(category, name, id, arg1_name, arg1_val, \
-- arg2_name, arg2_val) \
-- RTC_NOOP()
--#define TRACE_EVENT_COPY_FLOW_BEGIN0(category, name, id) RTC_NOOP()
--#define TRACE_EVENT_COPY_FLOW_BEGIN1(category, name, id, arg1_name, arg1_val) \
-- RTC_NOOP()
--#define TRACE_EVENT_COPY_FLOW_BEGIN2(category, name, id, arg1_name, arg1_val, \
-- arg2_name, arg2_val) \
-- RTC_NOOP()
--
--#define TRACE_EVENT_FLOW_STEP0(category, name, id, step) RTC_NOOP()
--#define TRACE_EVENT_FLOW_STEP1(category, name, id, step, arg1_name, arg1_val) \
-- RTC_NOOP()
--#define TRACE_EVENT_COPY_FLOW_STEP0(category, name, id, step) RTC_NOOP()
--#define TRACE_EVENT_COPY_FLOW_STEP1(category, name, id, step, arg1_name, \
-- arg1_val) \
-- RTC_NOOP()
--
--#define TRACE_EVENT_FLOW_END0(category, name, id) RTC_NOOP()
--#define TRACE_EVENT_FLOW_END1(category, name, id, arg1_name, arg1_val) \
-- RTC_NOOP()
--#define TRACE_EVENT_FLOW_END2(category, name, id, arg1_name, arg1_val, \
-- arg2_name, arg2_val) \
-- RTC_NOOP()
--#define TRACE_EVENT_COPY_FLOW_END0(category, name, id) RTC_NOOP()
--#define TRACE_EVENT_COPY_FLOW_END1(category, name, id, arg1_name, arg1_val) \
-- RTC_NOOP()
--#define TRACE_EVENT_COPY_FLOW_END2(category, name, id, arg1_name, arg1_val, \
-- arg2_name, arg2_val) \
-- RTC_NOOP()
--
-#define TRACE_EVENT_API_GET_CATEGORY_ENABLED ""
-
-#define TRACE_EVENT_API_ADD_TRACE_EVENT RTC_NOOP()
diff --git a/third_party/libwebrtc/moz-patch-stack/0049.patch b/third_party/libwebrtc/moz-patch-stack/0049.patch
index e94ab52a26..3074531967 100644
--- a/third_party/libwebrtc/moz-patch-stack/0049.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0049.patch
@@ -13,7 +13,7 @@ diff --git a/modules/desktop_capture/window_capturer_mac.mm b/modules/desktop_ca
index 56c94baada..8f5dc25516 100644
--- a/modules/desktop_capture/window_capturer_mac.mm
+++ b/modules/desktop_capture/window_capturer_mac.mm
-@@ -169,8 +169,9 @@ explicit WindowCapturerMac(
+@@ -169,8 +169,9 @@ void WindowCapturerMac::CaptureFrame() {
return webrtc::GetWindowList(
[sources](CFDictionaryRef window) {
WindowId window_id = GetWindowId(window);
diff --git a/third_party/libwebrtc/moz-patch-stack/0050.patch b/third_party/libwebrtc/moz-patch-stack/0050.patch
index e36c3b83c6..a3c23bc40a 100644
--- a/third_party/libwebrtc/moz-patch-stack/0050.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0050.patch
@@ -37,7 +37,7 @@ index 01fb08a009..87ee39e142 100644
// Timing frame info: all important timestamps for a full lifetime of a
// single 'timing frame'.
diff --git a/modules/rtp_rtcp/source/rtcp_receiver.cc b/modules/rtp_rtcp/source/rtcp_receiver.cc
-index e2ad674012..94de316421 100644
+index 9c6ceb2403..5c85734e58 100644
--- a/modules/rtp_rtcp/source/rtcp_receiver.cc
+++ b/modules/rtp_rtcp/source/rtcp_receiver.cc
@@ -365,11 +365,13 @@ RTCPReceiver::ConsumeReceivedXrReferenceTimeInfo() {
@@ -159,10 +159,10 @@ index 2c56dccd2a..f196d11b58 100644
// Within this list, the sender-source SSRC pair is unique and per-pair the
// ReportBlockData represents the latest Report Block that was received for
diff --git a/video/rtp_video_stream_receiver2.cc b/video/rtp_video_stream_receiver2.cc
-index 0a215f79cc..47c31812f3 100644
+index 79ce90794e..029e7a7405 100644
--- a/video/rtp_video_stream_receiver2.cc
+++ b/video/rtp_video_stream_receiver2.cc
-@@ -1071,9 +1071,10 @@ absl::optional<int64_t> RtpVideoStreamReceiver2::LastReceivedKeyframePacketMs()
+@@ -1067,9 +1067,10 @@ absl::optional<int64_t> RtpVideoStreamReceiver2::LastReceivedKeyframePacketMs()
// seem to be any support for these stats right now. So, we hack this in.
void RtpVideoStreamReceiver2::RemoteRTCPSenderInfo(
uint32_t* packet_count, uint32_t* octet_count,
@@ -176,10 +176,10 @@ index 0a215f79cc..47c31812f3 100644
void RtpVideoStreamReceiver2::ManageFrame(
diff --git a/video/rtp_video_stream_receiver2.h b/video/rtp_video_stream_receiver2.h
-index be8bce770f..0e96d7f2cd 100644
+index 4a93e53356..00b17a77bd 100644
--- a/video/rtp_video_stream_receiver2.h
+++ b/video/rtp_video_stream_receiver2.h
-@@ -211,7 +211,8 @@ class RtpVideoStreamReceiver2 : public LossNotificationSender,
+@@ -213,7 +213,8 @@ class RtpVideoStreamReceiver2 : public LossNotificationSender,
// stats at all, and even on the most recent libwebrtc code there does not
// seem to be any support for these stats right now. So, we hack this in.
void RemoteRTCPSenderInfo(uint32_t* packet_count, uint32_t* octet_count,
@@ -190,10 +190,10 @@ index be8bce770f..0e96d7f2cd 100644
private:
// Implements RtpVideoFrameReceiver.
diff --git a/video/video_receive_stream2.cc b/video/video_receive_stream2.cc
-index 776c49042b..c04b43a1d1 100644
+index 3d0534bf10..f135f42f3b 100644
--- a/video/video_receive_stream2.cc
+++ b/video/video_receive_stream2.cc
-@@ -576,7 +576,8 @@ VideoReceiveStreamInterface::Stats VideoReceiveStream2::GetStats() const {
+@@ -574,7 +574,8 @@ VideoReceiveStreamInterface::Stats VideoReceiveStream2::GetStats() const {
// seem to be any support for these stats right now. So, we hack this in.
rtp_video_stream_receiver_.RemoteRTCPSenderInfo(
&stats.rtcp_sender_packets_sent, &stats.rtcp_sender_octets_sent,
diff --git a/third_party/libwebrtc/moz-patch-stack/0052.patch b/third_party/libwebrtc/moz-patch-stack/0052.patch
index 616b4fdcc7..98c4d5dafd 100644
--- a/third_party/libwebrtc/moz-patch-stack/0052.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0052.patch
@@ -12,8 +12,6 @@ Differential Revision: https://phabricator.services.mozilla.com/D127714
Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/0744d68b8c944e69945de4ac5c4ca71332e78ad8
---
audio/channel_send.cc | 2 +-
- call/call.cc | 2 ++
- call/call_factory.cc | 4 ++++
call/degraded_call.cc | 2 ++
modules/audio_coding/acm2/acm_receiver.cc | 2 +-
modules/rtp_rtcp/include/flexfec_receiver.h | 2 ++
@@ -21,10 +19,10 @@ Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/0744d68b8c944e699
rtc_base/task_utils/repeating_task.h | 4 ++--
system_wrappers/include/clock.h | 2 +-
system_wrappers/source/clock.cc | 2 +-
- 10 files changed, 18 insertions(+), 6 deletions(-)
+ 8 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/audio/channel_send.cc b/audio/channel_send.cc
-index 549e65a59c..8080f4a3b8 100644
+index db632b3aa8..c8251b4b52 100644
--- a/audio/channel_send.cc
+++ b/audio/channel_send.cc
@@ -443,7 +443,7 @@ ChannelSend::ChannelSend(
@@ -36,48 +34,6 @@ index 549e65a59c..8080f4a3b8 100644
configuration.audio = true;
configuration.outgoing_transport = rtp_transport;
-diff --git a/call/call.cc b/call/call.cc
-index 42b3b988ea..d2ac705274 100644
---- a/call/call.cc
-+++ b/call/call.cc
-@@ -473,6 +473,7 @@ std::string Call::Stats::ToString(int64_t time_ms) const {
- return ss.str();
- }
-
-+/* Mozilla: Avoid this since it could use GetRealTimeClock().
- std::unique_ptr<Call> Call::Create(const CallConfig& config) {
- Clock* clock =
- config.env.has_value() ? &config.env->clock() : Clock::GetRealTimeClock();
-@@ -480,6 +481,7 @@ std::unique_ptr<Call> Call::Create(const CallConfig& config) {
- RtpTransportControllerSendFactory().Create(
- config.ExtractTransportConfig(), clock));
- }
-+ */
-
- std::unique_ptr<Call> Call::Create(
- const CallConfig& config,
-diff --git a/call/call_factory.cc b/call/call_factory.cc
-index 043b11da37..78a4f1635f 100644
---- a/call/call_factory.cc
-+++ b/call/call_factory.cc
-@@ -94,6 +94,9 @@ std::unique_ptr<Call> CallFactory::CreateCall(const CallConfig& config) {
-
- RtpTransportConfig transportConfig = config.ExtractTransportConfig();
-
-+ RTC_CHECK(false);
-+ return nullptr;
-+ /* Mozilla: Avoid this since it could use GetRealTimeClock().
- std::unique_ptr<Call> call =
- Call::Create(config, Clock::GetRealTimeClock(),
- config.rtp_transport_controller_send_factory->Create(
-@@ -106,6 +109,7 @@ std::unique_ptr<Call> CallFactory::CreateCall(const CallConfig& config) {
- }
-
- return call;
-+ */
- }
-
- std::unique_ptr<CallFactoryInterface> CreateCallFactory() {
diff --git a/call/degraded_call.cc b/call/degraded_call.cc
index a511eda7bd..75a4a1cac0 100644
--- a/call/degraded_call.cc
diff --git a/third_party/libwebrtc/moz-patch-stack/0053.patch b/third_party/libwebrtc/moz-patch-stack/0053.patch
index 56c0b13b8e..26d747a4fc 100644
--- a/third_party/libwebrtc/moz-patch-stack/0053.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0053.patch
@@ -32,7 +32,7 @@ index d2ede84941..f595a2951a 100644
defines += [ "WEBRTC_MAC" ]
}
diff --git a/modules/video_capture/BUILD.gn b/modules/video_capture/BUILD.gn
-index d473dbb74c..8f89918359 100644
+index aabf545728..8dda0e6ef1 100644
--- a/modules/video_capture/BUILD.gn
+++ b/modules/video_capture/BUILD.gn
@@ -71,7 +71,7 @@ if (!build_with_chromium || is_linux || is_chromeos) {
diff --git a/third_party/libwebrtc/moz-patch-stack/0054.patch b/third_party/libwebrtc/moz-patch-stack/0054.patch
index 928198e5a4..f07a706f54 100644
--- a/third_party/libwebrtc/moz-patch-stack/0054.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0054.patch
@@ -4,18 +4,17 @@ Subject: Bug 1766646 - (fix) breakout Call::Stats and SharedModuleThread into
seperate files
---
- call/BUILD.gn | 6 ++++++
- call/call.cc | 13 -------------
- call/call.h | 12 ++----------
- call/call_basic_stats.cc | 20 ++++++++++++++++++++
- call/call_basic_stats.h | 21 +++++++++++++++++++++
- video/video_send_stream.h | 1 -
- 6 files changed, 49 insertions(+), 24 deletions(-)
+ call/BUILD.gn | 6 ++++++
+ call/call.cc | 13 -------------
+ call/call.h | 12 ++----------
+ call/call_basic_stats.cc | 20 ++++++++++++++++++++
+ call/call_basic_stats.h | 21 +++++++++++++++++++++
+ 5 files changed, 49 insertions(+), 23 deletions(-)
create mode 100644 call/call_basic_stats.cc
create mode 100644 call/call_basic_stats.h
diff --git a/call/BUILD.gn b/call/BUILD.gn
-index fa733a67b9..626ed95066 100644
+index cca88ea7bb..50a8257631 100644
--- a/call/BUILD.gn
+++ b/call/BUILD.gn
@@ -33,6 +33,12 @@ rtc_library("call_interfaces") {
@@ -32,10 +31,10 @@ index fa733a67b9..626ed95066 100644
deps = [
":audio_sender_interface",
diff --git a/call/call.cc b/call/call.cc
-index d2ac705274..63dc370f1a 100644
+index c97cb6d743..71511b2559 100644
--- a/call/call.cc
+++ b/call/call.cc
-@@ -460,19 +460,6 @@ class Call final : public webrtc::Call,
+@@ -454,19 +454,6 @@ class Call final : public webrtc::Call,
};
} // namespace internal
@@ -52,11 +51,11 @@ index d2ac705274..63dc370f1a 100644
- return ss.str();
-}
-
- /* Mozilla: Avoid this since it could use GetRealTimeClock().
std::unique_ptr<Call> Call::Create(const CallConfig& config) {
- Clock* clock =
+ std::unique_ptr<RtpTransportControllerSendInterface> transport_send;
+ if (config.rtp_transport_controller_send_factory != nullptr) {
diff --git a/call/call.h b/call/call.h
-index 6f8e4cd6d7..b36872f5b5 100644
+index a680335192..e7d37c0abd 100644
--- a/call/call.h
+++ b/call/call.h
@@ -21,6 +21,7 @@
@@ -67,7 +66,7 @@ index 6f8e4cd6d7..b36872f5b5 100644
#include "call/call_config.h"
#include "call/flexfec_receive_stream.h"
#include "call/packet_receiver.h"
-@@ -30,7 +31,6 @@
+@@ -29,7 +30,6 @@
#include "rtc_base/copy_on_write_buffer.h"
#include "rtc_base/network/sent_packet.h"
#include "rtc_base/network_route.h"
@@ -75,7 +74,7 @@ index 6f8e4cd6d7..b36872f5b5 100644
namespace webrtc {
-@@ -46,15 +46,7 @@ namespace webrtc {
+@@ -45,15 +45,7 @@ namespace webrtc {
class Call {
public:
@@ -91,7 +90,7 @@ index 6f8e4cd6d7..b36872f5b5 100644
+ using Stats = CallBasicStats;
static std::unique_ptr<Call> Create(const CallConfig& config);
- static std::unique_ptr<Call> Create(
+
diff --git a/call/call_basic_stats.cc b/call/call_basic_stats.cc
new file mode 100644
index 0000000000..74333a663b
@@ -145,15 +144,3 @@ index 0000000000..98febe9405
+} // namespace webrtc
+
+#endif // CALL_CALL_BASIC_STATS_H_
-diff --git a/video/video_send_stream.h b/video/video_send_stream.h
-index 05970d619e..4afafcf8e4 100644
---- a/video/video_send_stream.h
-+++ b/video/video_send_stream.h
-@@ -36,7 +36,6 @@ namespace test {
- class VideoSendStreamPeer;
- } // namespace test
-
--class CallStats;
- class IvfFileWriter;
- class RateLimiter;
- class RtpRtcp;
diff --git a/third_party/libwebrtc/moz-patch-stack/0064.patch b/third_party/libwebrtc/moz-patch-stack/0064.patch
index 4d9c0c6550..e379af2487 100644
--- a/third_party/libwebrtc/moz-patch-stack/0064.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0064.patch
@@ -13,7 +13,7 @@ Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/f097eb8cbd8b7686c
2 files changed, 9 insertions(+)
diff --git a/api/task_queue/BUILD.gn b/api/task_queue/BUILD.gn
-index d557d8f100..9b2f747e78 100644
+index afdf47dfc9..b9bc81171f 100644
--- a/api/task_queue/BUILD.gn
+++ b/api/task_queue/BUILD.gn
@@ -31,6 +31,11 @@ rtc_library("task_queue") {
@@ -29,7 +29,7 @@ index d557d8f100..9b2f747e78 100644
rtc_library("task_queue_test") {
visibility = [ "*" ]
diff --git a/rtc_base/BUILD.gn b/rtc_base/BUILD.gn
-index 089b9971a3..5392e5f472 100644
+index 7372b539c4..57a9c11f01 100644
--- a/rtc_base/BUILD.gn
+++ b/rtc_base/BUILD.gn
@@ -743,10 +743,14 @@ if (is_mac || is_ios) {
diff --git a/third_party/libwebrtc/moz-patch-stack/0068.patch b/third_party/libwebrtc/moz-patch-stack/0068.patch
index ab46127e4f..38bae4d1e8 100644
--- a/third_party/libwebrtc/moz-patch-stack/0068.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0068.patch
@@ -215,7 +215,7 @@ index 8e4941f961..7bcfc7c057 100644
int current_delay_ms,
int target_delay_ms,
diff --git a/video/rtp_video_stream_receiver2.cc b/video/rtp_video_stream_receiver2.cc
-index 47c31812f3..0954327f1c 100644
+index 029e7a7405..26bd60057d 100644
--- a/video/rtp_video_stream_receiver2.cc
+++ b/video/rtp_video_stream_receiver2.cc
@@ -244,6 +244,7 @@ RtpVideoStreamReceiver2::RtpVideoStreamReceiver2(
@@ -234,7 +234,7 @@ index 47c31812f3..0954327f1c 100644
packet_buffer_(kPacketBufferStartSize,
PacketBufferMaxSize(field_trials_)),
reference_finder_(std::make_unique<RtpFrameReferenceFinder>()),
-@@ -1219,7 +1221,8 @@ void RtpVideoStreamReceiver2::FrameDecoded(int64_t picture_id) {
+@@ -1250,7 +1252,8 @@ void RtpVideoStreamReceiver2::FrameDecoded(int64_t picture_id) {
int64_t unwrapped_rtp_seq_num = rtp_seq_num_unwrapper_.Unwrap(seq_num);
packet_infos_.erase(packet_infos_.begin(),
packet_infos_.upper_bound(unwrapped_rtp_seq_num));
@@ -245,7 +245,7 @@ index 47c31812f3..0954327f1c 100644
}
}
diff --git a/video/rtp_video_stream_receiver2.h b/video/rtp_video_stream_receiver2.h
-index 0e96d7f2cd..10329005ba 100644
+index 00b17a77bd..b942cb97a6 100644
--- a/video/rtp_video_stream_receiver2.h
+++ b/video/rtp_video_stream_receiver2.h
@@ -49,6 +49,7 @@
@@ -264,7 +264,7 @@ index 0e96d7f2cd..10329005ba 100644
// The KeyFrameRequestSender is optional; if not provided, key frame
// requests are sent via the internal RtpRtcp module.
OnCompleteFrameCallback* complete_frame_callback,
-@@ -362,6 +364,7 @@ class RtpVideoStreamReceiver2 : public LossNotificationSender,
+@@ -365,6 +367,7 @@ class RtpVideoStreamReceiver2 : public LossNotificationSender,
std::unique_ptr<LossNotificationController> loss_notification_controller_
RTC_GUARDED_BY(packet_sequence_checker_);
@@ -273,10 +273,10 @@ index 0e96d7f2cd..10329005ba 100644
RTC_GUARDED_BY(packet_sequence_checker_);
UniqueTimestampCounter frame_counter_
diff --git a/video/video_receive_stream2.cc b/video/video_receive_stream2.cc
-index c04b43a1d1..33e2f39ced 100644
+index f135f42f3b..8675ab9979 100644
--- a/video/video_receive_stream2.cc
+++ b/video/video_receive_stream2.cc
-@@ -210,6 +210,7 @@ VideoReceiveStream2::VideoReceiveStream2(
+@@ -209,6 +209,7 @@ VideoReceiveStream2::VideoReceiveStream2(
&stats_proxy_,
&stats_proxy_,
nack_periodic_processor,
diff --git a/third_party/libwebrtc/moz-patch-stack/0069.patch b/third_party/libwebrtc/moz-patch-stack/0069.patch
index 6b36a1a2b7..54357d2957 100644
--- a/third_party/libwebrtc/moz-patch-stack/0069.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0069.patch
@@ -30,10 +30,10 @@ index 8ef4d553ad..a0f19999d8 100644
void ReceiveStatisticsProxy::OnPreDecode(VideoCodecType codec_type, int qp) {
diff --git a/video/rtp_video_stream_receiver2.cc b/video/rtp_video_stream_receiver2.cc
-index 0954327f1c..12e777c58f 100644
+index 26bd60057d..daf601c6cb 100644
--- a/video/rtp_video_stream_receiver2.cc
+++ b/video/rtp_video_stream_receiver2.cc
-@@ -1222,7 +1222,9 @@ void RtpVideoStreamReceiver2::FrameDecoded(int64_t picture_id) {
+@@ -1253,7 +1253,9 @@ void RtpVideoStreamReceiver2::FrameDecoded(int64_t picture_id) {
packet_infos_.erase(packet_infos_.begin(),
packet_infos_.upper_bound(unwrapped_rtp_seq_num));
uint32_t num_packets_cleared = packet_buffer_.ClearTo(seq_num);
diff --git a/third_party/libwebrtc/moz-patch-stack/0070.patch b/third_party/libwebrtc/moz-patch-stack/0070.patch
index a63b0af9c2..842b9da193 100644
--- a/third_party/libwebrtc/moz-patch-stack/0070.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0070.patch
@@ -166,7 +166,7 @@ index a0f19999d8..1764308c0a 100644
}
diff --git a/video/rtp_video_stream_receiver2.cc b/video/rtp_video_stream_receiver2.cc
-index 12e777c58f..a07dad5d4b 100644
+index daf601c6cb..5e563d001c 100644
--- a/video/rtp_video_stream_receiver2.cc
+++ b/video/rtp_video_stream_receiver2.cc
@@ -44,6 +44,7 @@
@@ -177,7 +177,7 @@ index 12e777c58f..a07dad5d4b 100644
#include "system_wrappers/include/metrics.h"
#include "system_wrappers/include/ntp_time.h"
-@@ -1223,6 +1224,9 @@ void RtpVideoStreamReceiver2::FrameDecoded(int64_t picture_id) {
+@@ -1254,6 +1255,9 @@ void RtpVideoStreamReceiver2::FrameDecoded(int64_t picture_id) {
packet_infos_.upper_bound(unwrapped_rtp_seq_num));
uint32_t num_packets_cleared = packet_buffer_.ClearTo(seq_num);
if (num_packets_cleared > 0) {
diff --git a/third_party/libwebrtc/moz-patch-stack/0071.patch b/third_party/libwebrtc/moz-patch-stack/0071.patch
index 7aed95255b..5495f3d035 100644
--- a/third_party/libwebrtc/moz-patch-stack/0071.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0071.patch
@@ -9,10 +9,10 @@ Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/5b2a7894ef1cf096d
1 file changed, 6 insertions(+)
diff --git a/video/rtp_video_stream_receiver2.cc b/video/rtp_video_stream_receiver2.cc
-index a07dad5d4b..db0b87c736 100644
+index 5e563d001c..4df65659f9 100644
--- a/video/rtp_video_stream_receiver2.cc
+++ b/video/rtp_video_stream_receiver2.cc
-@@ -744,6 +744,12 @@ void RtpVideoStreamReceiver2::OnRtpPacket(const RtpPacketReceived& packet) {
+@@ -740,6 +740,12 @@ void RtpVideoStreamReceiver2::OnRtpPacket(const RtpPacketReceived& packet) {
void RtpVideoStreamReceiver2::RequestKeyFrame() {
RTC_DCHECK_RUN_ON(&worker_task_checker_);
diff --git a/third_party/libwebrtc/moz-patch-stack/0076.patch b/third_party/libwebrtc/moz-patch-stack/0076.patch
index ae3f880387..d3aec3677e 100644
--- a/third_party/libwebrtc/moz-patch-stack/0076.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0076.patch
@@ -9,10 +9,10 @@ Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/108046c7cbb21c6cf
1 file changed, 1 insertion(+)
diff --git a/modules/audio_processing/audio_processing_impl.cc b/modules/audio_processing/audio_processing_impl.cc
-index c80cc76a3d..c304453388 100644
+index 0104e0c486..4ac074526c 100644
--- a/modules/audio_processing/audio_processing_impl.cc
+++ b/modules/audio_processing/audio_processing_impl.cc
-@@ -450,6 +450,7 @@ AudioProcessingImpl::GetGainController2ExperimentParams() {
+@@ -452,6 +452,7 @@ AudioProcessingImpl::GetGainController2ExperimentParams() {
},
.adaptive_digital_controller =
{
diff --git a/third_party/libwebrtc/moz-patch-stack/0078.patch b/third_party/libwebrtc/moz-patch-stack/0078.patch
index c7d811ffd7..01ab27f1e6 100644
--- a/third_party/libwebrtc/moz-patch-stack/0078.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0078.patch
@@ -11,7 +11,7 @@ Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/8ff886a4d366b4be3
3 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/modules/video_capture/BUILD.gn b/modules/video_capture/BUILD.gn
-index 8f89918359..45a0272eee 100644
+index 8dda0e6ef1..3132e452ba 100644
--- a/modules/video_capture/BUILD.gn
+++ b/modules/video_capture/BUILD.gn
@@ -104,6 +104,10 @@ if (!build_with_chromium || is_linux || is_chromeos) {
diff --git a/third_party/libwebrtc/moz-patch-stack/0079.patch b/third_party/libwebrtc/moz-patch-stack/0079.patch
index 1e8257408f..15d16557c2 100644
--- a/third_party/libwebrtc/moz-patch-stack/0079.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0079.patch
@@ -9,7 +9,7 @@ Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/154c9cdb386d0f50c
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/modules/rtp_rtcp/source/rtcp_receiver.cc b/modules/rtp_rtcp/source/rtcp_receiver.cc
-index 94de316421..bda6ad9a52 100644
+index 5c85734e58..756136866d 100644
--- a/modules/rtp_rtcp/source/rtcp_receiver.cc
+++ b/modules/rtp_rtcp/source/rtcp_receiver.cc
@@ -368,10 +368,10 @@ void RTCPReceiver::RemoteRTCPSenderInfo(uint32_t* packet_count,
diff --git a/third_party/libwebrtc/moz-patch-stack/0081.patch b/third_party/libwebrtc/moz-patch-stack/0081.patch
index 8a60e356af..2fbb974454 100644
--- a/third_party/libwebrtc/moz-patch-stack/0081.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0081.patch
@@ -10,10 +10,10 @@ Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/a7179d8d75313b6c9
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/video/video_stream_encoder.cc b/video/video_stream_encoder.cc
-index 552463e143..669f165635 100644
+index 3730f6eecb..d74f440996 100644
--- a/video/video_stream_encoder.cc
+++ b/video/video_stream_encoder.cc
-@@ -1375,7 +1375,7 @@ void VideoStreamEncoder::ReconfigureEncoder() {
+@@ -1383,7 +1383,7 @@ void VideoStreamEncoder::ReconfigureEncoder() {
bool is_svc = false;
bool single_stream_or_non_first_inactive = true;
diff --git a/third_party/libwebrtc/moz-patch-stack/0083.patch b/third_party/libwebrtc/moz-patch-stack/0083.patch
index 181d1439da..3c6ab41ddf 100644
--- a/third_party/libwebrtc/moz-patch-stack/0083.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0083.patch
@@ -12,7 +12,7 @@ Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/6ac6592a04a839a61
1 file changed, 2 deletions(-)
diff --git a/audio/channel_send.cc b/audio/channel_send.cc
-index 8080f4a3b8..61e68d19df 100644
+index c8251b4b52..b8aa573a53 100644
--- a/audio/channel_send.cc
+++ b/audio/channel_send.cc
@@ -474,8 +474,6 @@ ChannelSend::ChannelSend(
diff --git a/third_party/libwebrtc/moz-patch-stack/0084.patch b/third_party/libwebrtc/moz-patch-stack/0084.patch
index 8a565cc5d3..f4d9cf5e0a 100644
--- a/third_party/libwebrtc/moz-patch-stack/0084.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0084.patch
@@ -15,10 +15,10 @@ Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/56555ecee7f36ae73
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/audio/channel_send.cc b/audio/channel_send.cc
-index 61e68d19df..3c59be52b4 100644
+index b8aa573a53..ae264a4c77 100644
--- a/audio/channel_send.cc
+++ b/audio/channel_send.cc
-@@ -286,12 +286,16 @@ class RtpPacketSenderProxy : public RtpPacketSender {
+@@ -285,12 +285,16 @@ class RtpPacketSenderProxy : public RtpPacketSender {
void EnqueuePackets(
std::vector<std::unique_ptr<RtpPacketToSend>> packets) override {
MutexLock lock(&mutex_);
diff --git a/third_party/libwebrtc/moz-patch-stack/0085.patch b/third_party/libwebrtc/moz-patch-stack/0085.patch
index 62d24fdc20..0593b7ae45 100644
--- a/third_party/libwebrtc/moz-patch-stack/0085.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0085.patch
@@ -52,7 +52,7 @@ index f08fc692dd..02f2e53923 100644
Clock* const clock_;
bool short_circuit_ RTC_GUARDED_BY(network_sequence_checker_) = false;
diff --git a/video/rtp_video_stream_receiver2.cc b/video/rtp_video_stream_receiver2.cc
-index db0b87c736..c4a021d6c0 100644
+index 4df65659f9..077f522d41 100644
--- a/video/rtp_video_stream_receiver2.cc
+++ b/video/rtp_video_stream_receiver2.cc
@@ -341,7 +341,7 @@ RtpVideoStreamReceiver2::RtpVideoStreamReceiver2(
diff --git a/third_party/libwebrtc/moz-patch-stack/0097.patch b/third_party/libwebrtc/moz-patch-stack/0097.patch
index 56c8dca72b..1faafdf8cf 100644
--- a/third_party/libwebrtc/moz-patch-stack/0097.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0097.patch
@@ -1,2186 +1,35362 @@
-From: Dan Minor <dminor@mozilla.com>
-Date: Thu, 24 Sep 2020 18:28:00 +0000
-Subject: Bug 1665166 - Move media/webrtc/trunk/* to third-party/libwebrtc;
- r=ng
+From: Nico Grunbaum <na-g@nostrum.com>
+Date: Fri, 30 Apr 2021 21:51:00 +0000
+Subject: Bug 1654112 - Add grit dep for building webrtc on android; r=mjf
-Differential Revision: https://phabricator.services.mozilla.com/D91317
-Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/57e3c54bd7b9a0203e19ff1df272d24bb551ed29
+Differential Revision: https://phabricator.services.mozilla.com/D114027
+Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/3cce5e6938f0df87bd9ab12a5f556aceb93dfa1d
---
- tools/clang/OWNERS | 2 +
- tools/clang/plugins/ChromeClassTester.cpp | 294 ++++++++++++
- tools/clang/plugins/ChromeClassTester.h | 84 ++++
- tools/clang/plugins/FindBadConstructs.cpp | 435 ++++++++++++++++++
- tools/clang/plugins/Makefile | 19 +
- tools/clang/plugins/OWNERS | 1 +
- tools/clang/plugins/README.chromium | 4 +
- tools/clang/plugins/tests/base_refcounted.cpp | 72 +++
- tools/clang/plugins/tests/base_refcounted.h | 121 +++++
- tools/clang/plugins/tests/base_refcounted.txt | 23 +
- .../clang/plugins/tests/inline_copy_ctor.cpp | 5 +
- tools/clang/plugins/tests/inline_copy_ctor.h | 12 +
- .../clang/plugins/tests/inline_copy_ctor.txt | 5 +
- tools/clang/plugins/tests/inline_ctor.cpp | 25 +
- tools/clang/plugins/tests/inline_ctor.h | 21 +
- tools/clang/plugins/tests/inline_ctor.txt | 8 +
- tools/clang/plugins/tests/missing_ctor.cpp | 23 +
- tools/clang/plugins/tests/missing_ctor.h | 19 +
- tools/clang/plugins/tests/missing_ctor.txt | 6 +
- .../tests/nested_class_inline_ctor.cpp | 5 +
- .../plugins/tests/nested_class_inline_ctor.h | 22 +
- .../tests/nested_class_inline_ctor.txt | 8 +
- .../plugins/tests/overridden_methods.cpp | 38 ++
- .../clang/plugins/tests/overridden_methods.h | 54 +++
- .../plugins/tests/overridden_methods.txt | 20 +
- tools/clang/plugins/tests/test.sh | 72 +++
- tools/clang/plugins/tests/virtual_methods.cpp | 36 ++
- tools/clang/plugins/tests/virtual_methods.h | 39 ++
- tools/clang/plugins/tests/virtual_methods.txt | 8 +
- tools/clang/scripts/package.sh | 87 ++++
- tools/clang/scripts/plugin_flags.sh | 24 +
- tools/clang/scripts/update.py | 34 ++
- tools/clang/scripts/update.sh | 286 ++++++++++++
- 33 files changed, 1912 insertions(+)
- create mode 100644 tools/clang/OWNERS
- create mode 100644 tools/clang/plugins/ChromeClassTester.cpp
- create mode 100644 tools/clang/plugins/ChromeClassTester.h
- create mode 100644 tools/clang/plugins/FindBadConstructs.cpp
- create mode 100644 tools/clang/plugins/Makefile
- create mode 100644 tools/clang/plugins/OWNERS
- create mode 100644 tools/clang/plugins/README.chromium
- create mode 100644 tools/clang/plugins/tests/base_refcounted.cpp
- create mode 100644 tools/clang/plugins/tests/base_refcounted.h
- create mode 100644 tools/clang/plugins/tests/base_refcounted.txt
- create mode 100644 tools/clang/plugins/tests/inline_copy_ctor.cpp
- create mode 100644 tools/clang/plugins/tests/inline_copy_ctor.h
- create mode 100644 tools/clang/plugins/tests/inline_copy_ctor.txt
- create mode 100644 tools/clang/plugins/tests/inline_ctor.cpp
- create mode 100644 tools/clang/plugins/tests/inline_ctor.h
- create mode 100644 tools/clang/plugins/tests/inline_ctor.txt
- create mode 100644 tools/clang/plugins/tests/missing_ctor.cpp
- create mode 100644 tools/clang/plugins/tests/missing_ctor.h
- create mode 100644 tools/clang/plugins/tests/missing_ctor.txt
- create mode 100644 tools/clang/plugins/tests/nested_class_inline_ctor.cpp
- create mode 100644 tools/clang/plugins/tests/nested_class_inline_ctor.h
- create mode 100644 tools/clang/plugins/tests/nested_class_inline_ctor.txt
- create mode 100644 tools/clang/plugins/tests/overridden_methods.cpp
- create mode 100644 tools/clang/plugins/tests/overridden_methods.h
- create mode 100644 tools/clang/plugins/tests/overridden_methods.txt
- create mode 100755 tools/clang/plugins/tests/test.sh
- create mode 100644 tools/clang/plugins/tests/virtual_methods.cpp
- create mode 100644 tools/clang/plugins/tests/virtual_methods.h
- create mode 100644 tools/clang/plugins/tests/virtual_methods.txt
- create mode 100755 tools/clang/scripts/package.sh
- create mode 100755 tools/clang/scripts/plugin_flags.sh
- create mode 100755 tools/clang/scripts/update.py
- create mode 100755 tools/clang/scripts/update.sh
+ tools/grit/.gitignore | 1 +
+ tools/grit/BUILD.gn | 48 +
+ tools/grit/MANIFEST.in | 3 +
+ tools/grit/OWNERS | 8 +
+ tools/grit/PRESUBMIT.py | 22 +
+ tools/grit/README.md | 19 +
+ tools/grit/grit.py | 31 +
+ tools/grit/grit/__init__.py | 19 +
+ tools/grit/grit/clique.py | 491 +++
+ tools/grit/grit/clique_unittest.py | 265 ++
+ tools/grit/grit/constants.py | 23 +
+ tools/grit/grit/exception.py | 139 +
+ tools/grit/grit/extern/BogoFP.py | 22 +
+ tools/grit/grit/extern/FP.py | 72 +
+ tools/grit/grit/extern/__init__.py | 0
+ tools/grit/grit/extern/tclib.py | 503 +++
+ tools/grit/grit/format/__init__.py | 8 +
+ tools/grit/grit/format/android_xml.py | 212 ++
+ .../grit/grit/format/android_xml_unittest.py | 149 +
+ tools/grit/grit/format/c_format.py | 95 +
+ tools/grit/grit/format/c_format_unittest.py | 81 +
+ .../grit/grit/format/chrome_messages_json.py | 59 +
+ .../format/chrome_messages_json_unittest.py | 190 +
+ tools/grit/grit/format/data_pack.py | 321 ++
+ tools/grit/grit/format/data_pack_unittest.py | 102 +
+ .../grit/grit/format/gen_predetermined_ids.py | 144 +
+ .../format/gen_predetermined_ids_unittest.py | 46 +
+ tools/grit/grit/format/gzip_string.py | 46 +
+ .../grit/grit/format/gzip_string_unittest.py | 65 +
+ tools/grit/grit/format/html_inline.py | 602 ++++
+ .../grit/grit/format/html_inline_unittest.py | 927 +++++
+ tools/grit/grit/format/minifier.py | 45 +
+ .../grit/grit/format/policy_templates_json.py | 26 +
+ .../format/policy_templates_json_unittest.py | 207 ++
+ tools/grit/grit/format/rc.py | 474 +++
+ tools/grit/grit/format/rc_header.py | 48 +
+ tools/grit/grit/format/rc_header_unittest.py | 138 +
+ tools/grit/grit/format/rc_unittest.py | 415 +++
+ tools/grit/grit/format/resource_map.py | 159 +
+ .../grit/grit/format/resource_map_unittest.py | 345 ++
+ tools/grit/grit/gather/__init__.py | 8 +
+ tools/grit/grit/gather/admin_template.py | 62 +
+ .../grit/gather/admin_template_unittest.py | 115 +
+ tools/grit/grit/gather/chrome_html.py | 377 ++
+ .../grit/grit/gather/chrome_html_unittest.py | 610 ++++
+ tools/grit/grit/gather/chrome_scaled_image.py | 157 +
+ .../gather/chrome_scaled_image_unittest.py | 209 ++
+ tools/grit/grit/gather/interface.py | 172 +
+ tools/grit/grit/gather/json_loader.py | 27 +
+ tools/grit/grit/gather/policy_json.py | 325 ++
+ .../grit/grit/gather/policy_json_unittest.py | 347 ++
+ tools/grit/grit/gather/rc.py | 343 ++
+ tools/grit/grit/gather/rc_unittest.py | 372 ++
+ tools/grit/grit/gather/regexp.py | 82 +
+ tools/grit/grit/gather/skeleton_gatherer.py | 149 +
+ tools/grit/grit/gather/tr_html.py | 743 ++++
+ tools/grit/grit/gather/tr_html_unittest.py | 524 +++
+ tools/grit/grit/gather/txt.py | 38 +
+ tools/grit/grit/gather/txt_unittest.py | 35 +
+ tools/grit/grit/grd_reader.py | 238 ++
+ tools/grit/grit/grd_reader_unittest.py | 346 ++
+ tools/grit/grit/grit-todo.xml | 62 +
+ tools/grit/grit/grit_runner.py | 334 ++
+ tools/grit/grit/grit_runner_unittest.py | 42 +
+ tools/grit/grit/lazy_re.py | 46 +
+ tools/grit/grit/lazy_re_unittest.py | 40 +
+ tools/grit/grit/node/__init__.py | 8 +
+ tools/grit/grit/node/base.py | 670 ++++
+ tools/grit/grit/node/base_unittest.py | 259 ++
+ tools/grit/grit/node/brotli_util.py | 29 +
+ tools/grit/grit/node/custom/__init__.py | 8 +
+ tools/grit/grit/node/custom/filename.py | 29 +
+ .../grit/node/custom/filename_unittest.py | 34 +
+ tools/grit/grit/node/empty.py | 64 +
+ tools/grit/grit/node/include.py | 170 +
+ tools/grit/grit/node/include_unittest.py | 134 +
+ tools/grit/grit/node/mapping.py | 60 +
+ tools/grit/grit/node/message.py | 362 ++
+ tools/grit/grit/node/message_unittest.py | 380 ++
+ tools/grit/grit/node/misc.py | 707 ++++
+ tools/grit/grit/node/misc_unittest.py | 590 ++++
+ tools/grit/grit/node/mock_brotli.py | 10 +
+ tools/grit/grit/node/node_io.py | 117 +
+ tools/grit/grit/node/node_io_unittest.py | 182 +
+ tools/grit/grit/node/structure.py | 375 ++
+ tools/grit/grit/node/structure_unittest.py | 178 +
+ tools/grit/grit/node/variant.py | 41 +
+ tools/grit/grit/pseudo.py | 129 +
+ tools/grit/grit/pseudo_rtl.py | 104 +
+ tools/grit/grit/pseudo_unittest.py | 55 +
+ tools/grit/grit/shortcuts.py | 93 +
+ tools/grit/grit/shortcuts_unittest.py | 79 +
+ tools/grit/grit/tclib.py | 246 ++
+ tools/grit/grit/tclib_unittest.py | 180 +
+ tools/grit/grit/test_suite_all.py | 34 +
+ tools/grit/grit/testdata/GoogleDesktop.adm | 945 +++++
+ tools/grit/grit/testdata/README.txt | 87 +
+ tools/grit/grit/testdata/about.html | 45 +
+ tools/grit/grit/testdata/android.xml | 24 +
+ tools/grit/grit/testdata/bad_browser.html | 16 +
+ tools/grit/grit/testdata/browser.html | 42 +
+ tools/grit/grit/testdata/buildinfo.grd | 46 +
+ tools/grit/grit/testdata/cache_prefix.html | 24 +
+ .../grit/grit/testdata/cache_prefix_file.html | 25 +
+ tools/grit/grit/testdata/chat_result.html | 24 +
+ .../chrome/app/generated_resources.grd | 199 ++
+ tools/grit/grit/testdata/chrome_html.html | 6 +
+ .../grit/testdata/default_100_percent/a.png | Bin 0 -> 159 bytes
+ .../grit/testdata/default_100_percent/b.png | 1 +
+ tools/grit/grit/testdata/del_footer.html | 8 +
+ tools/grit/grit/testdata/del_header.html | 60 +
+ tools/grit/grit/testdata/deleted.html | 21 +
+ tools/grit/grit/testdata/depfile.grd | 18 +
+ tools/grit/grit/testdata/details.html | 10 +
+ .../grit/testdata/duplicate-name-input.xml | 26 +
+ tools/grit/grit/testdata/email_result.html | 34 +
+ tools/grit/grit/testdata/email_thread.html | 10 +
+ tools/grit/grit/testdata/error.html | 8 +
+ tools/grit/grit/testdata/explicit_web.html | 11 +
+ tools/grit/grit/testdata/footer.html | 14 +
+ .../grit/testdata/generated_resources_fr.xtb | 3079 +++++++++++++++++
+ .../grit/testdata/generated_resources_iw.xtb | 4 +
+ .../grit/testdata/generated_resources_no.xtb | 4 +
+ tools/grit/grit/testdata/grit_part.grdp | 5 +
+ tools/grit/grit/testdata/header.html | 39 +
+ tools/grit/grit/testdata/homepage.html | 37 +
+ tools/grit/grit/testdata/hover.html | 177 +
+ tools/grit/grit/testdata/include_test.html | 31 +
+ tools/grit/grit/testdata/included_sample.html | 1 +
+ tools/grit/grit/testdata/indexing_speed.html | 58 +
+ tools/grit/grit/testdata/install_prefs.html | 92 +
+ tools/grit/grit/testdata/install_prefs2.html | 52 +
+ .../grit/testdata/klonk-alternate-skeleton.rc | Bin 0 -> 1088 bytes
+ tools/grit/grit/testdata/klonk.ico | Bin 0 -> 766 bytes
+ tools/grit/grit/testdata/klonk.rc | Bin 0 -> 9824 bytes
+ .../grit/grit/testdata/ko_oem_enable_bug.html | 1 +
+ .../grit/testdata/ko_oem_non_admin_bug.html | 1 +
+ tools/grit/grit/testdata/mini.html | 36 +
+ tools/grit/grit/testdata/oem_enable.html | 106 +
+ tools/grit/grit/testdata/oem_non_admin.html | 39 +
+ tools/grit/grit/testdata/onebox.html | 21 +
+ tools/grit/grit/testdata/oneclick.html | 34 +
+ tools/grit/grit/testdata/password.html | 37 +
+ tools/grit/grit/testdata/preferences.html | 234 ++
+ tools/grit/grit/testdata/preprocess_test.html | 7 +
+ tools/grit/grit/testdata/privacy.html | 35 +
+ tools/grit/grit/testdata/quit_apps.html | 49 +
+ tools/grit/grit/testdata/recrawl.html | 30 +
+ tools/grit/grit/testdata/resource_ids | 13 +
+ tools/grit/grit/testdata/script.html | 38 +
+ tools/grit/grit/testdata/searchbox.html | 22 +
+ tools/grit/grit/testdata/sidebar_h.html | 82 +
+ tools/grit/grit/testdata/sidebar_v.html | 267 ++
+ tools/grit/grit/testdata/simple-input.xml | 52 +
+ tools/grit/grit/testdata/simple.html | 3 +
+ tools/grit/grit/testdata/source.rc | 57 +
+ .../grit/testdata/special_100_percent/a.png | Bin 0 -> 159 bytes
+ tools/grit/grit/testdata/status.html | 44 +
+ .../grit/testdata/structure_variables.html | 4 +
+ tools/grit/grit/testdata/substitute.grd | 31 +
+ tools/grit/grit/testdata/substitute.xmb | 10 +
+ .../grit/grit/testdata/substitute_no_ids.grd | 31 +
+ tools/grit/grit/testdata/substitute_tmpl.grd | 31 +
+ tools/grit/grit/testdata/test_css.css | 1 +
+ tools/grit/grit/testdata/test_html.html | 1 +
+ tools/grit/grit/testdata/test_js.js | 1 +
+ tools/grit/grit/testdata/test_svg.svg | 1 +
+ tools/grit/grit/testdata/test_text.txt | 1 +
+ tools/grit/grit/testdata/time_related.html | 11 +
+ tools/grit/grit/testdata/toolbar_about.html | 138 +
+ .../grit/testdata/tools/grit/resource_ids | 176 +
+ tools/grit/grit/testdata/transl.rc | 56 +
+ tools/grit/grit/testdata/versions.html | 7 +
+ tools/grit/grit/testdata/whitelist.txt | 4 +
+ .../grit/testdata/whitelist_resources.grd | 54 +
+ .../grit/grit/testdata/whitelist_strings.grd | 23 +
+ tools/grit/grit/tool/__init__.py | 8 +
+ tools/grit/grit/tool/android2grd.py | 484 +++
+ tools/grit/grit/tool/android2grd_unittest.py | 181 +
+ tools/grit/grit/tool/build.py | 556 +++
+ tools/grit/grit/tool/build_unittest.py | 341 ++
+ tools/grit/grit/tool/buildinfo.py | 78 +
+ tools/grit/grit/tool/buildinfo_unittest.py | 90 +
+ tools/grit/grit/tool/count.py | 52 +
+ tools/grit/grit/tool/diff_structures.py | 119 +
+ .../grit/tool/diff_structures_unittest.py | 46 +
+ tools/grit/grit/tool/interface.py | 62 +
+ tools/grit/grit/tool/menu_from_parts.py | 79 +
+ tools/grit/grit/tool/newgrd.py | 85 +
+ tools/grit/grit/tool/newgrd_unittest.py | 51 +
+ tools/grit/grit/tool/postprocess_interface.py | 29 +
+ tools/grit/grit/tool/postprocess_unittest.py | 64 +
+ tools/grit/grit/tool/preprocess_interface.py | 25 +
+ tools/grit/grit/tool/preprocess_unittest.py | 50 +
+ tools/grit/grit/tool/rc2grd.py | 418 +++
+ tools/grit/grit/tool/rc2grd_unittest.py | 163 +
+ tools/grit/grit/tool/resize.py | 295 ++
+ tools/grit/grit/tool/test.py | 24 +
+ tools/grit/grit/tool/transl2tc.py | 251 ++
+ tools/grit/grit/tool/transl2tc_unittest.py | 133 +
+ tools/grit/grit/tool/unit.py | 43 +
+ .../grit/tool/update_resource_ids/__init__.py | 305 ++
+ .../grit/tool/update_resource_ids/assigner.py | 286 ++
+ .../update_resource_ids/assigner_unittest.py | 154 +
+ .../grit/tool/update_resource_ids/common.py | 101 +
+ .../grit/tool/update_resource_ids/parser.py | 231 ++
+ .../grit/tool/update_resource_ids/reader.py | 83 +
+ tools/grit/grit/tool/xmb.py | 295 ++
+ tools/grit/grit/tool/xmb_unittest.py | 132 +
+ tools/grit/grit/util.py | 691 ++++
+ tools/grit/grit/util_unittest.py | 118 +
+ tools/grit/grit/xtb_reader.py | 140 +
+ tools/grit/grit/xtb_reader_unittest.py | 110 +
+ tools/grit/grit_info.py | 173 +
+ tools/grit/grit_rule.gni | 485 +++
+ tools/grit/minify_with_uglify.py | 44 +
+ tools/grit/minimize_css.py | 105 +
+ tools/grit/minimize_css_unittest.py | 58 +
+ tools/grit/pak_util.py | 223 ++
+ tools/grit/repack.gni | 189 +
+ tools/grit/setup.py | 46 +
+ tools/grit/stamp_grit_sources.py | 57 +
+ tools/grit/third_party/six/LICENSE | 18 +
+ tools/grit/third_party/six/README | 16 +
+ tools/grit/third_party/six/README.chromium | 13 +
+ tools/grit/third_party/six/__init__.py | 868 +++++
+ 226 files changed, 33440 insertions(+)
+ create mode 100644 tools/grit/.gitignore
+ create mode 100644 tools/grit/BUILD.gn
+ create mode 100644 tools/grit/MANIFEST.in
+ create mode 100644 tools/grit/OWNERS
+ create mode 100644 tools/grit/PRESUBMIT.py
+ create mode 100644 tools/grit/README.md
+ create mode 100644 tools/grit/grit.py
+ create mode 100644 tools/grit/grit/__init__.py
+ create mode 100644 tools/grit/grit/clique.py
+ create mode 100644 tools/grit/grit/clique_unittest.py
+ create mode 100644 tools/grit/grit/constants.py
+ create mode 100644 tools/grit/grit/exception.py
+ create mode 100644 tools/grit/grit/extern/BogoFP.py
+ create mode 100644 tools/grit/grit/extern/FP.py
+ create mode 100644 tools/grit/grit/extern/__init__.py
+ create mode 100644 tools/grit/grit/extern/tclib.py
+ create mode 100644 tools/grit/grit/format/__init__.py
+ create mode 100644 tools/grit/grit/format/android_xml.py
+ create mode 100644 tools/grit/grit/format/android_xml_unittest.py
+ create mode 100644 tools/grit/grit/format/c_format.py
+ create mode 100644 tools/grit/grit/format/c_format_unittest.py
+ create mode 100644 tools/grit/grit/format/chrome_messages_json.py
+ create mode 100644 tools/grit/grit/format/chrome_messages_json_unittest.py
+ create mode 100644 tools/grit/grit/format/data_pack.py
+ create mode 100644 tools/grit/grit/format/data_pack_unittest.py
+ create mode 100644 tools/grit/grit/format/gen_predetermined_ids.py
+ create mode 100644 tools/grit/grit/format/gen_predetermined_ids_unittest.py
+ create mode 100644 tools/grit/grit/format/gzip_string.py
+ create mode 100644 tools/grit/grit/format/gzip_string_unittest.py
+ create mode 100644 tools/grit/grit/format/html_inline.py
+ create mode 100644 tools/grit/grit/format/html_inline_unittest.py
+ create mode 100644 tools/grit/grit/format/minifier.py
+ create mode 100644 tools/grit/grit/format/policy_templates_json.py
+ create mode 100644 tools/grit/grit/format/policy_templates_json_unittest.py
+ create mode 100644 tools/grit/grit/format/rc.py
+ create mode 100644 tools/grit/grit/format/rc_header.py
+ create mode 100644 tools/grit/grit/format/rc_header_unittest.py
+ create mode 100644 tools/grit/grit/format/rc_unittest.py
+ create mode 100644 tools/grit/grit/format/resource_map.py
+ create mode 100644 tools/grit/grit/format/resource_map_unittest.py
+ create mode 100644 tools/grit/grit/gather/__init__.py
+ create mode 100644 tools/grit/grit/gather/admin_template.py
+ create mode 100644 tools/grit/grit/gather/admin_template_unittest.py
+ create mode 100644 tools/grit/grit/gather/chrome_html.py
+ create mode 100644 tools/grit/grit/gather/chrome_html_unittest.py
+ create mode 100644 tools/grit/grit/gather/chrome_scaled_image.py
+ create mode 100644 tools/grit/grit/gather/chrome_scaled_image_unittest.py
+ create mode 100644 tools/grit/grit/gather/interface.py
+ create mode 100644 tools/grit/grit/gather/json_loader.py
+ create mode 100644 tools/grit/grit/gather/policy_json.py
+ create mode 100644 tools/grit/grit/gather/policy_json_unittest.py
+ create mode 100644 tools/grit/grit/gather/rc.py
+ create mode 100644 tools/grit/grit/gather/rc_unittest.py
+ create mode 100644 tools/grit/grit/gather/regexp.py
+ create mode 100644 tools/grit/grit/gather/skeleton_gatherer.py
+ create mode 100644 tools/grit/grit/gather/tr_html.py
+ create mode 100644 tools/grit/grit/gather/tr_html_unittest.py
+ create mode 100644 tools/grit/grit/gather/txt.py
+ create mode 100644 tools/grit/grit/gather/txt_unittest.py
+ create mode 100644 tools/grit/grit/grd_reader.py
+ create mode 100644 tools/grit/grit/grd_reader_unittest.py
+ create mode 100644 tools/grit/grit/grit-todo.xml
+ create mode 100644 tools/grit/grit/grit_runner.py
+ create mode 100644 tools/grit/grit/grit_runner_unittest.py
+ create mode 100644 tools/grit/grit/lazy_re.py
+ create mode 100644 tools/grit/grit/lazy_re_unittest.py
+ create mode 100644 tools/grit/grit/node/__init__.py
+ create mode 100644 tools/grit/grit/node/base.py
+ create mode 100644 tools/grit/grit/node/base_unittest.py
+ create mode 100644 tools/grit/grit/node/brotli_util.py
+ create mode 100644 tools/grit/grit/node/custom/__init__.py
+ create mode 100644 tools/grit/grit/node/custom/filename.py
+ create mode 100644 tools/grit/grit/node/custom/filename_unittest.py
+ create mode 100644 tools/grit/grit/node/empty.py
+ create mode 100644 tools/grit/grit/node/include.py
+ create mode 100644 tools/grit/grit/node/include_unittest.py
+ create mode 100644 tools/grit/grit/node/mapping.py
+ create mode 100644 tools/grit/grit/node/message.py
+ create mode 100644 tools/grit/grit/node/message_unittest.py
+ create mode 100644 tools/grit/grit/node/misc.py
+ create mode 100644 tools/grit/grit/node/misc_unittest.py
+ create mode 100644 tools/grit/grit/node/mock_brotli.py
+ create mode 100644 tools/grit/grit/node/node_io.py
+ create mode 100644 tools/grit/grit/node/node_io_unittest.py
+ create mode 100644 tools/grit/grit/node/structure.py
+ create mode 100644 tools/grit/grit/node/structure_unittest.py
+ create mode 100644 tools/grit/grit/node/variant.py
+ create mode 100644 tools/grit/grit/pseudo.py
+ create mode 100644 tools/grit/grit/pseudo_rtl.py
+ create mode 100644 tools/grit/grit/pseudo_unittest.py
+ create mode 100644 tools/grit/grit/shortcuts.py
+ create mode 100644 tools/grit/grit/shortcuts_unittest.py
+ create mode 100644 tools/grit/grit/tclib.py
+ create mode 100644 tools/grit/grit/tclib_unittest.py
+ create mode 100644 tools/grit/grit/test_suite_all.py
+ create mode 100644 tools/grit/grit/testdata/GoogleDesktop.adm
+ create mode 100644 tools/grit/grit/testdata/README.txt
+ create mode 100644 tools/grit/grit/testdata/about.html
+ create mode 100644 tools/grit/grit/testdata/android.xml
+ create mode 100644 tools/grit/grit/testdata/bad_browser.html
+ create mode 100644 tools/grit/grit/testdata/browser.html
+ create mode 100644 tools/grit/grit/testdata/buildinfo.grd
+ create mode 100644 tools/grit/grit/testdata/cache_prefix.html
+ create mode 100644 tools/grit/grit/testdata/cache_prefix_file.html
+ create mode 100644 tools/grit/grit/testdata/chat_result.html
+ create mode 100644 tools/grit/grit/testdata/chrome/app/generated_resources.grd
+ create mode 100644 tools/grit/grit/testdata/chrome_html.html
+ create mode 100644 tools/grit/grit/testdata/default_100_percent/a.png
+ create mode 100644 tools/grit/grit/testdata/default_100_percent/b.png
+ create mode 100644 tools/grit/grit/testdata/del_footer.html
+ create mode 100644 tools/grit/grit/testdata/del_header.html
+ create mode 100644 tools/grit/grit/testdata/deleted.html
+ create mode 100644 tools/grit/grit/testdata/depfile.grd
+ create mode 100644 tools/grit/grit/testdata/details.html
+ create mode 100644 tools/grit/grit/testdata/duplicate-name-input.xml
+ create mode 100644 tools/grit/grit/testdata/email_result.html
+ create mode 100644 tools/grit/grit/testdata/email_thread.html
+ create mode 100644 tools/grit/grit/testdata/error.html
+ create mode 100644 tools/grit/grit/testdata/explicit_web.html
+ create mode 100644 tools/grit/grit/testdata/footer.html
+ create mode 100644 tools/grit/grit/testdata/generated_resources_fr.xtb
+ create mode 100644 tools/grit/grit/testdata/generated_resources_iw.xtb
+ create mode 100644 tools/grit/grit/testdata/generated_resources_no.xtb
+ create mode 100644 tools/grit/grit/testdata/grit_part.grdp
+ create mode 100644 tools/grit/grit/testdata/header.html
+ create mode 100644 tools/grit/grit/testdata/homepage.html
+ create mode 100644 tools/grit/grit/testdata/hover.html
+ create mode 100644 tools/grit/grit/testdata/include_test.html
+ create mode 100644 tools/grit/grit/testdata/included_sample.html
+ create mode 100644 tools/grit/grit/testdata/indexing_speed.html
+ create mode 100644 tools/grit/grit/testdata/install_prefs.html
+ create mode 100644 tools/grit/grit/testdata/install_prefs2.html
+ create mode 100644 tools/grit/grit/testdata/klonk-alternate-skeleton.rc
+ create mode 100644 tools/grit/grit/testdata/klonk.ico
+ create mode 100644 tools/grit/grit/testdata/klonk.rc
+ create mode 100644 tools/grit/grit/testdata/ko_oem_enable_bug.html
+ create mode 100644 tools/grit/grit/testdata/ko_oem_non_admin_bug.html
+ create mode 100644 tools/grit/grit/testdata/mini.html
+ create mode 100644 tools/grit/grit/testdata/oem_enable.html
+ create mode 100644 tools/grit/grit/testdata/oem_non_admin.html
+ create mode 100644 tools/grit/grit/testdata/onebox.html
+ create mode 100644 tools/grit/grit/testdata/oneclick.html
+ create mode 100644 tools/grit/grit/testdata/password.html
+ create mode 100644 tools/grit/grit/testdata/preferences.html
+ create mode 100644 tools/grit/grit/testdata/preprocess_test.html
+ create mode 100644 tools/grit/grit/testdata/privacy.html
+ create mode 100644 tools/grit/grit/testdata/quit_apps.html
+ create mode 100644 tools/grit/grit/testdata/recrawl.html
+ create mode 100644 tools/grit/grit/testdata/resource_ids
+ create mode 100644 tools/grit/grit/testdata/script.html
+ create mode 100644 tools/grit/grit/testdata/searchbox.html
+ create mode 100644 tools/grit/grit/testdata/sidebar_h.html
+ create mode 100644 tools/grit/grit/testdata/sidebar_v.html
+ create mode 100644 tools/grit/grit/testdata/simple-input.xml
+ create mode 100644 tools/grit/grit/testdata/simple.html
+ create mode 100644 tools/grit/grit/testdata/source.rc
+ create mode 100644 tools/grit/grit/testdata/special_100_percent/a.png
+ create mode 100644 tools/grit/grit/testdata/status.html
+ create mode 100644 tools/grit/grit/testdata/structure_variables.html
+ create mode 100644 tools/grit/grit/testdata/substitute.grd
+ create mode 100644 tools/grit/grit/testdata/substitute.xmb
+ create mode 100644 tools/grit/grit/testdata/substitute_no_ids.grd
+ create mode 100644 tools/grit/grit/testdata/substitute_tmpl.grd
+ create mode 100644 tools/grit/grit/testdata/test_css.css
+ create mode 100644 tools/grit/grit/testdata/test_html.html
+ create mode 100644 tools/grit/grit/testdata/test_js.js
+ create mode 100644 tools/grit/grit/testdata/test_svg.svg
+ create mode 100644 tools/grit/grit/testdata/test_text.txt
+ create mode 100644 tools/grit/grit/testdata/time_related.html
+ create mode 100644 tools/grit/grit/testdata/toolbar_about.html
+ create mode 100644 tools/grit/grit/testdata/tools/grit/resource_ids
+ create mode 100644 tools/grit/grit/testdata/transl.rc
+ create mode 100644 tools/grit/grit/testdata/versions.html
+ create mode 100644 tools/grit/grit/testdata/whitelist.txt
+ create mode 100644 tools/grit/grit/testdata/whitelist_resources.grd
+ create mode 100644 tools/grit/grit/testdata/whitelist_strings.grd
+ create mode 100644 tools/grit/grit/tool/__init__.py
+ create mode 100644 tools/grit/grit/tool/android2grd.py
+ create mode 100644 tools/grit/grit/tool/android2grd_unittest.py
+ create mode 100644 tools/grit/grit/tool/build.py
+ create mode 100644 tools/grit/grit/tool/build_unittest.py
+ create mode 100644 tools/grit/grit/tool/buildinfo.py
+ create mode 100644 tools/grit/grit/tool/buildinfo_unittest.py
+ create mode 100644 tools/grit/grit/tool/count.py
+ create mode 100644 tools/grit/grit/tool/diff_structures.py
+ create mode 100644 tools/grit/grit/tool/diff_structures_unittest.py
+ create mode 100644 tools/grit/grit/tool/interface.py
+ create mode 100644 tools/grit/grit/tool/menu_from_parts.py
+ create mode 100644 tools/grit/grit/tool/newgrd.py
+ create mode 100644 tools/grit/grit/tool/newgrd_unittest.py
+ create mode 100644 tools/grit/grit/tool/postprocess_interface.py
+ create mode 100644 tools/grit/grit/tool/postprocess_unittest.py
+ create mode 100644 tools/grit/grit/tool/preprocess_interface.py
+ create mode 100644 tools/grit/grit/tool/preprocess_unittest.py
+ create mode 100644 tools/grit/grit/tool/rc2grd.py
+ create mode 100644 tools/grit/grit/tool/rc2grd_unittest.py
+ create mode 100644 tools/grit/grit/tool/resize.py
+ create mode 100644 tools/grit/grit/tool/test.py
+ create mode 100644 tools/grit/grit/tool/transl2tc.py
+ create mode 100644 tools/grit/grit/tool/transl2tc_unittest.py
+ create mode 100644 tools/grit/grit/tool/unit.py
+ create mode 100644 tools/grit/grit/tool/update_resource_ids/__init__.py
+ create mode 100644 tools/grit/grit/tool/update_resource_ids/assigner.py
+ create mode 100644 tools/grit/grit/tool/update_resource_ids/assigner_unittest.py
+ create mode 100644 tools/grit/grit/tool/update_resource_ids/common.py
+ create mode 100644 tools/grit/grit/tool/update_resource_ids/parser.py
+ create mode 100644 tools/grit/grit/tool/update_resource_ids/reader.py
+ create mode 100644 tools/grit/grit/tool/xmb.py
+ create mode 100644 tools/grit/grit/tool/xmb_unittest.py
+ create mode 100644 tools/grit/grit/util.py
+ create mode 100644 tools/grit/grit/util_unittest.py
+ create mode 100644 tools/grit/grit/xtb_reader.py
+ create mode 100644 tools/grit/grit/xtb_reader_unittest.py
+ create mode 100644 tools/grit/grit_info.py
+ create mode 100644 tools/grit/grit_rule.gni
+ create mode 100644 tools/grit/minify_with_uglify.py
+ create mode 100644 tools/grit/minimize_css.py
+ create mode 100644 tools/grit/minimize_css_unittest.py
+ create mode 100644 tools/grit/pak_util.py
+ create mode 100644 tools/grit/repack.gni
+ create mode 100644 tools/grit/setup.py
+ create mode 100644 tools/grit/stamp_grit_sources.py
+ create mode 100644 tools/grit/third_party/six/LICENSE
+ create mode 100644 tools/grit/third_party/six/README
+ create mode 100644 tools/grit/third_party/six/README.chromium
+ create mode 100644 tools/grit/third_party/six/__init__.py
-diff --git a/tools/clang/OWNERS b/tools/clang/OWNERS
+diff --git a/tools/grit/.gitignore b/tools/grit/.gitignore
new file mode 100644
-index 0000000000..d86ef9424a
+index 0000000000..0d20b6487c
--- /dev/null
-+++ b/tools/clang/OWNERS
-@@ -0,0 +1,2 @@
-+hans@chromium.org
-+thakis@chromium.org
-diff --git a/tools/clang/plugins/ChromeClassTester.cpp b/tools/clang/plugins/ChromeClassTester.cpp
++++ b/tools/grit/.gitignore
+@@ -0,0 +1 @@
++*.pyc
+diff --git a/tools/grit/BUILD.gn b/tools/grit/BUILD.gn
new file mode 100644
-index 0000000000..055866c5c5
+index 0000000000..1cd3c75b55
--- /dev/null
-+++ b/tools/clang/plugins/ChromeClassTester.cpp
-@@ -0,0 +1,294 @@
-+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+// Use of this source code is governed by a BSD-style license that can be
-+// found in the LICENSE file.
-+
-+// A general interface for filtering and only acting on classes in Chromium C++
-+// code.
++++ b/tools/grit/BUILD.gn
+@@ -0,0 +1,48 @@
++# Copyright 2014 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
+
-+#include "ChromeClassTester.h"
++# This target creates a stamp file that depends on all the sources in the grit
++# directory. By depending on this, a target can force itself to be rebuilt if
++# grit itself changes.
+
-+#include <sys/param.h>
++import("//build/config/sanitizers/sanitizers.gni")
+
-+#include "clang/AST/AST.h"
-+#include "clang/Basic/FileManager.h"
-+#include "clang/Basic/SourceManager.h"
++action("grit_sources") {
++ depfile = "$target_out_dir/grit_sources.d"
++ script = "stamp_grit_sources.py"
+
-+using namespace clang;
++ inputs = [ "grit.py" ]
+
-+namespace {
++ # Note that we can't call this "grit_sources.stamp" because that file is
++ # implicitly created by GN for script actions.
++ outputs = [ "$target_out_dir/grit_sources.script.stamp" ]
+
-+bool starts_with(const std::string& one, const std::string& two) {
-+ return one.compare(0, two.size(), two) == 0;
++ args = [
++ rebase_path("//tools/grit", root_build_dir),
++ rebase_path(outputs[0], root_build_dir),
++ rebase_path(depfile, root_build_dir),
++ ]
+}
+
-+std::string lstrip(const std::string& one, const std::string& two) {
-+ if (starts_with(one, two))
-+ return one.substr(two.size());
-+ return one;
++group("grit_python_unittests") {
++ testonly = true
++
++ data = [
++ "//testing/scripts/common.py",
++ "//testing/scripts/run_isolated_script_test.py",
++ "//testing/xvfb.py",
++ "//tools/grit/",
++ "//third_party/catapult/third_party/typ/",
++ ]
+}
+
-+bool ends_with(const std::string& one, const std::string& two) {
-+ if (two.size() > one.size())
-+ return false;
++# See https://crbug.com/983200
++if (is_mac && is_asan) {
++ create_bundle("brotli_mac_asan_workaround") {
++ bundle_root_dir = "$target_out_dir/$target_name"
++ bundle_executable_dir = bundle_root_dir
+
-+ return one.compare(one.size() - two.size(), two.size(), two) == 0;
++ public_deps = [ "//third_party/brotli:brotli($host_toolchain)" ]
++ }
+}
+diff --git a/tools/grit/MANIFEST.in b/tools/grit/MANIFEST.in
+new file mode 100644
+index 0000000000..1cbff42400
+--- /dev/null
++++ b/tools/grit/MANIFEST.in
+@@ -0,0 +1,3 @@
++exclude grit/test_suite_all.py
++exclude grit/tool/test.py
++global-exclude *_unittest.py
+diff --git a/tools/grit/OWNERS b/tools/grit/OWNERS
+new file mode 100644
+index 0000000000..6a8f447b82
+--- /dev/null
++++ b/tools/grit/OWNERS
+@@ -0,0 +1,8 @@
++agrieve@chromium.org
++flackr@chromium.org
++thakis@chromium.org
++thestig@chromium.org
+
-+} // namespace
++# Admin policy related grit tools.
++per-file *policy*=file://components/policy/tools/OWNERS
++per-file *admin_template*=file://components/policy/tools/OWNERS
+diff --git a/tools/grit/PRESUBMIT.py b/tools/grit/PRESUBMIT.py
+new file mode 100644
+index 0000000000..03b7188551
+--- /dev/null
++++ b/tools/grit/PRESUBMIT.py
+@@ -0,0 +1,22 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
+
-+ChromeClassTester::ChromeClassTester(CompilerInstance& instance)
-+ : instance_(instance),
-+ diagnostic_(instance.getDiagnostics()) {
-+ BuildBannedLists();
-+}
++"""grit unittests presubmit script.
+
-+ChromeClassTester::~ChromeClassTester() {}
++See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts for
++details on the presubmit API built into gcl.
++"""
+
-+void ChromeClassTester::HandleTagDeclDefinition(TagDecl* tag) {
-+ pending_class_decls_.push_back(tag);
-+}
+
-+bool ChromeClassTester::HandleTopLevelDecl(DeclGroupRef group_ref) {
-+ for (size_t i = 0; i < pending_class_decls_.size(); ++i)
-+ CheckTag(pending_class_decls_[i]);
-+ pending_class_decls_.clear();
++def RunUnittests(input_api, output_api):
++ return input_api.canned_checks.RunUnitTests(input_api, output_api,
++ [input_api.os_path.join('grit', 'test_suite_all.py')])
+
-+ return true; // true means continue parsing.
-+}
+
-+void ChromeClassTester::CheckTag(TagDecl* tag) {
-+ // We handle class types here where we have semantic information. We can only
-+ // check structs/classes/enums here, but we get a bunch of nice semantic
-+ // information instead of just parsing information.
++def CheckChangeOnUpload(input_api, output_api):
++ return RunUnittests(input_api, output_api)
+
-+ if (CXXRecordDecl* record = dyn_cast<CXXRecordDecl>(tag)) {
-+ // If this is a POD or a class template or a type dependent on a
-+ // templated class, assume there's no ctor/dtor/virtual method
-+ // optimization that we can do.
-+ if (record->isPOD() ||
-+ record->getDescribedClassTemplate() ||
-+ record->getTemplateSpecializationKind() ||
-+ record->isDependentType())
-+ return;
+
-+ if (InBannedNamespace(record))
-+ return;
++def CheckChangeOnCommit(input_api, output_api):
++ return RunUnittests(input_api, output_api)
+diff --git a/tools/grit/README.md b/tools/grit/README.md
+new file mode 100644
+index 0000000000..b5c3f4b51b
+--- /dev/null
++++ b/tools/grit/README.md
+@@ -0,0 +1,19 @@
++# GRIT (Google Resource and Internationalization Tool)
+
-+ SourceLocation record_location = record->getInnerLocStart();
-+ if (InBannedDirectory(record_location))
-+ return;
++This is a tool for projects to manage resources and simplify the localization
++workflow.
+
-+ // We sadly need to maintain a blacklist of types that violate these
-+ // rules, but do so for good reason or due to limitations of this
-+ // checker (i.e., we don't handle extern templates very well).
-+ std::string base_name = record->getNameAsString();
-+ if (IsIgnoredType(base_name))
-+ return;
++See the user guide for more details on using this project:
++https://dev.chromium.org/developers/tools-we-use-in-chromium/grit/grit-users-guide
+
-+ // We ignore all classes that end with "Matcher" because they're probably
-+ // GMock artifacts.
-+ if (ends_with(base_name, "Matcher"))
-+ return;
++## History
+
-+ CheckChromeClass(record_location, record);
-+ }
-+}
++This code previously used to live at
++https://code.google.com/p/grit-i18n/source/checkout which still contains the
++project's history. https://chromium.googlesource.com/external/grit-i18n/ is
++a git mirror of the SVN repository that's identical except for the last two
++commits. The project is now developed in the Chromium project directly.
+
-+void ChromeClassTester::emitWarning(SourceLocation loc,
-+ const char* raw_error) {
-+ FullSourceLoc full(loc, instance().getSourceManager());
-+ std::string err;
-+ err = "[chromium-style] ";
-+ err += raw_error;
-+ DiagnosticsEngine::Level level =
-+ diagnostic().getWarningsAsErrors() ?
-+ DiagnosticsEngine::Error :
-+ DiagnosticsEngine::Warning;
-+ unsigned id = diagnostic().getCustomDiagID(level, err);
-+ DiagnosticBuilder builder = diagnostic().Report(full, id);
-+}
-+
-+bool ChromeClassTester::InBannedNamespace(const Decl* record) {
-+ std::string n = GetNamespace(record);
-+ if (!n.empty()) {
-+ return std::find(banned_namespaces_.begin(), banned_namespaces_.end(), n)
-+ != banned_namespaces_.end();
-+ }
++There is a read-only mirror of just this directory at
++https://chromium.googlesource.com/chromium/src/tools/grit/ if you don't want to
++check out all of Chromium.
+diff --git a/tools/grit/grit.py b/tools/grit/grit.py
+new file mode 100644
+index 0000000000..abd1ab6449
+--- /dev/null
++++ b/tools/grit/grit.py
+@@ -0,0 +1,31 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
+
-+ return false;
-+}
++'''Bootstrapping for GRIT.
++'''
++
++from __future__ import print_function
++
++import os
++import sys
++
++import grit.grit_runner
++
++sys.path.append(
++ os.path.join(
++ os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
++ 'diagnosis'))
++try:
++ import crbug_1001171
++except ImportError:
++ crbug_1001171 = None
++
++
++if __name__ == '__main__':
++ if crbug_1001171:
++ with crbug_1001171.DumpStateOnLookupError():
++ sys.exit(grit.grit_runner.Main(sys.argv[1:]))
++ else:
++ sys.exit(grit.grit_runner.Main(sys.argv[1:]))
+diff --git a/tools/grit/grit/__init__.py b/tools/grit/grit/__init__.py
+new file mode 100644
+index 0000000000..91ac9ee896
+--- /dev/null
++++ b/tools/grit/grit/__init__.py
+@@ -0,0 +1,19 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Package 'grit'
++'''
++
++from __future__ import print_function
++
++import os
++import sys
++
++
++_CUR_DIR = os.path.abspath(os.path.dirname(__file__))
++_GRIT_DIR = os.path.dirname(_CUR_DIR)
++_THIRD_PARTY_DIR = os.path.join(_GRIT_DIR, 'third_party')
++
++if _THIRD_PARTY_DIR not in sys.path:
++ sys.path.insert(0, _THIRD_PARTY_DIR)
+diff --git a/tools/grit/grit/clique.py b/tools/grit/grit/clique.py
+new file mode 100644
+index 0000000000..e7be3ec164
+--- /dev/null
++++ b/tools/grit/grit/clique.py
+@@ -0,0 +1,491 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Collections of messages and their translations, called cliques. Also
++collections of cliques (uber-cliques).
++'''
++
++from __future__ import print_function
++
++import re
++
++import six
++
++from grit import constants
++from grit import exception
++from grit import lazy_re
++from grit import pseudo
++from grit import pseudo_rtl
++from grit import tclib
++
++
++class UberClique(object):
++ '''A factory (NOT a singleton factory) for making cliques. It has several
++ methods for working with the cliques created using the factory.
++ '''
++
++ def __init__(self):
++ # A map from message ID to list of cliques whose source messages have
++ # that ID. This will contain all cliques created using this factory.
++ # Different messages can have the same ID because they have the
++ # same translateable portion and placeholder names, but occur in different
++ # places in the resource tree.
++ #
++ # Each list of cliques is kept sorted by description, to achieve
++ # stable results from the BestClique method, see below.
++ self.cliques_ = {}
++
++ # A map of clique IDs to list of languages to indicate translations where we
++ # fell back to English.
++ self.fallback_translations_ = {}
++
++ # A map of clique IDs to list of languages to indicate missing translations.
++ self.missing_translations_ = {}
++
++ def _AddMissingTranslation(self, lang, clique, is_error):
++ tl = self.fallback_translations_
++ if is_error:
++ tl = self.missing_translations_
++ id = clique.GetId()
++ if id not in tl:
++ tl[id] = {}
++ if lang not in tl[id]:
++ tl[id][lang] = 1
++
++ def HasMissingTranslations(self):
++ return len(self.missing_translations_) > 0
++
++ def MissingTranslationsReport(self):
++ '''Returns a string suitable for printing to report missing
++ and fallback translations to the user.
++ '''
++ def ReportTranslation(clique, langs):
++ text = clique.GetMessage().GetPresentableContent()
++ # The text 'error' (usually 'Error:' but we are conservative)
++ # can trigger some build environments (Visual Studio, we're
++ # looking at you) to consider invocation of grit to have failed,
++ # so we make sure never to output that word.
++ extract = re.sub(r'(?i)error', 'REDACTED', text[0:40])[0:40]
++ ellipsis = ''
++ if len(text) > 40:
++ ellipsis = '...'
++ langs_extract = langs[0:6]
++ describe_langs = ','.join(langs_extract)
++ if len(langs) > 6:
++ describe_langs += " and %d more" % (len(langs) - 6)
++ return " %s \"%s%s\" %s" % (clique.GetId(), extract, ellipsis,
++ describe_langs)
++ lines = []
++ if len(self.fallback_translations_):
++ lines.append(
++ "WARNING: Fell back to English for the following translations:")
++ for (id, langs) in self.fallback_translations_.items():
++ lines.append(
++ ReportTranslation(self.cliques_[id][0], list(langs.keys())))
++ if len(self.missing_translations_):
++ lines.append("ERROR: The following translations are MISSING:")
++ for (id, langs) in self.missing_translations_.items():
++ lines.append(
++ ReportTranslation(self.cliques_[id][0], list(langs.keys())))
++ return '\n'.join(lines)
++
++ def MakeClique(self, message, translateable=True):
++ '''Create a new clique initialized with a message.
++
++ Args:
++ message: tclib.Message()
++ translateable: True | False
++ '''
++ clique = MessageClique(self, message, translateable)
++
++ # Enable others to find this clique by its message ID
++ if message.GetId() in self.cliques_:
++ presentable_text = clique.GetMessage().GetPresentableContent()
++ if not message.HasAssignedId():
++ for c in self.cliques_[message.GetId()]:
++ assert c.GetMessage().GetPresentableContent() == presentable_text
++ self.cliques_[message.GetId()].append(clique)
++ # We need to keep each list of cliques sorted by description, to
++ # achieve stable results from the BestClique method, see below.
++ self.cliques_[message.GetId()].sort(
++ key=lambda c:c.GetMessage().GetDescription())
++ else:
++ self.cliques_[message.GetId()] = [clique]
++
++ return clique
++
++ def FindCliqueAndAddTranslation(self, translation, language):
++ '''Adds the specified translation to the clique with the source message
++ it is a translation of.
++
++ Args:
++ translation: tclib.Translation()
++ language: 'en' | 'fr' ...
++
++ Return:
++ True if the source message was found, otherwise false.
++ '''
++ if translation.GetId() in self.cliques_:
++ for clique in self.cliques_[translation.GetId()]:
++ clique.AddTranslation(translation, language)
++ return True
++ else:
++ return False
++
++ def BestClique(self, id):
++ '''Returns the "best" clique from a list of cliques. All the cliques
++ must have the same ID. The "best" clique is chosen in the following
++ order of preference:
++ - The first clique that has a non-ID-based description.
++ - If no such clique found, the first clique with an ID-based description.
++ - Otherwise the first clique.
++
++ This method is stable in terms of always returning a clique with
++ an identical description (on different runs of GRIT on the same
++ data) because self.cliques_ is sorted by description.
++ '''
++ clique_list = self.cliques_[id]
++ clique_with_id = None
++ clique_default = None
++ for clique in clique_list:
++ if not clique_default:
++ clique_default = clique
++
++ description = clique.GetMessage().GetDescription()
++ if description and len(description) > 0:
++ if not description.startswith('ID:'):
++ # this is the preferred case so we exit right away
++ return clique
++ elif not clique_with_id:
++ clique_with_id = clique
++ if clique_with_id:
++ return clique_with_id
++ else:
++ return clique_default
++
++ def BestCliquePerId(self):
++ '''Iterates over the list of all cliques and returns the best clique for
++ each ID. This will be the first clique with a source message that has a
++ non-empty description, or an arbitrary clique if none of them has a
++ description.
++ '''
++ for id in self.cliques_:
++ yield self.BestClique(id)
++
++ def BestCliqueByOriginalText(self, text, meaning):
++ '''Finds the "best" (as in BestClique()) clique that has original text
++ 'text' and meaning 'meaning'. Returns None if there is no such clique.
++ '''
++ # If needed, this can be optimized by maintaining a map of
++ # fingerprints of original text+meaning to cliques.
++ for c in self.BestCliquePerId():
++ msg = c.GetMessage()
++ if msg.GetRealContent() == text and msg.GetMeaning() == meaning:
++ return msg
++ return None
++
++ def AllMessageIds(self):
++ '''Returns a list of all defined message IDs.
++ '''
++ return list(self.cliques_.keys())
++
++ def AllCliques(self):
++ '''Iterates over all cliques. Note that this can return multiple cliques
++ with the same ID.
++ '''
++ for cliques in self.cliques_.values():
++ for c in cliques:
++ yield c
++
++ def GenerateXtbParserCallback(self, lang, debug=False):
++ '''Creates a callback function as required by grit.xtb_reader.Parse().
++ This callback will create Translation objects for each message from
++ the XTB that exists in this uberclique, and add them as translations for
++ the relevant cliques. The callback will add translations to the language
++ specified by 'lang'
++
++ Args:
++ lang: 'fr'
++ debug: True | False
++ '''
++ def Callback(id, structure):
++ if id not in self.cliques_:
++ if debug:
++ print("Ignoring translation #%s" % id)
++ return
++
++ if debug:
++ print("Adding translation #%s" % id)
++
++ # We fetch placeholder information from the original message (the XTB file
++ # only contains placeholder names).
++ original_msg = self.BestClique(id).GetMessage()
++
++ translation = tclib.Translation(id=id)
++ for is_ph,text in structure:
++ if not is_ph:
++ translation.AppendText(text)
++ else:
++ found_placeholder = False
++ for ph in original_msg.GetPlaceholders():
++ if ph.GetPresentation() == text:
++ translation.AppendPlaceholder(tclib.Placeholder(
++ ph.GetPresentation(), ph.GetOriginal(), ph.GetExample()))
++ found_placeholder = True
++ break
++ if not found_placeholder:
++ raise exception.MismatchingPlaceholders(
++ 'Translation for message ID %s had <ph name="%s"/>, no match\n'
++ 'in original message' % (id, text))
++ self.FindCliqueAndAddTranslation(translation, lang)
++ return Callback
++
++
++class CustomType(object):
++ '''A base class you should implement if you wish to specify a custom type
++ for a message clique (i.e. custom validation and optional modification of
++ translations).'''
++
++ def Validate(self, message):
++ '''Returns true if the message (a tclib.Message object) is valid,
++ otherwise false.
++ '''
++ raise NotImplementedError()
++
++ def ValidateAndModify(self, lang, translation):
++ '''Returns true if the translation (a tclib.Translation object) is valid,
++ otherwise false. The language is also passed in. This method may modify
++ the translation that is passed in, if it so wishes.
++ '''
++ raise NotImplementedError()
++
++ def ModifyTextPart(self, lang, text):
++ '''If you call ModifyEachTextPart, it will turn around and call this method
++ for each text part of the translation. You should return the modified
++ version of the text, or just the original text to not change anything.
++ '''
++ raise NotImplementedError()
++
++ def ModifyEachTextPart(self, lang, translation):
++ '''Call this to easily modify one or more of the textual parts of a
++ translation. It will call ModifyTextPart for each part of the
++ translation.
++ '''
++ contents = translation.GetContent()
++ for ix in range(len(contents)):
++ if (isinstance(contents[ix], six.string_types)):
++ contents[ix] = self.ModifyTextPart(lang, contents[ix])
++
++
++class OneOffCustomType(CustomType):
++ '''A very simple custom type that performs the validation expressed by
++ the input expression on all languages including the source language.
++ The expression can access the variables 'lang', 'msg' and 'text()' where
++ 'lang' is the language of 'msg', 'msg' is the message or translation being
++ validated and 'text()' returns the real contents of 'msg' (for shorthand).
++ '''
++ def __init__(self, expression):
++ self.expr = expression
++ def Validate(self, message):
++ return self.ValidateAndModify(MessageClique.source_language, message)
++ def ValidateAndModify(self, lang, msg):
++ def text():
++ return msg.GetRealContent()
++ return eval(self.expr, {},
++ {'lang' : lang,
++ 'text' : text,
++ 'msg' : msg,
++ })
++
++
++class MessageClique(object):
++ '''A message along with all of its translations. Also code to bring
++ translations together with their original message.'''
++
++ # change this to the language code of Messages you add to cliques_.
++ # TODO(joi) Actually change this based on the <grit> node's source language
++ source_language = 'en'
++
++ # A constant translation we use when asked for a translation into the
++ # special language constants.CONSTANT_LANGUAGE.
++ CONSTANT_TRANSLATION = tclib.Translation(text='TTTTTT')
++
++ # A pattern to match messages that are empty or whitespace only.
++ WHITESPACE_MESSAGE = lazy_re.compile(r'^\s*$')
++
++ def __init__(self, uber_clique, message, translateable=True,
++ custom_type=None):
++ '''Create a new clique initialized with just a message.
++
++ Note that messages with a body comprised only of whitespace will implicitly
++ be marked non-translatable.
++
++ Args:
++ uber_clique: Our uber-clique (collection of cliques)
++ message: tclib.Message()
++ translateable: True | False
++ custom_type: instance of clique.CustomType interface
++ '''
++ # Our parent
++ self.uber_clique = uber_clique
++ # If not translateable, we only store the original message.
++ self.translateable = translateable
++
++ # We implicitly mark messages that have a whitespace-only body as
++ # non-translateable.
++ if MessageClique.WHITESPACE_MESSAGE.match(message.GetRealContent()):
++ self.translateable = False
++
++ # A mapping of language identifiers to tclib.BaseMessage and its
++ # subclasses (i.e. tclib.Message and tclib.Translation).
++ self.clique = { MessageClique.source_language : message }
++ # A list of the "shortcut groups" this clique is
++ # part of. Within any given shortcut group, no shortcut key (e.g. &J)
++ # must appear more than once in each language for all cliques that
++ # belong to the group.
++ self.shortcut_groups = []
++ # An instance of the CustomType interface, or None. If this is set, it will
++ # be used to validate the original message and translations thereof, and
++ # will also get a chance to modify translations of the message.
++ self.SetCustomType(custom_type)
++
++ def GetMessage(self):
++ '''Retrieves the tclib.Message that is the source for this clique.'''
++ return self.clique[MessageClique.source_language]
++
++ def GetId(self):
++ '''Retrieves the message ID of the messages in this clique.'''
++ return self.GetMessage().GetId()
++
++ def IsTranslateable(self):
++ return self.translateable
++
++ def AddToShortcutGroup(self, group):
++ self.shortcut_groups.append(group)
++
++ def SetCustomType(self, custom_type):
++ '''Makes this clique use custom_type for validating messages and
++ translations, and optionally modifying translations.
++ '''
++ self.custom_type = custom_type
++ if custom_type and not custom_type.Validate(self.GetMessage()):
++ raise exception.InvalidMessage(self.GetMessage().GetRealContent())
++
++ def MessageForLanguage(self, lang, pseudo_if_no_match=True,
++ fallback_to_english=False):
++ '''Returns the message/translation for the specified language, providing
++ a pseudotranslation if there is no available translation and a pseudo-
++ translation is requested.
++
++ The translation of any message whatsoever in the special language
++ 'x_constant' is the message "TTTTTT".
++
++ Args:
++ lang: 'en'
++ pseudo_if_no_match: True
++ fallback_to_english: False
++
++ Return:
++ tclib.BaseMessage
++ '''
++ if not self.translateable:
++ return self.GetMessage()
++
++ if lang == constants.CONSTANT_LANGUAGE:
++ return self.CONSTANT_TRANSLATION
++
++ for msglang in self.clique:
++ if lang == msglang:
++ return self.clique[msglang]
++
++ if lang == constants.FAKE_BIDI:
++ return pseudo_rtl.PseudoRTLMessage(self.GetMessage())
++
++ if fallback_to_english:
++ self.uber_clique._AddMissingTranslation(lang, self, is_error=False)
++ return self.GetMessage()
++
++ # If we're not supposed to generate pseudotranslations, we add an error
++ # report to a list of errors, then fail at a higher level, so that we
++ # get a list of all messages that are missing translations.
++ if not pseudo_if_no_match:
++ self.uber_clique._AddMissingTranslation(lang, self, is_error=True)
++
++ return pseudo.PseudoMessage(self.GetMessage())
++
++ def AllMessagesThatMatch(self, lang_re, include_pseudo = True):
++ '''Returns a map of all messages that match 'lang', including the pseudo
++ translation if requested.
++
++ Args:
++ lang_re: re.compile(r'fr|en')
++ include_pseudo: True
++
++ Return:
++ { 'en' : tclib.Message,
++ 'fr' : tclib.Translation,
++ pseudo.PSEUDO_LANG : tclib.Translation }
++ '''
++ if not self.translateable:
++ return [self.GetMessage()]
++
++ matches = {}
++ for msglang in self.clique:
++ if lang_re.match(msglang):
++ matches[msglang] = self.clique[msglang]
++
++ if include_pseudo:
++ matches[pseudo.PSEUDO_LANG] = pseudo.PseudoMessage(self.GetMessage())
++
++ return matches
++
++ def AddTranslation(self, translation, language):
++ '''Add a translation to this clique. The translation must have the same
++ ID as the message that is the source for this clique.
++
++ If this clique is not translateable, the function just returns.
++
++ Args:
++ translation: tclib.Translation()
++ language: 'en'
++
++ Throws:
++ grit.exception.InvalidTranslation if the translation you're trying to add
++ doesn't have the same message ID as the source message of this clique.
++ '''
++ if not self.translateable:
++ return
++ if translation.GetId() != self.GetId():
++ raise exception.InvalidTranslation(
++ 'Msg ID %s, transl ID %s' % (self.GetId(), translation.GetId()))
++
++ assert not language in self.clique
++
++ # Because two messages can differ in the original content of their
++ # placeholders yet share the same ID (because they are otherwise the
++ # same), the translation we are getting may have different original
++ # content for placeholders than our message, yet it is still the right
++ # translation for our message (because it is for the same ID). We must
++ # therefore fetch the original content of placeholders from our original
++ # English message.
++ #
++ # See grit.clique_unittest.MessageCliqueUnittest.testSemiIdenticalCliques
++ # for a concrete explanation of why this is necessary.
++
++ original = self.MessageForLanguage(self.source_language, False)
++ if len(original.GetPlaceholders()) != len(translation.GetPlaceholders()):
++ print("ERROR: '%s' translation of message id %s does not match" %
++ (language, translation.GetId()))
++ assert False
++
++ transl_msg = tclib.Translation(id=self.GetId(),
++ text=translation.GetPresentableContent(),
++ placeholders=original.GetPlaceholders())
++
++ if (self.custom_type and
++ not self.custom_type.ValidateAndModify(language, transl_msg)):
++ print("WARNING: %s translation failed validation: %s" %
++ (language, transl_msg.GetId()))
++
++ self.clique[language] = transl_msg
+diff --git a/tools/grit/grit/clique_unittest.py b/tools/grit/grit/clique_unittest.py
+new file mode 100644
+index 0000000000..7d2d7318ba
+--- /dev/null
++++ b/tools/grit/grit/clique_unittest.py
+@@ -0,0 +1,265 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit tests for grit.clique'''
++
++from __future__ import print_function
++
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
++
++import re
++import unittest
++
++from six import StringIO
++
++from grit import clique
++from grit import exception
++from grit import pseudo
++from grit import tclib
++from grit import grd_reader
++from grit import util
++
++class MessageCliqueUnittest(unittest.TestCase):
++ def testClique(self):
++ factory = clique.UberClique()
++ msg = tclib.Message(text='Hello USERNAME, how are you?',
++ placeholders=[
++ tclib.Placeholder('USERNAME', '%s', 'Joi')])
++ c = factory.MakeClique(msg)
++
++ self.failUnless(c.GetMessage() == msg)
++ self.failUnless(c.GetId() == msg.GetId())
++
++ msg_fr = tclib.Translation(text='Bonjour USERNAME, comment ca va?',
++ id=msg.GetId(), placeholders=[
++ tclib.Placeholder('USERNAME', '%s', 'Joi')])
++ msg_de = tclib.Translation(text='Guten tag USERNAME, wie geht es dir?',
++ id=msg.GetId(), placeholders=[
++ tclib.Placeholder('USERNAME', '%s', 'Joi')])
++
++ c.AddTranslation(msg_fr, 'fr')
++ factory.FindCliqueAndAddTranslation(msg_de, 'de')
++
++ # sort() sorts lists in-place and does not return them
++ for lang in ('en', 'fr', 'de'):
++ self.failUnless(lang in c.clique)
++
++ self.failUnless(c.MessageForLanguage('fr').GetRealContent() ==
++ msg_fr.GetRealContent())
++
++ try:
++ c.MessageForLanguage('zh-CN', False)
++ self.fail('Should have gotten exception')
++ except:
++ pass
++
++ self.failUnless(c.MessageForLanguage('zh-CN', True) != None)
++
++ rex = re.compile('fr|de|bingo')
++ self.failUnless(len(c.AllMessagesThatMatch(rex, False)) == 2)
++ self.failUnless(
++ c.AllMessagesThatMatch(rex, True)[pseudo.PSEUDO_LANG] is not None)
++
++ def testBestClique(self):
++ factory = clique.UberClique()
++ factory.MakeClique(tclib.Message(text='Alfur', description='alfaholl'))
++ factory.MakeClique(tclib.Message(text='Alfur', description=''))
++ factory.MakeClique(tclib.Message(text='Vaettur', description=''))
++ factory.MakeClique(tclib.Message(text='Vaettur', description=''))
++ factory.MakeClique(tclib.Message(text='Troll', description=''))
++ factory.MakeClique(tclib.Message(text='Gryla', description='ID: IDS_GRYLA'))
++ factory.MakeClique(tclib.Message(text='Gryla', description='vondakerling'))
++ factory.MakeClique(tclib.Message(text='Leppaludi', description='ID: IDS_LL'))
++ factory.MakeClique(tclib.Message(text='Leppaludi', description=''))
++
++ count_best_cliques = 0
++ for c in factory.BestCliquePerId():
++ count_best_cliques += 1
++ msg = c.GetMessage()
++ text = msg.GetRealContent()
++ description = msg.GetDescription()
++ if text == 'Alfur':
++ self.failUnless(description == 'alfaholl')
++ elif text == 'Gryla':
++ self.failUnless(description == 'vondakerling')
++ elif text == 'Leppaludi':
++ self.failUnless(description == 'ID: IDS_LL')
++ self.failUnless(count_best_cliques == 5)
++
++ def testAllInUberClique(self):
++ resources = grd_reader.Parse(
++ StringIO(u'''<?xml version="1.0" encoding="UTF-8"?>
++<grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
++ <release seq="3">
++ <messages>
++ <message name="IDS_GREETING" desc="Printed to greet the currently logged in user">
++ Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, how are you doing today?
++ </message>
++ </messages>
++ <structures>
++ <structure type="dialog" name="IDD_ABOUTBOX" encoding="utf-16" file="grit/testdata/klonk.rc" />
++ <structure type="tr_html" name="ID_HTML" file="grit/testdata/simple.html" />
++ </structures>
++ </release>
++</grit>'''), util.PathFromRoot('.'))
++ resources.SetOutputLanguage('en')
++ resources.RunGatherers()
++ content_list = []
++ for clique_list in resources.UberClique().cliques_.values():
++ for clique in clique_list:
++ content_list.append(clique.GetMessage().GetRealContent())
++ self.failUnless('Hello %s, how are you doing today?' in content_list)
++ self.failUnless('Jack "Black" Daniels' in content_list)
++ self.failUnless('Hello!' in content_list)
++
++ def testCorrectExceptionIfWrongEncodingOnResourceFile(self):
++ '''This doesn't really belong in this unittest file, but what the heck.'''
++ resources = grd_reader.Parse(
++ StringIO(u'''<?xml version="1.0" encoding="UTF-8"?>
++<grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
++ <release seq="3">
++ <structures>
++ <structure type="dialog" name="IDD_ABOUTBOX" file="grit/testdata/klonk.rc" />
++ </structures>
++ </release>
++</grit>'''), util.PathFromRoot('.'))
++ self.assertRaises(exception.SectionNotFound, resources.RunGatherers)
++
++ def testSemiIdenticalCliques(self):
++ messages = [
++ tclib.Message(text='Hello USERNAME',
++ placeholders=[tclib.Placeholder('USERNAME', '$1', 'Joi')]),
++ tclib.Message(text='Hello USERNAME',
++ placeholders=[tclib.Placeholder('USERNAME', '%s', 'Joi')]),
++ ]
++ self.failUnless(messages[0].GetId() == messages[1].GetId())
++
++ # Both of the above would share a translation.
++ translation = tclib.Translation(id=messages[0].GetId(),
++ text='Bonjour USERNAME',
++ placeholders=[tclib.Placeholder(
++ 'USERNAME', '$1', 'Joi')])
++
++ factory = clique.UberClique()
++ cliques = [factory.MakeClique(msg) for msg in messages]
++
++ for clq in cliques:
++ clq.AddTranslation(translation, 'fr')
++
++ self.failUnless(cliques[0].MessageForLanguage('fr').GetRealContent() ==
++ 'Bonjour $1')
++ self.failUnless(cliques[1].MessageForLanguage('fr').GetRealContent() ==
++ 'Bonjour %s')
++
++ def testMissingTranslations(self):
++ messages = [ tclib.Message(text='Hello'), tclib.Message(text='Goodbye') ]
++ factory = clique.UberClique()
++ cliques = [factory.MakeClique(msg) for msg in messages]
++
++ cliques[1].MessageForLanguage('fr', False, True)
++
++ self.failUnless(not factory.HasMissingTranslations())
++
++ cliques[0].MessageForLanguage('de', False, False)
++
++ self.failUnless(factory.HasMissingTranslations())
++
++ report = factory.MissingTranslationsReport()
++ self.failUnless(report.count('WARNING') == 1)
++ self.failUnless(report.count('8053599568341804890 "Goodbye" fr') == 1)
++ self.failUnless(report.count('ERROR') == 1)
++ self.failUnless(report.count('800120468867715734 "Hello" de') == 1)
++
++ def testCustomTypes(self):
++ factory = clique.UberClique()
++ message = tclib.Message(text='Bingo bongo')
++ c = factory.MakeClique(message)
++ try:
++ c.SetCustomType(DummyCustomType())
++ self.fail()
++ except:
++ pass # expected case - 'Bingo bongo' does not start with 'jjj'
++
++ message = tclib.Message(text='jjjBingo bongo')
++ c = factory.MakeClique(message)
++ c.SetCustomType(util.NewClassInstance(
++ 'grit.clique_unittest.DummyCustomType', clique.CustomType))
++ translation = tclib.Translation(id=message.GetId(), text='Bilingo bolongo')
++ c.AddTranslation(translation, 'fr')
++ self.failUnless(c.MessageForLanguage('fr').GetRealContent().startswith('jjj'))
++
++ def testWhitespaceMessagesAreNontranslateable(self):
++ factory = clique.UberClique()
++
++ message = tclib.Message(text=' \t')
++ c = factory.MakeClique(message, translateable=True)
++ self.failIf(c.IsTranslateable())
++
++ message = tclib.Message(text='\n \n ')
++ c = factory.MakeClique(message, translateable=True)
++ self.failIf(c.IsTranslateable())
++
++ message = tclib.Message(text='\n hello')
++ c = factory.MakeClique(message, translateable=True)
++ self.failUnless(c.IsTranslateable())
++
++ def testEachCliqueKeptSorted(self):
++ factory = clique.UberClique()
++ msg_a = tclib.Message(text='hello', description='a')
++ msg_b = tclib.Message(text='hello', description='b')
++ msg_c = tclib.Message(text='hello', description='c')
++ # Insert out of order
++ clique_b = factory.MakeClique(msg_b, translateable=True)
++ clique_a = factory.MakeClique(msg_a, translateable=True)
++ clique_c = factory.MakeClique(msg_c, translateable=True)
++ clique_list = factory.cliques_[clique_a.GetId()]
++ self.failUnless(len(clique_list) == 3)
++ self.failUnless(clique_list[0] == clique_a)
++ self.failUnless(clique_list[1] == clique_b)
++ self.failUnless(clique_list[2] == clique_c)
++
++ def testBestCliqueSortIsStable(self):
++ factory = clique.UberClique()
++ text = 'hello'
++ msg_no_description = tclib.Message(text=text)
++ msg_id_description_a = tclib.Message(text=text, description='ID: a')
++ msg_id_description_b = tclib.Message(text=text, description='ID: b')
++ msg_description_x = tclib.Message(text=text, description='x')
++ msg_description_y = tclib.Message(text=text, description='y')
++ clique_id = msg_no_description.GetId()
++
++ # Insert in an order that tests all outcomes.
++ clique_no_description = factory.MakeClique(msg_no_description,
++ translateable=True)
++ self.failUnless(factory.BestClique(clique_id) == clique_no_description)
++ clique_id_description_b = factory.MakeClique(msg_id_description_b,
++ translateable=True)
++ self.failUnless(factory.BestClique(clique_id) == clique_id_description_b)
++ clique_id_description_a = factory.MakeClique(msg_id_description_a,
++ translateable=True)
++ self.failUnless(factory.BestClique(clique_id) == clique_id_description_a)
++ clique_description_y = factory.MakeClique(msg_description_y,
++ translateable=True)
++ self.failUnless(factory.BestClique(clique_id) == clique_description_y)
++ clique_description_x = factory.MakeClique(msg_description_x,
++ translateable=True)
++ self.failUnless(factory.BestClique(clique_id) == clique_description_x)
++
++
++class DummyCustomType(clique.CustomType):
++ def Validate(self, message):
++ return message.GetRealContent().startswith('jjj')
++ def ValidateAndModify(self, lang, translation):
++ is_ok = self.Validate(translation)
++ self.ModifyEachTextPart(lang, translation)
++ def ModifyTextPart(self, lang, text):
++ return 'jjj%s' % text
++
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/constants.py b/tools/grit/grit/constants.py
+new file mode 100644
+index 0000000000..8229c94b09
+--- /dev/null
++++ b/tools/grit/grit/constants.py
+@@ -0,0 +1,23 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Constant definitions for GRIT.
++'''
++
++from __future__ import print_function
++
++# This is the Icelandic noun meaning "grit" and is used to check that our
++# input files are in the correct encoding. The middle character gets encoded
++# as two bytes in UTF-8, so this is sufficient to detect incorrect encoding.
++ENCODING_CHECK = u'm\u00f6l'
++
++# A special language, translations into which are always "TTTTTT".
++CONSTANT_LANGUAGE = 'x_constant'
++
++FAKE_BIDI = 'fake-bidi'
++
++# Magic number added to the header of resources brotli compressed by grit. Used
++# to easily identify resources as being brotli compressed. See
++# ui/base/resource/resource_bundle.h for decompression usage.
++BROTLI_CONST = b'\x1e\x9b'
+diff --git a/tools/grit/grit/exception.py b/tools/grit/grit/exception.py
+new file mode 100644
+index 0000000000..2a363fb077
+--- /dev/null
++++ b/tools/grit/grit/exception.py
+@@ -0,0 +1,139 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Exception types for GRIT.
++'''
++
++from __future__ import print_function
++
++class Base(Exception):
++ '''A base exception that uses the class's docstring in addition to any
++ user-provided message as the body of the Base.
++ '''
++ def __init__(self, msg=''):
++ if len(msg):
++ if self.__doc__:
++ msg = self.__doc__ + ': ' + msg
++ else:
++ msg = self.__doc__
++ super(Base, self).__init__(msg)
++
++
++class Parsing(Base):
++ '''An error occurred parsing a GRD or XTB file.'''
++ pass
++
++
++class UnknownElement(Parsing):
++ '''An unknown node type was encountered.'''
++ pass
++
++
++class MissingElement(Parsing):
++ '''An expected element was missing.'''
++ pass
++
++
++class UnexpectedChild(Parsing):
++ '''An unexpected child element was encountered (on a leaf node).'''
++ pass
++
++
++class UnexpectedAttribute(Parsing):
++ '''The attribute was not expected'''
++ pass
++
++
++class UnexpectedContent(Parsing):
++ '''This element should not have content'''
++ pass
++
++class MissingMandatoryAttribute(Parsing):
++ '''This element is missing a mandatory attribute'''
++ pass
++
++
++class MutuallyExclusiveMandatoryAttribute(Parsing):
++ '''This element has 2 mutually exclusive mandatory attributes'''
++ pass
++
++
++class DuplicateKey(Parsing):
++ '''A duplicate key attribute was found.'''
++ pass
++
++
++class TooManyExamples(Parsing):
++ '''Only one <ex> element is allowed for each <ph> element.'''
++ pass
++
++
++class FileNotFound(Parsing):
++ '''The resource file was not found.'''
++ pass
++
++
++class InvalidMessage(Base):
++ '''The specified message failed validation.'''
++ pass
++
++
++class InvalidTranslation(Base):
++ '''Attempt to add an invalid translation to a clique.'''
++ pass
++
++
++class NoSuchTranslation(Base):
++ '''Requested translation not available'''
++ pass
++
++
++class NotReady(Base):
++ '''Attempt to use an object before it is ready, or attempt to translate \
++an empty document.'''
++ pass
++
++
++class MismatchingPlaceholders(Base):
++ '''Placeholders do not match.'''
++ pass
++
++
++class InvalidPlaceholderName(Base):
++ '''Placeholder name can only contain A-Z, a-z, 0-9 and underscore.'''
++ pass
++
++
++class BlockTagInTranslateableChunk(Base):
++ '''A block tag was encountered where it wasn't expected.'''
++ pass
++
++
++class SectionNotFound(Base):
++ '''The section you requested was not found in the RC file. Make \
++sure the section ID is correct (matches the section's ID in the RC file). \
++Also note that you may need to specify the RC file's encoding (using the \
++encoding="" attribute) if it is not in the default Windows-1252 encoding. \
++'''
++ pass
++
++
++class IdRangeOverlap(Base):
++ '''ID range overlap.'''
++ pass
++
++
++class ReservedHeaderCollision(Base):
++ '''Resource included with first 3 bytes matching reserved header.'''
++ pass
++
++
++class PlaceholderNotInsidePhNode(Base):
++ '''Placeholder formatters should be inside <ph> element.'''
++ pass
++
++
++class InvalidCharactersInsidePhNode(Base):
++ '''Invalid characters found inside <ph> element.'''
++ pass
+diff --git a/tools/grit/grit/extern/BogoFP.py b/tools/grit/grit/extern/BogoFP.py
+new file mode 100644
+index 0000000000..fc90145833
+--- /dev/null
++++ b/tools/grit/grit/extern/BogoFP.py
+@@ -0,0 +1,22 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++"""Bogus fingerprint implementation, do not use for production,
++provided only as an example.
++
++Usage:
++ grit.py -h grit.extern.BogoFP xmb /tmp/foo
++"""
++
++from __future__ import print_function
++
++import grit.extern.FP
++
++
++def UnsignedFingerPrint(str, encoding='utf-8'):
++ """Generate a fingerprint not intended for production from str (it
++ reduces the precision of the production fingerprint by one bit).
++ """
++ return (0xFFFFF7FFFFFFFFFF &
++ grit.extern.FP._UnsignedFingerPrintImpl(str, encoding))
+diff --git a/tools/grit/grit/extern/FP.py b/tools/grit/grit/extern/FP.py
+new file mode 100644
+index 0000000000..f4ec4d943f
+--- /dev/null
++++ b/tools/grit/grit/extern/FP.py
+@@ -0,0 +1,72 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++from __future__ import print_function
++
++try:
++ import hashlib
++ _new_md5 = hashlib.md5
++except ImportError:
++ import md5
++ _new_md5 = md5.new
++
++
++"""64-bit fingerprint support for strings.
++
++Usage:
++ from extern import FP
++ print('Fingerprint is %ld' % FP.FingerPrint('Hello world!'))
++"""
++
++
++def _UnsignedFingerPrintImpl(str, encoding='utf-8'):
++ """Generate a 64-bit fingerprint by taking the first half of the md5
++ of the string.
++ """
++ hex128 = _new_md5(str.encode(encoding)).hexdigest()
++ int64 = int(hex128[:16], 16)
++ return int64
++
++
++def UnsignedFingerPrint(str, encoding='utf-8'):
++ """Generate a 64-bit fingerprint.
++
++ The default implementation uses _UnsignedFingerPrintImpl, which
++ takes the first half of the md5 of the string, but the
++ implementation may be switched using SetUnsignedFingerPrintImpl.
++ """
++ return _UnsignedFingerPrintImpl(str, encoding)
++
++
++def FingerPrint(str, encoding='utf-8'):
++ fp = UnsignedFingerPrint(str, encoding=encoding)
++ # interpret fingerprint as signed longs
++ if fp & 0x8000000000000000:
++ fp = -((~fp & 0xFFFFFFFFFFFFFFFF) + 1)
++ return fp
++
++
++def UseUnsignedFingerPrintFromModule(module_name):
++ """Imports module_name and replaces UnsignedFingerPrint in the
++ current module with the function of the same name from the imported
++ module.
++
++ Returns the function object previously known as
++ grit.extern.FP.UnsignedFingerPrint.
++ """
++ hash_module = __import__(module_name, fromlist=[module_name])
++ return SetUnsignedFingerPrint(hash_module.UnsignedFingerPrint)
++
++
++def SetUnsignedFingerPrint(function_object):
++ """Sets grit.extern.FP.UnsignedFingerPrint to point to
++ function_object.
+
-+std::string ChromeClassTester::GetNamespace(const Decl* record) {
-+ return GetNamespaceImpl(record->getDeclContext(), "");
++ Returns the function object previously known as
++ grit.extern.FP.UnsignedFingerPrint.
++ """
++ global UnsignedFingerPrint
++ original_function_object = UnsignedFingerPrint
++ UnsignedFingerPrint = function_object
++ return original_function_object
+diff --git a/tools/grit/grit/extern/__init__.py b/tools/grit/grit/extern/__init__.py
+new file mode 100644
+index 0000000000..e69de29bb2
+diff --git a/tools/grit/grit/extern/tclib.py b/tools/grit/grit/extern/tclib.py
+new file mode 100644
+index 0000000000..9952a87c11
+--- /dev/null
++++ b/tools/grit/grit/extern/tclib.py
+@@ -0,0 +1,503 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++# The tclib module contains tools for aggregating, verifying, and storing
++# messages destined for the Translation Console, as well as for reading
++# translations back and outputting them in some desired format.
++#
++# This has been stripped down to include only the functionality needed by grit
++# for creating Windows .rc and .h files. These are the only parts needed by
++# the Chrome build process.
++
++from __future__ import print_function
++
++from grit.extern import FP
++
++# This module assumes that within a bundle no two messages can have the
++# same id unless they're identical.
++
++# The basic classes defined here for external use are Message and Translation,
++# where the former is used for English messages and the latter for
++# translations. These classes have a lot of common functionality, as expressed
++# by the common parent class BaseMessage. Perhaps the most important
++# distinction is that translated text is stored in UTF-8, whereas original text
++# is stored in whatever encoding the client uses (presumably Latin-1).
++
++# --------------------
++# The public interface
++# --------------------
++
++# Generate message id from message text and meaning string (optional),
++# both in utf-8 encoding
++#
++def GenerateMessageId(message, meaning=''):
++ fp = FP.FingerPrint(message)
++ if meaning:
++ # combine the fingerprints of message and meaning
++ fp2 = FP.FingerPrint(meaning)
++ if fp < 0:
++ fp = fp2 + (fp << 1) + 1
++ else:
++ fp = fp2 + (fp << 1)
++ # To avoid negative ids we strip the high-order bit
++ return str(fp & 0x7fffffffffffffff)
++
++# -------------------------------------------------------------------------
++# The MessageTranslationError class is used to signal tclib-specific errors.
++
++
++class MessageTranslationError(Exception):
++
++ def __init__(self, args = ''):
++ self.args = args
++
++
++# -----------------------------------------------------------
++# The Placeholder class represents a placeholder in a message.
++
++class Placeholder(object):
++ # String representation
++ def __str__(self):
++ return '%s, "%s", "%s"' % \
++ (self.__presentation, self.__original, self.__example)
++
++ # Getters
++ def GetOriginal(self):
++ return self.__original
++
++ def GetPresentation(self):
++ return self.__presentation
++
++ def GetExample(self):
++ return self.__example
++
++ def __eq__(self, other):
++ return self.EqualTo(other, strict=1, ignore_trailing_spaces=0)
++
++ # Equality test
++ #
++ # ignore_trailing_spaces: TC is using varchar to store the
++ # phrwr fields, as a result of that, the trailing spaces
++ # are removed by MySQL when the strings are stored into TC:-(
++ # ignore_trailing_spaces parameter is used to ignore
++ # trailing spaces during equivalence comparison.
++ #
++ def EqualTo(self, other, strict = 1, ignore_trailing_spaces = 1):
++ if type(other) is not Placeholder:
++ return 0
++ if StringEquals(self.__presentation, other.__presentation,
++ ignore_trailing_spaces):
++ if not strict or (StringEquals(self.__original, other.__original,
++ ignore_trailing_spaces) and
++ StringEquals(self.__example, other.__example,
++ ignore_trailing_spaces)):
++ return 1
++ return 0
++
++
++# -----------------------------------------------------------------
++# BaseMessage is the common parent class of Message and Translation.
++# It is not meant for direct use.
++
++class BaseMessage(object):
++ # Three types of message construction is supported. If the message text is a
++ # simple string with no dynamic content, you can pass it to the constructor
++ # as the "text" parameter. Otherwise, you can omit "text" and assemble the
++ # message step by step using AppendText() and AppendPlaceholder(). Or, as an
++ # alternative, you can give the constructor the "presentable" version of the
++ # message and a list of placeholders; it will then parse the presentation and
++ # build the message accordingly. For example:
++ # Message(text = "There are NUM_BUGS bugs in your code",
++ # placeholders = [Placeholder("NUM_BUGS", "%d", "33")],
++ # description = "Bla bla bla")
++ def __eq__(self, other):
++ # "source encoding" is nonsense, so ignore it
++ return _ObjectEquals(self, other, ['_BaseMessage__source_encoding'])
++
++ def GetName(self):
++ return self.__name
++
++ def GetSourceEncoding(self):
++ return self.__source_encoding
++
++ # Append a placeholder to the message
++ def AppendPlaceholder(self, placeholder):
++ if not isinstance(placeholder, Placeholder):
++ raise MessageTranslationError("Invalid message placeholder %s in "
++ "message %s" % (placeholder, self.GetId()))
++ # Are there other placeholders with the same presentation?
++ # If so, they need to be the same.
++ for other in self.GetPlaceholders():
++ if placeholder.GetPresentation() == other.GetPresentation():
++ if not placeholder.EqualTo(other):
++ raise MessageTranslationError(
++ "Conflicting declarations of %s within message" %
++ placeholder.GetPresentation())
++ # update placeholder list
++ dup = 0
++ for item in self.__content:
++ if isinstance(item, Placeholder) and placeholder.EqualTo(item):
++ dup = 1
++ break
++ if not dup:
++ self.__placeholders.append(placeholder)
++
++ # update content
++ self.__content.append(placeholder)
++
++ # Strips leading and trailing whitespace, and returns a tuple
++ # containing the leading and trailing space that was removed.
++ def Strip(self):
++ leading = trailing = ''
++ if len(self.__content) > 0:
++ s0 = self.__content[0]
++ if not isinstance(s0, Placeholder):
++ s = s0.lstrip()
++ leading = s0[:-len(s)]
++ self.__content[0] = s
++
++ s0 = self.__content[-1]
++ if not isinstance(s0, Placeholder):
++ s = s0.rstrip()
++ trailing = s0[len(s):]
++ self.__content[-1] = s
++ return leading, trailing
++
++ # Return the id of this message
++ def GetId(self):
++ if self.__id is None:
++ return self.GenerateId()
++ return self.__id
++
++ # Set the id of this message
++ def SetId(self, id):
++ if id is None:
++ self.__id = None
++ else:
++ self.__id = str(id) # Treat numerical ids as strings
++
++ # Return content of this message as a list (internal use only)
++ def GetContent(self):
++ return self.__content
++
++ # Return a human-readable version of this message
++ def GetPresentableContent(self):
++ presentable_content = ""
++ for item in self.__content:
++ if isinstance(item, Placeholder):
++ presentable_content += item.GetPresentation()
++ else:
++ presentable_content += item
++
++ return presentable_content
++
++ # Return a fragment of a message in escaped format
++ def EscapeFragment(self, fragment):
++ return fragment.replace('%', '%%')
++
++ # Return the "original" version of this message, doing %-escaping
++ # properly. If source_msg is specified, the placeholder original
++ # information inside source_msg will be used instead.
++ def GetOriginalContent(self, source_msg = None):
++ original_content = ""
++ for item in self.__content:
++ if isinstance(item, Placeholder):
++ if source_msg:
++ ph = source_msg.GetPlaceholder(item.GetPresentation())
++ if not ph:
++ raise MessageTranslationError(
++ "Placeholder %s doesn't exist in message: %s" %
++ (item.GetPresentation(), source_msg))
++ original_content += ph.GetOriginal()
++ else:
++ original_content += item.GetOriginal()
++ else:
++ original_content += self.EscapeFragment(item)
++ return original_content
++
++ # Return the example of this message
++ def GetExampleContent(self):
++ example_content = ""
++ for item in self.__content:
++ if isinstance(item, Placeholder):
++ example_content += item.GetExample()
++ else:
++ example_content += item
++ return example_content
++
++ # Return a list of all unique placeholders in this message
++ def GetPlaceholders(self):
++ return self.__placeholders
++
++ # Return a placeholder in this message
++ def GetPlaceholder(self, presentation):
++ for item in self.__content:
++ if (isinstance(item, Placeholder) and
++ item.GetPresentation() == presentation):
++ return item
++ return None
++
++ # Return this message's description
++ def GetDescription(self):
++ return self.__description
++
++ # Add a message source
++ def AddSource(self, source):
++ self.__sources.append(source)
++
++ # Return this message's sources as a list
++ def GetSources(self):
++ return self.__sources
++
++ # Return this message's sources as a string
++ def GetSourcesAsText(self, delimiter = "; "):
++ return delimiter.join(self.__sources)
++
++ # Set the obsolete flag for a message (internal use only)
++ def SetObsolete(self):
++ self.__obsolete = 1
++
++ # Get the obsolete flag for a message (internal use only)
++ def IsObsolete(self):
++ return self.__obsolete
++
++ # Get the sequence number (0 by default)
++ def GetSequenceNumber(self):
++ return self.__sequence_number
++
++ # Set the sequence number
++ def SetSequenceNumber(self, number):
++ self.__sequence_number = number
++
++ # Increment instance counter
++ def AddInstance(self):
++ self.__num_instances += 1
++
++ # Return instance count
++ def GetNumInstances(self):
++ return self.__num_instances
++
++ def GetErrors(self, from_tc=0):
++ """
++ Returns a description of the problem if the message is not
++ syntactically valid, or None if everything is fine.
++
++ Args:
++ from_tc: indicates whether this message came from the TC. We let
++ the TC get away with some things we normally wouldn't allow for
++ historical reasons.
++ """
++ # check that placeholders are unambiguous
++ pos = 0
++ phs = {}
++ for item in self.__content:
++ if isinstance(item, Placeholder):
++ phs[pos] = item
++ pos += len(item.GetPresentation())
++ else:
++ pos += len(item)
++ presentation = self.GetPresentableContent()
++ for ph in self.GetPlaceholders():
++ for pos in FindOverlapping(presentation, ph.GetPresentation()):
++ # message contains the same text as a placeholder presentation
++ other_ph = phs.get(pos)
++ if ((not other_ph
++ and not IsSubstringInPlaceholder(pos, len(ph.GetPresentation()), phs))
++ or
++ (other_ph and len(other_ph.GetPresentation()) < len(ph.GetPresentation()))):
++ return "message contains placeholder name '%s':\n%s" % (
++ ph.GetPresentation(), presentation)
++ return None
++
++
++ def __CopyTo(self, other):
++ """
++ Returns a copy of this BaseMessage.
++ """
++ assert isinstance(other, self.__class__) or isinstance(self, other.__class__)
++ other.__source_encoding = self.__source_encoding
++ other.__content = self.__content[:]
++ other.__description = self.__description
++ other.__id = self.__id
++ other.__num_instances = self.__num_instances
++ other.__obsolete = self.__obsolete
++ other.__name = self.__name
++ other.__placeholders = self.__placeholders[:]
++ other.__sequence_number = self.__sequence_number
++ other.__sources = self.__sources[:]
++
++ return other
++
++ def HasText(self):
++ """Returns true iff this message has anything other than placeholders."""
++ for item in self.__content:
++ if not isinstance(item, Placeholder):
++ return True
++ return False
++
++# --------------------------------------------------------
++# The Message class represents original (English) messages
++
++class Message(BaseMessage):
++ # See BaseMessage constructor
++ def __init__(self, source_encoding, text=None, id=None,
++ description=None, meaning="", placeholders=None,
++ source=None, sequence_number=0, clone_from=None,
++ time_created=0, name=None, is_hidden = 0):
++
++ if clone_from is not None:
++ BaseMessage.__init__(self, None, clone_from=clone_from)
++ self.__meaning = clone_from.__meaning
++ self.__time_created = clone_from.__time_created
++ self.__is_hidden = clone_from.__is_hidden
++ return
++
++ BaseMessage.__init__(self, source_encoding, text, id, description,
++ placeholders, source, sequence_number,
++ name=name)
++ self.__meaning = meaning
++ self.__time_created = time_created
++ self.SetIsHidden(is_hidden)
++
++ # String representation
++ def __str__(self):
++ s = 'source: %s, id: %s, content: "%s", meaning: "%s", ' \
++ 'description: "%s"' % \
++ (self.GetSourcesAsText(), self.GetId(), self.GetPresentableContent(),
++ self.__meaning, self.GetDescription())
++ if self.GetName() is not None:
++ s += ', name: "%s"' % self.GetName()
++ placeholders = self.GetPlaceholders()
++ for i in range(len(placeholders)):
++ s += ", placeholder[%d]: %s" % (i, placeholders[i])
++ return s
++
++ # Strips leading and trailing whitespace, and returns a tuple
++ # containing the leading and trailing space that was removed.
++ def Strip(self):
++ leading = trailing = ''
++ content = self.GetContent()
++ if len(content) > 0:
++ s0 = content[0]
++ if not isinstance(s0, Placeholder):
++ s = s0.lstrip()
++ leading = s0[:-len(s)]
++ content[0] = s
++
++ s0 = content[-1]
++ if not isinstance(s0, Placeholder):
++ s = s0.rstrip()
++ trailing = s0[len(s):]
++ content[-1] = s
++ return leading, trailing
++
++ # Generate an id by hashing message content
++ def GenerateId(self):
++ self.SetId(GenerateMessageId(self.GetPresentableContent(),
++ self.__meaning))
++ return self.GetId()
++
++ def GetMeaning(self):
++ return self.__meaning
++
++ def GetTimeCreated(self):
++ return self.__time_created
++
++ # Equality operator
++ def EqualTo(self, other, strict = 1):
++ # Check id, meaning, content
++ if self.GetId() != other.GetId():
++ return 0
++ if self.__meaning != other.__meaning:
++ return 0
++ if self.GetPresentableContent() != other.GetPresentableContent():
++ return 0
++ # Check descriptions if comparison is strict
++ if (strict and
++ self.GetDescription() is not None and
++ other.GetDescription() is not None and
++ self.GetDescription() != other.GetDescription()):
++ return 0
++ # Check placeholders
++ ph1 = self.GetPlaceholders()
++ ph2 = other.GetPlaceholders()
++ if len(ph1) != len(ph2):
++ return 0
++ for i in range(len(ph1)):
++ if not ph1[i].EqualTo(ph2[i], strict):
++ return 0
++
++ return 1
++
++ def Copy(self):
++ """
++ Returns a copy of this Message.
++ """
++ assert isinstance(self, Message)
++ return Message(None, clone_from=self)
++
++ def SetIsHidden(self, is_hidden):
++ """Sets whether this message should be hidden.
++
++ Args:
++ is_hidden : 0 or 1 - if the message should be hidden, 0 otherwise
++ """
++ if is_hidden not in [0, 1]:
++ raise MessageTranslationError("is_hidden must be 0 or 1, got %s")
++ self.__is_hidden = is_hidden
++
++ def IsHidden(self):
++ """Returns 1 if this message is hidden, and 0 otherwise."""
++ return self.__is_hidden
++
++# ----------------------------------------------------
++# The Translation class represents translated messages
++
++class Translation(BaseMessage):
++ # See BaseMessage constructor
++ def __init__(self, source_encoding, text=None, id=None,
++ description=None, placeholders=None, source=None,
++ sequence_number=0, clone_from=None, ignore_ph_errors=0,
++ name=None):
++ if clone_from is not None:
++ BaseMessage.__init__(self, None, clone_from=clone_from)
++ return
++
++ BaseMessage.__init__(self, source_encoding, text, id, description,
++ placeholders, source, sequence_number,
++ ignore_ph_errors=ignore_ph_errors, name=name)
++
++ # String representation
++ def __str__(self):
++ s = 'source: %s, id: %s, content: "%s", description: "%s"' % \
++ (self.GetSourcesAsText(), self.GetId(), self.GetPresentableContent(),
++ self.GetDescription());
++ placeholders = self.GetPlaceholders()
++ for i in range(len(placeholders)):
++ s += ", placeholder[%d]: %s" % (i, placeholders[i])
++ return s
++
++ # Equality operator
++ def EqualTo(self, other, strict=1):
++ # Check id and content
++ if self.GetId() != other.GetId():
++ return 0
++ if self.GetPresentableContent() != other.GetPresentableContent():
++ return 0
++ # Check placeholders
++ ph1 = self.GetPlaceholders()
++ ph2 = other.GetPlaceholders()
++ if len(ph1) != len(ph2):
++ return 0
++ for i in range(len(ph1)):
++ if not ph1[i].EqualTo(ph2[i], strict):
++ return 0
++
++ return 1
++
++ def Copy(self):
++ """
++ Returns a copy of this Translation.
++ """
++ return Translation(None, clone_from=self)
+diff --git a/tools/grit/grit/format/__init__.py b/tools/grit/grit/format/__init__.py
+new file mode 100644
+index 0000000000..55d56b8cfd
+--- /dev/null
++++ b/tools/grit/grit/format/__init__.py
+@@ -0,0 +1,8 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Module grit.format
++'''
++
++pass
+diff --git a/tools/grit/grit/format/android_xml.py b/tools/grit/grit/format/android_xml.py
+new file mode 100644
+index 0000000000..7eb288891f
+--- /dev/null
++++ b/tools/grit/grit/format/android_xml.py
+@@ -0,0 +1,212 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++"""Produces localized strings.xml files for Android.
++
++In cases where an "android" type output file is requested in a grd, the classes
++in android_xml will process the messages and translations to produce a valid
++strings.xml that is properly localized with the specified language.
++
++For example if the following output tag were to be included in a grd file
++ <outputs>
++ ...
++ <output filename="values-es/strings.xml" type="android" lang="es" />
++ ...
++ </outputs>
++
++for a grd file with the following messages:
++
++ <message name="IDS_HELLO" desc="Simple greeting">Hello</message>
++ <message name="IDS_WORLD" desc="The world">world</message>
++
++and there existed an appropriate xtb file containing the Spanish translations,
++then the output would be:
++
++ <?xml version="1.0" encoding="utf-8"?>
++ <resources xmlns:android="http://schemas.android.com/apk/res/android">
++ <string name="hello">"Hola"</string>
++ <string name="world">"mundo"</string>
++ </resources>
++
++which would be written to values-es/strings.xml and usable by the Android
++resource framework.
++
++Advanced usage
++--------------
++
++To process only certain messages in a grd file, tag each desired message by
++adding "android_java" to formatter_data. Then set the environmental variable
++ANDROID_JAVA_TAGGED_ONLY to "true" when building the grd file. For example:
++
++ <message name="IDS_HELLO" formatter_data="android_java">Hello</message>
++
++To generate Android plurals (aka "quantity strings"), use the ICU plural syntax
++in the grd file. This will automatically be transformed into a <purals> element
++in the output xml file. For example:
++
++ <message name="IDS_CATS">
++ {NUM_CATS, plural,
++ =1 {1 cat}
++ other {# cats}}
++ </message>
++
++ will produce
++
++ <plurals name="cats">
++ <item quantity="one">1 Katze</item>
++ <item quantity="other">%d Katzen</item>
++ </plurals>
++"""
++
++from __future__ import print_function
++
++import os
++import re
++import xml.sax.saxutils
++
++from grit import lazy_re
++from grit.node import message
++
++
++# When this environmental variable has value "true", only tagged messages will
++# be outputted.
++_TAGGED_ONLY_ENV_VAR = 'ANDROID_JAVA_TAGGED_ONLY'
++_TAGGED_ONLY_DEFAULT = False
++
++# In tagged-only mode, only messages with this tag will be ouputted.
++_EMIT_TAG = 'android_java'
++
++_NAME_PATTERN = lazy_re.compile(r'IDS_(?P<name>[A-Z0-9_]+)\Z')
++
++# Most strings are output as a <string> element. Note the double quotes
++# around the value to preserve whitespace.
++_STRING_TEMPLATE = u'<string name="%s">"%s"</string>\n'
++
++# Some strings are output as a <plurals> element.
++_PLURALS_TEMPLATE = '<plurals name="%s">\n%s</plurals>\n'
++_PLURALS_ITEM_TEMPLATE = ' <item quantity="%s">%s</item>\n'
++
++# Matches e.g. "{HELLO, plural, HOW ARE YOU DOING}", while capturing
++# "HOW ARE YOU DOING" in <items>.
++_PLURALS_PATTERN = lazy_re.compile(r'\{[A-Z_]+,\s*plural,(?P<items>.*)\}$',
++ flags=re.S)
++
++# Repeatedly matched against the <items> capture in _PLURALS_PATTERN,
++# to match "<quantity>{<value>}".
++_PLURALS_ITEM_PATTERN = lazy_re.compile(r'(?P<quantity>\S+?)\s*'
++ r'\{(?P<value>.*?)\}')
++_PLURALS_QUANTITY_MAP = {
++ '=0': 'zero',
++ 'zero': 'zero',
++ '=1': 'one',
++ 'one': 'one',
++ '=2': 'two',
++ 'two': 'two',
++ 'few': 'few',
++ 'many': 'many',
++ 'other': 'other',
+}
+
-+bool ChromeClassTester::InImplementationFile(SourceLocation record_location) {
-+ std::string filename;
-+ if (!GetFilename(record_location, &filename))
-+ return false;
+
-+ if (ends_with(filename, ".cc") || ends_with(filename, ".cpp") ||
-+ ends_with(filename, ".mm")) {
-+ return true;
++def Format(root, lang='en', output_dir='.'):
++ yield ('<?xml version="1.0" encoding="utf-8"?>\n'
++ '<resources '
++ 'xmlns:android="http://schemas.android.com/apk/res/android">\n')
++
++ tagged_only = _TAGGED_ONLY_DEFAULT
++ if _TAGGED_ONLY_ENV_VAR in os.environ:
++ tagged_only = os.environ[_TAGGED_ONLY_ENV_VAR].lower()
++ if tagged_only == 'true':
++ tagged_only = True
++ elif tagged_only == 'false':
++ tagged_only = False
++ else:
++ raise Exception('env variable ANDROID_JAVA_TAGGED_ONLY must have value '
++ 'true or false. Invalid value: %s' % tagged_only)
++
++ for item in root.ActiveDescendants():
++ with item:
++ if ShouldOutputNode(item, tagged_only):
++ yield _FormatMessage(item, lang)
++
++ yield '</resources>\n'
++
++
++def ShouldOutputNode(node, tagged_only):
++ """Returns true if node should be outputted.
++
++ Args:
++ node: a Node from the grd dom
++ tagged_only: true, if only tagged messages should be outputted
++ """
++ return (isinstance(node, message.MessageNode) and
++ (not tagged_only or _EMIT_TAG in node.formatter_data))
++
++
++def _FormatPluralMessage(message):
++ """Compiles ICU plural syntax to the body of an Android <plurals> element.
++
++ 1. In a .grd file, we can write a plural string like this:
++
++ <message name="IDS_THINGS">
++ {NUM_THINGS, plural,
++ =1 {1 thing}
++ other {# things}}
++ </message>
++
++ 2. The Android equivalent looks like this:
++
++ <plurals name="things">
++ <item quantity="one">1 thing</item>
++ <item quantity="other">%d things</item>
++ </plurals>
++
++ This method takes the body of (1) and converts it to the body of (2).
++
++ If the message is *not* a plural string, this function returns `None`.
++ If the message includes quantities without an equivalent format in Android,
++ it raises an exception.
++ """
++ ret = {}
++ plural_match = _PLURALS_PATTERN.match(message)
++ if not plural_match:
++ return None
++ body_in = plural_match.group('items').strip()
++ lines = []
++ quantities_so_far = set()
++ for item_match in _PLURALS_ITEM_PATTERN.finditer(body_in):
++ quantity_in = item_match.group('quantity')
++ quantity_out = _PLURALS_QUANTITY_MAP.get(quantity_in)
++ value_in = item_match.group('value')
++ value_out = '"' + value_in.replace('#', '%d') + '"'
++ if quantity_out:
++ # only one line per quantity out (https://crbug.com/787488)
++ if quantity_out not in quantities_so_far:
++ quantities_so_far.add(quantity_out)
++ lines.append(_PLURALS_ITEM_TEMPLATE % (quantity_out, value_out))
++ else:
++ raise Exception('Unsupported plural quantity for android '
++ 'strings.xml: %s' % quantity_in)
++ return ''.join(lines)
++
++
++def _FormatMessage(item, lang):
++ """Writes out a single string as a <resource/> element."""
++
++ mangled_name = item.GetTextualIds()[0]
++ match = _NAME_PATTERN.match(mangled_name)
++ if not match:
++ raise Exception('Unexpected resource name: %s' % mangled_name)
++ name = match.group('name').lower()
++
++ value = item.ws_at_start + item.Translate(lang) + item.ws_at_end
++ # Replace < > & with &lt; &gt; &amp; to ensure we generate valid XML and
++ # replace ' " with \' \" to conform to Android's string formatting rules.
++ value = xml.sax.saxutils.escape(value, {"'": "\\'", '"': '\\"'})
++
++ plurals = _FormatPluralMessage(value)
++ if plurals:
++ return _PLURALS_TEMPLATE % (name, plurals)
++ else:
++ return _STRING_TEMPLATE % (name, value)
+diff --git a/tools/grit/grit/format/android_xml_unittest.py b/tools/grit/grit/format/android_xml_unittest.py
+new file mode 100644
+index 0000000000..d9f476fddf
+--- /dev/null
++++ b/tools/grit/grit/format/android_xml_unittest.py
+@@ -0,0 +1,149 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++"""Unittest for android_xml.py."""
++
++from __future__ import print_function
++
++import os
++import sys
++import unittest
++
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++from six import StringIO
++
++from grit import util
++from grit.format import android_xml
++from grit.node import message
++from grit.tool import build
++
++
++class AndroidXmlUnittest(unittest.TestCase):
++
++ def testMessages(self):
++ root = util.ParseGrdForUnittest(r"""
++ <messages>
++ <message name="IDS_SIMPLE" desc="A vanilla string">
++ Martha
++ </message>
++ <message name="IDS_ONE_LINE" desc="On one line">sat and wondered</message>
++ <message name="IDS_QUOTES" desc="A string with quotation marks">
++ out loud, "Why don't I build a flying car?"
++ </message>
++ <message name="IDS_MULTILINE" desc="A string split over several lines">
++ She gathered
++wood, charcoal, and
++a sledge hammer.
++ </message>
++ <message name="IDS_WHITESPACE" desc="A string with extra whitespace.">
++ ''' How old fashioned -- she thought. '''
++ </message>
++ <message name="IDS_PLACEHOLDERS" desc="A string with placeholders">
++ I'll buy a <ph name="WAVELENGTH">%d<ex>200</ex></ph> nm laser at <ph name="STORE_NAME">%s<ex>the grocery store</ex></ph>.
++ </message>
++ <message name="IDS_PLURALS" desc="A string using the ICU plural format">
++ {NUM_THINGS, plural,
++ =1 {Maybe I'll get one laser.}
++ other {Maybe I'll get # lasers.}}
++ </message>
++ <message name="IDS_PLURALS_NO_SPACE" desc="A string using the ICU plural format with no space">
++ {NUM_MISSISSIPPIS, plural,
++ =1{OneMississippi}other{ManyMississippis}}
++ </message>
++ </messages>
++ """)
++
++ buf = StringIO()
++ build.RcBuilder.ProcessNode(root, DummyOutput('android', 'en'), buf)
++ output = buf.getvalue()
++ expected = r"""
++<?xml version="1.0" encoding="utf-8"?>
++<resources xmlns:android="http://schemas.android.com/apk/res/android">
++<string name="simple">"Martha"</string>
++<string name="one_line">"sat and wondered"</string>
++<string name="quotes">"out loud, \"Why don\'t I build a flying car?\""</string>
++<string name="multiline">"She gathered
++wood, charcoal, and
++a sledge hammer."</string>
++<string name="whitespace">" How old fashioned -- she thought. "</string>
++<string name="placeholders">"I\'ll buy a %d nm laser at %s."</string>
++<plurals name="plurals">
++ <item quantity="one">"Maybe I\'ll get one laser."</item>
++ <item quantity="other">"Maybe I\'ll get %d lasers."</item>
++</plurals>
++<plurals name="plurals_no_space">
++ <item quantity="one">"OneMississippi"</item>
++ <item quantity="other">"ManyMississippis"</item>
++</plurals>
++</resources>
++"""
++ self.assertEqual(output.strip(), expected.strip())
++
++
++ def testConflictingPlurals(self):
++ root = util.ParseGrdForUnittest(r"""
++ <messages>
++ <message name="IDS_PLURALS" desc="A string using the ICU plural format">
++ {NUM_THINGS, plural,
++ =1 {Maybe I'll get one laser.}
++ one {Maybe I'll get one laser.}
++ other {Maybe I'll get # lasers.}}
++ </message>
++ </messages>
++ """)
++
++ buf = StringIO()
++ build.RcBuilder.ProcessNode(root, DummyOutput('android', 'en'), buf)
++ output = buf.getvalue()
++ expected = r"""
++<?xml version="1.0" encoding="utf-8"?>
++<resources xmlns:android="http://schemas.android.com/apk/res/android">
++<plurals name="plurals">
++ <item quantity="one">"Maybe I\'ll get one laser."</item>
++ <item quantity="other">"Maybe I\'ll get %d lasers."</item>
++</plurals>
++</resources>
++"""
++ self.assertEqual(output.strip(), expected.strip())
++
++
++ def testTaggedOnly(self):
++ root = util.ParseGrdForUnittest(r"""
++ <messages>
++ <message name="IDS_HELLO" desc="" formatter_data="android_java">
++ Hello
++ </message>
++ <message name="IDS_WORLD" desc="">
++ world
++ </message>
++ </messages>
++ """)
++
++ msg_hello, msg_world = root.GetChildrenOfType(message.MessageNode)
++ self.assertTrue(android_xml.ShouldOutputNode(msg_hello, tagged_only=True))
++ self.assertFalse(android_xml.ShouldOutputNode(msg_world, tagged_only=True))
++ self.assertTrue(android_xml.ShouldOutputNode(msg_hello, tagged_only=False))
++ self.assertTrue(android_xml.ShouldOutputNode(msg_world, tagged_only=False))
++
++
++class DummyOutput(object):
++
++ def __init__(self, type, language):
++ self.type = type
++ self.language = language
++
++ def GetType(self):
++ return self.type
++
++ def GetLanguage(self):
++ return self.language
++
++ def GetOutputFilename(self):
++ return 'hello.gif'
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/format/c_format.py b/tools/grit/grit/format/c_format.py
+new file mode 100644
+index 0000000000..16809a9f70
+--- /dev/null
++++ b/tools/grit/grit/format/c_format.py
+@@ -0,0 +1,95 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++"""Formats as a .C file for compilation.
++"""
++
++from __future__ import print_function
++
++import codecs
++import os
++import re
++
++import six
++
++from grit import util
++
++
++def _FormatHeader(root, output_dir):
++ """Returns the required preamble for C files."""
++ # Find the location of the resource header file, so that we can include
++ # it.
++ resource_header = 'resource.h' # fall back to this
++ for output in root.GetOutputFiles():
++ if output.attrs['type'] == 'rc_header':
++ resource_header = os.path.abspath(output.GetOutputFilename())
++ resource_header = util.MakeRelativePath(output_dir, resource_header)
++ return """// This file is automatically generated by GRIT. Do not edit.
++
++#include "%s"
++
++// All strings are UTF-8
++""" % (resource_header)
++# end _FormatHeader() function
++
++
++def Format(root, lang='en', output_dir='.'):
++ """Outputs a C switch statement representing the string table."""
++ from grit.node import message
++ assert isinstance(lang, six.string_types)
++
++ yield _FormatHeader(root, output_dir)
++
++ yield 'const char* GetString(int id) {\n switch (id) {'
++
++ for item in root.ActiveDescendants():
++ with item:
++ if isinstance(item, message.MessageNode):
++ yield _FormatMessage(item, lang)
++
++ yield '\n default:\n return 0;\n }\n}\n'
++
++
++def _HexToOct(match):
++ "Return the octal form of the hex numbers"
++ hex = match.group("hex")
++ result = ""
++ while len(hex):
++ next_num = int(hex[2:4], 16)
++ result += "\\" + '%03o' % next_num
++ hex = hex[4:]
++ return match.group("escaped_backslashes") + result
++
++
++def _FormatMessage(item, lang):
++ """Format a single <message> element."""
++
++ message = item.ws_at_start + item.Translate(lang) + item.ws_at_end
++ # Output message with non-ascii chars escaped as octal numbers C's grammar
++ # allows escaped hexadecimal numbers to be infinite, but octal is always of
++ # the form \OOO. Python 3 doesn't support string-escape, so we have to jump
++ # through some hoops here via codecs.escape_encode.
++ # This basically does:
++ # - message - the starting string
++ # - message.encode(...) - convert to bytes
++ # - codecs.escape_encode(...) - convert non-ASCII bytes to \x## escapes
++ # - (...).decode() - convert bytes back to a string
++ message = codecs.escape_encode(message.encode('utf-8'))[0].decode('utf-8')
++ # an escaped char is (\xHH)+ but only if the initial
++ # backslash is not escaped.
++ not_a_backslash = r"(^|[^\\])" # beginning of line or a non-backslash char
++ escaped_backslashes = not_a_backslash + r"(\\\\)*"
++ hex_digits = r"((\\x)[0-9a-f]{2})+"
++ two_digit_hex_num = re.compile(
++ r"(?P<escaped_backslashes>%s)(?P<hex>%s)"
++ % (escaped_backslashes, hex_digits))
++ message = two_digit_hex_num.sub(_HexToOct, message)
++ # unescape \ (convert \\ back to \)
++ message = message.replace('\\\\', '\\')
++ message = message.replace('"', '\\"')
++ message = util.LINEBREAKS.sub(r'\\n', message)
++
++ name_attr = item.GetTextualIds()[0]
++
++ return '\n case %s:\n return "%s";' % (name_attr, message)
+diff --git a/tools/grit/grit/format/c_format_unittest.py b/tools/grit/grit/format/c_format_unittest.py
+new file mode 100644
+index 0000000000..380120c42f
+--- /dev/null
++++ b/tools/grit/grit/format/c_format_unittest.py
+@@ -0,0 +1,81 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++"""Unittest for c_format.py.
++"""
++
++from __future__ import print_function
++
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++import unittest
++
++from six import StringIO
++
++from grit import util
++from grit.tool import build
++
++
++class CFormatUnittest(unittest.TestCase):
++
++ def testMessages(self):
++ root = util.ParseGrdForUnittest(u"""
++ <messages>
++ <message name="IDS_QUESTIONS">Do you want to play questions?</message>
++ <message name="IDS_QUOTES">
++ "What's in a name, <ph name="NAME">%s<ex>Brandon</ex></ph>?"
++ </message>
++ <message name="IDS_LINE_BREAKS">
++ Was that rhetoric?
++No.
++Statement. Two all. Game point.
++</message>
++ <message name="IDS_NON_ASCII">
++ \u00f5\\xc2\\xa4\\\u00a4\\\\xc3\\xb5\u4924
++ </message>
++ </messages>
++ """)
++
++ buf = StringIO()
++ build.RcBuilder.ProcessNode(root, DummyOutput('c_format', 'en'), buf)
++ output = util.StripBlankLinesAndComments(buf.getvalue())
++ self.assertEqual(u"""\
++#include "resource.h"
++const char* GetString(int id) {
++ switch (id) {
++ case IDS_QUESTIONS:
++ return "Do you want to play questions?";
++ case IDS_QUOTES:
++ return "\\"What\\'s in a name, %s?\\"";
++ case IDS_LINE_BREAKS:
++ return "Was that rhetoric?\\nNo.\\nStatement. Two all. Game point.";
++ case IDS_NON_ASCII:
++ return "\\303\\265\\xc2\\xa4\\\\302\\244\\\\xc3\\xb5\\344\\244\\244";
++ default:
++ return 0;
+ }
++}""", output)
+
-+ return false;
-+}
+
-+void ChromeClassTester::BuildBannedLists() {
-+ banned_namespaces_.push_back("std");
-+ banned_namespaces_.push_back("__gnu_cxx");
-+ banned_namespaces_.push_back("WebKit");
-+
-+ banned_directories_.push_back("third_party/");
-+ banned_directories_.push_back("native_client/");
-+ banned_directories_.push_back("breakpad/");
-+ banned_directories_.push_back("courgette/");
-+ banned_directories_.push_back("pdf/");
-+ banned_directories_.push_back("ppapi/");
-+ banned_directories_.push_back("usr/");
-+ banned_directories_.push_back("testing/");
-+ banned_directories_.push_back("googleurl/");
-+ banned_directories_.push_back("v8/");
-+ banned_directories_.push_back("dart/");
-+ banned_directories_.push_back("sdch/");
-+ banned_directories_.push_back("icu4c/");
-+ banned_directories_.push_back("frameworks/");
-+
-+ // Don't check autogenerated headers.
-+ // Make puts them below $(builddir_name)/.../gen and geni.
-+ // Ninja puts them below OUTPUT_DIR/.../gen
-+ // Xcode has a fixed output directory for everything.
-+ banned_directories_.push_back("gen/");
-+ banned_directories_.push_back("geni/");
-+ banned_directories_.push_back("xcodebuild/");
-+
-+ // You are standing in a mazy of twisty dependencies, all resolved by
-+ // putting everything in the header.
-+ banned_directories_.push_back("automation/");
-+
-+ // Don't check system headers.
-+ banned_directories_.push_back("/Developer/");
-+
-+ // Used in really low level threading code that probably shouldn't be out of
-+ // lined.
-+ ignored_record_names_.insert("ThreadLocalBoolean");
-+
-+ // A complicated pickle derived struct that is all packed integers.
-+ ignored_record_names_.insert("Header");
-+
-+ // Part of the GPU system that uses multiple included header
-+ // weirdness. Never getting this right.
-+ ignored_record_names_.insert("Validators");
-+
-+ // Has a UNIT_TEST only constructor. Isn't *terribly* complex...
-+ ignored_record_names_.insert("AutocompleteController");
-+ ignored_record_names_.insert("HistoryURLProvider");
-+
-+ // Because of chrome frame
-+ ignored_record_names_.insert("ReliabilityTestSuite");
-+
-+ // Used over in the net unittests. A large enough bundle of integers with 1
-+ // non-pod class member. Probably harmless.
-+ ignored_record_names_.insert("MockTransaction");
-+
-+ // Used heavily in ui_unittests and once in views_unittests. Fixing this
-+ // isn't worth the overhead of an additional library.
-+ ignored_record_names_.insert("TestAnimationDelegate");
-+
-+ // Part of our public interface that nacl and friends use. (Arguably, this
-+ // should mean that this is a higher priority but fixing this looks hard.)
-+ ignored_record_names_.insert("PluginVersionInfo");
-+}
-+
-+std::string ChromeClassTester::GetNamespaceImpl(const DeclContext* context,
-+ const std::string& candidate) {
-+ switch (context->getDeclKind()) {
-+ case Decl::TranslationUnit: {
-+ return candidate;
-+ }
-+ case Decl::Namespace: {
-+ const NamespaceDecl* decl = dyn_cast<NamespaceDecl>(context);
-+ std::string name_str;
-+ llvm::raw_string_ostream OS(name_str);
-+ if (decl->isAnonymousNamespace())
-+ OS << "<anonymous namespace>";
-+ else
-+ OS << *decl;
-+ return GetNamespaceImpl(context->getParent(),
-+ OS.str());
-+ }
-+ default: {
-+ return GetNamespaceImpl(context->getParent(), candidate);
++class DummyOutput(object):
++
++ def __init__(self, type, language):
++ self.type = type
++ self.language = language
++
++ def GetType(self):
++ return self.type
++
++ def GetLanguage(self):
++ return self.language
++
++ def GetOutputFilename(self):
++ return 'hello.gif'
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/format/chrome_messages_json.py b/tools/grit/grit/format/chrome_messages_json.py
+new file mode 100644
+index 0000000000..88ec1d914b
+--- /dev/null
++++ b/tools/grit/grit/format/chrome_messages_json.py
+@@ -0,0 +1,59 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++"""Formats as a .json file that can be used to localize Google Chrome
++extensions."""
++
++from __future__ import print_function
++
++from json import JSONEncoder
++
++from grit import constants
++from grit.node import message
++
++def Format(root, lang='en', output_dir='.'):
++ """Format the messages as JSON."""
++ yield '{'
++
++ encoder = JSONEncoder(ensure_ascii=False)
++ format = '"%s":{"message":%s%s}'
++ placeholder_format = '"%i":{"content":"$%i"}'
++ first = True
++ for child in root.ActiveDescendants():
++ if isinstance(child, message.MessageNode):
++ id = child.attrs['name']
++ if id.startswith('IDR_') or id.startswith('IDS_'):
++ id = id[4:]
++
++ translation_missing = child.GetCliques()[0].clique.get(lang) is None;
++ if (child.ShouldFallbackToEnglish() and translation_missing and
++ lang != constants.FAKE_BIDI):
++ # Skip the string if it's not translated. Chrome will fallback
++ # to English automatically.
++ continue
++
++ loc_message = encoder.encode(child.ws_at_start + child.Translate(lang) +
++ child.ws_at_end)
++
++ # Replace $n place-holders with $n$ and add an appropriate "placeholders"
++ # entry. Note that chrome.i18n.getMessage only supports 9 placeholders:
++ # https://developer.chrome.com/extensions/i18n#method-getMessage
++ placeholders = ''
++ for i in range(1, 10):
++ if loc_message.find('$%d' % i) == -1:
++ break
++ loc_message = loc_message.replace('$%d' % i, '$%d$' % i)
++ if placeholders:
++ placeholders += ','
++ placeholders += placeholder_format % (i, i)
++
++ if not first:
++ yield ','
++ first = False
++
++ if placeholders:
++ placeholders = ',"placeholders":{%s}' % placeholders
++ yield format % (id, loc_message, placeholders)
++
++ yield '}'
+diff --git a/tools/grit/grit/format/chrome_messages_json_unittest.py b/tools/grit/grit/format/chrome_messages_json_unittest.py
+new file mode 100644
+index 0000000000..a54e6bdc1c
+--- /dev/null
++++ b/tools/grit/grit/format/chrome_messages_json_unittest.py
+@@ -0,0 +1,190 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++"""Unittest for chrome_messages_json.py.
++"""
++
++from __future__ import print_function
++
++import json
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++import unittest
++
++from six import StringIO
++
++from grit import grd_reader
++from grit import util
++from grit.tool import build
++
++class ChromeMessagesJsonFormatUnittest(unittest.TestCase):
++
++ # The default unittest diff limit is too low for our unittests.
++ # Allow the framework to show the full diff output all the time.
++ maxDiff = None
++
++ def testMessages(self):
++ root = util.ParseGrdForUnittest(u"""
++ <messages>
++ <message name="IDS_SIMPLE_MESSAGE">
++ Simple message.
++ </message>
++ <message name="IDS_QUOTES">
++ element\u2019s \u201c<ph name="NAME">%s<ex>name</ex></ph>\u201d attribute
++ </message>
++ <message name="IDS_PLACEHOLDERS">
++ <ph name="ERROR_COUNT">%1$d<ex>1</ex></ph> error, <ph name="WARNING_COUNT">%2$d<ex>1</ex></ph> warning
++ </message>
++ <message name="IDS_PLACEHOLDERS_SUBSTITUTED_BY_GETMESSAGE">
++ <ph name="BEGIN">$1<ex>a</ex></ph>test<ph name="END">$2<ex>b</ex></ph>
++ </message>
++ <message name="IDS_STARTS_WITH_SPACE">
++ ''' (<ph name="COUNT">%d<ex>2</ex></ph>)
++ </message>
++ <message name="IDS_ENDS_WITH_SPACE">
++ (<ph name="COUNT">%d<ex>2</ex></ph>) '''
++ </message>
++ <message name="IDS_SPACE_AT_BOTH_ENDS">
++ ''' (<ph name="COUNT">%d<ex>2</ex></ph>) '''
++ </message>
++ <message name="IDS_DOUBLE_QUOTES">
++ A "double quoted" message.
++ </message>
++ <message name="IDS_BACKSLASH">
++ \\
++ </message>
++ </messages>
++ """)
++
++ buf = StringIO()
++ build.RcBuilder.ProcessNode(root, DummyOutput('chrome_messages_json', 'en'),
++ buf)
++ output = buf.getvalue()
++ test = u"""
++{
++ "SIMPLE_MESSAGE": {
++ "message": "Simple message."
++ },
++ "QUOTES": {
++ "message": "element\u2019s \u201c%s\u201d attribute"
++ },
++ "PLACEHOLDERS": {
++ "message": "%1$d error, %2$d warning"
++ },
++ "PLACEHOLDERS_SUBSTITUTED_BY_GETMESSAGE": {
++ "message": "$1$test$2$",
++ "placeholders": {
++ "1": {
++ "content": "$1"
++ },
++ "2": {
++ "content": "$2"
++ }
+ }
++ },
++ "STARTS_WITH_SPACE": {
++ "message": " (%d)"
++ },
++ "ENDS_WITH_SPACE": {
++ "message": "(%d) "
++ },
++ "SPACE_AT_BOTH_ENDS": {
++ "message": " (%d) "
++ },
++ "DOUBLE_QUOTES": {
++ "message": "A \\"double quoted\\" message."
++ },
++ "BACKSLASH": {
++ "message": "\\\\"
+ }
+}
-+
-+bool ChromeClassTester::InBannedDirectory(SourceLocation loc) {
-+ std::string filename;
-+ if (!GetFilename(loc, &filename)) {
-+ // If the filename cannot be determined, simply treat this as a banned
-+ // location, instead of going through the full lookup process.
-+ return true;
++"""
++ self.assertEqual(json.loads(test), json.loads(output))
++
++ def testTranslations(self):
++ root = util.ParseGrdForUnittest("""
++ <messages>
++ <message name="ID_HELLO">Hello!</message>
++ <message name="ID_HELLO_USER">Hello <ph name="USERNAME">%s<ex>
++ Joi</ex></ph></message>
++ </messages>
++ """)
++
++ buf = StringIO()
++ build.RcBuilder.ProcessNode(root, DummyOutput('chrome_messages_json', 'fr'),
++ buf)
++ output = buf.getvalue()
++ test = u"""
++{
++ "ID_HELLO": {
++ "message": "H\u00e9P\u00e9ll\u00f4P\u00f4!"
++ },
++ "ID_HELLO_USER": {
++ "message": "H\u00e9P\u00e9ll\u00f4P\u00f4 %s"
+ }
++}
++"""
++ self.assertEqual(json.loads(test), json.loads(output))
++
++ def testSkipMissingTranslations(self):
++ grd = """<?xml version="1.0" encoding="UTF-8"?>
++<grit latest_public_release="2" current_release="3" source_lang_id="en"
++ base_dir="%s">
++ <outputs>
++ </outputs>
++ <release seq="3" allow_pseudo="False">
++ <messages fallback_to_english="true">
++ <message name="ID_HELLO_NO_TRANSLATION">Hello not translated</message>
++ </messages>
++ </release>
++</grit>"""
++ root = grd_reader.Parse(StringIO(grd), dir=".")
++
++ buf = StringIO()
++ build.RcBuilder.ProcessNode(root, DummyOutput('chrome_messages_json', 'fr'),
++ buf)
++ output = buf.getvalue()
++ test = u'{}'
++ self.assertEqual(test, output)
++
++ def testVerifyMinification(self):
++ root = util.ParseGrdForUnittest(u"""
++ <messages>
++ <message name="IDS">
++ <ph name="BEGIN">$1<ex>a</ex></ph>test<ph name="END">$2<ex>b</ex></ph>
++ </message>
++ </messages>
++ """)
++
++ buf = StringIO()
++ build.RcBuilder.ProcessNode(root, DummyOutput('chrome_messages_json', 'en'),
++ buf)
++ output = buf.getvalue()
++ test = (u'{"IDS":{"message":"$1$test$2$","placeholders":'
++ u'{"1":{"content":"$1"},"2":{"content":"$2"}}}}')
++ self.assertEqual(test, output)
++
++
++class DummyOutput(object):
++
++ def __init__(self, type, language):
++ self.type = type
++ self.language = language
++
++ def GetType(self):
++ return self.type
++
++ def GetLanguage(self):
++ return self.language
++
++ def GetOutputFilename(self):
++ return 'hello.gif'
+
-+ // We need to special case scratch space; which is where clang does its
-+ // macro expansion. We explicitly want to allow people to do otherwise bad
-+ // things through macros that were defined due to third party libraries.
-+ if (filename == "<scratch space>")
-+ return true;
+
-+ // Don't complain about autogenerated protobuf files.
-+ if (ends_with(filename, ".pb.h")) {
-+ return true;
-+ }
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/format/data_pack.py b/tools/grit/grit/format/data_pack.py
+new file mode 100644
+index 0000000000..f7128a4725
+--- /dev/null
++++ b/tools/grit/grit/format/data_pack.py
+@@ -0,0 +1,321 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
+
-+ // We need to munge the paths so that they are relative to the repository
-+ // srcroot. We first resolve the symlinktastic relative path and then
-+ // remove our known srcroot from it if needed.
-+ char resolvedPath[MAXPATHLEN];
-+ if (realpath(filename.c_str(), resolvedPath)) {
-+ filename = resolvedPath;
-+ }
++"""Support for formatting a data pack file used for platform agnostic resource
++files.
++"""
++
++from __future__ import print_function
++
++import collections
++import os
++import struct
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
+
-+ // On linux, chrome is often checked out to /usr/local/google. Due to the
-+ // "usr" rule in banned_directories_, all diagnostics would be suppressed
-+ // in that case. As a workaround, strip that prefix.
-+ filename = lstrip(filename, "/usr/local/google");
-+
-+ for (std::vector<std::string>::const_iterator it =
-+ banned_directories_.begin();
-+ it != banned_directories_.end(); ++it) {
-+ // If we can find any of the banned path components in this path, then
-+ // this file is rejected.
-+ size_t index = filename.find(*it);
-+ if (index != std::string::npos) {
-+ bool matches_full_dir_name = index == 0 || filename[index - 1] == '/';
-+ if ((*it)[0] == '/')
-+ matches_full_dir_name = true;
-+ if (matches_full_dir_name)
-+ return true;
++import six
++
++from grit import util
++from grit.node import include
++from grit.node import message
++from grit.node import structure
++
++
++PACK_FILE_VERSION = 5
++BINARY, UTF8, UTF16 = range(3)
++
++
++GrdInfoItem = collections.namedtuple('GrdInfoItem',
++ ['textual_id', 'id', 'path'])
++
++
++class WrongFileVersion(Exception):
++ pass
++
++
++class CorruptDataPack(Exception):
++ pass
++
++
++class DataPackSizes(object):
++ def __init__(self, header, id_table, alias_table, data):
++ self.header = header
++ self.id_table = id_table
++ self.alias_table = alias_table
++ self.data = data
++
++ @property
++ def total(self):
++ return sum(v for v in self.__dict__.values())
++
++ def __iter__(self):
++ yield ('header', self.header)
++ yield ('id_table', self.id_table)
++ yield ('alias_table', self.alias_table)
++ yield ('data', self.data)
++
++ def __eq__(self, other):
++ return self.__dict__ == other.__dict__
++
++ def __repr__(self):
++ return self.__class__.__name__ + repr(self.__dict__)
++
++
++class DataPackContents(object):
++ def __init__(self, resources, encoding, version, aliases, sizes):
++ # Map of resource_id -> str.
++ self.resources = resources
++ # Encoding (int).
++ self.encoding = encoding
++ # Version (int).
++ self.version = version
++ # Map of resource_id->canonical_resource_id
++ self.aliases = aliases
++ # DataPackSizes instance.
++ self.sizes = sizes
++
++
++def Format(root, lang='en', output_dir='.'):
++ """Writes out the data pack file format (platform agnostic resource file)."""
++ id_map = root.GetIdMap()
++ data = {}
++ root.info = []
++ for node in root.ActiveDescendants():
++ with node:
++ if isinstance(node, (include.IncludeNode, message.MessageNode,
++ structure.StructureNode)):
++ value = node.GetDataPackValue(lang, util.BINARY)
++ if value is not None:
++ resource_id = id_map[node.GetTextualIds()[0]]
++ data[resource_id] = value
++ root.info.append('{},{},{}'.format(
++ node.attrs.get('name'), resource_id, node.source))
++ return WriteDataPackToString(data, UTF8)
++
++
++def ReadDataPack(input_file):
++ return ReadDataPackFromString(util.ReadFile(input_file, util.BINARY))
++
++
++def ReadDataPackFromString(data):
++ """Reads a data pack file and returns a dictionary."""
++ # Read the header.
++ version = struct.unpack('<I', data[:4])[0]
++ if version == 4:
++ resource_count, encoding = struct.unpack('<IB', data[4:9])
++ alias_count = 0
++ header_size = 9
++ elif version == 5:
++ encoding, resource_count, alias_count = struct.unpack('<BxxxHH', data[4:12])
++ header_size = 12
++ else:
++ raise WrongFileVersion('Found version: ' + str(version))
++
++ resources = {}
++ kIndexEntrySize = 2 + 4 # Each entry is a uint16 and a uint32.
++ def entry_at_index(idx):
++ offset = header_size + idx * kIndexEntrySize
++ return struct.unpack('<HI', data[offset:offset + kIndexEntrySize])
++
++ prev_resource_id, prev_offset = entry_at_index(0)
++ for i in range(1, resource_count + 1):
++ resource_id, offset = entry_at_index(i)
++ resources[prev_resource_id] = data[prev_offset:offset]
++ prev_resource_id, prev_offset = resource_id, offset
++
++ id_table_size = (resource_count + 1) * kIndexEntrySize
++ # Read the alias table.
++ kAliasEntrySize = 2 + 2 # uint16, uint16
++ def alias_at_index(idx):
++ offset = header_size + id_table_size + idx * kAliasEntrySize
++ return struct.unpack('<HH', data[offset:offset + kAliasEntrySize])
++
++ aliases = {}
++ for i in range(alias_count):
++ resource_id, index = alias_at_index(i)
++ aliased_id = entry_at_index(index)[0]
++ aliases[resource_id] = aliased_id
++ resources[resource_id] = resources[aliased_id]
++
++ alias_table_size = kAliasEntrySize * alias_count
++ sizes = DataPackSizes(
++ header_size, id_table_size, alias_table_size,
++ len(data) - header_size - id_table_size - alias_table_size)
++ assert sizes.total == len(data), 'original={} computed={}'.format(
++ len(data), sizes.total)
++ return DataPackContents(resources, encoding, version, aliases, sizes)
++
++
++def WriteDataPackToString(resources, encoding):
++ """Returns bytes with a map of id=>data in the data pack format."""
++ ret = []
++
++ # Compute alias map.
++ resource_ids = sorted(resources)
++ # Use reversed() so that for duplicates lower IDs clobber higher ones.
++ id_by_data = {resources[k]: k for k in reversed(resource_ids)}
++ # Map of resource_id -> resource_id, where value < key.
++ alias_map = {k: id_by_data[v] for k, v in resources.items()
++ if id_by_data[v] != k}
++
++ # Write file header.
++ resource_count = len(resources) - len(alias_map)
++ # Padding bytes added for alignment.
++ ret.append(struct.pack('<IBxxxHH', PACK_FILE_VERSION, encoding,
++ resource_count, len(alias_map)))
++ HEADER_LENGTH = 4 + 4 + 2 + 2
++
++ # Each main table entry is: uint16 + uint32 (and an extra entry at the end).
++ # Each alias table entry is: uint16 + uint16.
++ data_offset = HEADER_LENGTH + (resource_count + 1) * 6 + len(alias_map) * 4
++
++ # Write main table.
++ index_by_id = {}
++ deduped_data = []
++ index = 0
++ for resource_id in resource_ids:
++ if resource_id in alias_map:
++ continue
++ data = resources[resource_id]
++ if isinstance(data, six.text_type):
++ data = data.encode('utf-8')
++ index_by_id[resource_id] = index
++ ret.append(struct.pack('<HI', resource_id, data_offset))
++ data_offset += len(data)
++ deduped_data.append(data)
++ index += 1
++
++ assert index == resource_count
++ # Add an extra entry at the end.
++ ret.append(struct.pack('<HI', 0, data_offset))
++
++ # Write alias table.
++ for resource_id in sorted(alias_map):
++ index = index_by_id[alias_map[resource_id]]
++ ret.append(struct.pack('<HH', resource_id, index))
++
++ # Write data.
++ ret.extend(deduped_data)
++ return b''.join(ret)
++
++
++def WriteDataPack(resources, output_file, encoding):
++ """Writes a map of id=>data into output_file as a data pack."""
++ content = WriteDataPackToString(resources, encoding)
++ with open(output_file, 'wb') as file:
++ file.write(content)
++
++
++def ReadGrdInfo(grd_file):
++ info_dict = {}
++ with open(grd_file + '.info', 'rt') as f:
++ for line in f:
++ item = GrdInfoItem._make(line.strip().split(','))
++ info_dict[int(item.id)] = item
++ return info_dict
++
++
++def RePack(output_file, input_files, whitelist_file=None,
++ suppress_removed_key_output=False,
++ output_info_filepath=None):
++ """Write a new data pack file by combining input pack files.
++
++ Args:
++ output_file: path to the new data pack file.
++ input_files: a list of paths to the data pack files to combine.
++ whitelist_file: path to the file that contains the list of resource IDs
++ that should be kept in the output file or None to include
++ all resources.
++ suppress_removed_key_output: allows the caller to suppress the output from
++ RePackFromDataPackStrings.
++ output_info_file: If not None, specify the output .info filepath.
++
++ Raises:
++ KeyError: if there are duplicate keys or resource encoding is
++ inconsistent.
++ """
++ input_data_packs = [ReadDataPack(filename) for filename in input_files]
++ input_info_files = [filename + '.info' for filename in input_files]
++ whitelist = None
++ if whitelist_file:
++ lines = util.ReadFile(whitelist_file, 'utf-8').strip().splitlines()
++ if not lines:
++ raise Exception('Whitelist file should not be empty')
++ whitelist = set(int(x) for x in lines)
++ inputs = [(p.resources, p.encoding) for p in input_data_packs]
++ resources, encoding = RePackFromDataPackStrings(
++ inputs, whitelist, suppress_removed_key_output)
++ WriteDataPack(resources, output_file, encoding)
++ if output_info_filepath is None:
++ output_info_filepath = output_file + '.info'
++ with open(output_info_filepath, 'w') as output_info_file:
++ for filename in input_info_files:
++ with open(filename, 'r') as info_file:
++ output_info_file.writelines(info_file.readlines())
++
++
++def RePackFromDataPackStrings(inputs, whitelist,
++ suppress_removed_key_output=False):
++ """Combines all inputs into one.
++
++ Args:
++ inputs: a list of (resources_by_id, encoding) tuples to be combined.
++ whitelist: a list of resource IDs that should be kept in the output string
++ or None to include all resources.
++ suppress_removed_key_output: Do not print removed keys.
++
++ Returns:
++ Returns (resources_by_id, encoding).
++
++ Raises:
++ KeyError: if there are duplicate keys or resource encoding is
++ inconsistent.
++ """
++ resources = {}
++ encoding = None
++ for input_resources, input_encoding in inputs:
++ # Make sure we have no dups.
++ duplicate_keys = set(input_resources.keys()) & set(resources.keys())
++ if duplicate_keys:
++ raise KeyError('Duplicate keys: ' + str(list(duplicate_keys)))
++
++ # Make sure encoding is consistent.
++ if encoding in (None, BINARY):
++ encoding = input_encoding
++ elif input_encoding not in (BINARY, encoding):
++ raise KeyError('Inconsistent encodings: ' + str(encoding) +
++ ' vs ' + str(input_encoding))
++
++ if whitelist:
++ whitelisted_resources = dict([(key, input_resources[key])
++ for key in input_resources.keys()
++ if key in whitelist])
++ resources.update(whitelisted_resources)
++ removed_keys = [key for key in input_resources.keys()
++ if key not in whitelist]
++ if not suppress_removed_key_output:
++ for key in removed_keys:
++ print('RePackFromDataPackStrings Removed Key:', key)
++ else:
++ resources.update(input_resources)
++
++ # Encoding is 0 for BINARY, 1 for UTF8 and 2 for UTF16
++ if encoding is None:
++ encoding = BINARY
++ return resources, encoding
++
++
++def main():
++ # Write a simple file.
++ data = {1: '', 4: 'this is id 4', 6: 'this is id 6', 10: ''}
++ WriteDataPack(data, 'datapack1.pak', UTF8)
++ data2 = {1000: 'test', 5: 'five'}
++ WriteDataPack(data2, 'datapack2.pak', UTF8)
++ print('wrote datapack1 and datapack2 to current directory.')
++
++
++if __name__ == '__main__':
++ main()
+diff --git a/tools/grit/grit/format/data_pack_unittest.py b/tools/grit/grit/format/data_pack_unittest.py
+new file mode 100644
+index 0000000000..fcd7035473
+--- /dev/null
++++ b/tools/grit/grit/format/data_pack_unittest.py
+@@ -0,0 +1,102 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit tests for grit.format.data_pack'''
++
++from __future__ import print_function
++
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++import unittest
++
++from grit.format import data_pack
++
++
++class FormatDataPackUnittest(unittest.TestCase):
++ def testReadDataPackV4(self):
++ expected_data = (
++ b'\x04\x00\x00\x00' # header(version
++ b'\x04\x00\x00\x00' # no. entries,
++ b'\x01' # encoding)
++ b'\x01\x00\x27\x00\x00\x00' # index entry 1
++ b'\x04\x00\x27\x00\x00\x00' # index entry 4
++ b'\x06\x00\x33\x00\x00\x00' # index entry 6
++ b'\x0a\x00\x3f\x00\x00\x00' # index entry 10
++ b'\x00\x00\x3f\x00\x00\x00' # extra entry for the size of last
++ b'this is id 4this is id 6') # data
++ expected_data_pack = data_pack.DataPackContents(
++ {
++ 1: b'',
++ 4: b'this is id 4',
++ 6: b'this is id 6',
++ 10: b'',
++ }, data_pack.UTF8, 4, {}, data_pack.DataPackSizes(9, 30, 0, 24))
++ loaded = data_pack.ReadDataPackFromString(expected_data)
++ self.assertDictEqual(expected_data_pack.__dict__, loaded.__dict__)
++
++ def testReadWriteDataPackV5(self):
++ expected_data = (
++ b'\x05\x00\x00\x00' # version
++ b'\x01\x00\x00\x00' # encoding & padding
++ b'\x03\x00' # resource_count
++ b'\x01\x00' # alias_count
++ b'\x01\x00\x28\x00\x00\x00' # index entry 1
++ b'\x04\x00\x28\x00\x00\x00' # index entry 4
++ b'\x06\x00\x34\x00\x00\x00' # index entry 6
++ b'\x00\x00\x40\x00\x00\x00' # extra entry for the size of last
++ b'\x0a\x00\x01\x00' # alias table
++ b'this is id 4this is id 6') # data
++ input_resources = {
++ 1: b'',
++ 4: b'this is id 4',
++ 6: b'this is id 6',
++ 10: b'this is id 4',
+ }
-+ }
++ data = data_pack.WriteDataPackToString(input_resources, data_pack.UTF8)
++ self.assertEquals(data, expected_data)
++
++ expected_data_pack = data_pack.DataPackContents({
++ 1: b'',
++ 4: input_resources[4],
++ 6: input_resources[6],
++ 10: input_resources[4],
++ }, data_pack.UTF8, 5, {10: 4}, data_pack.DataPackSizes(12, 24, 4, 24))
++ loaded = data_pack.ReadDataPackFromString(expected_data)
++ self.assertDictEqual(expected_data_pack.__dict__, loaded.__dict__)
++
++ def testRePackUnittest(self):
++ expected_with_whitelist = {
++ 1: 'Never gonna', 10: 'give you up', 20: 'Never gonna let',
++ 30: 'you down', 40: 'Never', 50: 'gonna run around and',
++ 60: 'desert you'}
++ expected_without_whitelist = {
++ 1: 'Never gonna', 10: 'give you up', 20: 'Never gonna let', 65: 'Close',
++ 30: 'you down', 40: 'Never', 50: 'gonna run around and', 4: 'click',
++ 60: 'desert you', 6: 'chirr', 32: 'oops, try again', 70: 'Awww, snap!'}
++ inputs = [{1: 'Never gonna', 4: 'click', 6: 'chirr', 10: 'give you up'},
++ {20: 'Never gonna let', 30: 'you down', 32: 'oops, try again'},
++ {40: 'Never', 50: 'gonna run around and', 60: 'desert you'},
++ {65: 'Close', 70: 'Awww, snap!'}]
++ whitelist = [1, 10, 20, 30, 40, 50, 60]
++ inputs = [(i, data_pack.UTF8) for i in inputs]
++
++ # RePack using whitelist
++ output, _ = data_pack.RePackFromDataPackStrings(
++ inputs, whitelist, suppress_removed_key_output=True)
++ self.assertDictEqual(expected_with_whitelist, output,
++ 'Incorrect resource output')
++
++ # RePack a None whitelist
++ output, _ = data_pack.RePackFromDataPackStrings(
++ inputs, None, suppress_removed_key_output=True)
++ self.assertDictEqual(expected_without_whitelist, output,
++ 'Incorrect resource output')
+
-+ return false;
-+}
+
-+bool ChromeClassTester::IsIgnoredType(const std::string& base_name) {
-+ return ignored_record_names_.find(base_name) != ignored_record_names_.end();
-+}
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/format/gen_predetermined_ids.py b/tools/grit/grit/format/gen_predetermined_ids.py
+new file mode 100644
+index 0000000000..9b2aa7b1a5
+--- /dev/null
++++ b/tools/grit/grit/format/gen_predetermined_ids.py
+@@ -0,0 +1,144 @@
++#!/usr/bin/env python
++# Copyright 2017 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
+
-+bool ChromeClassTester::GetFilename(SourceLocation loc,
-+ std::string* filename) {
-+ const SourceManager& source_manager = instance_.getSourceManager();
-+ SourceLocation spelling_location = source_manager.getSpellingLoc(loc);
-+ PresumedLoc ploc = source_manager.getPresumedLoc(spelling_location);
-+ if (ploc.isInvalid()) {
-+ // If we're in an invalid location, we're looking at things that aren't
-+ // actually stated in the source.
-+ return false;
-+ }
++"""
++A tool to generate a predetermined resource ids file that can be used as an
++input to grit via the -p option. This is meant to be run manually every once in
++a while and its output checked in. See tools/gritsettings/README.md for details.
++"""
+
-+ *filename = ploc.getFilename();
-+ return true;
-+}
-diff --git a/tools/clang/plugins/ChromeClassTester.h b/tools/clang/plugins/ChromeClassTester.h
++from __future__ import print_function
++
++import os
++import re
++import sys
++
++# Regular expression for parsing the #define macro format. Matches both the
++# version of the macro with whitelist support and the one without. For example,
++# Without generate whitelist flag:
++# #define IDS_FOO_MESSAGE 1234
++# With generate whitelist flag:
++# #define IDS_FOO_MESSAGE (::ui::WhitelistedResource<1234>(), 1234)
++RESOURCE_EXTRACT_REGEX = re.compile(r'^#define (\S*).* (\d+)\)?$', re.MULTILINE)
++
++ORDERED_RESOURCE_IDS_REGEX = re.compile(r'^Resource=(\d*)$', re.MULTILINE)
++
++
++def _GetResourceNameIdPairsIter(string_to_scan):
++ """Gets an iterator of the resource name and id pairs of the given string.
++
++ Scans the input string for lines of the form "#define NAME ID" and returns
++ an iterator over all matching (NAME, ID) pairs.
++
++ Args:
++ string_to_scan: The input string to scan.
++
++ Yields:
++ A tuple of name and id.
++ """
++ for match in RESOURCE_EXTRACT_REGEX.finditer(string_to_scan):
++ yield match.group(1, 2)
++
++
++def _ReadOrderedResourceIds(path):
++ """Reads ordered resource ids from the given file.
++
++ The resources are expected to be of the format produced by running Chrome
++ with --print-resource-ids command line.
++
++ Args:
++ path: File path to read resource ids from.
++
++ Returns:
++ An array of ordered resource ids.
++ """
++ ordered_resource_ids = []
++ with open(path, "r") as f:
++ for match in ORDERED_RESOURCE_IDS_REGEX.finditer(f.read()):
++ ordered_resource_ids.append(int(match.group(1)))
++ return ordered_resource_ids
++
++
++def GenerateResourceMapping(original_resources, ordered_resource_ids):
++ """Generates a resource mapping from the ordered ids and the original mapping.
++
++ The returned dict will assign new ids to ordered_resource_ids numerically
++ increasing from 101.
++
++ Args:
++ original_resources: A dict of original resource ids to resource names.
++ ordered_resource_ids: An array of ordered resource ids.
++
++ Returns:
++ A dict of resource ids to resource names.
++ """
++ output_resource_map = {}
++ # 101 is used as the starting value since other parts of GRIT require it to be
++ # the minimum (e.g. rc_header.py) based on Windows resource numbering.
++ next_id = 101
++ for original_id in ordered_resource_ids:
++ resource_name = original_resources[original_id]
++ output_resource_map[next_id] = resource_name
++ next_id += 1
++ return output_resource_map
++
++
++def ReadResourceIdsFromFile(file, original_resources):
++ """Reads resource ids from a GRIT-produced header file.
++
++ Args:
++ file: File to a GRIT-produced header file to read from.
++ original_resources: Dict of resource ids to resource names to add to.
++ """
++ for resource_name, resource_id in _GetResourceNameIdPairsIter(file.read()):
++ original_resources[int(resource_id)] = resource_name
++
++
++def _ReadOriginalResourceIds(out_dir):
++ """Reads resource ids from GRIT header files in the specified directory.
++
++ Args:
++ out_dir: A Chrome build output directory (e.g. out/gn) to scan.
++
++ Returns:
++ A dict of resource ids to resource names.
++ """
++ original_resources = {}
++ for root, dirnames, filenames in os.walk(out_dir + '/gen'):
++ for filename in filenames:
++ if filename.endswith(('_resources.h', '_settings.h', '_strings.h')):
++ with open(os.path.join(root, filename), "r") as f:
++ ReadResourceIdsFromFile(f, original_resources)
++ return original_resources
++
++
++def _GeneratePredeterminedIdsFile(ordered_resources_file, out_dir):
++ """Generates a predetermined ids file.
++
++ Args:
++ ordered_resources_file: File path to read ordered resource ids from.
++ out_dir: A Chrome build output directory (e.g. out/gn) to scan.
++
++ Returns:
++ A dict of resource ids to resource names.
++ """
++ original_resources = _ReadOriginalResourceIds(out_dir)
++ ordered_resource_ids = _ReadOrderedResourceIds(ordered_resources_file)
++ output_resource_map = GenerateResourceMapping(original_resources,
++ ordered_resource_ids)
++ for res_id in sorted(output_resource_map.keys()):
++ print(output_resource_map[res_id], res_id)
++
++
++def main(argv):
++ if len(argv) != 2:
++ print("usage: gen_predetermined_ids.py <ordered_resources_file> <out_dir>")
++ sys.exit(1)
++ ordered_resources_file, out_dir = argv[0], argv[1]
++ _GeneratePredeterminedIdsFile(ordered_resources_file, out_dir)
++
++
++if '__main__' == __name__:
++ main(sys.argv[1:])
+diff --git a/tools/grit/grit/format/gen_predetermined_ids_unittest.py b/tools/grit/grit/format/gen_predetermined_ids_unittest.py
+new file mode 100644
+index 0000000000..bd0331adb4
+--- /dev/null
++++ b/tools/grit/grit/format/gen_predetermined_ids_unittest.py
+@@ -0,0 +1,46 @@
++#!/usr/bin/env python
++# Copyright 2017 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit tests for the gen_predetermined_ids module.'''
++
++from __future__ import print_function
++
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++import unittest
++
++from six import StringIO
++
++from grit.format import gen_predetermined_ids
++
++class GenPredeterminedIdsUnittest(unittest.TestCase):
++ def testGenerateResourceMapping(self):
++ original_resources = {200: 'A', 201: 'B', 300: 'C', 350: 'D', 370: 'E'}
++ ordered_resource_ids = [300, 201, 370]
++ mapping = gen_predetermined_ids.GenerateResourceMapping(
++ original_resources, ordered_resource_ids)
++ self.assertEqual({101: 'C', 102: 'B', 103: 'E'}, mapping)
++
++ def testReadResourceIdsFromFile(self):
++ f = StringIO('''
++// This file is automatically generated by GRIT. Do not edit.
++
++#pragma once
++
++#define IDS_BOOKMARKS_NO_ITEMS 12500
++#define IDS_BOOKMARK_BAR_IMPORT_LINK (::ui::WhitelistedResource<12501>(), 12501)
++#define IDS_BOOKMARK_X (::ui::WhitelistedResource<12502>(), 12502)
++''')
++ resources = {}
++ gen_predetermined_ids.ReadResourceIdsFromFile(f, resources)
++ self.assertEqual({12500: 'IDS_BOOKMARKS_OPEN_ALL',
++ 12501: 'IDS_BOOKMARKS_OPEN_ALL_INCOGNITO',
++ 12502: 'IDS_BOOKMARK_X'}, resources)
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/format/gzip_string.py b/tools/grit/grit/format/gzip_string.py
new file mode 100644
-index 0000000000..588ae9cae5
+index 0000000000..3cd17185c9
--- /dev/null
-+++ b/tools/clang/plugins/ChromeClassTester.h
-@@ -0,0 +1,84 @@
-+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+// Use of this source code is governed by a BSD-style license that can be
-+// found in the LICENSE file.
++++ b/tools/grit/grit/format/gzip_string.py
+@@ -0,0 +1,46 @@
++# Copyright (c) 2016 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++"""Provides gzip utilities for strings.
++"""
+
-+#ifndef TOOLS_CLANG_PLUGINS_CHROMECLASSTESTER_H_
-+#define TOOLS_CLANG_PLUGINS_CHROMECLASSTESTER_H_
++from __future__ import print_function
+
-+#include <set>
-+#include <vector>
++import gzip
++import io
++import subprocess
+
-+#include "clang/AST/ASTConsumer.h"
-+#include "clang/AST/TypeLoc.h"
-+#include "clang/Frontend/CompilerInstance.h"
+
-+// A class on top of ASTConsumer that forwards classes defined in Chromium
-+// headers to subclasses which implement CheckChromeClass().
-+class ChromeClassTester : public clang::ASTConsumer {
-+ public:
-+ explicit ChromeClassTester(clang::CompilerInstance& instance);
-+ virtual ~ChromeClassTester();
++def GzipStringRsyncable(data):
++ # Make call to host system's gzip to get access to --rsyncable option. This
++ # option makes updates much smaller - if one line is changed in the resource,
++ # it won't have to push the entire compressed resource with the update.
++ # Instead, --rsyncable breaks the file into small chunks, so that one doesn't
++ # affect the other in compression, and then only that chunk will have to be
++ # updated.
++ gzip_proc = subprocess.Popen(['gzip', '--stdout', '--rsyncable',
++ '--best', '--no-name'],
++ stdin=subprocess.PIPE,
++ stdout=subprocess.PIPE,
++ stderr=subprocess.PIPE)
++ data, stderr = gzip_proc.communicate(data)
++ if gzip_proc.returncode != 0:
++ raise subprocess.CalledProcessError(gzip_proc.returncode, 'gzip',
++ stderr)
++ return data
++
++
++def GzipString(data):
++ # Gzipping using Python's built in gzip: Windows doesn't ship with gzip, and
++ # OSX's gzip does not have an --rsyncable option built in. Although this is
++ # not preferable to --rsyncable, it is an option for the systems that do
++ # not have --rsyncable. If used over GzipStringRsyncable, the primary
++ # difference of this function's compression will be larger updates every time
++ # a compressed resource is changed.
++ gzip_output = io.BytesIO()
++ with gzip.GzipFile(mode='wb', compresslevel=9, fileobj=gzip_output,
++ mtime=0) as gzip_file:
++ gzip_file.write(data)
++ data = gzip_output.getvalue()
++ gzip_output.close()
++ return data
+diff --git a/tools/grit/grit/format/gzip_string_unittest.py b/tools/grit/grit/format/gzip_string_unittest.py
+new file mode 100644
+index 0000000000..c0cfbe1837
+--- /dev/null
++++ b/tools/grit/grit/format/gzip_string_unittest.py
+@@ -0,0 +1,65 @@
++#!/usr/bin/env python
++# Copyright (c) 2016 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
+
-+ // clang::ASTConsumer:
-+ virtual void HandleTagDeclDefinition(clang::TagDecl* tag);
-+ virtual bool HandleTopLevelDecl(clang::DeclGroupRef group_ref);
++'''Unit tests for grit.format.gzip_string'''
+
-+ protected:
-+ clang::CompilerInstance& instance() { return instance_; }
-+ clang::DiagnosticsEngine& diagnostic() { return diagnostic_; }
++from __future__ import print_function
+
-+ // Emits a simple warning; this shouldn't be used if you require printf-style
-+ // printing.
-+ void emitWarning(clang::SourceLocation loc, const char* error);
++import gzip
++import io
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
+
-+ // Utility method for subclasses to check if this class is in a banned
-+ // namespace.
-+ bool InBannedNamespace(const clang::Decl* record);
++import unittest
+
-+ // Utility method for subclasses to determine the namespace of the
-+ // specified record, if any. Unnamed namespaces will be identified as
-+ // "<anonymous namespace>".
-+ std::string GetNamespace(const clang::Decl* record);
++from grit.format import gzip_string
+
-+ // Utility method for subclasses to check if this class is within an
-+ // implementation (.cc, .cpp, .mm) file.
-+ bool InImplementationFile(clang::SourceLocation location);
+
-+ private:
-+ void BuildBannedLists();
++class FormatGzipStringUnittest(unittest.TestCase):
+
-+ void CheckTag(clang::TagDecl*);
++ def testGzipStringRsyncable(self):
++ # Can only test the rsyncable version on platforms which support rsyncable,
++ # which at the moment is Linux.
++ if sys.platform == 'linux2':
++ header_begin = (b'\x1f\x8b') # gzip first two bytes
++ input = (b'TEST STRING STARTING NOW'
++ b'continuing'
++ b'<even more>'
++ b'<finished NOW>')
+
-+ // Filtered versions of tags that are only called with things defined in
-+ // chrome header files.
-+ virtual void CheckChromeClass(clang::SourceLocation record_location,
-+ clang::CXXRecordDecl* record) = 0;
++ compressed = gzip_string.GzipStringRsyncable(input)
++ self.failUnless(header_begin == compressed[:2])
+
-+ // Utility methods used for filtering out non-chrome classes (and ones we
-+ // deliberately ignore) in HandleTagDeclDefinition().
-+ std::string GetNamespaceImpl(const clang::DeclContext* context,
-+ const std::string& candidate);
-+ bool InBannedDirectory(clang::SourceLocation loc);
-+ bool IsIgnoredType(const std::string& base_name);
++ compressed_file = io.BytesIO()
++ compressed_file.write(compressed)
++ compressed_file.seek(0)
+
-+ // Attempts to determine the filename for the given SourceLocation.
-+ // Returns false if the filename could not be determined.
-+ bool GetFilename(clang::SourceLocation loc, std::string* filename);
++ with gzip.GzipFile(mode='rb', fileobj=compressed_file) as f:
++ output = f.read()
++ self.failUnless(output == input)
+
-+ clang::CompilerInstance& instance_;
-+ clang::DiagnosticsEngine& diagnostic_;
++ def testGzipString(self):
++ header_begin = b'\x1f\x8b' # gzip first two bytes
++ input = (b'TEST STRING STARTING NOW'
++ b'continuing'
++ b'<even more>'
++ b'<finished NOW>')
+
-+ // List of banned namespaces.
-+ std::vector<std::string> banned_namespaces_;
++ compressed = gzip_string.GzipString(input)
++ self.failUnless(header_begin == compressed[:2])
+
-+ // List of banned directories.
-+ std::vector<std::string> banned_directories_;
++ compressed_file = io.BytesIO()
++ compressed_file.write(compressed)
++ compressed_file.seek(0)
+
-+ // List of types that we don't check.
-+ std::set<std::string> ignored_record_names_;
++ with gzip.GzipFile(mode='rb', fileobj=compressed_file) as f:
++ output = f.read()
++ self.failUnless(output == input)
+
-+ // List of decls to check once the current top-level decl is parsed.
-+ std::vector<clang::TagDecl*> pending_class_decls_;
-+};
+
-+#endif // TOOLS_CLANG_PLUGINS_CHROMECLASSTESTER_H_
-diff --git a/tools/clang/plugins/FindBadConstructs.cpp b/tools/clang/plugins/FindBadConstructs.cpp
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/format/html_inline.py b/tools/grit/grit/format/html_inline.py
new file mode 100644
-index 0000000000..b79a64dbd1
+index 0000000000..da55216ea4
--- /dev/null
-+++ b/tools/clang/plugins/FindBadConstructs.cpp
-@@ -0,0 +1,435 @@
-+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+// Use of this source code is governed by a BSD-style license that can be
-+// found in the LICENSE file.
++++ b/tools/grit/grit/format/html_inline.py
+@@ -0,0 +1,602 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
+
-+// This file defines a bunch of recurring problems in the Chromium C++ code.
-+//
-+// Checks that are implemented:
-+// - Constructors/Destructors should not be inlined if they are of a complex
-+// class type.
-+// - Missing "virtual" keywords on methods that should be virtual.
-+// - Non-annotated overriding virtual methods.
-+// - Virtual methods with nonempty implementations in their headers.
-+// - Classes that derive from base::RefCounted / base::RefCountedThreadSafe
-+// should have protected or private destructors.
++"""Flattens a HTML file by inlining its external resources.
+
-+#include "clang/Frontend/FrontendPluginRegistry.h"
-+#include "clang/AST/ASTConsumer.h"
-+#include "clang/AST/AST.h"
-+#include "clang/AST/CXXInheritance.h"
-+#include "clang/AST/TypeLoc.h"
-+#include "clang/Basic/SourceManager.h"
-+#include "clang/Frontend/CompilerInstance.h"
-+#include "llvm/Support/raw_ostream.h"
++This is a small script that takes a HTML file, looks for src attributes
++and inlines the specified file, producing one HTML file with no external
++dependencies. It recursively inlines the included files.
++"""
+
-+#include "ChromeClassTester.h"
++from __future__ import print_function
+
-+using namespace clang;
++import os
++import re
++import sys
++import base64
++import mimetypes
++
++from grit import lazy_re
++from grit import util
++from grit.format import minifier
++
++# There is a python bug that makes mimetypes crash if the Windows
++# registry contains non-Latin keys ( http://bugs.python.org/issue9291
++# ). Initing manually and blocking external mime-type databases will
++# prevent that bug and if we add svg manually, it will still give us
++# the data we need.
++mimetypes.init([])
++mimetypes.add_type('image/svg+xml', '.svg')
++
++# webm video type is not always available if mimetype package is outdated.
++mimetypes.add_type('video/webm', '.webm')
++
++DIST_DEFAULT = 'chromium'
++DIST_ENV_VAR = 'CHROMIUM_BUILD'
++DIST_SUBSTR = '%DISTRIBUTION%'
++
++# Matches beginning of an "if" block.
++_BEGIN_IF_BLOCK = lazy_re.compile(
++ r'<if [^>]*?expr=("(?P<expr1>[^">]*)"|\'(?P<expr2>[^\'>]*)\')[^>]*?>')
++
++# Matches ending of an "if" block.
++_END_IF_BLOCK = lazy_re.compile(r'</if>')
++
++# Used by DoInline to replace various links with inline content.
++_STYLESHEET_RE = lazy_re.compile(
++ r'<link rel="stylesheet"[^>]+?href="(?P<filename>[^"]*)".*?>(\s*</link>)?',
++ re.DOTALL)
++_INCLUDE_RE = lazy_re.compile(
++ r'(?P<comment>\/\/ )?<include[^>]+?'
++ r'src=("(?P<file1>[^">]*)"|\'(?P<file2>[^\'>]*)\').*?>(\s*</include>)?',
++ re.DOTALL)
++_SRC_RE = lazy_re.compile(
++ r'<(?!script)(?:[^>]+?\s)src="(?!\[\[|{{)(?P<filename>[^"\']*)"',
++ re.MULTILINE)
++# This re matches '<img srcset="..."' or '<source srcset="..."'
++_SRCSET_RE = lazy_re.compile(
++ r'<(img|source)\b(?:[^>]*?\s)srcset="(?!\[\[|{{|\$i18n{)'
++ r'(?P<srcset>[^"\']*)"',
++ re.MULTILINE)
++# This re is for splitting srcset value string into "image candidate strings".
++# Notes:
++# - HTML 5.2 states that URL cannot start or end with comma.
++# - the "descriptor" is either "width descriptor" or "pixel density descriptor".
++# The first one consists of "valid non-negative integer + letter 'x'",
++# the second one is formed of "positive valid floating-point number +
++# letter 'w'". As a reasonable compromise, we match a list of characters
++# that form both of them.
++# Matches for example "img2.png 2x" or "img9.png 11E-2w".
++_SRCSET_ENTRY_RE = lazy_re.compile(
++ r'\s*(?P<url>[^,\s]\S+[^,\s])'
++ r'(?:\s+(?P<descriptor>[\deE.-]+[wx]))?\s*'
++ r'(?P<separator>,|$)',
++ re.MULTILINE)
++_ICON_RE = lazy_re.compile(
++ r'<link rel="icon"\s(?:[^>]+?\s)?'
++ r'href=(?P<quote>")(?P<filename>[^"\']*)\1',
++ re.MULTILINE)
++
++
++def GetDistribution():
++ """Helper function that gets the distribution we are building.
++
++ Returns:
++ string
++ """
++ distribution = DIST_DEFAULT
++ if DIST_ENV_VAR in os.environ:
++ distribution = os.environ[DIST_ENV_VAR]
++ if len(distribution) > 1 and distribution[0] == '_':
++ distribution = distribution[1:].lower()
++ return distribution
++
++def ConvertFileToDataURL(filename, base_path, distribution, inlined_files,
++ names_only):
++ """Convert filename to inlined data URI.
++
++ Takes a filename from ether "src" or "srcset", and attempts to read the file
++ at 'filename'. Returns data URI as string with given file inlined.
++ If it finds DIST_SUBSTR string in file name, replaces it with distribution.
++ If filename contains ':', it is considered URL and not translated.
++
++ Args:
++ filename: filename string from ether src or srcset attributes.
++ base_path: path that to look for files in
++ distribution: string that should replace DIST_SUBSTR
++ inlined_files: The name of the opened file is appended to this list.
++ names_only: If true, the function will not read the file but just return "".
++ It will still add the filename to |inlined_files|.
++
++ Returns:
++ string
++ """
++ if filename.find(':') != -1:
++ # filename is probably a URL, which we don't want to bother inlining
++ return filename
++
++ filename = filename.replace(DIST_SUBSTR , distribution)
++ filepath = os.path.normpath(os.path.join(base_path, filename))
++ inlined_files.add(filepath)
++
++ if names_only:
++ return ""
++
++ mimetype = mimetypes.guess_type(filename)[0]
++ if mimetype is None:
++ raise Exception('%s is of an an unknown type and '
++ 'cannot be stored in a data url.' % filename)
++ inline_data = base64.standard_b64encode(util.ReadFile(filepath, util.BINARY))
++ return 'data:%s;base64,%s' % (mimetype, inline_data.decode('utf-8'))
++
++
++def SrcInlineAsDataURL(
++ src_match, base_path, distribution, inlined_files, names_only=False,
++ filename_expansion_function=None):
++ """regex replace function.
++
++ Takes a regex match for src="filename", attempts to read the file
++ at 'filename' and returns the src attribute with the file inlined
++ as a data URI. If it finds DIST_SUBSTR string in file name, replaces
++ it with distribution.
++
++ Args:
++ src_match: regex match object with 'filename' named capturing group
++ base_path: path that to look for files in
++ distribution: string that should replace DIST_SUBSTR
++ inlined_files: The name of the opened file is appended to this list.
++ names_only: If true, the function will not read the file but just return "".
++ It will still add the filename to |inlined_files|.
++
++ Returns:
++ string
++ """
++ filename = src_match.group('filename')
++ if filename_expansion_function:
++ filename = filename_expansion_function(filename)
++
++ data_url = ConvertFileToDataURL(filename, base_path, distribution,
++ inlined_files, names_only)
++
++ if not data_url:
++ return data_url
++
++ prefix = src_match.string[src_match.start():src_match.start('filename')]
++ suffix = src_match.string[src_match.end('filename'):src_match.end()]
++ return prefix + data_url + suffix
++
++def SrcsetInlineAsDataURL(
++ srcset_match, base_path, distribution, inlined_files, names_only=False,
++ filename_expansion_function=None):
++ """regex replace function to inline files in srcset="..." attributes
++
++ Takes a regex match for srcset="filename 1x, filename 2x, ...", attempts to
++ read the files referenced by filenames and returns the srcset attribute with
++ the files inlined as a data URI. If it finds DIST_SUBSTR string in file name,
++ replaces it with distribution.
++
++ Args:
++ srcset_match: regex match object with 'srcset' named capturing group
++ base_path: path that to look for files in
++ distribution: string that should replace DIST_SUBSTR
++ inlined_files: The name of the opened file is appended to this list.
++ names_only: If true, the function will not read the file but just return "".
++ It will still add the filename to |inlined_files|.
++
++ Returns:
++ string
++ """
++ srcset = srcset_match.group('srcset')
++
++ if not srcset:
++ return srcset_match.group(0)
++
++ # HTML 5.2 defines srcset as a list of "image candidate strings".
++ # Each of them consists of URL and descriptor.
++ # _SRCSET_ENTRY_RE splits srcset into a list of URLs, descriptors and
++ # commas.
++ # The descriptor part will be None if that optional regex didn't match
++ parts = _SRCSET_ENTRY_RE.split(srcset)
++
++ if not parts:
++ return srcset_match.group(0)
++
++ # List of image candidate strings that will form new srcset="..."
++ new_candidates = []
++
++ # When iterating over split srcset we fill this parts of a single image
++ # candidate string: [url, descriptor]
++ candidate = [];
++
++ # Each entry should consist of some text before the entry, the url,
++ # the descriptor or None if the entry has no descriptor, a comma separator or
++ # the end of the line, and finally some text after the entry (which is the
++ # same as the text before the next entry).
++ for i in range(0, len(parts) - 1, 4):
++ before, url, descriptor, separator, after = parts[i:i+5]
++
++ # There must be a comma-separated next entry or this must be the last entry.
++ assert separator == "," or (separator == "" and i == len(parts) - 5), (
++ "Bad srcset format in {}".format(srcset_match.group(0)))
++ # Both before and after the entry must be empty
++ assert before == after == "", (
++ "Bad srcset format in {}".format(srcset_match.group(0)))
++
++ if filename_expansion_function:
++ filename = filename_expansion_function(url)
++ else:
++ filename = url
++
++ data_url = ConvertFileToDataURL(filename, base_path, distribution,
++ inlined_files, names_only)
++
++ # This is not "names_only" mode
++ if data_url:
++ candidate = [data_url]
++ if descriptor:
++ candidate.append(descriptor)
++
++ new_candidates.append(" ".join(candidate))
++
++ prefix = srcset_match.string[srcset_match.start():
++ srcset_match.start('srcset')]
++ suffix = srcset_match.string[srcset_match.end('srcset'):srcset_match.end()]
++ return prefix + ','.join(new_candidates) + suffix
++
++class InlinedData:
++ """Helper class holding the results from DoInline().
++
++ Holds the inlined data and the set of filenames of all the inlined
++ files.
++ """
++ def __init__(self, inlined_data, inlined_files):
++ self.inlined_data = inlined_data
++ self.inlined_files = inlined_files
++
++def DoInline(
++ input_filename, grd_node, allow_external_script=False,
++ preprocess_only=False, names_only=False, strip_whitespace=False,
++ rewrite_function=None, filename_expansion_function=None):
++ """Helper function that inlines the resources in a specified file.
++
++ Reads input_filename, finds all the src attributes and attempts to
++ inline the files they are referring to, then returns the result and
++ the set of inlined files.
++
++ Args:
++ input_filename: name of file to read in
++ grd_node: html node from the grd file for this include tag
++ preprocess_only: Skip all HTML processing, only handle <if> and <include>.
++ names_only: |nil| will be returned for the inlined contents (faster).
++ strip_whitespace: remove whitespace and comments in the input files.
++ rewrite_function: function(filepath, text, distribution) which will be
++ called to rewrite html content before inlining images.
++ filename_expansion_function: function(filename) which will be called to
++ rewrite filenames before attempting to read them.
++ Returns:
++ a tuple of the inlined data as a string and the set of filenames
++ of all the inlined files
++ """
++ if filename_expansion_function:
++ input_filename = filename_expansion_function(input_filename)
++ input_filepath = os.path.dirname(input_filename)
++ distribution = GetDistribution()
++
++ # Keep track of all the files we inline.
++ inlined_files = set()
++
++ def SrcReplace(src_match, filepath=input_filepath,
++ inlined_files=inlined_files):
++ """Helper function to provide SrcInlineAsDataURL with the base file path"""
++ return SrcInlineAsDataURL(
++ src_match, filepath, distribution, inlined_files, names_only=names_only,
++ filename_expansion_function=filename_expansion_function)
++
++ def SrcsetReplace(srcset_match, filepath=input_filepath,
++ inlined_files=inlined_files):
++ """Helper function to provide SrcsetInlineAsDataURL with the base file
++ path.
++ """
++ return SrcsetInlineAsDataURL(
++ srcset_match, filepath, distribution, inlined_files,
++ names_only=names_only,
++ filename_expansion_function=filename_expansion_function)
++
++ def GetFilepath(src_match, base_path = input_filepath):
++ filename = [v for k, v in src_match.groupdict().items()
++ if k.startswith('file') and v][0]
++
++ if filename.find(':') != -1:
++ # filename is probably a URL, which we don't want to bother inlining
++ return None
++
++ filename = filename.replace('%DISTRIBUTION%', distribution)
++ if filename_expansion_function:
++ filename = filename_expansion_function(filename)
++ return os.path.normpath(os.path.join(base_path, filename))
++
++ def IsConditionSatisfied(src_match):
++ expr1 = src_match.group('expr1') or ''
++ expr2 = src_match.group('expr2') or ''
++ return grd_node is None or grd_node.EvaluateCondition(expr1 + expr2)
++
++ def CheckConditionalElements(str):
++ """Helper function to conditionally inline inner elements"""
++ while True:
++ begin_if = _BEGIN_IF_BLOCK.search(str)
++ if begin_if is None:
++ if _END_IF_BLOCK.search(str) is not None:
++ raise Exception('Unmatched </if>')
++ return str
++
++ condition_satisfied = IsConditionSatisfied(begin_if)
++ leading = str[0:begin_if.start()]
++ content_start = begin_if.end()
++
++ # Find matching "if" block end.
++ count = 1
++ pos = begin_if.end()
++ while True:
++ end_if = _END_IF_BLOCK.search(str, pos)
++ if end_if is None:
++ raise Exception('Unmatched <if>')
++
++ next_if = _BEGIN_IF_BLOCK.search(str, pos)
++ if next_if is None or next_if.start() >= end_if.end():
++ count = count - 1
++ if count == 0:
++ break
++ pos = end_if.end()
++ else:
++ count = count + 1
++ pos = next_if.end()
++
++ content = str[content_start:end_if.start()]
++ trailing = str[end_if.end():]
++
++ if condition_satisfied:
++ str = leading + CheckConditionalElements(content) + trailing
++ else:
++ str = leading + trailing
++
++ def InlineFileContents(src_match,
++ pattern,
++ inlined_files=inlined_files,
++ strip_whitespace=False):
++ """Helper function to inline external files of various types"""
++ filepath = GetFilepath(src_match)
++ if filepath is None:
++ return src_match.group(0)
++ inlined_files.add(filepath)
++
++ if names_only:
++ inlined_files.update(GetResourceFilenames(
++ filepath,
++ grd_node,
++ allow_external_script,
++ rewrite_function,
++ filename_expansion_function=filename_expansion_function))
++ return ""
++ # To recursively save inlined files, we need InlinedData instance returned
++ # by DoInline.
++ inlined_data_inst=DoInline(filepath, grd_node,
++ allow_external_script=allow_external_script,
++ preprocess_only=preprocess_only,
++ strip_whitespace=strip_whitespace,
++ filename_expansion_function=filename_expansion_function)
++
++ inlined_files.update(inlined_data_inst.inlined_files)
++
++ return pattern % inlined_data_inst.inlined_data;
++
++
++ def InlineIncludeFiles(src_match):
++ """Helper function to directly inline generic external files (without
++ wrapping them with any kind of tags).
++ """
++ return InlineFileContents(src_match, '%s')
++
++ def InlineScript(match):
++ """Helper function to inline external script files"""
++ attrs = (match.group('attrs1') + match.group('attrs2')).strip()
++ if attrs:
++ attrs = ' ' + attrs
++ return InlineFileContents(match, '<script' + attrs + '>%s</script>',
++ strip_whitespace=True)
++
++ def InlineCSSText(text, css_filepath):
++ """Helper function that inlines external resources in CSS text"""
++ filepath = os.path.dirname(css_filepath)
++ # Allow custom modifications before inlining images.
++ if rewrite_function:
++ text = rewrite_function(filepath, text, distribution)
++ text = InlineCSSImages(text, filepath)
++ return InlineCSSImports(text, filepath)
++
++ def InlineCSSFile(src_match, pattern, base_path=input_filepath):
++ """Helper function to inline external CSS files.
++
++ Args:
++ src_match: A regular expression match with a named group named "filename".
++ pattern: The pattern to replace with the contents of the CSS file.
++ base_path: The base path to use for resolving the CSS file.
++
++ Returns:
++ The text that should replace the reference to the CSS file.
++ """
++ filepath = GetFilepath(src_match, base_path)
++ if filepath is None:
++ return src_match.group(0)
++
++ # Even if names_only is set, the CSS file needs to be opened, because it
++ # can link to images that need to be added to the file set.
++ inlined_files.add(filepath)
++
++ # Inline stylesheets included in this css file.
++ text = _INCLUDE_RE.sub(InlineIncludeFiles, util.ReadFile(filepath, 'utf-8'))
++ # When resolving CSS files we need to pass in the path so that relative URLs
++ # can be resolved.
++
++ return pattern % InlineCSSText(text, filepath)
++
++ def GetUrlRegexString(postfix=''):
++ """Helper function that returns a string for a regex that matches url('')
++ but not url([[ ]]) or url({{ }}). Appends |postfix| to group names.
++ """
++ url_re = (r'url\((?!\[\[|{{)(?P<q%s>"|\'|)(?P<filename%s>[^"\'()]*)'
++ r'(?P=q%s)\)')
++ return url_re % (postfix, postfix, postfix)
++
++ def InlineCSSImages(text, filepath=input_filepath):
++ """Helper function that inlines external images in CSS backgrounds."""
++ # Replace contents of url() for css attributes: content, background,
++ # or *-image.
++ property_re = r'(content|background|[\w-]*-image):[^;]*'
++ # Replace group names to prevent duplicates when forming value_re.
++ image_set_value_re = (r'image-set\(([ ]*' + GetUrlRegexString('2') +
++ r'[ ]*[0-9.]*x[ ]*(,[ ]*)?)+\)')
++ value_re = '(%s|%s)' % (GetUrlRegexString(), image_set_value_re)
++ css_re = property_re + value_re
++ return re.sub(css_re, lambda m: InlineCSSUrls(m, filepath), text)
++
++ def InlineCSSUrls(src_match, filepath=input_filepath):
++ """Helper function that inlines each url on a CSS image rule match."""
++ # Replace contents of url() references in matches.
++ return re.sub(GetUrlRegexString(),
++ lambda m: SrcReplace(m, filepath),
++ src_match.group(0))
++
++ def InlineCSSImports(text, filepath=input_filepath):
++ """Helper function that inlines CSS files included via the @import
++ directive.
++ """
++ return re.sub(r'@import\s+' + GetUrlRegexString() + r';',
++ lambda m: InlineCSSFile(m, '%s', filepath),
++ text)
++
++
++ flat_text = util.ReadFile(input_filename, 'utf-8')
++
++ # Check conditional elements, remove unsatisfied ones from the file. We do
++ # this twice. The first pass is so that we don't even bother calling
++ # InlineScript, InlineCSSFile and InlineIncludeFiles on text we're eventually
++ # going to throw out anyway.
++ flat_text = CheckConditionalElements(flat_text)
++
++ flat_text = _INCLUDE_RE.sub(InlineIncludeFiles, flat_text)
++
++ if not preprocess_only:
++ if strip_whitespace:
++ flat_text = minifier.Minify(flat_text.encode('utf-8'),
++ input_filename).decode('utf-8')
++
++ if not allow_external_script:
++ # We need to inline css and js before we inline images so that image
++ # references gets inlined in the css and js
++ flat_text = re.sub(r'<script (?P<attrs1>.*?)src="(?P<filename>[^"\']*)"'
++ r'(?P<attrs2>.*?)></script>',
++ InlineScript,
++ flat_text)
++
++ flat_text = _STYLESHEET_RE.sub(
++ lambda m: InlineCSSFile(m, '<style>%s</style>'),
++ flat_text)
++
++ # Check conditional elements, second pass. This catches conditionals in any
++ # of the text we just inlined.
++ flat_text = CheckConditionalElements(flat_text)
++
++ # Allow custom modifications before inlining images.
++ if rewrite_function:
++ flat_text = rewrite_function(input_filepath, flat_text, distribution)
++
++ if not preprocess_only:
++ flat_text = _SRC_RE.sub(SrcReplace, flat_text)
++ flat_text = _SRCSET_RE.sub(SrcsetReplace, flat_text)
++
++ # TODO(arv): Only do this inside <style> tags.
++ flat_text = InlineCSSImages(flat_text)
++
++ flat_text = _ICON_RE.sub(SrcReplace, flat_text)
++
++ if names_only:
++ flat_text = None # Will contains garbage if the flag is set anyway.
++ return InlinedData(flat_text, inlined_files)
++
++
++def InlineToString(input_filename, grd_node, preprocess_only = False,
++ allow_external_script=False, strip_whitespace=False,
++ rewrite_function=None, filename_expansion_function=None):
++ """Inlines the resources in a specified file and returns it as a string.
++
++ Args:
++ input_filename: name of file to read in
++ grd_node: html node from the grd file for this include tag
++ Returns:
++ the inlined data as a string
++ """
++ try:
++ return DoInline(
++ input_filename,
++ grd_node,
++ preprocess_only=preprocess_only,
++ allow_external_script=allow_external_script,
++ strip_whitespace=strip_whitespace,
++ rewrite_function=rewrite_function,
++ filename_expansion_function=filename_expansion_function).inlined_data
++ except IOError as e:
++ raise Exception("Failed to open %s while trying to flatten %s. (%s)" %
++ (e.filename, input_filename, e.strerror))
++
++
++def InlineToFile(input_filename, output_filename, grd_node):
++ """Inlines the resources in a specified file and writes it.
++
++ Reads input_filename, finds all the src attributes and attempts to
++ inline the files they are referring to, then writes the result
++ to output_filename.
++
++ Args:
++ input_filename: name of file to read in
++ output_filename: name of file to be written to
++ grd_node: html node from the grd file for this include tag
++ Returns:
++ a set of filenames of all the inlined files
++ """
++ inlined_data = InlineToString(input_filename, grd_node)
++ with open(output_filename, 'wb') as out_file:
++ out_file.write(inlined_data)
++
++
++def GetResourceFilenames(filename,
++ grd_node,
++ allow_external_script=False,
++ rewrite_function=None,
++ filename_expansion_function=None):
++ """For a grd file, returns a set of all the files that would be inline."""
++ try:
++ return DoInline(
++ filename,
++ grd_node,
++ names_only=True,
++ preprocess_only=False,
++ allow_external_script=allow_external_script,
++ strip_whitespace=False,
++ rewrite_function=rewrite_function,
++ filename_expansion_function=filename_expansion_function).inlined_files
++ except IOError as e:
++ raise Exception("Failed to open %s while trying to flatten %s. (%s)" %
++ (e.filename, filename, e.strerror))
+
-+namespace {
+
-+bool TypeHasNonTrivialDtor(const Type* type) {
-+ if (const CXXRecordDecl* cxx_r = type->getCXXRecordDeclForPointerType())
-+ return cxx_r->hasTrivialDestructor();
++def main():
++ if len(sys.argv) <= 2:
++ print("Flattens a HTML file by inlining its external resources.\n")
++ print("html_inline.py inputfile outputfile")
++ else:
++ InlineToFile(sys.argv[1], sys.argv[2], None)
+
-+ return false;
-+}
++if __name__ == '__main__':
++ main()
+diff --git a/tools/grit/grit/format/html_inline_unittest.py b/tools/grit/grit/format/html_inline_unittest.py
+new file mode 100644
+index 0000000000..1b11e9e476
+--- /dev/null
++++ b/tools/grit/grit/format/html_inline_unittest.py
+@@ -0,0 +1,927 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
+
-+// Returns the underlying Type for |type| by expanding typedefs and removing
-+// any namespace qualifiers.
-+const Type* UnwrapType(const Type* type) {
-+ if (const ElaboratedType* elaborated = dyn_cast<ElaboratedType>(type))
-+ return UnwrapType(elaborated->getNamedType().getTypePtr());
-+ if (const TypedefType* typedefed = dyn_cast<TypedefType>(type))
-+ return UnwrapType(typedefed->desugar().getTypePtr());
-+ return type;
-+}
-+
-+// Searches for constructs that we know we don't want in the Chromium code base.
-+class FindBadConstructsConsumer : public ChromeClassTester {
-+ public:
-+ FindBadConstructsConsumer(CompilerInstance& instance,
-+ bool check_refcounted_dtors,
-+ bool check_virtuals_in_implementations)
-+ : ChromeClassTester(instance),
-+ check_refcounted_dtors_(check_refcounted_dtors),
-+ check_virtuals_in_implementations_(check_virtuals_in_implementations) {
-+ }
++'''Unit tests for grit.format.html_inline'''
+
-+ virtual void CheckChromeClass(SourceLocation record_location,
-+ CXXRecordDecl* record) {
-+ bool implementation_file = InImplementationFile(record_location);
++from __future__ import print_function
+
-+ if (!implementation_file) {
-+ // Only check for "heavy" constructors/destructors in header files;
-+ // within implementation files, there is no performance cost.
-+ CheckCtorDtorWeight(record_location, record);
-+ }
++import os
++import re
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++import unittest
++
++from grit import util
++from grit.format import html_inline
++
++
++class HtmlInlineUnittest(unittest.TestCase):
++ '''Unit tests for HtmlInline.'''
++
++ def testGetResourceFilenames(self):
++ '''Tests that all included files are returned by GetResourceFilenames.'''
++
++ files = {
++ 'index.html': '''
++ <!DOCTYPE HTML>
++ <html>
++ <head>
++ <link rel="stylesheet" href="test.css">
++ <link rel="stylesheet"
++ href="really-long-long-long-long-long-test.css">
++ </head>
++ <body>
++ <include src='test.html'>
++ <include
++ src="really-long-long-long-long-long-test-file-omg-so-long.html">
++ </body>
++ </html>
++ ''',
++
++ 'test.html': '''
++ <include src="test2.html">
++ ''',
++
++ 'really-long-long-long-long-long-test-file-omg-so-long.html': '''
++ <!-- This really long named resource should be included. -->
++ ''',
++
++ 'test2.html': '''
++ <!-- This second level resource should also be included. -->
++ ''',
++
++ 'test.css': '''
++ .image {
++ background: url('test.png');
++ }
++ ''',
++
++ 'really-long-long-long-long-long-test.css': '''
++ a:hover {
++ font-weight: bold; /* Awesome effect is awesome! */
++ }
++ ''',
+
-+ if (!implementation_file || check_virtuals_in_implementations_) {
-+ bool warn_on_inline_bodies = !implementation_file;
++ 'test.png': 'PNG DATA',
++ }
+
-+ // Check that all virtual methods are marked accordingly with both
-+ // virtual and OVERRIDE.
-+ CheckVirtualMethods(record_location, record, warn_on_inline_bodies);
++ source_resources = set()
++ tmp_dir = util.TempDir(files)
++ for filename in files:
++ source_resources.add(tmp_dir.GetPath(filename))
++
++ resources = html_inline.GetResourceFilenames(tmp_dir.GetPath('index.html'),
++ None)
++ resources.add(tmp_dir.GetPath('index.html'))
++ self.failUnlessEqual(resources, source_resources)
++ tmp_dir.CleanUp()
++
++ def testUnmatchedEndIfBlock(self):
++ '''Tests that an unmatched </if> raises an exception.'''
++
++ files = {
++ 'index.html': '''
++ <!DOCTYPE HTML>
++ <html>
++ <if expr="lang == 'fr'">
++ bonjour
++ </if>
++ <if expr='lang == "de"'>
++ hallo
++ </if>
++ </if>
++ </html>
++ ''',
+ }
+
-+ if (check_refcounted_dtors_)
-+ CheckRefCountedDtors(record_location, record);
-+ }
++ tmp_dir = util.TempDir(files)
++
++ with self.assertRaises(Exception) as cm:
++ html_inline.GetResourceFilenames(tmp_dir.GetPath('index.html'), None)
++ self.failUnlessEqual(str(cm.exception), 'Unmatched </if>')
++ tmp_dir.CleanUp()
++
++ def testCompressedJavaScript(self):
++ '''Tests that ".src=" doesn't treat as a tag.'''
+
-+ private:
-+ bool check_refcounted_dtors_;
-+ bool check_virtuals_in_implementations_;
-+
-+ // Returns true if |base| specifies one of the Chromium reference counted
-+ // classes (base::RefCounted / base::RefCountedThreadSafe). |user_data| is
-+ // ignored.
-+ static bool IsRefCountedCallback(const CXXBaseSpecifier* base,
-+ CXXBasePath& path,
-+ void* user_data) {
-+ FindBadConstructsConsumer* self =
-+ static_cast<FindBadConstructsConsumer*>(user_data);
-+
-+ const TemplateSpecializationType* base_type =
-+ dyn_cast<TemplateSpecializationType>(
-+ UnwrapType(base->getType().getTypePtr()));
-+ if (!base_type) {
-+ // Base-most definition is not a template, so this cannot derive from
-+ // base::RefCounted. However, it may still be possible to use with a
-+ // scoped_refptr<> and support ref-counting, so this is not a perfect
-+ // guarantee of safety.
-+ return false;
++ files = {
++ 'index.js': '''
++ if(i<j)a.src="hoge.png";
++ ''',
+ }
+
-+ TemplateName name = base_type->getTemplateName();
-+ if (TemplateDecl* decl = name.getAsTemplateDecl()) {
-+ std::string base_name = decl->getNameAsString();
++ source_resources = set()
++ tmp_dir = util.TempDir(files)
++ for filename in files:
++ source_resources.add(tmp_dir.GetPath(filename))
++
++ resources = html_inline.GetResourceFilenames(tmp_dir.GetPath('index.js'),
++ None)
++ resources.add(tmp_dir.GetPath('index.js'))
++ self.failUnlessEqual(resources, source_resources)
++ tmp_dir.CleanUp()
++
++ def testInlineCSSImports(self):
++ '''Tests that @import directives in inlined CSS files are inlined too.
++ '''
++
++ files = {
++ 'index.html': '''
++ <html>
++ <head>
++ <link rel="stylesheet" href="css/test.css">
++ </head>
++ </html>
++ ''',
++
++ 'css/test.css': '''
++ @import url('test2.css');
++ blink {
++ display: none;
++ }
++ ''',
+
-+ // Check for both base::RefCounted and base::RefCountedThreadSafe.
-+ if (base_name.compare(0, 10, "RefCounted") == 0 &&
-+ self->GetNamespace(decl) == "base") {
-+ return true;
++ 'css/test2.css': '''
++ .image {
++ background: url('../images/test.png');
+ }
-+ }
-+ return false;
-+ }
++ '''.strip(),
+
-+ // Prints errors if the destructor of a RefCounted class is public.
-+ void CheckRefCountedDtors(SourceLocation record_location,
-+ CXXRecordDecl* record) {
-+ // Skip anonymous structs.
-+ if (record->getIdentifier() == NULL)
-+ return;
-+
-+ CXXBasePaths paths;
-+ if (!record->lookupInBases(
-+ &FindBadConstructsConsumer::IsRefCountedCallback, this, paths)) {
-+ return; // Class does not derive from a ref-counted base class.
++ 'images/test.png': 'PNG DATA'
+ }
+
-+ if (!record->hasUserDeclaredDestructor()) {
-+ emitWarning(
-+ record_location,
-+ "Classes that are ref-counted should have explicit "
-+ "destructors that are protected or private.");
-+ } else if (CXXDestructorDecl* dtor = record->getDestructor()) {
-+ if (dtor->getAccess() == AS_public) {
-+ emitWarning(
-+ dtor->getInnerLocStart(),
-+ "Classes that are ref-counted should not have "
-+ "public destructors.");
++ expected_inlined = '''
++ <html>
++ <head>
++ <style>
++ .image {
++ background: url('data:image/png;base64,UE5HIERBVEE=');
++ }
++ blink {
++ display: none;
++ }
++ </style>
++ </head>
++ </html>
++ '''
++
++ source_resources = set()
++ tmp_dir = util.TempDir(files)
++ for filename in files:
++ source_resources.add(tmp_dir.GetPath(util.normpath(filename)))
++
++ result = html_inline.DoInline(tmp_dir.GetPath('index.html'), None)
++ resources = result.inlined_files
++ resources.add(tmp_dir.GetPath('index.html'))
++ self.failUnlessEqual(resources, source_resources)
++ self.failUnlessEqual(expected_inlined,
++ util.FixLineEnd(result.inlined_data, '\n'))
++
++ tmp_dir.CleanUp()
++
++ def testInlineIgnoresPolymerBindings(self):
++ '''Tests that polymer bindings are ignored when inlining.
++ '''
++
++ files = {
++ 'index.html': '''
++ <html>
++ <head>
++ <link rel="stylesheet" href="test.css">
++ </head>
++ <body>
++ <iron-icon src="[[icon]]"></iron-icon><!-- Should be ignored. -->
++ <iron-icon src="{{src}}"></iron-icon><!-- Also ignored. -->
++ <!-- [[image]] should be ignored. -->
++ <div style="background: url([[image]]),
++ url('test.png');">
++ </div>
++ <div style="background: url('test.png'),
++ url([[image]]);">
++ </div>
++ </body>
++ </html>
++ ''',
++
++ 'test.css': '''
++ .image {
++ background: url('test.png');
++ background-image: url([[ignoreMe]]);
++ background-image: image-set(url({{alsoMe}}), 1x);
++ background-image: image-set(
++ url({{ignore}}) 1x,
++ url('test.png') 2x);
+ }
++ ''',
++
++ 'test.png': 'PNG DATA'
+ }
-+ }
+
-+ // Prints errors if the constructor/destructor weight is too heavy.
-+ void CheckCtorDtorWeight(SourceLocation record_location,
-+ CXXRecordDecl* record) {
-+ // We don't handle anonymous structs. If this record doesn't have a
-+ // name, it's of the form:
-+ //
-+ // struct {
-+ // ...
-+ // } name_;
-+ if (record->getIdentifier() == NULL)
-+ return;
-+
-+ // Count the number of templated base classes as a feature of whether the
-+ // destructor can be inlined.
-+ int templated_base_classes = 0;
-+ for (CXXRecordDecl::base_class_const_iterator it = record->bases_begin();
-+ it != record->bases_end(); ++it) {
-+ if (it->getTypeSourceInfo()->getTypeLoc().getTypeLocClass() ==
-+ TypeLoc::TemplateSpecialization) {
-+ ++templated_base_classes;
++ expected_inlined = '''
++ <html>
++ <head>
++ <style>
++ .image {
++ background: url('data:image/png;base64,UE5HIERBVEE=');
++ background-image: url([[ignoreMe]]);
++ background-image: image-set(url({{alsoMe}}), 1x);
++ background-image: image-set(
++ url({{ignore}}) 1x,
++ url('data:image/png;base64,UE5HIERBVEE=') 2x);
++ }
++ </style>
++ </head>
++ <body>
++ <iron-icon src="[[icon]]"></iron-icon><!-- Should be ignored. -->
++ <iron-icon src="{{src}}"></iron-icon><!-- Also ignored. -->
++ <!-- [[image]] should be ignored. -->
++ <div style="background: url([[image]]),
++ url('data:image/png;base64,UE5HIERBVEE=');">
++ </div>
++ <div style="background: url('data:image/png;base64,UE5HIERBVEE='),
++ url([[image]]);">
++ </div>
++ </body>
++ </html>
++ '''
++
++ source_resources = set()
++ tmp_dir = util.TempDir(files)
++ for filename in files:
++ source_resources.add(tmp_dir.GetPath(util.normpath(filename)))
++
++ result = html_inline.DoInline(tmp_dir.GetPath('index.html'), None)
++ resources = result.inlined_files
++ resources.add(tmp_dir.GetPath('index.html'))
++ self.failUnlessEqual(resources, source_resources)
++ self.failUnlessEqual(expected_inlined,
++ util.FixLineEnd(result.inlined_data, '\n'))
++
++ tmp_dir.CleanUp()
++
++ def testInlineCSSWithIncludeDirective(self):
++ '''Tests that include directive in external css files also inlined'''
++
++ files = {
++ 'index.html': '''
++ <html>
++ <head>
++ <link rel="stylesheet" href="foo.css">
++ </head>
++ </html>
++ ''',
++
++ 'foo.css': '''<include src="style.css">''',
++
++ 'style.css': '''
++ <include src="style2.css">
++ blink {
++ display: none;
+ }
++ ''',
++ 'style2.css': '''h1 {}''',
+ }
+
-+ // Count the number of trivial and non-trivial member variables.
-+ int trivial_member = 0;
-+ int non_trivial_member = 0;
-+ int templated_non_trivial_member = 0;
-+ for (RecordDecl::field_iterator it = record->field_begin();
-+ it != record->field_end(); ++it) {
-+ CountType(it->getType().getTypePtr(),
-+ &trivial_member,
-+ &non_trivial_member,
-+ &templated_non_trivial_member);
++ expected_inlined = '''
++ <html>
++ <head>
++ <style>
++ h1 {}
++ blink {
++ display: none;
++ }
++ </style>
++ </head>
++ </html>
++ '''
++
++ source_resources = set()
++ tmp_dir = util.TempDir(files)
++ for filename in files:
++ source_resources.add(tmp_dir.GetPath(filename))
++
++ result = html_inline.DoInline(tmp_dir.GetPath('index.html'), None)
++ resources = result.inlined_files
++ resources.add(tmp_dir.GetPath('index.html'))
++ self.failUnlessEqual(resources, source_resources)
++ self.failUnlessEqual(expected_inlined,
++ util.FixLineEnd(result.inlined_data, '\n'))
++ tmp_dir.CleanUp()
++
++ def testCssIncludedFileNames(self):
++ '''Tests that all included files from css are returned'''
++
++ files = {
++ 'index.html': '''
++ <!DOCTYPE HTML>
++ <html>
++ <head>
++ <link rel="stylesheet" href="test.css">
++ </head>
++ <body>
++ </body>
++ </html>
++ ''',
++
++ 'test.css': '''
++ <include src="test2.css">
++ ''',
++
++ 'test2.css': '''
++ <include src="test3.css">
++ .image {
++ background: url('test.png');
++ }
++ ''',
++
++ 'test3.css': '''h1 {}''',
++
++ 'test.png': 'PNG DATA'
+ }
+
-+ // Check to see if we need to ban inlined/synthesized constructors. Note
-+ // that the cutoffs here are kind of arbitrary. Scores over 10 break.
-+ int dtor_score = 0;
-+ // Deriving from a templated base class shouldn't be enough to trigger
-+ // the ctor warning, but if you do *anything* else, it should.
-+ //
-+ // TODO(erg): This is motivated by templated base classes that don't have
-+ // any data members. Somehow detect when templated base classes have data
-+ // members and treat them differently.
-+ dtor_score += templated_base_classes * 9;
-+ // Instantiating a template is an insta-hit.
-+ dtor_score += templated_non_trivial_member * 10;
-+ // The fourth normal class member should trigger the warning.
-+ dtor_score += non_trivial_member * 3;
-+
-+ int ctor_score = dtor_score;
-+ // You should be able to have 9 ints before we warn you.
-+ ctor_score += trivial_member;
-+
-+ if (ctor_score >= 10) {
-+ if (!record->hasUserDeclaredConstructor()) {
-+ emitWarning(record_location,
-+ "Complex class/struct needs an explicit out-of-line "
-+ "constructor.");
-+ } else {
-+ // Iterate across all the constructors in this file and yell if we
-+ // find one that tries to be inline.
-+ for (CXXRecordDecl::ctor_iterator it = record->ctor_begin();
-+ it != record->ctor_end(); ++it) {
-+ if (it->hasInlineBody()) {
-+ if (it->isCopyConstructor() &&
-+ !record->hasUserDeclaredCopyConstructor()) {
-+ emitWarning(record_location,
-+ "Complex class/struct needs an explicit out-of-line "
-+ "copy constructor.");
-+ } else {
-+ emitWarning(it->getInnerLocStart(),
-+ "Complex constructor has an inlined body.");
-+ }
-+ }
-+ }
++ source_resources = set()
++ tmp_dir = util.TempDir(files)
++ for filename in files:
++ source_resources.add(tmp_dir.GetPath(filename))
++
++ resources = html_inline.GetResourceFilenames(tmp_dir.GetPath('index.html'),
++ None)
++ resources.add(tmp_dir.GetPath('index.html'))
++ self.failUnlessEqual(resources, source_resources)
++ tmp_dir.CleanUp()
++
++ def testInlineCSSLinks(self):
++ '''Tests that only CSS files referenced via relative URLs are inlined.'''
++
++ files = {
++ 'index.html': '''
++ <html>
++ <head>
++ <link rel="stylesheet" href="foo.css">
++ <link rel="stylesheet" href="chrome://resources/bar.css">
++ </head>
++ </html>
++ ''',
++
++ 'foo.css': '''
++ @import url(chrome://resources/blurp.css);
++ blink {
++ display: none;
+ }
++ ''',
+ }
+
-+ // The destructor side is equivalent except that we don't check for
-+ // trivial members; 20 ints don't need a destructor.
-+ if (dtor_score >= 10 && !record->hasTrivialDestructor()) {
-+ if (!record->hasUserDeclaredDestructor()) {
-+ emitWarning(
-+ record_location,
-+ "Complex class/struct needs an explicit out-of-line "
-+ "destructor.");
-+ } else if (CXXDestructorDecl* dtor = record->getDestructor()) {
-+ if (dtor->hasInlineBody()) {
-+ emitWarning(dtor->getInnerLocStart(),
-+ "Complex destructor has an inline body.");
-+ }
++ expected_inlined = '''
++ <html>
++ <head>
++ <style>
++ @import url(chrome://resources/blurp.css);
++ blink {
++ display: none;
+ }
++ </style>
++ <link rel="stylesheet" href="chrome://resources/bar.css">
++ </head>
++ </html>
++ '''
++
++ source_resources = set()
++ tmp_dir = util.TempDir(files)
++ for filename in files:
++ source_resources.add(tmp_dir.GetPath(filename))
++
++ result = html_inline.DoInline(tmp_dir.GetPath('index.html'), None)
++ resources = result.inlined_files
++ resources.add(tmp_dir.GetPath('index.html'))
++ self.failUnlessEqual(resources, source_resources)
++ self.failUnlessEqual(expected_inlined,
++ util.FixLineEnd(result.inlined_data, '\n'))
++ tmp_dir.CleanUp()
++
++ def testFilenameVariableExpansion(self):
++ '''Tests that variables are expanded in filenames before inlining.'''
++
++ files = {
++ 'index.html': '''
++ <html>
++ <head>
++ <link rel="stylesheet" href="style[WHICH].css">
++ <script src="script[WHICH].js"></script>
++ </head>
++ <include src="tmpl[WHICH].html">
++ <img src="img[WHICH].png">
++ </html>
++ ''',
++ 'style1.css': '''h1 {}''',
++ 'tmpl1.html': '''<h1></h1>''',
++ 'script1.js': '''console.log('hello');''',
++ 'img1.png': '''abc''',
+ }
-+ }
+
-+ void CheckVirtualMethod(const CXXMethodDecl* method,
-+ bool warn_on_inline_bodies) {
-+ if (!method->isVirtual())
-+ return;
++ expected_inlined = '''
++ <html>
++ <head>
++ <style>h1 {}</style>
++ <script>console.log('hello');</script>
++ </head>
++ <h1></h1>
++ <img src="data:image/png;base64,YWJj">
++ </html>
++ '''
++
++ source_resources = set()
++ tmp_dir = util.TempDir(files)
++ for filename in files:
++ source_resources.add(tmp_dir.GetPath(filename))
++
++ def replacer(var, repl):
++ return lambda filename: filename.replace('[%s]' % var, repl)
++
++ # Test normal inlining.
++ result = html_inline.DoInline(
++ tmp_dir.GetPath('index.html'),
++ None,
++ filename_expansion_function=replacer('WHICH', '1'))
++ resources = result.inlined_files
++ resources.add(tmp_dir.GetPath('index.html'))
++ self.failUnlessEqual(resources, source_resources)
++ self.failUnlessEqual(expected_inlined,
++ util.FixLineEnd(result.inlined_data, '\n'))
++
++ # Test names-only inlining.
++ result = html_inline.DoInline(
++ tmp_dir.GetPath('index.html'),
++ None,
++ names_only=True,
++ filename_expansion_function=replacer('WHICH', '1'))
++ resources = result.inlined_files
++ resources.add(tmp_dir.GetPath('index.html'))
++ self.failUnlessEqual(resources, source_resources)
++ tmp_dir.CleanUp()
++
++ def testWithCloseTags(self):
++ '''Tests that close tags are removed.'''
++
++ files = {
++ 'index.html': '''
++ <html>
++ <head>
++ <link rel="stylesheet" href="style1.css"></link>
++ <link rel="stylesheet" href="style2.css">
++ </link>
++ <link rel="stylesheet" href="style2.css"
++ >
++ </link>
++ <script src="script1.js"></script>
++ </head>
++ <include src="tmpl1.html"></include>
++ <include src="tmpl2.html">
++ </include>
++ <include src="tmpl2.html"
++ >
++ </include>
++ <img src="img1.png">
++ <include src='single-double-quotes.html"></include>
++ <include src="double-single-quotes.html'></include>
++ </html>
++ ''',
++ 'style1.css': '''h1 {}''',
++ 'style2.css': '''h2 {}''',
++ 'tmpl1.html': '''<h1></h1>''',
++ 'tmpl2.html': '''<h2></h2>''',
++ 'script1.js': '''console.log('hello');''',
++ 'img1.png': '''abc''',
++ }
+
-+ if (!method->isVirtualAsWritten()) {
-+ SourceLocation loc = method->getTypeSpecStartLoc();
-+ if (isa<CXXDestructorDecl>(method))
-+ loc = method->getInnerLocStart();
-+ emitWarning(loc, "Overriding method must have \"virtual\" keyword.");
++ expected_inlined = '''
++ <html>
++ <head>
++ <style>h1 {}</style>
++ <style>h2 {}</style>
++ <style>h2 {}</style>
++ <script>console.log('hello');</script>
++ </head>
++ <h1></h1>
++ <h2></h2>
++ <h2></h2>
++ <img src="data:image/png;base64,YWJj">
++ <include src='single-double-quotes.html"></include>
++ <include src="double-single-quotes.html'></include>
++ </html>
++ '''
++
++ source_resources = set()
++ tmp_dir = util.TempDir(files)
++ for filename in files:
++ source_resources.add(tmp_dir.GetPath(filename))
++
++ # Test normal inlining.
++ result = html_inline.DoInline(
++ tmp_dir.GetPath('index.html'),
++ None)
++ resources = result.inlined_files
++ resources.add(tmp_dir.GetPath('index.html'))
++ self.failUnlessEqual(resources, source_resources)
++ self.failUnlessEqual(expected_inlined,
++ util.FixLineEnd(result.inlined_data, '\n'))
++ tmp_dir.CleanUp()
++
++ def testCommentedJsInclude(self):
++ '''Tests that <include> works inside a comment.'''
++
++ files = {
++ 'include.js': '// <include src="other.js">',
++ 'other.js': '// Copyright somebody\nalert(1);',
+ }
+
-+ // Virtual methods should not have inline definitions beyond "{}". This
-+ // only matters for header files.
-+ if (warn_on_inline_bodies && method->hasBody() &&
-+ method->hasInlineBody()) {
-+ if (CompoundStmt* cs = dyn_cast<CompoundStmt>(method->getBody())) {
-+ if (cs->size()) {
-+ emitWarning(
-+ cs->getLBracLoc(),
-+ "virtual methods with non-empty bodies shouldn't be "
-+ "declared inline.");
-+ }
-+ }
++ expected_inlined = '// Copyright somebody\nalert(1);'
++
++ source_resources = set()
++ tmp_dir = util.TempDir(files)
++ for filename in files:
++ source_resources.add(tmp_dir.GetPath(filename))
++
++ result = html_inline.DoInline(tmp_dir.GetPath('include.js'), None)
++ resources = result.inlined_files
++ resources.add(tmp_dir.GetPath('include.js'))
++ self.failUnlessEqual(resources, source_resources)
++ self.failUnlessEqual(expected_inlined,
++ util.FixLineEnd(result.inlined_data, '\n'))
++ tmp_dir.CleanUp()
++
++ def testCommentedJsIf(self):
++ '''Tests that <if> works inside a comment.'''
++
++ files = {
++ 'if.js': '''
++ // <if expr="True">
++ yep();
++ // </if>
++
++ // <if expr="False">
++ nope();
++ // </if>
++ ''',
+ }
-+ }
+
-+ bool InTestingNamespace(const Decl* record) {
-+ return GetNamespace(record).find("testing") != std::string::npos;
-+ }
++ expected_inlined = '''
++ //
++ yep();
++ //
++
++ //
++ '''
++
++ source_resources = set()
++ tmp_dir = util.TempDir(files)
++ for filename in files:
++ source_resources.add(tmp_dir.GetPath(filename))
++
++ class FakeGrdNode(object):
++ def EvaluateCondition(self, cond):
++ return eval(cond)
++
++ result = html_inline.DoInline(tmp_dir.GetPath('if.js'), FakeGrdNode())
++ resources = result.inlined_files
++
++ resources.add(tmp_dir.GetPath('if.js'))
++ self.failUnlessEqual(resources, source_resources)
++ self.failUnlessEqual(expected_inlined,
++ util.FixLineEnd(result.inlined_data, '\n'))
++ tmp_dir.CleanUp()
++
++ def testImgSrcset(self):
++ '''Tests that img srcset="" attributes are converted.'''
++
++ # Note that there is no space before "img10.png" and that
++ # "img11.png" has no descriptor.
++ files = {
++ 'index.html': '''
++ <html>
++ <img src="img1.png" srcset="img2.png 1x, img3.png 2x">
++ <img src="img4.png" srcset=" img5.png 1x , img6.png 2x ">
++ <img src="chrome://theme/img11.png" srcset="img7.png 1x, '''\
++ '''chrome://theme/img13.png 2x">
++ <img srcset="img8.png 300w, img9.png 11E-2w,img10.png -1e2w">
++ <img srcset="img11.png">
++ <img srcset="img11.png, img2.png 1x">
++ <img srcset="img2.png 1x, img11.png">
++ </html>
++ ''',
++ 'img1.png': '''a1''',
++ 'img2.png': '''a2''',
++ 'img3.png': '''a3''',
++ 'img4.png': '''a4''',
++ 'img5.png': '''a5''',
++ 'img6.png': '''a6''',
++ 'img7.png': '''a7''',
++ 'img8.png': '''a8''',
++ 'img9.png': '''a9''',
++ 'img10.png': '''a10''',
++ 'img11.png': '''a11''',
++ }
+
-+ bool IsMethodInBannedNamespace(const CXXMethodDecl* method) {
-+ if (InBannedNamespace(method))
-+ return true;
-+ for (CXXMethodDecl::method_iterator i = method->begin_overridden_methods();
-+ i != method->end_overridden_methods();
-+ ++i) {
-+ const CXXMethodDecl* overridden = *i;
-+ if (IsMethodInBannedNamespace(overridden))
-+ return true;
++ expected_inlined = '''
++ <html>
++ <img src="data:image/png;base64,YTE=" srcset="data:image/png;base64,'''\
++ '''YTI= 1x,data:image/png;base64,YTM= 2x">
++ <img src="data:image/png;base64,YTQ=" srcset="data:image/png;base64,'''\
++ '''YTU= 1x,data:image/png;base64,YTY= 2x">
++ <img src="chrome://theme/img11.png" srcset="data:image/png;base64,'''\
++ '''YTc= 1x,chrome://theme/img13.png 2x">
++ <img srcset="data:image/png;base64,YTg= 300w,data:image/png;base64,'''\
++ '''YTk= 11E-2w,data:image/png;base64,YTEw -1e2w">
++ <img srcset="data:image/png;base64,YTEx">
++ <img srcset="data:image/png;base64,YTEx,data:image/png;base64,YTI= 1x">
++ <img srcset="data:image/png;base64,YTI= 1x,data:image/png;base64,YTEx">
++ </html>
++ '''
++
++ source_resources = set()
++ tmp_dir = util.TempDir(files)
++ for filename in files:
++ source_resources.add(tmp_dir.GetPath(filename))
++
++ # Test normal inlining.
++ result = html_inline.DoInline(
++ tmp_dir.GetPath('index.html'),
++ None)
++ resources = result.inlined_files
++ resources.add(tmp_dir.GetPath('index.html'))
++ self.failUnlessEqual(resources, source_resources)
++ self.failUnlessEqual(expected_inlined,
++ util.FixLineEnd(result.inlined_data, '\n'))
++ tmp_dir.CleanUp()
++
++ def testImgSrcsetIgnoresI18n(self):
++ '''Tests that $i18n{...} strings are ignored when inlining.
++ '''
++
++ src_html = '''
++ <html>
++ <head></head>
++ <body>
++ <img srcset="$i18n{foo}">
++ </body>
++ </html>
++ '''
++
++ files = {
++ 'index.html': src_html,
+ }
+
-+ return false;
-+ }
++ expected_inlined = src_html
++
++ source_resources = set()
++ tmp_dir = util.TempDir(files)
++ for filename in files:
++ source_resources.add(tmp_dir.GetPath(util.normpath(filename)))
++
++ result = html_inline.DoInline(tmp_dir.GetPath('index.html'), None)
++ resources = result.inlined_files
++ resources.add(tmp_dir.GetPath('index.html'))
++ self.failUnlessEqual(resources, source_resources)
++ self.failUnlessEqual(expected_inlined,
++ util.FixLineEnd(result.inlined_data, '\n'))
++ tmp_dir.CleanUp()
++
++ def testSourceSrcset(self):
++ '''Tests that source srcset="" attributes are converted.'''
++
++ # Note that there is no space before "img10.png" and that
++ # "img11.png" has no descriptor.
++ files = {
++ 'index.html': '''
++ <html>
++ <source src="img1.png" srcset="img2.png 1x, img3.png 2x">
++ <source src="img4.png" srcset=" img5.png 1x , img6.png 2x ">
++ <source src="chrome://theme/img11.png" srcset="img7.png 1x, '''\
++ '''chrome://theme/img13.png 2x">
++ <source srcset="img8.png 300w, img9.png 11E-2w,img10.png -1e2w">
++ <source srcset="img11.png">
++ </html>
++ ''',
++ 'img1.png': '''a1''',
++ 'img2.png': '''a2''',
++ 'img3.png': '''a3''',
++ 'img4.png': '''a4''',
++ 'img5.png': '''a5''',
++ 'img6.png': '''a6''',
++ 'img7.png': '''a7''',
++ 'img8.png': '''a8''',
++ 'img9.png': '''a9''',
++ 'img10.png': '''a10''',
++ 'img11.png': '''a11''',
++ }
++
++ expected_inlined = '''
++ <html>
++ <source src="data:image/png;base64,YTE=" srcset="data:image/png;'''\
++ '''base64,YTI= 1x,data:image/png;base64,YTM= 2x">
++ <source src="data:image/png;base64,YTQ=" srcset="data:image/png;'''\
++ '''base64,YTU= 1x,data:image/png;base64,YTY= 2x">
++ <source src="chrome://theme/img11.png" srcset="data:image/png;'''\
++ '''base64,YTc= 1x,chrome://theme/img13.png 2x">
++ <source srcset="data:image/png;base64,YTg= 300w,data:image/png;'''\
++ '''base64,YTk= 11E-2w,data:image/png;base64,YTEw -1e2w">
++ <source srcset="data:image/png;base64,YTEx">
++ </html>
++ '''
++
++ source_resources = set()
++ tmp_dir = util.TempDir(files)
++ for filename in files:
++ source_resources.add(tmp_dir.GetPath(filename))
++
++ # Test normal inlining.
++ result = html_inline.DoInline(tmp_dir.GetPath('index.html'), None)
++ resources = result.inlined_files
++ resources.add(tmp_dir.GetPath('index.html'))
++ self.failUnlessEqual(resources, source_resources)
++ self.failUnlessEqual(expected_inlined,
++ util.FixLineEnd(result.inlined_data, '\n'))
++ tmp_dir.CleanUp()
++
++ def testConditionalInclude(self):
++ '''Tests that output and dependency generation includes only files not'''\
++ ''' blocked by <if> macros.'''
++
++ files = {
++ 'index.html': '''
++ <html>
++ <if expr="True">
++ <img src="img1.png" srcset="img2.png 1x, img3.png 2x">
++ </if>
++ <if expr="False">
++ <img src="img4.png" srcset=" img5.png 1x, img6.png 2x ">
++ </if>
++ <if expr="True">
++ <img src="chrome://theme/img11.png" srcset="img7.png 1x, '''\
++ '''chrome://theme/img13.png 2x">
++ </if>
++ <img srcset="img8.png 300w, img9.png 11E-2w,img10.png -1e2w">
++ </html>
++ ''',
++ 'img1.png': '''a1''',
++ 'img2.png': '''a2''',
++ 'img3.png': '''a3''',
++ 'img4.png': '''a4''',
++ 'img5.png': '''a5''',
++ 'img6.png': '''a6''',
++ 'img7.png': '''a7''',
++ 'img8.png': '''a8''',
++ 'img9.png': '''a9''',
++ 'img10.png': '''a10''',
++ }
+
-+ void CheckOverriddenMethod(const CXXMethodDecl* method) {
-+ if (!method->size_overridden_methods() || method->getAttr<OverrideAttr>())
-+ return;
++ expected_inlined = '''
++ <html>
++ <img src="data:image/png;base64,YTE=" srcset="data:image/png;base64,'''\
++ '''YTI= 1x,data:image/png;base64,YTM= 2x">
++ <img src="chrome://theme/img11.png" srcset="data:image/png;base64,'''\
++ '''YTc= 1x,chrome://theme/img13.png 2x">
++ <img srcset="data:image/png;base64,YTg= 300w,data:image/png;base64,'''\
++ '''YTk= 11E-2w,data:image/png;base64,YTEw -1e2w">
++ </html>
++ '''
++
++ expected_files = [
++ 'index.html',
++ 'img1.png',
++ 'img2.png',
++ 'img3.png',
++ 'img7.png',
++ 'img8.png',
++ 'img9.png',
++ 'img10.png'
++ ]
++
++ source_resources = set()
++ tmp_dir = util.TempDir(files)
++ for filename in expected_files:
++ source_resources.add(tmp_dir.GetPath(filename))
++
++ class FakeGrdNode(object):
++ def EvaluateCondition(self, cond):
++ return eval(cond)
++
++ # Test normal inlining.
++ result = html_inline.DoInline(
++ tmp_dir.GetPath('index.html'),
++ FakeGrdNode())
++ resources = result.inlined_files
++ resources.add(tmp_dir.GetPath('index.html'))
++ self.failUnlessEqual(resources, source_resources)
++
++ # ignore whitespace
++ expected_inlined = re.sub(r'\s+', ' ', expected_inlined)
++ actually_inlined = re.sub(r'\s+', ' ',
++ util.FixLineEnd(result.inlined_data, '\n'))
++ self.failUnlessEqual(expected_inlined, actually_inlined);
++ tmp_dir.CleanUp()
++
++ def testPreprocessOnlyEvaluatesIncludeAndIf(self):
++ '''Tests that preprocess_only=true evaluates <include> and <if> only. '''
++
++ files = {
++ 'index.html': '''
++ <html>
++ <head>
++ <link rel="stylesheet" href="not_inlined.css">
++ <script src="also_not_inlined.js">
++ </head>
++ <body>
++ <include src="inline_this.html">
++ <if expr="True">
++ <p>'if' should be evaluated.</p>
++ </if>
++ </body>
++ </html>
++ ''',
++ 'not_inlined.css': ''' /* <link> should not be inlined. */ ''',
++ 'also_not_inlined.js': ''' // <script> should not be inlined. ''',
++ 'inline_this.html': ''' <p>'include' should be inlined.</p> '''
++ }
++
++ expected_inlined = '''
++ <html>
++ <head>
++ <link rel="stylesheet" href="not_inlined.css">
++ <script src="also_not_inlined.js">
++ </head>
++ <body>
++ <p>'include' should be inlined.</p>
++ <p>'if' should be evaluated.</p>
++ </body>
++ </html>
++ '''
++
++ source_resources = set()
++ tmp_dir = util.TempDir(files)
++ source_resources.add(tmp_dir.GetPath('index.html'))
++ source_resources.add(tmp_dir.GetPath('inline_this.html'))
++
++ result = html_inline.DoInline(tmp_dir.GetPath('index.html'), None,
++ preprocess_only=True)
++ resources = result.inlined_files
++ resources.add(tmp_dir.GetPath('index.html'))
++ self.failUnlessEqual(resources, source_resources)
++
++ # Ignore whitespace
++ expected_inlined = re.sub(r'\s+', ' ', expected_inlined)
++ actually_inlined = re.sub(r'\s+', ' ',
++ util.FixLineEnd(result.inlined_data, '\n'))
++ self.failUnlessEqual(expected_inlined, actually_inlined)
++
++ tmp_dir.CleanUp()
++
++ def testPreprocessOnlyAppliesRecursively(self):
++ '''Tests that preprocess_only=true propagates to included files. '''
++
++ files = {
++ 'index.html': '''
++ <html>
++ <include src="outer_include.html">
++ </html>
++ ''',
++ 'outer_include.html': '''
++ <include src="inner_include.html">
++ <link rel="stylesheet" href="not_inlined.css">
++ ''',
++ 'inner_include.html': ''' <p>This should be inlined in index.html</p> ''',
++ 'not_inlined.css': ''' /* This should not be inlined. */ '''
++ }
+
-+ if (isa<CXXDestructorDecl>(method) || method->isPure())
-+ return;
++ expected_inlined = '''
++ <html>
++ <p>This should be inlined in index.html</p>
++ <link rel="stylesheet" href="not_inlined.css">
++ </html>
++ '''
++
++ source_resources = set()
++ tmp_dir = util.TempDir(files)
++ source_resources.add(tmp_dir.GetPath('index.html'))
++ source_resources.add(tmp_dir.GetPath('outer_include.html'))
++ source_resources.add(tmp_dir.GetPath('inner_include.html'))
++
++ result = html_inline.DoInline(tmp_dir.GetPath('index.html'), None,
++ preprocess_only=True)
++ resources = result.inlined_files
++ resources.add(tmp_dir.GetPath('index.html'))
++ self.failUnlessEqual(resources, source_resources)
++
++ # Ignore whitespace
++ expected_inlined = re.sub(r'\s+', ' ', expected_inlined)
++ actually_inlined = re.sub(r'\s+', ' ',
++ util.FixLineEnd(result.inlined_data, '\n'))
++ self.failUnlessEqual(expected_inlined, actually_inlined)
++
++ tmp_dir.CleanUp()
+
-+ if (IsMethodInBannedNamespace(method))
-+ return;
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/format/minifier.py b/tools/grit/grit/format/minifier.py
+new file mode 100644
+index 0000000000..1a0ea34e49
+--- /dev/null
++++ b/tools/grit/grit/format/minifier.py
+@@ -0,0 +1,45 @@
++# Copyright 2016 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++"""Framework for stripping whitespace and comments from resource files"""
+
-+ SourceLocation loc = method->getTypeSpecStartLoc();
-+ emitWarning(loc, "Overriding method must be marked with OVERRIDE.");
++from __future__ import print_function
++
++from os import path
++import subprocess
++import sys
++
++import six
++
++__js_minifier = None
++__css_minifier = None
++
++def SetJsMinifier(minifier):
++ global __js_minifier
++ __js_minifier = minifier.split()
++
++def SetCssMinifier(minifier):
++ global __css_minifier
++ __css_minifier = minifier.split()
++
++def Minify(source, filename):
++ """Minify |source| (bytes) from |filename| and return bytes."""
++ file_type = path.splitext(filename)[1]
++ minifier = None
++ if file_type == '.js':
++ minifier = __js_minifier
++ elif file_type == '.css':
++ minifier = __css_minifier
++ if not minifier:
++ return source
++ p = subprocess.Popen(
++ minifier,
++ stdin=subprocess.PIPE,
++ stdout=subprocess.PIPE,
++ stderr=subprocess.PIPE)
++ (stdout, stderr) = p.communicate(source)
++ if p.returncode != 0:
++ print('Minification failed for %s' % filename)
++ print(stderr)
++ sys.exit(p.returncode)
++ return stdout
+diff --git a/tools/grit/grit/format/policy_templates_json.py b/tools/grit/grit/format/policy_templates_json.py
+new file mode 100644
+index 0000000000..2f9330bb9a
+--- /dev/null
++++ b/tools/grit/grit/format/policy_templates_json.py
+@@ -0,0 +1,26 @@
++# Copyright 2017 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++"""Translates policy_templates.json files.
++"""
++
++from __future__ import print_function
++
++from grit.node import structure
++
++
++def Format(root, lang='en', output_dir='.'):
++ policy_json = None
++ for item in root.ActiveDescendants():
++ with item:
++ if (isinstance(item, structure.StructureNode) and
++ item.attrs['type'] == 'policy_template_metafile'):
++ json_text = item.gatherer.Translate(
++ lang,
++ pseudo_if_not_available=item.PseudoIsAllowed(),
++ fallback_to_english=item.ShouldFallbackToEnglish())
++ # We're only expecting one node of this kind.
++ assert not policy_json
++ policy_json = json_text
++ return policy_json
+diff --git a/tools/grit/grit/format/policy_templates_json_unittest.py b/tools/grit/grit/format/policy_templates_json_unittest.py
+new file mode 100644
+index 0000000000..e252c94e2c
+--- /dev/null
++++ b/tools/grit/grit/format/policy_templates_json_unittest.py
+@@ -0,0 +1,207 @@
++#!/usr/bin/env python
++# coding: utf-8
++# Copyright 2017 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++"""Unittest for policy_templates_json.py.
++"""
++
++from __future__ import print_function
++
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++import grit.extern.tclib
++import tempfile
++import unittest
++
++from six import StringIO
++
++from grit import grd_reader
++from grit.tool import build
++
++
++class PolicyTemplatesJsonUnittest(unittest.TestCase):
++
++ def testPolicyTranslation(self):
++ # Create test policy_templates.json data.
++ caption = "The main policy"
++ caption_translation = "Die Hauptrichtlinie"
++
++ message = \
++ "Red cabbage stays red cabbage and wedding dress stays wedding dress"
++ message_translation = \
++ "Blaukraut bleibt Blaukraut und Brautkleid bleibt Brautkleid"
++
++ schema_key_description = "Number of users"
++ schema_key_description_translation = "Anzahl der Nutzer"
++
++ policy_json = """
++ {
++ "policy_definitions": [
++ {
++ 'name': 'MainPolicy',
++ 'type': 'main',
++ 'owners': ['foo@bar.com'],
++ 'schema': {
++ 'properties': {
++ 'default_launch_container': {
++ 'enum': [
++ 'tab',
++ 'window',
++ ],
++ 'type': 'string',
++ },
++ 'users_number': {
++ 'description': '''%s''',
++ 'type': 'integer',
++ },
++ },
++ 'type': 'object',
++ },
++ 'supported_on': ['chrome_os:29-'],
++ 'features': {
++ 'can_be_recommended': True,
++ 'dynamic_refresh': True,
++ },
++ 'example_value': True,
++ 'caption': '''%s''',
++ 'tags': [],
++ 'desc': '''This policy does stuff.'''
++ },
++ ],
++ "policy_atomic_group_definitions": [],
++ "placeholders": [],
++ "messages": {
++ 'message_string_id': {
++ 'desc': '''The description is removed from the grit output''',
++ 'text': '''%s'''
++ }
++ }
++ }""" % (schema_key_description, caption, message)
++
++ # Create translations. The translation IDs are hashed from the English text.
++ caption_id = grit.extern.tclib.GenerateMessageId(caption);
++ message_id = grit.extern.tclib.GenerateMessageId(message);
++ schema_key_description_id = grit.extern.tclib.GenerateMessageId(
++ schema_key_description)
++ policy_xtb = """
++<?xml version="1.0" ?>
++<!DOCTYPE translationbundle>
++<translationbundle lang="de">
++<translation id="%s">%s</translation>
++<translation id="%s">%s</translation>
++<translation id="%s">%s</translation>
++</translationbundle>""" % (caption_id, caption_translation,
++ message_id, message_translation,
++ schema_key_description_id,
++ schema_key_description_translation)
++
++ # Write both to a temp file.
++ tmp_dir_name = tempfile.gettempdir()
++
++ json_file_path = os.path.join(tmp_dir_name, 'test.json')
++ with open(json_file_path, 'w') as f:
++ f.write(policy_json.strip())
++
++ xtb_file_path = os.path.join(tmp_dir_name, 'test.xtb')
++ with open(xtb_file_path, 'w') as f:
++ f.write(policy_xtb.strip())
++
++ # Assemble a test grit tree, similar to policy_templates.grd.
++ grd_text = '''
++ <grit base_dir="." latest_public_release="0" current_release="1" source_lang_id="en">
++ <translations>
++ <file path="%s" lang="de" />
++ </translations>
++ <release seq="1">
++ <structures>
++ <structure name="IDD_POLICY_SOURCE_FILE" file="%s" type="policy_template_metafile" />
++ </structures>
++ </release>
++ </grit>''' % (xtb_file_path, json_file_path)
++ grd_string_io = StringIO(grd_text)
++
++ # Parse the grit tree and load the policies' JSON with a gatherer.
++ grd = grd_reader.Parse(grd_string_io, dir=tmp_dir_name, defines={'_google_chrome': True})
++ grd.SetOutputLanguage('en')
++ grd.RunGatherers()
++
++ # Remove the temp files.
++ os.unlink(xtb_file_path)
++ os.unlink(json_file_path)
++
++ # Run grit with en->de translation.
++ env_lang = 'en'
++ out_lang = 'de'
++ env_defs = {'_google_chrome': '1'}
++
++ grd.SetOutputLanguage(env_lang)
++ grd.SetDefines(env_defs)
++ buf = StringIO()
++ build.RcBuilder.ProcessNode(grd, DummyOutput('policy_templates', out_lang), buf)
++ output = buf.getvalue()
++
++ # Caption and message texts get taken from xtb.
++ # desc is 'translated' to some pseudo-English
++ # 'ThïPïs pôPôlïPïcýPý dôéPôés stüPüff'.
++ expected = u"""{
++ "policy_definitions": [
++ {
++ "caption": "%s",
++ "desc": "Th\xefP\xefs p\xf4P\xf4l\xefP\xefc\xfdP\xfd d\xf4\xe9P\xf4\xe9s st\xfcP\xfcff.",
++ "example_value": true,
++ "features": {"can_be_recommended": true, "dynamic_refresh": true},
++ "name": "MainPolicy",
++ "owners": ["foo@bar.com"],
++ "schema": {
++ "properties": {
++ "default_launch_container": {
++ "enum": [
++ "tab",
++ "window"
++ ],
++ "type": "string"
++ },
++ "users_number": {
++ "description": "%s",
++ "type": "integer"
++ }
++ },
++ "type": "object"
++ },
++ "supported_on": ["chrome_os:29-"],
++ "tags": [],
++ "type": "main"
++ }
++ ],
++ "policy_atomic_group_definitions": [
++ ],
++ "messages": {
++ "message_string_id": {
++ "text": "%s"
++ }
+ }
+
-+ // Makes sure there is a "virtual" keyword on virtual methods.
-+ //
-+ // Gmock objects trigger these for each MOCK_BLAH() macro used. So we have a
-+ // trick to get around that. If a class has member variables whose types are
-+ // in the "testing" namespace (which is how gmock works behind the scenes),
-+ // there's a really high chance we won't care about these errors
-+ void CheckVirtualMethods(SourceLocation record_location,
-+ CXXRecordDecl* record,
-+ bool warn_on_inline_bodies) {
-+ for (CXXRecordDecl::field_iterator it = record->field_begin();
-+ it != record->field_end(); ++it) {
-+ CXXRecordDecl* record_type =
-+ it->getTypeSourceInfo()->getTypeLoc().getTypePtr()->
-+ getAsCXXRecordDecl();
-+ if (record_type) {
-+ if (InTestingNamespace(record_type)) {
-+ return;
-+ }
++}""" % (caption_translation, schema_key_description_translation,
++ message_translation)
++ self.assertEqual(expected, output)
++
++
++class DummyOutput(object):
++
++ def __init__(self, type, language):
++ self.type = type
++ self.language = language
++
++ def GetType(self):
++ return self.type
++
++ def GetLanguage(self):
++ return self.language
++
++ def GetOutputFilename(self):
++ return 'hello.gif'
+diff --git a/tools/grit/grit/format/rc.py b/tools/grit/grit/format/rc.py
+new file mode 100644
+index 0000000000..ed32bb809e
+--- /dev/null
++++ b/tools/grit/grit/format/rc.py
+@@ -0,0 +1,474 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Support for formatting an RC file for compilation.
++'''
++
++from __future__ import print_function
++
++import os
++import re
++from functools import partial
++
++import six
++
++from grit import util
++from grit.node import misc
++
++
++def Format(root, lang='en', output_dir='.'):
++ from grit.node import empty, include, message, structure
++
++ yield _FormatHeader(root, lang, output_dir)
++
++ for item in root.ActiveDescendants():
++ if isinstance(item, empty.MessagesNode):
++ # Write one STRINGTABLE per <messages> container.
++ # This is hacky: it iterates over the children twice.
++ yield 'STRINGTABLE\nBEGIN\n'
++ for subitem in item.ActiveDescendants():
++ if isinstance(subitem, message.MessageNode):
++ with subitem:
++ yield FormatMessage(subitem, lang)
++ yield 'END\n\n'
++ elif isinstance(item, include.IncludeNode):
++ with item:
++ yield FormatInclude(item, lang, output_dir)
++ elif isinstance(item, structure.StructureNode):
++ with item:
++ yield FormatStructure(item, lang, output_dir)
++
++
++'''
++This dictionary defines the language charset pair lookup table, which is used
++for replacing the GRIT expand variables for language info in Product Version
++resource. The key is the language ISO country code, and the value
++is the language and character-set pair, which is a hexadecimal string
++consisting of the concatenation of the language and character-set identifiers.
++The first 4 digit of the value is the hex value of LCID, the remaining
++4 digits is the hex value of character-set id(code page)of the language.
++
++LCID resource: http://msdn.microsoft.com/en-us/library/ms776294.aspx
++Codepage resource: http://www.science.co.il/language/locale-codes.asp
++
++We have defined three GRIT expand_variables to be used in the version resource
++file to set the language info. Here is an example how they should be used in
++the VS_VERSION_INFO section of the resource file to allow GRIT to localize
++the language info correctly according to product locale.
++
++VS_VERSION_INFO VERSIONINFO
++...
++BEGIN
++ BLOCK "StringFileInfo"
++ BEGIN
++ BLOCK "[GRITVERLANGCHARSETHEX]"
++ BEGIN
++ ...
++ END
++ END
++ BLOCK "VarFileInfo"
++ BEGIN
++ VALUE "Translation", [GRITVERLANGID], [GRITVERCHARSETID]
++ END
++END
++
++'''
++
++_LANGUAGE_CHARSET_PAIR = {
++ # Language neutral LCID, unicode(1200) code page.
++ 'neutral' : '000004b0',
++ # LANG_USER_DEFAULT LCID, unicode(1200) code page.
++ 'userdefault' : '040004b0',
++ 'ar' : '040104e8',
++ 'fi' : '040b04e4',
++ 'ko' : '041203b5',
++ 'es' : '0c0a04e4',
++ 'bg' : '040204e3',
++ # No codepage for filipino, use unicode(1200).
++ 'fil' : '046404e4',
++ 'fr' : '040c04e4',
++ 'lv' : '042604e9',
++ 'sv' : '041d04e4',
++ 'ca' : '040304e4',
++ 'de' : '040704e4',
++ 'lt' : '042704e9',
++ # Do not use! This is only around for backwards
++ # compatibility and will be removed - use fil instead
++ 'tl' : '0c0004b0',
++ 'zh-CN' : '080403a8',
++ 'zh-TW' : '040403b6',
++ 'zh-HK' : '0c0403b6',
++ 'el' : '040804e5',
++ 'no' : '001404e4',
++ 'nb' : '041404e4',
++ 'nn' : '081404e4',
++ 'th' : '041e036a',
++ 'he' : '040d04e7',
++ 'iw' : '040d04e7',
++ 'pl' : '041504e2',
++ 'tr' : '041f04e6',
++ 'hr' : '041a04e4',
++ # No codepage for Hindi, use unicode(1200).
++ 'hi' : '043904b0',
++ 'pt-PT' : '081604e4',
++ 'pt-BR' : '041604e4',
++ 'uk' : '042204e3',
++ 'cs' : '040504e2',
++ 'hu' : '040e04e2',
++ 'ro' : '041804e2',
++ # No codepage for Urdu, use unicode(1200).
++ 'ur' : '042004b0',
++ 'da' : '040604e4',
++ 'is' : '040f04e4',
++ 'ru' : '041904e3',
++ 'vi' : '042a04ea',
++ 'nl' : '041304e4',
++ 'id' : '042104e4',
++ 'sr' : '081a04e2',
++ 'en-GB' : '0809040e',
++ 'it' : '041004e4',
++ 'sk' : '041b04e2',
++ 'et' : '042504e9',
++ 'ja' : '041103a4',
++ 'sl' : '042404e2',
++ 'en' : '040904b0',
++ # LCID for Mexico; Windows does not support L.A. LCID.
++ 'es-419' : '080a04e4',
++ # No codepage for Bengali, use unicode(1200).
++ 'bn' : '044504b0',
++ 'fa' : '042904e8',
++ # No codepage for Gujarati, use unicode(1200).
++ 'gu' : '044704b0',
++ # No codepage for Kannada, use unicode(1200).
++ 'kn' : '044b04b0',
++ # Malay (Malaysia) [ms-MY]
++ 'ms' : '043e04e4',
++ # No codepage for Malayalam, use unicode(1200).
++ 'ml' : '044c04b0',
++ # No codepage for Marathi, use unicode(1200).
++ 'mr' : '044e04b0',
++ # No codepage for Oriya , use unicode(1200).
++ 'or' : '044804b0',
++ # No codepage for Tamil, use unicode(1200).
++ 'ta' : '044904b0',
++ # No codepage for Telugu, use unicode(1200).
++ 'te' : '044a04b0',
++ # No codepage for Amharic, use unicode(1200). >= Vista.
++ 'am' : '045e04b0',
++ 'sw' : '044104e4',
++ 'af' : '043604e4',
++ 'eu' : '042d04e4',
++ 'fr-CA' : '0c0c04e4',
++ 'gl' : '045604e4',
++ # No codepage for Zulu, use unicode(1200).
++ 'zu' : '043504b0',
++ 'fake-bidi' : '040d04e7',
++}
++
++# Language ID resource: http://msdn.microsoft.com/en-us/library/ms776294.aspx
++#
++# There is no appropriate sublang for Spanish (Latin America) [es-419], so we
++# use Mexico. SUBLANG_DEFAULT would incorrectly map to Spain. Unlike other
++# Latin American countries, Mexican Spanish is supported by VERSIONINFO:
++# http://msdn.microsoft.com/en-us/library/aa381058.aspx
++
++_LANGUAGE_DIRECTIVE_PAIR = {
++ 'neutral' : 'LANG_NEUTRAL, SUBLANG_NEUTRAL',
++ 'userdefault' : 'LANG_NEUTRAL, SUBLANG_DEFAULT',
++ 'ar' : 'LANG_ARABIC, SUBLANG_DEFAULT',
++ 'fi' : 'LANG_FINNISH, SUBLANG_DEFAULT',
++ 'ko' : 'LANG_KOREAN, SUBLANG_KOREAN',
++ 'es' : 'LANG_SPANISH, SUBLANG_SPANISH_MODERN',
++ 'bg' : 'LANG_BULGARIAN, SUBLANG_DEFAULT',
++ # LANG_FILIPINO (100) not in VC 7 winnt.h.
++ 'fil' : '100, SUBLANG_DEFAULT',
++ 'fr' : 'LANG_FRENCH, SUBLANG_FRENCH',
++ 'lv' : 'LANG_LATVIAN, SUBLANG_DEFAULT',
++ 'sv' : 'LANG_SWEDISH, SUBLANG_SWEDISH',
++ 'ca' : 'LANG_CATALAN, SUBLANG_DEFAULT',
++ 'de' : 'LANG_GERMAN, SUBLANG_GERMAN',
++ 'lt' : 'LANG_LITHUANIAN, SUBLANG_LITHUANIAN',
++ # Do not use! See above.
++ 'tl' : 'LANG_NEUTRAL, SUBLANG_DEFAULT',
++ 'zh-CN' : 'LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED',
++ 'zh-TW' : 'LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL',
++ 'zh-HK' : 'LANG_CHINESE, SUBLANG_CHINESE_HONGKONG',
++ 'el' : 'LANG_GREEK, SUBLANG_DEFAULT',
++ 'no' : 'LANG_NORWEGIAN, SUBLANG_DEFAULT',
++ 'nb' : 'LANG_NORWEGIAN, SUBLANG_NORWEGIAN_BOKMAL',
++ 'nn' : 'LANG_NORWEGIAN, SUBLANG_NORWEGIAN_NYNORSK',
++ 'th' : 'LANG_THAI, SUBLANG_DEFAULT',
++ 'he' : 'LANG_HEBREW, SUBLANG_DEFAULT',
++ 'iw' : 'LANG_HEBREW, SUBLANG_DEFAULT',
++ 'pl' : 'LANG_POLISH, SUBLANG_DEFAULT',
++ 'tr' : 'LANG_TURKISH, SUBLANG_DEFAULT',
++ 'hr' : 'LANG_CROATIAN, SUBLANG_DEFAULT',
++ 'hi' : 'LANG_HINDI, SUBLANG_DEFAULT',
++ 'pt-PT' : 'LANG_PORTUGUESE, SUBLANG_PORTUGUESE',
++ 'pt-BR' : 'LANG_PORTUGUESE, SUBLANG_DEFAULT',
++ 'uk' : 'LANG_UKRAINIAN, SUBLANG_DEFAULT',
++ 'cs' : 'LANG_CZECH, SUBLANG_DEFAULT',
++ 'hu' : 'LANG_HUNGARIAN, SUBLANG_DEFAULT',
++ 'ro' : 'LANG_ROMANIAN, SUBLANG_DEFAULT',
++ 'ur' : 'LANG_URDU, SUBLANG_DEFAULT',
++ 'da' : 'LANG_DANISH, SUBLANG_DEFAULT',
++ 'is' : 'LANG_ICELANDIC, SUBLANG_DEFAULT',
++ 'ru' : 'LANG_RUSSIAN, SUBLANG_DEFAULT',
++ 'vi' : 'LANG_VIETNAMESE, SUBLANG_DEFAULT',
++ 'nl' : 'LANG_DUTCH, SUBLANG_DEFAULT',
++ 'id' : 'LANG_INDONESIAN, SUBLANG_DEFAULT',
++ 'sr' : 'LANG_SERBIAN, SUBLANG_SERBIAN_LATIN',
++ 'en-GB' : 'LANG_ENGLISH, SUBLANG_ENGLISH_UK',
++ 'it' : 'LANG_ITALIAN, SUBLANG_DEFAULT',
++ 'sk' : 'LANG_SLOVAK, SUBLANG_DEFAULT',
++ 'et' : 'LANG_ESTONIAN, SUBLANG_DEFAULT',
++ 'ja' : 'LANG_JAPANESE, SUBLANG_DEFAULT',
++ 'sl' : 'LANG_SLOVENIAN, SUBLANG_DEFAULT',
++ 'en' : 'LANG_ENGLISH, SUBLANG_ENGLISH_US',
++ # No L.A. sublang exists.
++ 'es-419' : 'LANG_SPANISH, SUBLANG_SPANISH_MEXICAN',
++ 'bn' : 'LANG_BENGALI, SUBLANG_DEFAULT',
++ 'fa' : 'LANG_PERSIAN, SUBLANG_DEFAULT',
++ 'gu' : 'LANG_GUJARATI, SUBLANG_DEFAULT',
++ 'kn' : 'LANG_KANNADA, SUBLANG_DEFAULT',
++ 'ms' : 'LANG_MALAY, SUBLANG_DEFAULT',
++ 'ml' : 'LANG_MALAYALAM, SUBLANG_DEFAULT',
++ 'mr' : 'LANG_MARATHI, SUBLANG_DEFAULT',
++ 'or' : 'LANG_ORIYA, SUBLANG_DEFAULT',
++ 'ta' : 'LANG_TAMIL, SUBLANG_DEFAULT',
++ 'te' : 'LANG_TELUGU, SUBLANG_DEFAULT',
++ 'am' : 'LANG_AMHARIC, SUBLANG_DEFAULT',
++ 'sw' : 'LANG_SWAHILI, SUBLANG_DEFAULT',
++ 'af' : 'LANG_AFRIKAANS, SUBLANG_DEFAULT',
++ 'eu' : 'LANG_BASQUE, SUBLANG_DEFAULT',
++ 'fr-CA' : 'LANG_FRENCH, SUBLANG_FRENCH_CANADIAN',
++ 'gl' : 'LANG_GALICIAN, SUBLANG_DEFAULT',
++ 'zu' : 'LANG_ZULU, SUBLANG_DEFAULT',
++ 'pa' : 'LANG_PUNJABI, SUBLANG_PUNJABI_INDIA',
++ 'sa' : 'LANG_SANSKRIT, SUBLANG_SANSKRIT_INDIA',
++ 'si' : 'LANG_SINHALESE, SUBLANG_SINHALESE_SRI_LANKA',
++ 'ne' : 'LANG_NEPALI, SUBLANG_NEPALI_NEPAL',
++ 'ti' : 'LANG_TIGRIGNA, SUBLANG_TIGRIGNA_ERITREA',
++ 'fake-bidi' : 'LANG_HEBREW, SUBLANG_DEFAULT',
++}
++
++# A note on 'no-specific-language' in the following few functions:
++# Some build systems may wish to call GRIT to scan for dependencies in
++# a language-agnostic way, and can then specify this fake language as
++# the output context. It should never be used when output is actually
++# being generated.
++
++def GetLangCharsetPair(language):
++ if language in _LANGUAGE_CHARSET_PAIR:
++ return _LANGUAGE_CHARSET_PAIR[language]
++ if language != 'no-specific-language':
++ print('Warning:GetLangCharsetPair() found undefined language %s' % language)
++ return ''
++
++def GetLangDirectivePair(language):
++ if language in _LANGUAGE_DIRECTIVE_PAIR:
++ return _LANGUAGE_DIRECTIVE_PAIR[language]
++
++ # We don't check for 'no-specific-language' here because this
++ # function should only get called when output is being formatted,
++ # and at that point we would not want to get
++ # 'no-specific-language' passed as the language.
++ print('Warning:GetLangDirectivePair() found undefined language %s' % language)
++ return 'unknown language: see tools/grit/format/rc.py'
++
++def GetLangIdHex(language):
++ if language in _LANGUAGE_CHARSET_PAIR:
++ langcharset = _LANGUAGE_CHARSET_PAIR[language]
++ lang_id = '0x' + langcharset[0:4]
++ return lang_id
++ if language != 'no-specific-language':
++ print('Warning:GetLangIdHex() found undefined language %s' % language)
++ return ''
++
++
++def GetCharsetIdDecimal(language):
++ if language in _LANGUAGE_CHARSET_PAIR:
++ langcharset = _LANGUAGE_CHARSET_PAIR[language]
++ charset_decimal = int(langcharset[4:], 16)
++ return str(charset_decimal)
++ if language != 'no-specific-language':
++ print('Warning:GetCharsetIdDecimal() found undefined language %s' % language)
++ return ''
++
++
++def GetUnifiedLangCode(language) :
++ r = re.compile('([a-z]{1,2})_([a-z]{1,2})')
++ if r.match(language) :
++ underscore = language.find('_')
++ return language[0:underscore] + '-' + language[underscore + 1:].upper()
++ return language
++
++
++def RcSubstitutions(substituter, lang):
++ '''Add language-based substitutions for Rc files to the substitutor.'''
++ unified_lang_code = GetUnifiedLangCode(lang)
++ substituter.AddSubstitutions({
++ 'GRITVERLANGCHARSETHEX': GetLangCharsetPair(unified_lang_code),
++ 'GRITVERLANGID': GetLangIdHex(unified_lang_code),
++ 'GRITVERCHARSETID': GetCharsetIdDecimal(unified_lang_code)})
++
++
++def _FormatHeader(root, lang, output_dir):
++ '''Returns the required preamble for RC files.'''
++ assert isinstance(lang, six.string_types)
++ assert isinstance(root, misc.GritNode)
++ # Find the location of the resource header file, so that we can include
++ # it.
++ resource_header = 'resource.h' # fall back to this
++ language_directive = ''
++ for output in root.GetOutputFiles():
++ if output.attrs['type'] == 'rc_header':
++ resource_header = os.path.abspath(output.GetOutputFilename())
++ resource_header = util.MakeRelativePath(output_dir, resource_header)
++ if output.attrs['lang'] != lang:
++ continue
++ if output.attrs['language_section'] == '':
++ # If no language_section is requested, no directive is added
++ # (Used when the generated rc will be included from another rc
++ # file that will have the appropriate language directive)
++ language_directive = ''
++ elif output.attrs['language_section'] == 'neutral':
++ # If a neutral language section is requested (default), add a
++ # neutral language directive
++ language_directive = 'LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL'
++ elif output.attrs['language_section'] == 'lang':
++ language_directive = 'LANGUAGE %s' % GetLangDirectivePair(lang)
++ resource_header = resource_header.replace('\\', '\\\\')
++ return '''// This file is automatically generated by GRIT. Do not edit.
++
++#include "%s"
++#include <winresrc.h>
++#ifdef IDC_STATIC
++#undef IDC_STATIC
++#endif
++#define IDC_STATIC (-1)
++
++%s
++
++
++''' % (resource_header, language_directive)
++# end _FormatHeader() function
++
++
++def FormatMessage(item, lang):
++ '''Returns a single message of a string table.'''
++ message = item.ws_at_start + item.Translate(lang) + item.ws_at_end
++ # Escape quotation marks (RC format uses doubling-up
++ message = message.replace('"', '""')
++ # Replace linebreaks with a \n escape
++ message = util.LINEBREAKS.sub(r'\\n', message)
++ if hasattr(item.GetRoot(), 'GetSubstituter'):
++ substituter = item.GetRoot().GetSubstituter()
++ message = substituter.Substitute(message)
++
++ name_attr = item.GetTextualIds()[0]
++
++ return ' %-15s "%s"\n' % (name_attr, message)
++
++
++def _FormatSection(item, lang, output_dir):
++ '''Writes out an .rc file section.'''
++ assert isinstance(lang, six.string_types)
++ from grit.node import structure
++ assert isinstance(item, structure.StructureNode)
++
++ if item.IsExcludedFromRc():
++ return ''
++
++ text = item.gatherer.Translate(
++ lang, skeleton_gatherer=item.GetSkeletonGatherer(),
++ pseudo_if_not_available=item.PseudoIsAllowed(),
++ fallback_to_english=item.ShouldFallbackToEnglish()) + '\n\n'
++
++ # Replace the language expand_variables in version rc info.
++ if item.ExpandVariables() and hasattr(item.GetRoot(), 'GetSubstituter'):
++ substituter = item.GetRoot().GetSubstituter()
++ text = substituter.Substitute(text)
++ return text
++
++
++def FormatInclude(item, lang, output_dir, type=None, process_html=False):
++ '''Formats an item that is included in an .rc file (e.g. an ICON).
++
++ Args:
++ item: an IncludeNode or StructureNode
++ lang, output_dir: standard formatter parameters
++ type: .rc file resource type, e.g. 'ICON' (ignored unless item is a
++ StructureNode)
++ process_html: False/True (ignored unless item is a StructureNode)
++ '''
++ assert isinstance(lang, six.string_types)
++ from grit.node import structure
++ from grit.node import include
++ assert isinstance(item, (structure.StructureNode, include.IncludeNode))
++
++ if isinstance(item, include.IncludeNode):
++ type = item.attrs['type'].upper()
++ process_html = item.attrs['flattenhtml'] == 'true'
++ filename_only = item.attrs['filenameonly'] == 'true'
++ relative_path = item.attrs['relativepath'] == 'true'
++ else:
++ assert (isinstance(item, structure.StructureNode) and item.attrs['type'] in
++ ['admin_template', 'chrome_html', 'chrome_scaled_image',
++ 'tr_html', 'txt'])
++ filename_only = False
++ relative_path = False
++
++ # By default, we use relative pathnames to included resources so that
++ # sharing the resulting .rc files is possible.
++ #
++ # The FileForLanguage() Function has the side effect of generating the file
++ # if needed (e.g. if it is an HTML file include).
++ file_for_lang = item.FileForLanguage(lang, output_dir)
++ if file_for_lang is None:
++ return ''
++
++ filename = os.path.abspath(file_for_lang)
++ if process_html:
++ filename = item.Process(output_dir)
++ elif filename_only:
++ filename = os.path.basename(filename)
++ elif relative_path:
++ filename = util.MakeRelativePath(output_dir, filename)
++
++ filename = filename.replace('\\', '\\\\') # escape for the RC format
++
++ if isinstance(item, structure.StructureNode) and item.IsExcludedFromRc():
++ return ''
++
++ name = item.attrs['name']
++ item_id = item.GetRoot().GetIdMap()[name]
++ return '// ID: %d\n%-18s %-18s "%s"\n' % (item_id, name, type, filename)
++
++
++def _DoNotFormat(item, lang, output_dir):
++ return ''
++
++
++# Formatter instance to use for each type attribute
++# when formatting Structure nodes.
++_STRUCTURE_FORMATTERS = {
++ 'accelerators' : _FormatSection,
++ 'dialog' : _FormatSection,
++ 'menu' : _FormatSection,
++ 'rcdata' : _FormatSection,
++ 'version' : _FormatSection,
++ 'admin_template' : partial(FormatInclude, type='ADM'),
++ 'chrome_html' : partial(FormatInclude, type='BINDATA',
++ process_html=True),
++ 'chrome_scaled_image' : partial(FormatInclude, type='BINDATA'),
++ 'tr_html' : partial(FormatInclude, type='HTML'),
++ 'txt' : partial(FormatInclude, type='TXT'),
++ 'policy_template_metafile': _DoNotFormat,
++}
++
++
++def FormatStructure(item, lang, output_dir):
++ formatter = _STRUCTURE_FORMATTERS[item.attrs['type']]
++ return formatter(item, lang, output_dir)
+diff --git a/tools/grit/grit/format/rc_header.py b/tools/grit/grit/format/rc_header.py
+new file mode 100644
+index 0000000000..ea2c217f53
+--- /dev/null
++++ b/tools/grit/grit/format/rc_header.py
+@@ -0,0 +1,48 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Item formatters for RC headers.
++'''
++
++from __future__ import print_function
++
++
++def Format(root, lang='en', output_dir='.'):
++ yield '''\
++// This file is automatically generated by GRIT. Do not edit.
++
++#pragma once
++'''
++ # Check for emit nodes under the rc_header. If any emit node
++ # is present, we assume it means the GRD file wants to override
++ # the default header, with no includes.
++ default_includes = ['#include <atlres.h>', '']
++ emit_lines = []
++ for output_node in root.GetOutputFiles():
++ if output_node.GetType() == 'rc_header':
++ for child in output_node.children:
++ if child.name == 'emit' and child.attrs['emit_type'] == 'prepend':
++ emit_lines.append(child.GetCdata())
++ for line in emit_lines or default_includes:
++ yield line + '\n'
++ if root.IsWhitelistSupportEnabled():
++ yield '#include "ui/base/resource/whitelist.h"\n'
++ for line in FormatDefines(root):
++ yield line
++
++
++def FormatDefines(root):
++ '''Yields #define SYMBOL 1234 lines.
++
++ Args:
++ root: A GritNode.
++ '''
++ tids = root.GetIdMap()
++ rc_header_format = '#define {0} {1}\n'
++ if root.IsWhitelistSupportEnabled():
++ rc_header_format = '#define {0} (::ui::WhitelistedResource<{1}>(), {1})\n'
++ for item in root.ActiveDescendants():
++ with item:
++ for tid in item.GetTextualIds():
++ yield rc_header_format.format(tid, tids[tid])
+diff --git a/tools/grit/grit/format/rc_header_unittest.py b/tools/grit/grit/format/rc_header_unittest.py
+new file mode 100644
+index 0000000000..eed4d70a99
+--- /dev/null
++++ b/tools/grit/grit/format/rc_header_unittest.py
+@@ -0,0 +1,138 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit tests for the rc_header formatter'''
++
++# GRD samples exceed the 80 character limit.
++# pylint: disable-msg=C6310
++
++from __future__ import print_function
++
++import os
++import sys
++import unittest
++
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++from grit import util
++from grit.format import rc_header
++
++
++class RcHeaderFormatterUnittest(unittest.TestCase):
++ def FormatAll(self, grd):
++ output = rc_header.FormatDefines(grd)
++ return ''.join(output).replace(' ', '')
++
++ def testFormatter(self):
++ grd = util.ParseGrdForUnittest('''
++ <includes first_id="300" comment="bingo">
++ <include type="gif" name="ID_LOGO" file="images/logo.gif" />
++ </includes>
++ <messages first_id="10000">
++ <message name="IDS_GREETING" desc="Printed to greet the currently logged in user">
++ Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, how are you doing today?
++ </message>
++ <message name="IDS_BONGO">
++ Bongo!
++ </message>
++ </messages>
++ <structures>
++ <structure type="dialog" name="IDD_NARROW_DIALOG" file="rc_files/dialogs.rc" />
++ <structure type="version" name="VS_VERSION_INFO" file="rc_files/version.rc" />
++ </structures>''')
++ output = self.FormatAll(grd)
++ self.failUnless(output.count('IDS_GREETING10000'))
++ self.failUnless(output.count('ID_LOGO300'))
++
++ def testOnlyDefineResourcesThatSatisfyOutputCondition(self):
++ grd = util.ParseGrdForUnittest('''
++ <includes first_id="300" comment="bingo">
++ <include type="gif" name="ID_LOGO" file="images/logo.gif" />
++ </includes>
++ <messages first_id="10000">
++ <message name="IDS_FIRSTPRESENTSTRING" desc="Present in .rc file.">
++ I will appear in the .rc file.
++ </message>
++ <if expr="False"> <!--Do not include in the .rc files until used.-->
++ <message name="IDS_MISSINGSTRING" desc="Not present in .rc file.">
++ I will not appear in the .rc file.
++ </message>
++ </if>
++ <if expr="lang != 'es'">
++ <message name="IDS_LANGUAGESPECIFICSTRING" desc="Present in .rc file.">
++ Hello.
++ </message>
++ </if>
++ <if expr="lang == 'es'">
++ <message name="IDS_LANGUAGESPECIFICSTRING" desc="Present in .rc file.">
++ Hola.
++ </message>
++ </if>
++ <message name="IDS_THIRDPRESENTSTRING" desc="Present in .rc file.">
++ I will also appear in the .rc file.
++ </message>
++ </messages>''')
++ output = self.FormatAll(grd)
++ self.failUnless(output.count('IDS_FIRSTPRESENTSTRING10000'))
++ self.failIf(output.count('IDS_MISSINGSTRING'))
++ self.failUnless(output.count('IDS_LANGUAGESPECIFICSTRING10002'))
++ self.failUnless(output.count('IDS_THIRDPRESENTSTRING10003'))
++
++ def testEmit(self):
++ grd = util.ParseGrdForUnittest('''
++ <outputs>
++ <output type="rc_all" filename="dummy">
++ <emit emit_type="prepend">Wrong</emit>
++ </output>
++ <if expr="False">
++ <output type="rc_header" filename="dummy">
++ <emit emit_type="prepend">No</emit>
++ </output>
++ </if>
++ <output type="rc_header" filename="dummy">
++ <emit emit_type="append">Error</emit>
++ </output>
++ <output type="rc_header" filename="dummy">
++ <emit emit_type="prepend">Bingo</emit>
++ </output>
++ </outputs>''')
++ output = ''.join(rc_header.Format(grd, 'en', '.'))
++ output = util.StripBlankLinesAndComments(output)
++ self.assertEqual('#pragma once\nBingo', output)
++
++ def testRcHeaderFormat(self):
++ grd = util.ParseGrdForUnittest('''
++ <includes first_id="300" comment="bingo">
++ <include type="gif" name="IDR_LOGO" file="images/logo.gif" />
++ </includes>
++ <messages first_id="10000">
++ <message name="IDS_GREETING" desc="Printed to greet the currently logged in user">
++ Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, how are you doing today?
++ </message>
++ <message name="IDS_BONGO">
++ Bongo!
++ </message>
++ </messages>''')
++
++ # Using the default settings.
++ output = rc_header.FormatDefines(grd)
++ self.assertEqual(('#define IDR_LOGO 300\n'
++ '#define IDS_GREETING 10000\n'
++ '#define IDS_BONGO 10001\n'), ''.join(output))
++
++ # Using resource whitelist support.
++ grd.SetWhitelistSupportEnabled(True)
++ output = rc_header.FormatDefines(grd)
++ self.assertEqual(('#define IDR_LOGO '
++ '(::ui::WhitelistedResource<300>(), 300)\n'
++ '#define IDS_GREETING '
++ '(::ui::WhitelistedResource<10000>(), 10000)\n'
++ '#define IDS_BONGO '
++ '(::ui::WhitelistedResource<10001>(), 10001)\n'),
++ ''.join(output))
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/format/rc_unittest.py b/tools/grit/grit/format/rc_unittest.py
+new file mode 100644
+index 0000000000..d23f063596
+--- /dev/null
++++ b/tools/grit/grit/format/rc_unittest.py
+@@ -0,0 +1,415 @@
++#!/usr/bin/env python
++# Copyright (c) 2011 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit tests for grit.format.rc'''
++
++from __future__ import print_function
++
++import os
++import re
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++import tempfile
++import unittest
++
++from six import StringIO
++
++from grit import grd_reader
++from grit import util
++from grit.node import structure
++from grit.tool import build
++
++
++_PREAMBLE = '''\
++#include "resource.h"
++#include <winresrc.h>
++#ifdef IDC_STATIC
++#undef IDC_STATIC
++#endif
++#define IDC_STATIC (-1)
++'''
++
++
++class DummyOutput(object):
++ def __init__(self, type, language, file = 'hello.gif'):
++ self.type = type
++ self.language = language
++ self.file = file
++
++ def GetType(self):
++ return self.type
++
++ def GetLanguage(self):
++ return self.language
++
++ def GetOutputFilename(self):
++ return self.file
++
++
++class FormatRcUnittest(unittest.TestCase):
++ def testMessages(self):
++ root = util.ParseGrdForUnittest("""
++ <messages>
++ <message name="IDS_BTN_GO" desc="Button text" meaning="verb">Go!</message>
++ <message name="IDS_GREETING" desc="Printed to greet the currently logged in user">
++ Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, how are you doing today?
++ </message>
++ <message name="BONGO" desc="Flippo nippo">
++ Howdie "Mr. Elephant", how are you doing? '''
++ </message>
++ <message name="IDS_WITH_LINEBREAKS">
++Good day sir,
++I am a bee
++Sting sting
++ </message>
++ </messages>
++ """)
++
++ buf = StringIO()
++ build.RcBuilder.ProcessNode(root, DummyOutput('rc_all', 'en'), buf)
++ output = util.StripBlankLinesAndComments(buf.getvalue())
++ self.assertEqual(_PREAMBLE + u'''\
++STRINGTABLE
++BEGIN
++ IDS_BTN_GO "Go!"
++ IDS_GREETING "Hello %s, how are you doing today?"
++ BONGO "Howdie ""Mr. Elephant"", how are you doing? "
++ IDS_WITH_LINEBREAKS "Good day sir,\\nI am a bee\\nSting sting"
++END''', output)
++
++ def testRcSection(self):
++ root = util.ParseGrdForUnittest(r'''
++ <structures>
++ <structure type="menu" name="IDC_KLONKMENU" file="grit\testdata\klonk.rc" encoding="utf-16" />
++ <structure type="dialog" name="IDD_ABOUTBOX" file="grit\testdata\klonk.rc" encoding="utf-16" />
++ <structure type="version" name="VS_VERSION_INFO" file="grit\testdata\klonk.rc" encoding="utf-16" />
++ </structures>''')
++ root.SetOutputLanguage('en')
++ root.RunGatherers()
++
++ buf = StringIO()
++ build.RcBuilder.ProcessNode(root, DummyOutput('rc_all', 'en'), buf)
++ output = util.StripBlankLinesAndComments(buf.getvalue())
++ expected = _PREAMBLE + u'''\
++IDC_KLONKMENU MENU
++BEGIN
++ POPUP "&File"
++ BEGIN
++ MENUITEM "E&xit", IDM_EXIT
++ MENUITEM "This be ""Klonk"" me like", ID_FILE_THISBE
++ POPUP "gonk"
++ BEGIN
++ MENUITEM "Klonk && is [good]", ID_GONK_KLONKIS
++ END
++ END
++ POPUP "&Help"
++ BEGIN
++ MENUITEM "&About ...", IDM_ABOUT
++ END
++END
++
++IDD_ABOUTBOX DIALOGEX 22, 17, 230, 75
++STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
++CAPTION "About"
++FONT 8, "System", 0, 0, 0x0
++BEGIN
++ ICON IDI_KLONK,IDC_MYICON,14,9,20,20
++ LTEXT "klonk Version ""yibbee"" 1.0",IDC_STATIC,49,10,119,8,
++ SS_NOPREFIX
++ LTEXT "Copyright (C) 2005",IDC_STATIC,49,20,119,8
++ DEFPUSHBUTTON "OK",IDOK,195,6,30,11,WS_GROUP
++ CONTROL "Jack ""Black"" Daniels",IDC_RADIO1,"Button",
++ BS_AUTORADIOBUTTON,46,51,84,10
++END
++
++VS_VERSION_INFO VERSIONINFO
++ FILEVERSION 1,0,0,1
++ PRODUCTVERSION 1,0,0,1
++ FILEFLAGSMASK 0x17L
++#ifdef _DEBUG
++ FILEFLAGS 0x1L
++#else
++ FILEFLAGS 0x0L
++#endif
++ FILEOS 0x4L
++ FILETYPE 0x1L
++ FILESUBTYPE 0x0L
++BEGIN
++ BLOCK "StringFileInfo"
++ BEGIN
++ BLOCK "040904b0"
++ BEGIN
++ VALUE "FileDescription", "klonk Application"
++ VALUE "FileVersion", "1, 0, 0, 1"
++ VALUE "InternalName", "klonk"
++ VALUE "LegalCopyright", "Copyright (C) 2005"
++ VALUE "OriginalFilename", "klonk.exe"
++ VALUE "ProductName", " klonk Application"
++ VALUE "ProductVersion", "1, 0, 0, 1"
++ END
++ END
++ BLOCK "VarFileInfo"
++ BEGIN
++ VALUE "Translation", 0x409, 1200
++ END
++END'''.strip()
++ for expected_line, output_line in zip(expected.split(), output.split()):
++ self.assertEqual(expected_line, output_line)
++
++ def testRcIncludeStructure(self):
++ root = util.ParseGrdForUnittest('''
++ <structures>
++ <structure type="tr_html" name="IDR_HTML" file="bingo.html"/>
++ <structure type="tr_html" name="IDR_HTML2" file="bingo2.html"/>
++ </structures>''', base_dir = '/temp')
++ # We do not run gatherers as it is not needed and wouldn't find the file
++
++ buf = StringIO()
++ build.RcBuilder.ProcessNode(root, DummyOutput('rc_all', 'en'), buf)
++ output = util.StripBlankLinesAndComments(buf.getvalue())
++ expected = (_PREAMBLE +
++ u'IDR_HTML HTML "%s"\n'
++ u'IDR_HTML2 HTML "%s"'
++ % (util.normpath('/temp/bingo.html').replace('\\', '\\\\'),
++ util.normpath('/temp/bingo2.html').replace('\\', '\\\\')))
++ # hackety hack to work on win32&lin
++ output = re.sub(r'"[c-zC-Z]:', '"', output)
++ self.assertEqual(expected, output)
++
++ def testRcIncludeFile(self):
++ root = util.ParseGrdForUnittest('''
++ <includes>
++ <include type="TXT" name="TEXT_ONE" file="bingo.txt"/>
++ <include type="TXT" name="TEXT_TWO" file="bingo2.txt" filenameonly="true" />
++ </includes>''', base_dir = '/temp')
++
++ buf = StringIO()
++ build.RcBuilder.ProcessNode(root, DummyOutput('rc_all', 'en'), buf)
++ output = util.StripBlankLinesAndComments(buf.getvalue())
++ expected = (_PREAMBLE +
++ u'TEXT_ONE TXT "%s"\n'
++ u'TEXT_TWO TXT "%s"'
++ % (util.normpath('/temp/bingo.txt').replace('\\', '\\\\'),
++ 'bingo2.txt'))
++ # hackety hack to work on win32&lin
++ output = re.sub(r'"[c-zC-Z]:', '"', output)
++ self.assertEqual(expected, output)
++
++ def testRcIncludeFlattenedHtmlFile(self):
++ input_file = util.PathFromRoot('grit/testdata/include_test.html')
++ output_file = '%s/HTML_FILE1_include_test.html' % tempfile.gettempdir()
++ root = util.ParseGrdForUnittest('''
++ <includes>
++ <include name="HTML_FILE1" flattenhtml="true" file="%s" type="BINDATA" />
++ </includes>''' % input_file)
++
++ buf = StringIO()
++ build.RcBuilder.ProcessNode(root, DummyOutput('rc_all', 'en', output_file),
++ buf)
++ output = util.StripBlankLinesAndComments(buf.getvalue())
++
++ expected = (_PREAMBLE +
++ u'HTML_FILE1 BINDATA "HTML_FILE1_include_test.html"')
++ # hackety hack to work on win32&lin
++ output = re.sub(r'"[c-zC-Z]:', '"', output)
++ self.assertEqual(expected, output)
++
++ file_contents = util.ReadFile(output_file, 'utf-8')
++
++ # Check for the content added by the <include> tag.
++ self.failUnless(file_contents.find('Hello Include!') != -1)
++ # Check for the content that was removed by if tag.
++ self.failUnless(file_contents.find('should be removed') == -1)
++ # Check for the content that was kept in place by if.
++ self.failUnless(file_contents.find('should be kept') != -1)
++ self.failUnless(file_contents.find('in the middle...') != -1)
++ self.failUnless(file_contents.find('at the end...') != -1)
++ # Check for nested content that was kept
++ self.failUnless(file_contents.find('nested true should be kept') != -1)
++ self.failUnless(file_contents.find('silbing true should be kept') != -1)
++ # Check for removed "<if>" and "</if>" tags.
++ self.failUnless(file_contents.find('<if expr=') == -1)
++ self.failUnless(file_contents.find('</if>') == -1)
++ os.remove(output_file)
++
++ def testStructureNodeOutputfile(self):
++ input_file = util.PathFromRoot('grit/testdata/simple.html')
++ root = util.ParseGrdForUnittest('''
++ <structures>
++ <structure type="tr_html" name="IDR_HTML" file="%s" />
++ </structures>''' % input_file)
++ struct, = root.GetChildrenOfType(structure.StructureNode)
++ # We must run the gatherer since we'll be wanting the translation of the
++ # file. The file exists in the location pointed to.
++ root.SetOutputLanguage('en')
++ root.RunGatherers()
++
++ output_dir = tempfile.gettempdir()
++ en_file = struct.FileForLanguage('en', output_dir)
++ self.failUnless(en_file == input_file)
++ fr_file = struct.FileForLanguage('fr', output_dir)
++ self.failUnless(fr_file == os.path.join(output_dir, 'fr_simple.html'))
++
++ contents = util.ReadFile(fr_file, 'utf-8')
++
++ self.failUnless(contents.find('<p>') != -1) # should contain the markup
++ self.failUnless(contents.find('Hello!') == -1) # should be translated
++ os.remove(fr_file)
++
++ def testChromeHtmlNodeOutputfile(self):
++ input_file = util.PathFromRoot('grit/testdata/chrome_html.html')
++ output_file = '%s/HTML_FILE1_chrome_html.html' % tempfile.gettempdir()
++ root = util.ParseGrdForUnittest('''
++ <structures>
++ <structure type="chrome_html" name="HTML_FILE1" file="%s" flattenhtml="true" />
++ </structures>''' % input_file)
++ struct, = root.GetChildrenOfType(structure.StructureNode)
++ struct.gatherer.SetDefines({'scale_factors': '2x'})
++ # We must run the gatherers since we'll be wanting the chrome_html output.
++ # The file exists in the location pointed to.
++ root.SetOutputLanguage('en')
++ root.RunGatherers()
++
++ buf = StringIO()
++ build.RcBuilder.ProcessNode(root, DummyOutput('rc_all', 'en', output_file),
++ buf)
++ output = util.StripBlankLinesAndComments(buf.getvalue())
++ expected = (_PREAMBLE +
++ u'HTML_FILE1 BINDATA "HTML_FILE1_chrome_html.html"')
++ # hackety hack to work on win32&lin
++ output = re.sub(r'"[c-zC-Z]:', '"', output)
++ self.assertEqual(expected, output)
++
++ file_contents = util.ReadFile(output_file, 'utf-8')
++
++ # Check for the content added by the <include> tag.
++ self.failUnless(file_contents.find('Hello Include!') != -1)
++ # Check for inserted -webkit-image-set.
++ self.failUnless(file_contents.find('content: -webkit-image-set') != -1)
++ os.remove(output_file)
++
++ def testSubstitutionHtml(self):
++ input_file = util.PathFromRoot('grit/testdata/toolbar_about.html')
++ root = grd_reader.Parse(StringIO('''<?xml version="1.0" encoding="UTF-8"?>
++ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
++ <release seq="1" allow_pseudo="False">
++ <structures fallback_to_english="True">
++ <structure type="tr_html" name="IDR_HTML" file="%s" expand_variables="true"/>
++ </structures>
++ </release>
++ </grit>
++ ''' % input_file), util.PathFromRoot('.'))
++ root.SetOutputLanguage('ar')
++ # We must run the gatherers since we'll be wanting the translation of the
++ # file. The file exists in the location pointed to.
++ root.RunGatherers()
++
++ output_dir = tempfile.gettempdir()
++ struct, = root.GetChildrenOfType(structure.StructureNode)
++ ar_file = struct.FileForLanguage('ar', output_dir)
++ self.failUnless(ar_file == os.path.join(output_dir,
++ 'ar_toolbar_about.html'))
++
++ contents = util.ReadFile(ar_file, 'utf-8')
++
++ self.failUnless(contents.find('dir="RTL"') != -1)
++ os.remove(ar_file)
++
++ def testFallbackToEnglish(self):
++ root = util.ParseGrdForUnittest(r'''
++ <structures fallback_to_english="True">
++ <structure type="dialog" name="IDD_ABOUTBOX" file="grit\testdata\klonk.rc" encoding="utf-16" />
++ </structures>''', base_dir=util.PathFromRoot('.'))
++ root.SetOutputLanguage('en')
++ root.RunGatherers()
++
++ buf = StringIO()
++ formatter = build.RcBuilder.ProcessNode(
++ root, DummyOutput('rc_all', 'bingobongo'), buf)
++ output = util.StripBlankLinesAndComments(buf.getvalue())
++ self.assertEqual(_PREAMBLE + '''\
++IDD_ABOUTBOX DIALOGEX 22, 17, 230, 75
++STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
++CAPTION "About"
++FONT 8, "System", 0, 0, 0x0
++BEGIN
++ ICON IDI_KLONK,IDC_MYICON,14,9,20,20
++ LTEXT "klonk Version ""yibbee"" 1.0",IDC_STATIC,49,10,119,8,
++ SS_NOPREFIX
++ LTEXT "Copyright (C) 2005",IDC_STATIC,49,20,119,8
++ DEFPUSHBUTTON "OK",IDOK,195,6,30,11,WS_GROUP
++ CONTROL "Jack ""Black"" Daniels",IDC_RADIO1,"Button",
++ BS_AUTORADIOBUTTON,46,51,84,10
++END''', output)
++
++
++ def testSubstitutionRc(self):
++ root = grd_reader.Parse(StringIO(r'''<?xml version="1.0" encoding="UTF-8"?>
++ <grit latest_public_release="2" source_lang_id="en-US" current_release="3"
++ base_dir=".">
++ <outputs>
++ <output lang="en" type="rc_all" filename="grit\testdata\klonk_resources.rc"/>
++ </outputs>
++ <release seq="1" allow_pseudo="False">
++ <structures>
++ <structure type="menu" name="IDC_KLONKMENU"
++ file="grit\testdata\klonk.rc" encoding="utf-16"
++ expand_variables="true" />
++ </structures>
++ <messages>
++ <message name="good" sub_variable="true">
++ excellent
++ </message>
++ </messages>
++ </release>
++ </grit>
++ '''), util.PathFromRoot('.'))
++ root.SetOutputLanguage('en')
++ root.RunGatherers()
++
++ buf = StringIO()
++ build.RcBuilder.ProcessNode(root, DummyOutput('rc_all', 'en'), buf)
++ output = buf.getvalue()
++ self.assertEqual('''
++// This file is automatically generated by GRIT. Do not edit.
++
++#include "resource.h"
++#include <winresrc.h>
++#ifdef IDC_STATIC
++#undef IDC_STATIC
++#endif
++#define IDC_STATIC (-1)
++
++LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
++
++
++IDC_KLONKMENU MENU
++BEGIN
++ POPUP "&File"
++ BEGIN
++ MENUITEM "E&xit", IDM_EXIT
++ MENUITEM "This be ""Klonk"" me like", ID_FILE_THISBE
++ POPUP "gonk"
++ BEGIN
++ MENUITEM "Klonk && is excellent", ID_GONK_KLONKIS
++ END
++ END
++ POPUP "&Help"
++ BEGIN
++ MENUITEM "&About ...", IDM_ABOUT
++ END
++END
++
++STRINGTABLE
++BEGIN
++ good "excellent"
++END
++'''.strip(), output.strip())
++
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/format/resource_map.py b/tools/grit/grit/format/resource_map.py
+new file mode 100644
+index 0000000000..95a8b83160
+--- /dev/null
++++ b/tools/grit/grit/format/resource_map.py
+@@ -0,0 +1,159 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''This file contains item formatters for resource_map_header and
++resource_map_source files. A resource map is a mapping between resource names
++(string) and the internal resource ID.'''
++
++from __future__ import print_function
++
++import os
++from functools import partial
++
++from grit import util
++
++
++def GetFormatter(type):
++ if type == 'resource_map_header':
++ return _FormatHeader
++ if type == 'resource_file_map_source':
++ return partial(_FormatSource, _GetItemPath)
++ if type == 'resource_map_source':
++ return partial(_FormatSource, _GetItemName)
++
++
++def GetMapName(root):
++ '''Get the name of the resource map based on the header file name. E.g.,
++ if our header filename is theme_resources.h, we name our resource map
++ kThemeResourcesMap.
++
++ |root| is the grd file root.'''
++ outputs = root.GetOutputFiles()
++ rc_header_file = None
++ for output in outputs:
++ if 'rc_header' == output.GetType():
++ rc_header_file = output.GetFilename()
++ if not rc_header_file:
++ raise Exception('unable to find resource header filename')
++ filename = os.path.splitext(os.path.split(rc_header_file)[1])[0]
++ filename = filename[0].upper() + filename[1:]
++ while True:
++ pos = filename.find('_')
++ if pos == -1 or pos >= len(filename):
++ break
++ filename = filename[:pos] + filename[pos + 1].upper() + filename[pos + 2:]
++ return 'k' + filename
++
++
++def _FormatHeader(root, lang='en', output_dir='.'):
++ '''Create the header file for the resource mapping. This file just declares
++ an array of name/value pairs.'''
++ return '''\
++// This file is automatically generated by GRIT. Do not edit.
++
++#include <stddef.h>
++
++#ifndef GRIT_RESOURCE_MAP_STRUCT_
++#define GRIT_RESOURCE_MAP_STRUCT_
++struct GritResourceMap {
++ const char* const name;
++ int value;
++};
++#endif // GRIT_RESOURCE_MAP_STRUCT_
++
++extern const GritResourceMap %(map_name)s[];
++extern const size_t %(map_name)sSize;
++''' % { 'map_name': GetMapName(root) }
++
++
++def _FormatSourceHeader(root, output_dir):
++ '''Create the header of the C++ source file for the resource mapping.'''
++ rc_header_file = None
++ map_header_file = None
++ for output in root.GetOutputFiles():
++ type = output.GetType()
++ if 'rc_header' == type:
++ rc_header_file = util.MakeRelativePath(output_dir,
++ output.GetOutputFilename())
++ elif 'resource_map_header' == type:
++ map_header_file = util.MakeRelativePath(output_dir,
++ output.GetOutputFilename())
++ if not rc_header_file or not map_header_file:
++ raise Exception('resource_map_source output type requires '
++ 'a resource_map_header and rc_header outputs')
++ return '''\
++// This file is automatically generated by GRIT. Do not edit.
++
++#include "%(map_header_file)s"
++
++#include <stddef.h>
++
++#include "base/stl_util.h"
++
++#include "%(rc_header_file)s"
++
++const GritResourceMap %(map_name)s[] = {
++''' % { 'map_header_file': map_header_file,
++ 'rc_header_file': rc_header_file,
++ 'map_name': GetMapName(root),
+ }
-+ }
+
-+ for (CXXRecordDecl::method_iterator it = record->method_begin();
-+ it != record->method_end(); ++it) {
-+ if (it->isCopyAssignmentOperator() || isa<CXXConstructorDecl>(*it)) {
-+ // Ignore constructors and assignment operators.
-+ } else if (isa<CXXDestructorDecl>(*it) &&
-+ !record->hasUserDeclaredDestructor()) {
-+ // Ignore non-user-declared destructors.
-+ } else {
-+ CheckVirtualMethod(*it, warn_on_inline_bodies);
-+ CheckOverriddenMethod(*it);
++
++def _FormatSourceFooter(root):
++ # Return the footer text.
++ return '''\
++};
++
++const size_t %(map_name)sSize = base::size(%(map_name)s);
++''' % { 'map_name': GetMapName(root) }
++
++
++def _FormatSource(get_key, root, lang, output_dir):
++ from grit.node import include, structure, message
++ id_map = root.GetIdMap()
++ yield _FormatSourceHeader(root, output_dir)
++ seen = set()
++ for item in root.ActiveDescendants():
++ if not item.IsResourceMapSource():
++ continue
++ key = get_key(item)
++ tid = item.attrs['name']
++ if tid not in id_map or key in seen:
++ continue
++ seen.add(key)
++ yield ' {"%s", %s},\n' % (key, tid)
++ yield _FormatSourceFooter(root)
++
++
++def _GetItemName(item):
++ return item.attrs['name']
++
++# Check if |path2| is a subpath of |path1|.
++def _IsSubpath(path1, path2):
++ path1_abs = os.path.abspath(path1)
++ path2_abs = os.path.abspath(path2)
++ common = os.path.commonprefix([path1_abs, path2_abs])
++ return path1_abs == common
++
++def _GetItemPath(item):
++ path = item.GetInputPath().replace("\\", "/")
++
++ # Handle the case where the file resides within the output folder,
++ # by expanding any variables as well as replacing the output folder name with
++ # a fixed string such that the key added to the map does not depend on a given
++ # developer's setup.
++ #
++ # For example this will convert the following path:
++ # ../../out/gchrome/${root_gen_dir}/ui/webui/resources/js/foo.js
++ # to:
++ # @out_folder@/gen/ui/webui/resources/js/foo.js
++
++ real_path = item.ToRealPath(item.GetInputPath())
++ if (item.attrs.get('use_base_dir', 'true') != 'true' and
++ _IsSubpath(os.path.curdir, real_path)):
++ path = os.path.join(
++ '@out_folder@', os.path.relpath(real_path)).replace("\\", "/")
++
++ assert '$' not in path, 'all variables should have been expanded'
++ return path
+diff --git a/tools/grit/grit/format/resource_map_unittest.py b/tools/grit/grit/format/resource_map_unittest.py
+new file mode 100644
+index 0000000000..3499b321ef
+--- /dev/null
++++ b/tools/grit/grit/format/resource_map_unittest.py
+@@ -0,0 +1,345 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit tests for grit.format.resource_map'''
++
++from __future__ import print_function
++
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++import unittest
++
++from grit import util
++from grit.format import resource_map
++
++
++class FormatResourceMapUnittest(unittest.TestCase):
++ def testFormatResourceMap(self):
++ grd = util.ParseGrdForUnittest('''
++ <outputs>
++ <output type="rc_header" filename="the_rc_header.h" />
++ <output type="resource_map_header"
++ filename="the_resource_map_header.h" />
++ </outputs>
++ <release seq="3">
++ <structures first_id="300">
++ <structure type="menu" name="IDC_KLONKMENU"
++ file="grit\\testdata\\klonk.rc" encoding="utf-16" />
++ </structures>
++ <includes first_id="10000">
++ <include type="foo" file="abc" name="IDS_FIRSTPRESENT" />
++ <if expr="False">
++ <include type="foo" file="def" name="IDS_MISSING" />
++ </if>
++ <if expr="lang != 'es'">
++ <include type="foo" file="ghi" name="IDS_LANGUAGESPECIFIC" />
++ </if>
++ <if expr="lang == 'es'">
++ <include type="foo" file="jkl" name="IDS_LANGUAGESPECIFIC" />
++ </if>
++ <include type="foo" file="mno" name="IDS_THIRDPRESENT" />
++ <include type="foo" file="opq" name="IDS_FOURTHPRESENT"
++ skip_in_resource_map="true" />
++ </includes>
++ </release>''', run_gatherers=True)
++ output = util.StripBlankLinesAndComments(''.join(
++ resource_map.GetFormatter('resource_map_header')(grd, 'en', '.')))
++ self.assertEqual('''\
++#include <stddef.h>
++#ifndef GRIT_RESOURCE_MAP_STRUCT_
++#define GRIT_RESOURCE_MAP_STRUCT_
++struct GritResourceMap {
++ const char* const name;
++ int value;
++};
++#endif // GRIT_RESOURCE_MAP_STRUCT_
++extern const GritResourceMap kTheRcHeader[];
++extern const size_t kTheRcHeaderSize;''', output)
++ output = util.StripBlankLinesAndComments(''.join(
++ resource_map.GetFormatter('resource_map_source')(grd, 'en', '.')))
++ self.assertEqual('''\
++#include "the_resource_map_header.h"
++#include <stddef.h>
++#include "base/stl_util.h"
++#include "the_rc_header.h"
++const GritResourceMap kTheRcHeader[] = {
++ {"IDC_KLONKMENU", IDC_KLONKMENU},
++ {"IDS_FIRSTPRESENT", IDS_FIRSTPRESENT},
++ {"IDS_LANGUAGESPECIFIC", IDS_LANGUAGESPECIFIC},
++ {"IDS_THIRDPRESENT", IDS_THIRDPRESENT},
++};
++const size_t kTheRcHeaderSize = base::size(kTheRcHeader);''', output)
++ output = util.StripBlankLinesAndComments(''.join(
++ resource_map.GetFormatter('resource_file_map_source')(grd, 'en', '.')))
++ self.assertEqual('''\
++#include "the_resource_map_header.h"
++#include <stddef.h>
++#include "base/stl_util.h"
++#include "the_rc_header.h"
++const GritResourceMap kTheRcHeader[] = {
++ {"grit/testdata/klonk.rc", IDC_KLONKMENU},
++ {"abc", IDS_FIRSTPRESENT},
++ {"ghi", IDS_LANGUAGESPECIFIC},
++ {"mno", IDS_THIRDPRESENT},
++};
++const size_t kTheRcHeaderSize = base::size(kTheRcHeader);''', output)
++
++ def testFormatResourceMapWithGeneratedFile(self):
++ os.environ["root_gen_dir"] = "gen"
++
++ grd = util.ParseGrdForUnittest('''\
++ <outputs>
++ <output type="rc_header" filename="the_rc_header.h" />
++ <output type="resource_map_header"
++ filename="resource_map_header.h" />
++ </outputs>
++ <release seq="3">
++ <includes first_id="10000">
++ <include type="BINDATA"
++ file="${root_gen_dir}/foo/bar/baz.js"
++ name="IDR_FOO_BAR_BAZ_JS"
++ use_base_dir="false"
++ compress="gzip" />
++ </includes>
++ </release>''', run_gatherers=True)
++
++ formatter = resource_map.GetFormatter('resource_file_map_source')
++ output = util.StripBlankLinesAndComments(''.join(formatter(grd, 'en', '.')))
++ expected = '''\
++#include "resource_map_header.h"
++#include <stddef.h>
++#include "base/stl_util.h"
++#include "the_rc_header.h"
++const GritResourceMap kTheRcHeader[] = {
++ {"@out_folder@/gen/foo/bar/baz.js", IDR_FOO_BAR_BAZ_JS},
++};
++const size_t kTheRcHeaderSize = base::size(kTheRcHeader);'''
++ self.assertEqual(expected, output)
++
++ def testFormatResourceMapWithOutputAllEqualsFalseForStructures(self):
++ grd = util.ParseGrdForUnittest('''
++ <outputs>
++ <output type="rc_header" filename="the_rc_header.h" />
++ <output type="resource_map_header"
++ filename="the_resource_map_header.h" />
++ <output type="resource_map_source"
++ filename="the_resource_map_header.cc" />
++ </outputs>
++ <release seq="3">
++ <structures first_id="300">
++ <structure type="chrome_scaled_image" name="IDR_KLONKMENU"
++ file="foo.png" />
++ <if expr="False">
++ <structure type="chrome_scaled_image" name="IDR_MISSING"
++ file="bar.png" />
++ </if>
++ <if expr="True">
++ <structure type="chrome_scaled_image" name="IDR_BLOB"
++ file="blob.png" />
++ </if>
++ <if expr="True">
++ <then>
++ <structure type="chrome_scaled_image" name="IDR_METEOR"
++ file="meteor.png" />
++ </then>
++ <else>
++ <structure type="chrome_scaled_image" name="IDR_METEOR"
++ file="roetem.png" />
++ </else>
++ </if>
++ <if expr="False">
++ <structure type="chrome_scaled_image" name="IDR_LAST"
++ file="zyx.png" />
++ </if>
++ <if expr="True">
++ <structure type="chrome_scaled_image" name="IDR_LAST"
++ file="xyz.png" />
++ </if>
++ </structures>
++ </release>''', run_gatherers=True)
++ output = util.StripBlankLinesAndComments(''.join(
++ resource_map.GetFormatter('resource_map_header')(grd, 'en', '.')))
++ self.assertEqual('''\
++#include <stddef.h>
++#ifndef GRIT_RESOURCE_MAP_STRUCT_
++#define GRIT_RESOURCE_MAP_STRUCT_
++struct GritResourceMap {
++ const char* const name;
++ int value;
++};
++#endif // GRIT_RESOURCE_MAP_STRUCT_
++extern const GritResourceMap kTheRcHeader[];
++extern const size_t kTheRcHeaderSize;''', output)
++ output = util.StripBlankLinesAndComments(''.join(
++ resource_map.GetFormatter('resource_map_source')(grd, 'en', '.')))
++ self.assertEqual('''\
++#include "the_resource_map_header.h"
++#include <stddef.h>
++#include "base/stl_util.h"
++#include "the_rc_header.h"
++const GritResourceMap kTheRcHeader[] = {
++ {"IDR_KLONKMENU", IDR_KLONKMENU},
++ {"IDR_BLOB", IDR_BLOB},
++ {"IDR_METEOR", IDR_METEOR},
++ {"IDR_LAST", IDR_LAST},
++};
++const size_t kTheRcHeaderSize = base::size(kTheRcHeader);''', output)
++ output = util.StripBlankLinesAndComments(''.join(
++ resource_map.GetFormatter('resource_map_source')(grd, 'en', '.')))
++ self.assertEqual('''\
++#include "the_resource_map_header.h"
++#include <stddef.h>
++#include "base/stl_util.h"
++#include "the_rc_header.h"
++const GritResourceMap kTheRcHeader[] = {
++ {"IDR_KLONKMENU", IDR_KLONKMENU},
++ {"IDR_BLOB", IDR_BLOB},
++ {"IDR_METEOR", IDR_METEOR},
++ {"IDR_LAST", IDR_LAST},
++};
++const size_t kTheRcHeaderSize = base::size(kTheRcHeader);''', output)
++
++ def testFormatResourceMapWithOutputAllEqualsFalseForIncludes(self):
++ grd = util.ParseGrdForUnittest('''
++ <outputs>
++ <output type="rc_header" filename="the_rc_header.h" />
++ <output type="resource_map_header"
++ filename="the_resource_map_header.h" />
++ </outputs>
++ <release seq="3">
++ <structures first_id="300">
++ <structure type="menu" name="IDC_KLONKMENU"
++ file="grit\\testdata\\klonk.rc" encoding="utf-16" />
++ </structures>
++ <includes first_id="10000">
++ <include type="foo" file="abc" name="IDS_FIRSTPRESENT" />
++ <if expr="False">
++ <include type="foo" file="def" name="IDS_MISSING" />
++ </if>
++ <include type="foo" file="mno" name="IDS_THIRDPRESENT" />
++ <if expr="True">
++ <include type="foo" file="blob" name="IDS_BLOB" />
++ </if>
++ <if expr="True">
++ <then>
++ <include type="foo" file="meteor" name="IDS_METEOR" />
++ </then>
++ <else>
++ <include type="foo" file="roetem" name="IDS_METEOR" />
++ </else>
++ </if>
++ <if expr="False">
++ <include type="foo" file="zyx" name="IDS_LAST" />
++ </if>
++ <if expr="True">
++ <include type="foo" file="xyz" name="IDS_LAST" />
++ </if>
++ </includes>
++ </release>''', run_gatherers=True)
++ output = util.StripBlankLinesAndComments(''.join(
++ resource_map.GetFormatter('resource_map_header')(grd, 'en', '.')))
++ self.assertEqual('''\
++#include <stddef.h>
++#ifndef GRIT_RESOURCE_MAP_STRUCT_
++#define GRIT_RESOURCE_MAP_STRUCT_
++struct GritResourceMap {
++ const char* const name;
++ int value;
++};
++#endif // GRIT_RESOURCE_MAP_STRUCT_
++extern const GritResourceMap kTheRcHeader[];
++extern const size_t kTheRcHeaderSize;''', output)
++ output = util.StripBlankLinesAndComments(''.join(
++ resource_map.GetFormatter('resource_map_source')(grd, 'en', '.')))
++ self.assertEqual('''\
++#include "the_resource_map_header.h"
++#include <stddef.h>
++#include "base/stl_util.h"
++#include "the_rc_header.h"
++const GritResourceMap kTheRcHeader[] = {
++ {"IDC_KLONKMENU", IDC_KLONKMENU},
++ {"IDS_FIRSTPRESENT", IDS_FIRSTPRESENT},
++ {"IDS_THIRDPRESENT", IDS_THIRDPRESENT},
++ {"IDS_BLOB", IDS_BLOB},
++ {"IDS_METEOR", IDS_METEOR},
++ {"IDS_LAST", IDS_LAST},
++};
++const size_t kTheRcHeaderSize = base::size(kTheRcHeader);''', output)
++ output = util.StripBlankLinesAndComments(''.join(
++ resource_map.GetFormatter('resource_file_map_source')(grd, 'en', '.')))
++ self.assertEqual('''\
++#include "the_resource_map_header.h"
++#include <stddef.h>
++#include "base/stl_util.h"
++#include "the_rc_header.h"
++const GritResourceMap kTheRcHeader[] = {
++ {"grit/testdata/klonk.rc", IDC_KLONKMENU},
++ {"abc", IDS_FIRSTPRESENT},
++ {"mno", IDS_THIRDPRESENT},
++ {"blob", IDS_BLOB},
++ {"meteor", IDS_METEOR},
++ {"xyz", IDS_LAST},
++};
++const size_t kTheRcHeaderSize = base::size(kTheRcHeader);''', output)
++
++ def testFormatStringResourceMap(self):
++ grd = util.ParseGrdForUnittest('''
++ <outputs>
++ <output type="rc_header" filename="the_rc_header.h" />
++ <output type="resource_map_header" filename="the_rc_map_header.h" />
++ <output type="resource_map_source" filename="the_rc_map_source.cc" />
++ </outputs>
++ <release seq="1" allow_pseudo="false">
++ <messages fallback_to_english="true">
++ <message name="IDS_PRODUCT_NAME" desc="The application name">
++ Application
++ </message>
++ <if expr="True">
++ <message name="IDS_DEFAULT_TAB_TITLE_TITLE_CASE"
++ desc="In Title Case: The default title in a tab.">
++ New Tab
++ </message>
++ </if>
++ <if expr="False">
++ <message name="IDS_DEFAULT_TAB_TITLE"
++ desc="The default title in a tab.">
++ New tab
++ </message>
++ </if>
++ </messages>
++ </release>''', run_gatherers=True)
++ grd.InitializeIds()
++ output = util.StripBlankLinesAndComments(''.join(
++ resource_map.GetFormatter('resource_map_header')(grd, 'en', '.')))
++ self.assertEqual('''\
++#include <stddef.h>
++#ifndef GRIT_RESOURCE_MAP_STRUCT_
++#define GRIT_RESOURCE_MAP_STRUCT_
++struct GritResourceMap {
++ const char* const name;
++ int value;
++};
++#endif // GRIT_RESOURCE_MAP_STRUCT_
++extern const GritResourceMap kTheRcHeader[];
++extern const size_t kTheRcHeaderSize;''', output)
++ output = util.StripBlankLinesAndComments(''.join(
++ resource_map.GetFormatter('resource_map_source')(grd, 'en', '.')))
++ self.assertEqual('''\
++#include "the_rc_map_header.h"
++#include <stddef.h>
++#include "base/stl_util.h"
++#include "the_rc_header.h"
++const GritResourceMap kTheRcHeader[] = {
++ {"IDS_PRODUCT_NAME", IDS_PRODUCT_NAME},
++ {"IDS_DEFAULT_TAB_TITLE_TITLE_CASE", IDS_DEFAULT_TAB_TITLE_TITLE_CASE},
++};
++const size_t kTheRcHeaderSize = base::size(kTheRcHeader);''', output)
++
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/gather/__init__.py b/tools/grit/grit/gather/__init__.py
+new file mode 100644
+index 0000000000..2d578f5643
+--- /dev/null
++++ b/tools/grit/grit/gather/__init__.py
+@@ -0,0 +1,8 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Module grit.gather
++'''
++
++pass
+diff --git a/tools/grit/grit/gather/admin_template.py b/tools/grit/grit/gather/admin_template.py
+new file mode 100644
+index 0000000000..c26b6a88d7
+--- /dev/null
++++ b/tools/grit/grit/gather/admin_template.py
+@@ -0,0 +1,62 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Gatherer for administrative template files.
++'''
++
++from __future__ import print_function
++
++import re
++
++from grit.gather import regexp
++from grit import exception
++from grit import lazy_re
++
++
++class MalformedAdminTemplateException(exception.Base):
++ '''This file doesn't look like a .adm file to me.'''
++ pass
++
++
++class AdmGatherer(regexp.RegexpGatherer):
++ '''Gatherer for the translateable portions of an admin template.
++
++ This gatherer currently makes the following assumptions:
++ - there is only one [strings] section and it is always the last section
++ of the file
++ - translateable strings do not need to be escaped.
++ '''
++
++ # Finds the strings section as the group named 'strings'
++ _STRINGS_SECTION = lazy_re.compile(
++ r'(?P<first_part>.+^\[strings\])(?P<strings>.+)\Z',
++ re.MULTILINE | re.DOTALL)
++
++ # Finds the translateable sections from within the [strings] section.
++ _TRANSLATEABLES = lazy_re.compile(
++ r'^\s*[A-Za-z0-9_]+\s*=\s*"(?P<text>.+)"\s*$',
++ re.MULTILINE)
++
++ def Escape(self, text):
++ return text.replace('\n', '\\n')
++
++ def UnEscape(self, text):
++ return text.replace('\\n', '\n')
++
++ def Parse(self):
++ if self.have_parsed_:
++ return
++ self.have_parsed_ = True
++
++ self.text_ = self._LoadInputFile().strip()
++ m = self._STRINGS_SECTION.match(self.text_)
++ if not m:
++ raise MalformedAdminTemplateException()
++ # Add the first part, which is all nontranslateable, to the skeleton
++ self._AddNontranslateableChunk(m.group('first_part'))
++ # Then parse the rest using the _TRANSLATEABLES regexp.
++ self._RegExpParse(self._TRANSLATEABLES, m.group('strings'))
++
++ def GetTextualIds(self):
++ return [self.extkey]
+diff --git a/tools/grit/grit/gather/admin_template_unittest.py b/tools/grit/grit/gather/admin_template_unittest.py
+new file mode 100644
+index 0000000000..c637af3a75
+--- /dev/null
++++ b/tools/grit/grit/gather/admin_template_unittest.py
+@@ -0,0 +1,115 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit tests for the admin template gatherer.'''
++
++from __future__ import print_function
++
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++import unittest
++
++from six import StringIO
++
++from grit.gather import admin_template
++from grit import util
++from grit import grd_reader
++from grit import grit_runner
++from grit.tool import build
++
++
++class AdmGathererUnittest(unittest.TestCase):
++ def testParsingAndTranslating(self):
++ pseudofile = StringIO(
++ 'bingo bongo\n'
++ 'ding dong\n'
++ '[strings] \n'
++ 'whatcha="bingo bongo"\n'
++ 'gotcha = "bingolabongola "the wise" fingulafongula" \n')
++ gatherer = admin_template.AdmGatherer(pseudofile)
++ gatherer.Parse()
++ self.failUnless(len(gatherer.GetCliques()) == 2)
++ self.failUnless(gatherer.GetCliques()[1].GetMessage().GetRealContent() ==
++ 'bingolabongola "the wise" fingulafongula')
++
++ translation = gatherer.Translate('en')
++ self.failUnless(translation == gatherer.GetText().strip())
++
++ def testErrorHandling(self):
++ pseudofile = StringIO(
++ 'bingo bongo\n'
++ 'ding dong\n'
++ 'whatcha="bingo bongo"\n'
++ 'gotcha = "bingolabongola "the wise" fingulafongula" \n')
++ gatherer = admin_template.AdmGatherer(pseudofile)
++ self.assertRaises(admin_template.MalformedAdminTemplateException,
++ gatherer.Parse)
++
++ _TRANSLATABLES_FROM_FILE = (
++ 'Google', 'Google Desktop', 'Preferences',
++ 'Controls Google Desktop preferences',
++ 'Indexing and Capture Control',
++ 'Controls what files, web pages, and other content will be indexed by Google Desktop.',
++ 'Prevent indexing of email',
++ # there are lots more but we don't check any further
++ )
++
++ def VerifyCliquesFromAdmFile(self, cliques):
++ self.failUnless(len(cliques) > 20)
++ for clique, expected in zip(cliques, self._TRANSLATABLES_FROM_FILE):
++ text = clique.GetMessage().GetRealContent()
++ self.failUnless(text == expected)
++
++ def testFromFile(self):
++ fname = util.PathFromRoot('grit/testdata/GoogleDesktop.adm')
++ gatherer = admin_template.AdmGatherer(fname)
++ gatherer.Parse()
++ cliques = gatherer.GetCliques()
++ self.VerifyCliquesFromAdmFile(cliques)
++
++ def MakeGrd(self):
++ grd = grd_reader.Parse(StringIO('''<?xml version="1.0" encoding="UTF-8"?>
++ <grit latest_public_release="2" source_lang_id="en-US" current_release="3">
++ <release seq="3">
++ <structures>
++ <structure type="admin_template" name="IDAT_GOOGLE_DESKTOP_SEARCH"
++ file="GoogleDesktop.adm" exclude_from_rc="true" />
++ <structure type="txt" name="BINGOBONGO"
++ file="README.txt" exclude_from_rc="true" />
++ </structures>
++ </release>
++ <outputs>
++ <output filename="de_res.rc" type="rc_all" lang="de" />
++ </outputs>
++ </grit>'''), util.PathFromRoot('grit/testdata'))
++ grd.SetOutputLanguage('en')
++ grd.RunGatherers()
++ return grd
++
++ def testInGrd(self):
++ grd = self.MakeGrd()
++ cliques = grd.children[0].children[0].children[0].GetCliques()
++ self.VerifyCliquesFromAdmFile(cliques)
++
++ def testFileIsOutput(self):
++ grd = self.MakeGrd()
++ dirname = util.TempDir({})
++ try:
++ tool = build.RcBuilder()
++ tool.o = grit_runner.Options()
++ tool.output_directory = dirname.GetPath()
++ tool.res = grd
++ tool.Process()
++
++ self.failUnless(os.path.isfile(dirname.GetPath('de_GoogleDesktop.adm')))
++ self.failUnless(os.path.isfile(dirname.GetPath('de_README.txt')))
++ finally:
++ dirname.CleanUp()
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/gather/chrome_html.py b/tools/grit/grit/gather/chrome_html.py
+new file mode 100644
+index 0000000000..71c1332d66
+--- /dev/null
++++ b/tools/grit/grit/gather/chrome_html.py
+@@ -0,0 +1,377 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++"""Prepares a Chrome HTML file by inlining resources and adding references to
++high DPI resources and removing references to unsupported scale factors.
++
++This is a small gatherer that takes a HTML file, looks for src attributes
++and inlines the specified file, producing one HTML file with no external
++dependencies. It recursively inlines the included files. When inlining CSS
++image files this script also checks for the existence of high DPI versions
++of the inlined file including those on relevant platforms. Unsupported scale
++factors are also removed from existing image sets to support explicitly
++referencing all available images.
++"""
++
++from __future__ import print_function
++
++import os
++import re
++
++from grit import lazy_re
++from grit import util
++from grit.format import html_inline
++from grit.gather import interface
++
++
++# Distribution string to replace with distribution.
++DIST_SUBSTR = '%DISTRIBUTION%'
++
++
++# Matches a chrome theme source URL.
++_THEME_SOURCE = lazy_re.compile(
++ r'(?P<baseurl>chrome://theme/IDR_[A-Z0-9_]*)(?P<query>\?.*)?')
++# Pattern for matching CSS url() function.
++_CSS_URL_PATTERN = r'url\((?P<quote>"|\'|)(?P<filename>[^"\'()]*)(?P=quote)\)'
++# Matches CSS url() functions with the capture group 'filename'.
++_CSS_URL = lazy_re.compile(_CSS_URL_PATTERN)
++# Matches one or more CSS image urls used in given properties.
++_CSS_IMAGE_URLS = lazy_re.compile(
++ r'(?P<attribute>content|background|[\w-]*-image):\s*'
++ r'(?P<urls>(' + _CSS_URL_PATTERN + r'\s*,?\s*)+)')
++# Matches CSS image sets.
++_CSS_IMAGE_SETS = lazy_re.compile(
++ r'(?P<attribute>content|background|[\w-]*-image):[ ]*'
++ r'-webkit-image-set\((?P<images>'
++ r'(\s*,?\s*url\((?P<quote>"|\'|)[^"\'()]*(?P=quote)\)[ ]*[0-9.]*x)*)\)',
++ re.MULTILINE)
++# Matches a single image in a CSS image set with the capture group scale.
++_CSS_IMAGE_SET_IMAGE = lazy_re.compile(r'\s*,?\s*'
++ r'url\((?P<quote>"|\'|)[^"\'()]*(?P=quote)\)[ ]*(?P<scale>[0-9.]*x)',
++ re.MULTILINE)
++_HTML_IMAGE_SRC = lazy_re.compile(
++ r'<img[^>]+src=\"(?P<filename>[^">]*)\"[^>]*>')
++
++def GetImageList(
++ base_path, filename, scale_factors, distribution,
++ filename_expansion_function=None):
++ """Generate the list of images which match the provided scale factors.
++
++ Takes an image filename and checks for files of the same name in folders
++ corresponding to the supported scale factors. If the file is from a
++ chrome://theme/ source, inserts supported @Nx scale factors as high DPI
++ versions.
++
++ Args:
++ base_path: path to look for relative file paths in
++ filename: name of the base image file
++ scale_factors: a list of the supported scale factors (i.e. ['2x'])
++ distribution: string that should replace %DISTRIBUTION%
++
++ Returns:
++ array of tuples containing scale factor and image (i.e.
++ [('1x', 'image.png'), ('2x', '2x/image.png')]).
++ """
++ # Any matches for which a chrome URL handler will serve all scale factors
++ # can simply request all scale factors.
++ theme_match = _THEME_SOURCE.match(filename)
++ if theme_match:
++ images = [('1x', filename)]
++ for scale_factor in scale_factors:
++ scale_filename = "%s@%s" % (theme_match.group('baseurl'), scale_factor)
++ if theme_match.group('query'):
++ scale_filename += theme_match.group('query')
++ images.append((scale_factor, scale_filename))
++ return images
++
++ if filename.find(':') != -1:
++ # filename is probably a URL, only return filename itself.
++ return [('1x', filename)]
++
++ filename = filename.replace(DIST_SUBSTR, distribution)
++ if filename_expansion_function:
++ filename = filename_expansion_function(filename)
++ filepath = os.path.join(base_path, filename)
++ images = [('1x', filename)]
++
++ for scale_factor in scale_factors:
++ # Check for existence of file and add to image set.
++ scale_path = os.path.split(os.path.join(base_path, filename))
++ scale_image_path = os.path.join(scale_path[0], scale_factor, scale_path[1])
++ if os.path.isfile(scale_image_path):
++ # HTML/CSS always uses forward slashed paths.
++ parts = filename.rsplit('/', 1)
++ if len(parts) == 1:
++ path = ''
++ else:
++ path = parts[0] + '/'
++ scale_image_name = path + scale_factor + '/' + parts[-1]
++ images.append((scale_factor, scale_image_name))
++ return images
++
++
++def GenerateImageSet(images, quote):
++ """Generates a -webkit-image-set for the provided list of images.
++
++ Args:
++ images: an array of tuples giving scale factor and file path
++ (i.e. [('1x', 'image.png'), ('2x', '2x/image.png')]).
++ quote: a string giving the quotation character to use (i.e. "'")
++
++ Returns:
++ string giving a -webkit-image-set rule referencing the provided images.
++ (i.e. '-webkit-image-set(url('image.png') 1x, url('2x/image.png') 2x)')
++ """
++ imageset = []
++ for (scale_factor, filename) in images:
++ imageset.append("url(%s%s%s) %s" % (quote, filename, quote, scale_factor))
++ return "-webkit-image-set(%s)" % (', '.join(imageset))
++
++
++def UrlToImageSet(
++ src_match, base_path, scale_factors, distribution,
++ filename_expansion_function=None):
++ """Regex replace function which replaces url() with -webkit-image-set.
++
++ Takes a regex match for url('path'). If the file is local, checks for
++ files of the same name in folders corresponding to the supported scale
++ factors. If the file is from a chrome://theme/ source, inserts the
++ supported @Nx scale factor request. In either case inserts a
++ -webkit-image-set rule to fetch the appropriate image for the current
++ scale factor.
++
++ Args:
++ src_match: regex match object from _CSS_URLS
++ base_path: path to look for relative file paths in
++ scale_factors: a list of the supported scale factors (i.e. ['2x'])
++ distribution: string that should replace %DISTRIBUTION%.
++
++ Returns:
++ string
++ """
++ quote = src_match.group('quote')
++ filename = src_match.group('filename')
++ image_list = GetImageList(
++ base_path, filename, scale_factors, distribution,
++ filename_expansion_function=filename_expansion_function)
++
++ # Don't modify the source if there is only one image.
++ if len(image_list) == 1:
++ return src_match.group(0)
++
++ return GenerateImageSet(image_list, quote)
++
++
++def InsertImageSet(
++ src_match, base_path, scale_factors, distribution,
++ filename_expansion_function=None):
++ """Regex replace function which inserts -webkit-image-set rules.
++
++ Takes a regex match for `property: url('path')[, url('path')]+`.
++ Replaces one or more occurances of the match with image set rules.
++
++ Args:
++ src_match: regex match object from _CSS_IMAGE_URLS
++ base_path: path to look for relative file paths in
++ scale_factors: a list of the supported scale factors (i.e. ['2x'])
++ distribution: string that should replace %DISTRIBUTION%.
++
++ Returns:
++ string
++ """
++ attr = src_match.group('attribute')
++ urls = _CSS_URL.sub(
++ lambda m: UrlToImageSet(m, base_path, scale_factors, distribution,
++ filename_expansion_function),
++ src_match.group('urls'))
++
++ return "%s: %s" % (attr, urls)
++
++
++def InsertImageStyle(
++ src_match, base_path, scale_factors, distribution,
++ filename_expansion_function=None):
++ """Regex replace function which adds a content style to an <img>.
++
++ Takes a regex match from _HTML_IMAGE_SRC and replaces the attribute with a CSS
++ style which defines the image set.
++ """
++ filename = src_match.group('filename')
++ image_list = GetImageList(
++ base_path, filename, scale_factors, distribution,
++ filename_expansion_function=filename_expansion_function)
++
++ # Don't modify the source if there is only one image or image already defines
++ # a style.
++ if src_match.group(0).find(" style=\"") != -1 or len(image_list) == 1:
++ return src_match.group(0)
++
++ return "%s style=\"content: %s;\">" % (src_match.group(0)[:-1],
++ GenerateImageSet(image_list, "'"))
++
++
++def InsertImageSets(
++ filepath, text, scale_factors, distribution,
++ filename_expansion_function=None):
++ """Helper function that adds references to external images available in any of
++ scale_factors in CSS backgrounds.
++ """
++ # Add high DPI urls for css attributes: content, background,
++ # or *-image or <img src="foo">.
++ return _CSS_IMAGE_URLS.sub(
++ lambda m: InsertImageSet(
++ m, filepath, scale_factors, distribution,
++ filename_expansion_function=filename_expansion_function),
++ _HTML_IMAGE_SRC.sub(
++ lambda m: InsertImageStyle(
++ m, filepath, scale_factors, distribution,
++ filename_expansion_function=filename_expansion_function),
++ text))
++
++
++def RemoveImagesNotIn(scale_factors, src_match):
++ """Regex replace function which removes images for scale factors not in
++ scale_factors.
++
++ Takes a regex match for _CSS_IMAGE_SETS. For each image in the group images,
++ checks if this scale factor is in scale_factors and if not, removes it.
++
++ Args:
++ scale_factors: a list of the supported scale factors (i.e. ['1x', '2x'])
++ src_match: regex match object from _CSS_IMAGE_SETS
++
++ Returns:
++ string
++ """
++ attr = src_match.group('attribute')
++ images = _CSS_IMAGE_SET_IMAGE.sub(
++ lambda m: m.group(0) if m.group('scale') in scale_factors else '',
++ src_match.group('images'))
++ return "%s: -webkit-image-set(%s)" % (attr, images)
++
++
++def RemoveImageSetImages(text, scale_factors):
++ """Helper function which removes images in image sets not in the list of
++ supported scale_factors.
++ """
++ return _CSS_IMAGE_SETS.sub(
++ lambda m: RemoveImagesNotIn(scale_factors, m), text)
++
++
++def ProcessImageSets(
++ filepath, text, scale_factors, distribution,
++ filename_expansion_function=None):
++ """Helper function that adds references to external images available in other
++ scale_factors and removes images from image-sets in unsupported scale_factors.
++ """
++ # Explicitly add 1x to supported scale factors so that it is not removed.
++ supported_scale_factors = ['1x']
++ supported_scale_factors.extend(scale_factors)
++ return InsertImageSets(
++ filepath,
++ RemoveImageSetImages(text, supported_scale_factors),
++ scale_factors,
++ distribution,
++ filename_expansion_function=filename_expansion_function)
++
++
++class ChromeHtml(interface.GathererBase):
++ """Represents an HTML document processed for Chrome WebUI.
++
++ HTML documents used in Chrome WebUI have local resources inlined and
++ automatically insert references to high DPI assets used in CSS properties
++ with the use of the -webkit-image-set value. References to unsupported scale
++ factors in image sets are also removed. This does not generate any
++ translateable messages and instead generates a single DataPack resource.
++ """
++
++ def __init__(self, *args, **kwargs):
++ super(ChromeHtml, self).__init__(*args, **kwargs)
++ self.allow_external_script_ = False
++ self.flatten_html_ = False
++ self.preprocess_only_ = False
++ # 1x resources are implicitly already in the source and do not need to be
++ # added.
++ self.scale_factors_ = []
++ self.filename_expansion_function = None
++
++ def SetAttributes(self, attrs):
++ self.allow_external_script_ = ('allowexternalscript' in attrs and
++ attrs['allowexternalscript'] == 'true')
++ self.preprocess_only_ = ('preprocess' in attrs and
++ attrs['preprocess'] == 'true')
++ self.flatten_html_ = (self.preprocess_only_ or ('flattenhtml' in attrs and
++ attrs['flattenhtml'] == 'true'))
++
++ def SetDefines(self, defines):
++ if 'scale_factors' in defines:
++ self.scale_factors_ = defines['scale_factors'].split(',')
++
++ def GetText(self):
++ """Returns inlined text of the HTML document."""
++ return self.inlined_text_
++
++ def GetTextualIds(self):
++ return [self.extkey]
++
++ def GetData(self, lang, encoding):
++ """Returns inlined text of the HTML document."""
++ ret = self.inlined_text_
++ if encoding == util.BINARY:
++ ret = ret.encode('utf-8')
++ return ret
++
++ def GetHtmlResourceFilenames(self):
++ """Returns a set of all filenames inlined by this file."""
++ if self.flatten_html_:
++ return html_inline.GetResourceFilenames(
++ self.grd_node.ToRealPath(self.GetInputPath()),
++ self.grd_node,
++ allow_external_script=self.allow_external_script_,
++ rewrite_function=lambda fp, t, d: ProcessImageSets(
++ fp, t, self.scale_factors_, d,
++ filename_expansion_function=self.filename_expansion_function),
++ filename_expansion_function=self.filename_expansion_function)
++ return []
++
++ def Translate(self, lang, pseudo_if_not_available=True,
++ skeleton_gatherer=None, fallback_to_english=False):
++ """Returns this document translated."""
++ return self.inlined_text_
++
++ def SetFilenameExpansionFunction(self, fn):
++ self.filename_expansion_function = fn
++
++ def Parse(self):
++ """Parses and inlines the represented file."""
++
++ filename = self.GetInputPath()
++ # If there is a grd_node, prefer its GetInputPath(), as that may do more
++ # processing to make the call to ToRealPath() below work correctly.
++ if self.grd_node:
++ filename = self.grd_node.GetInputPath()
++ if self.filename_expansion_function:
++ filename = self.filename_expansion_function(filename)
++ # Hack: some unit tests supply an absolute path and no root node.
++ if not os.path.isabs(filename):
++ filename = self.grd_node.ToRealPath(filename)
++ if self.flatten_html_:
++ self.inlined_text_ = html_inline.InlineToString(
++ filename,
++ self.grd_node,
++ allow_external_script = self.allow_external_script_,
++ strip_whitespace=True,
++ preprocess_only = self.preprocess_only_,
++ rewrite_function=lambda fp, t, d: ProcessImageSets(
++ fp, t, self.scale_factors_, d,
++ filename_expansion_function=self.filename_expansion_function),
++ filename_expansion_function=self.filename_expansion_function)
++ else:
++ distribution = html_inline.GetDistribution()
++ self.inlined_text_ = ProcessImageSets(
++ os.path.dirname(filename),
++ util.ReadFile(filename, 'utf-8'),
++ self.scale_factors_,
++ distribution,
++ filename_expansion_function=self.filename_expansion_function)
+diff --git a/tools/grit/grit/gather/chrome_html_unittest.py b/tools/grit/grit/gather/chrome_html_unittest.py
+new file mode 100644
+index 0000000000..8c75ee5bf4
+--- /dev/null
++++ b/tools/grit/grit/gather/chrome_html_unittest.py
+@@ -0,0 +1,610 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit tests for grit.gather.chrome_html'''
++
++from __future__ import print_function
++
++import os
++import re
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++import unittest
++
++from grit import lazy_re
++from grit import util
++from grit.gather import chrome_html
++
++
++_NEW_LINE = lazy_re.compile('(\r\n|\r|\n)', re.MULTILINE)
++
++
++def StandardizeHtml(text):
++ '''Standardizes the newline format and png mime type in Html text.'''
++ return _NEW_LINE.sub('\n', text).replace('data:image/x-png;',
++ 'data:image/png;')
++
++
++class ChromeHtmlUnittest(unittest.TestCase):
++ '''Unit tests for ChromeHtml.'''
++
++ def testFileResources(self):
++ '''Tests inlined image file resources with available high DPI assets.'''
++
++ tmp_dir = util.TempDir({
++ 'index.html': '''
++ <!DOCTYPE HTML>
++ <html>
++ <head>
++ <link rel="stylesheet" href="test.css">
++ </head>
++ <body>
++ <!-- Don't need a body. -->
++ </body>
++ </html>
++ ''',
++
++ 'test.css': '''
++ .image {
++ background: url('test.png');
+ }
-+ }
-+ }
++ ''',
++
++ 'test.png': 'PNG DATA',
++
++ '1.4x/test.png': '1.4x PNG DATA',
++
++ '1.8x/test.png': '1.8x PNG DATA',
++ })
++
++ html = chrome_html.ChromeHtml(tmp_dir.GetPath('index.html'))
++ html.SetDefines({'scale_factors': '1.4x,1.8x'})
++ html.SetAttributes({'flattenhtml': 'true'})
++ html.Parse()
++ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
++ StandardizeHtml('''
++ <!DOCTYPE HTML>
++ <html>
++ <head>
++ <style>
++ .image {
++ background: -webkit-image-set(url('data:image/png;base64,UE5HIERBVEE=') 1x, url('data:image/png;base64,MS40eCBQTkcgREFUQQ==') 1.4x, url('data:image/png;base64,MS44eCBQTkcgREFUQQ==') 1.8x);
++ }
++ </style>
++ </head>
++ <body>
++ <!-- Don't need a body. -->
++ </body>
++ </html>
++ '''))
++ tmp_dir.CleanUp()
++
++ def testFileResourcesImageTag(self):
++ '''Tests inlined image file resources with available high DPI assets on
++ an image tag.'''
++
++ tmp_dir = util.TempDir({
++ 'index.html': '''
++ <!DOCTYPE HTML>
++ <html>
++ <body>
++ <img id="foo" src="test.png">
++ </body>
++ </html>
++ ''',
++
++ 'test.png': 'PNG DATA',
++
++ '2x/test.png': '2x PNG DATA',
++ })
++
++ html = chrome_html.ChromeHtml(tmp_dir.GetPath('index.html'))
++ html.SetDefines({'scale_factors': '2x'})
++ html.SetAttributes({'flattenhtml': 'true'})
++ html.Parse()
++ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
++ StandardizeHtml('''
++ <!DOCTYPE HTML>
++ <html>
++ <body>
++ <img id="foo" src="data:image/png;base64,UE5HIERBVEE=" style="content: -webkit-image-set(url('data:image/png;base64,UE5HIERBVEE=') 1x, url('data:image/png;base64,MnggUE5HIERBVEE=') 2x);">
++ </body>
++ </html>
++ '''))
++ tmp_dir.CleanUp()
++
++ def testFileResourcesNoFlatten(self):
++ '''Tests non-inlined image file resources with available high DPI assets.'''
++
++ tmp_dir = util.TempDir({
++ 'test.css': '''
++ .image {
++ background: url('test.png');
++ }
++ ''',
++
++ 'test.png': 'PNG DATA',
++
++ '1.4x/test.png': '1.4x PNG DATA',
+
-+ void CountType(const Type* type,
-+ int* trivial_member,
-+ int* non_trivial_member,
-+ int* templated_non_trivial_member) {
-+ switch (type->getTypeClass()) {
-+ case Type::Record: {
-+ // Simplifying; the whole class isn't trivial if the dtor is, but
-+ // we use this as a signal about complexity.
-+ if (TypeHasNonTrivialDtor(type))
-+ (*trivial_member)++;
-+ else
-+ (*non_trivial_member)++;
-+ break;
++ '1.8x/test.png': '1.8x PNG DATA',
++ })
++
++ html = chrome_html.ChromeHtml(tmp_dir.GetPath('test.css'))
++ html.SetDefines({'scale_factors': '1.4x,1.8x'})
++ html.SetAttributes({'flattenhtml': 'false'})
++ html.Parse()
++ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
++ StandardizeHtml('''
++ .image {
++ background: -webkit-image-set(url('test.png') 1x, url('1.4x/test.png') 1.4x, url('1.8x/test.png') 1.8x);
+ }
-+ case Type::TemplateSpecialization: {
-+ TemplateName name =
-+ dyn_cast<TemplateSpecializationType>(type)->getTemplateName();
-+ bool whitelisted_template = false;
-+
-+ // HACK: I'm at a loss about how to get the syntax checker to get
-+ // whether a template is exterened or not. For the first pass here,
-+ // just do retarded string comparisons.
-+ if (TemplateDecl* decl = name.getAsTemplateDecl()) {
-+ std::string base_name = decl->getNameAsString();
-+ if (base_name == "basic_string")
-+ whitelisted_template = true;
-+ }
++ '''))
++ tmp_dir.CleanUp()
+
-+ if (whitelisted_template)
-+ (*non_trivial_member)++;
-+ else
-+ (*templated_non_trivial_member)++;
-+ break;
++ def testFileResourcesNoFlattenSubdir(self):
++ '''Tests non-inlined image file resources w/high DPI assets in subdirs.'''
++
++ tmp_dir = util.TempDir({
++ 'test.css': '''
++ .image {
++ background: url('sub/test.png');
+ }
-+ case Type::Elaborated: {
-+ CountType(
-+ dyn_cast<ElaboratedType>(type)->getNamedType().getTypePtr(),
-+ trivial_member, non_trivial_member, templated_non_trivial_member);
-+ break;
++ ''',
++
++ 'sub/test.png': 'PNG DATA',
++
++ 'sub/1.4x/test.png': '1.4x PNG DATA',
++
++ 'sub/1.8x/test.png': '1.8x PNG DATA',
++ })
++
++ html = chrome_html.ChromeHtml(tmp_dir.GetPath('test.css'))
++ html.SetDefines({'scale_factors': '1.4x,1.8x'})
++ html.SetAttributes({'flattenhtml': 'false'})
++ html.Parse()
++ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
++ StandardizeHtml('''
++ .image {
++ background: -webkit-image-set(url('sub/test.png') 1x, url('sub/1.4x/test.png') 1.4x, url('sub/1.8x/test.png') 1.8x);
+ }
-+ case Type::Typedef: {
-+ while (const TypedefType* TT = dyn_cast<TypedefType>(type)) {
-+ type = TT->getDecl()->getUnderlyingType().getTypePtr();
-+ }
-+ CountType(type, trivial_member, non_trivial_member,
-+ templated_non_trivial_member);
-+ break;
++ '''))
++ tmp_dir.CleanUp()
++
++ def testFileResourcesPreprocess(self):
++ '''Tests preprocessed image file resources with available high DPI
++ assets.'''
++
++ tmp_dir = util.TempDir({
++ 'test.css': '''
++ .image {
++ background: url('test.png');
+ }
-+ default: {
-+ // Stupid assumption: anything we see that isn't the above is one of
-+ // the 20 integer types.
-+ (*trivial_member)++;
-+ break;
++ ''',
++
++ 'test.png': 'PNG DATA',
++
++ '1.4x/test.png': '1.4x PNG DATA',
++
++ '1.8x/test.png': '1.8x PNG DATA',
++ })
++
++ html = chrome_html.ChromeHtml(tmp_dir.GetPath('test.css'))
++ html.SetDefines({'scale_factors': '1.4x,1.8x'})
++ html.SetAttributes({'flattenhtml': 'false', 'preprocess': 'true'})
++ html.Parse()
++ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
++ StandardizeHtml('''
++ .image {
++ background: -webkit-image-set(url('test.png') 1x, url('1.4x/test.png') 1.4x, url('1.8x/test.png') 1.8x);
+ }
-+ }
-+ }
-+};
++ '''))
++ tmp_dir.CleanUp()
+
-+class FindBadConstructsAction : public PluginASTAction {
-+ public:
-+ FindBadConstructsAction()
-+ : check_refcounted_dtors_(true),
-+ check_virtuals_in_implementations_(true) {
-+ }
++ def testFileResourcesDoubleQuotes(self):
++ '''Tests inlined image file resources if url() filename is double quoted.'''
++
++ tmp_dir = util.TempDir({
++ 'test.css': '''
++ .image {
++ background: url("test.png");
++ }
++ ''',
++
++ 'test.png': 'PNG DATA',
++
++ '2x/test.png': '2x PNG DATA',
++ })
++
++ html = chrome_html.ChromeHtml(tmp_dir.GetPath('test.css'))
++ html.SetDefines({'scale_factors': '2x'})
++ html.SetAttributes({'flattenhtml': 'true'})
++ html.Parse()
++ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
++ StandardizeHtml('''
++ .image {
++ background: -webkit-image-set(url("data:image/png;base64,UE5HIERBVEE=") 1x, url("data:image/png;base64,MnggUE5HIERBVEE=") 2x);
++ }
++ '''))
++ tmp_dir.CleanUp()
++
++ def testFileResourcesNoQuotes(self):
++ '''Tests inlined image file resources when url() filename is unquoted.'''
++
++ tmp_dir = util.TempDir({
++ 'test.css': '''
++ .image {
++ background: url(test.png);
++ }
++ ''',
++
++ 'test.png': 'PNG DATA',
+
-+ protected:
-+ // Overridden from PluginASTAction:
-+ virtual ASTConsumer* CreateASTConsumer(CompilerInstance& instance,
-+ llvm::StringRef ref) {
-+ return new FindBadConstructsConsumer(
-+ instance, check_refcounted_dtors_, check_virtuals_in_implementations_);
++ '2x/test.png': '2x PNG DATA',
++ })
++
++ html = chrome_html.ChromeHtml(tmp_dir.GetPath('test.css'))
++ html.SetDefines({'scale_factors': '2x'})
++ html.SetAttributes({'flattenhtml': 'true'})
++ html.Parse()
++ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
++ StandardizeHtml('''
++ .image {
++ background: -webkit-image-set(url(data:image/png;base64,UE5HIERBVEE=) 1x, url(data:image/png;base64,MnggUE5HIERBVEE=) 2x);
++ }
++ '''))
++ tmp_dir.CleanUp()
++
++ def testFileResourcesSubdirs(self):
++ '''Tests inlined image file resources if url() filename is in a subdir.'''
++
++ tmp_dir = util.TempDir({
++ 'test.css': '''
++ .image {
++ background: url('some/sub/path/test.png');
++ }
++ ''',
++
++ 'some/sub/path/test.png': 'PNG DATA',
++
++ 'some/sub/path/2x/test.png': '2x PNG DATA',
++ })
++
++ html = chrome_html.ChromeHtml(tmp_dir.GetPath('test.css'))
++ html.SetDefines({'scale_factors': '2x'})
++ html.SetAttributes({'flattenhtml': 'true'})
++ html.Parse()
++ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
++ StandardizeHtml('''
++ .image {
++ background: -webkit-image-set(url('data:image/png;base64,UE5HIERBVEE=') 1x, url('data:image/png;base64,MnggUE5HIERBVEE=') 2x);
++ }
++ '''))
++ tmp_dir.CleanUp()
++
++ def testFileResourcesNoFile(self):
++ '''Tests inlined image file resources without available high DPI assets.'''
++
++ tmp_dir = util.TempDir({
++ 'index.html': '''
++ <!DOCTYPE HTML>
++ <html>
++ <head>
++ <link rel="stylesheet" href="test.css">
++ </head>
++ <body>
++ <!-- Don't need a body. -->
++ </body>
++ </html>
++ ''',
++
++ 'test.css': '''
++ .image {
++ background: url('test.png');
++ }
++ ''',
++
++ 'test.png': 'PNG DATA',
++ })
++
++ html = chrome_html.ChromeHtml(tmp_dir.GetPath('index.html'))
++ html.SetDefines({'scale_factors': '2x'})
++ html.SetAttributes({'flattenhtml': 'true'})
++ html.Parse()
++ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
++ StandardizeHtml('''
++ <!DOCTYPE HTML>
++ <html>
++ <head>
++ <style>
++ .image {
++ background: url('data:image/png;base64,UE5HIERBVEE=');
++ }
++ </style>
++ </head>
++ <body>
++ <!-- Don't need a body. -->
++ </body>
++ </html>
++ '''))
++ tmp_dir.CleanUp()
++
++ def testFileResourcesMultipleBackgrounds(self):
++ '''Tests inlined image file resources with two url()s.'''
++
++ tmp_dir = util.TempDir({
++ 'test.css': '''
++ .image {
++ background: url(test.png), url(test.png);
++ }
++ ''',
++
++ 'test.png': 'PNG DATA',
++
++ '2x/test.png': '2x PNG DATA',
++ })
++
++ html = chrome_html.ChromeHtml(tmp_dir.GetPath('test.css'))
++ html.SetDefines({'scale_factors': '2x'})
++ html.SetAttributes({'flattenhtml': 'true'})
++ html.Parse()
++ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
++ StandardizeHtml('''
++ .image {
++ background: -webkit-image-set(url(data:image/png;base64,UE5HIERBVEE=) 1x, url(data:image/png;base64,MnggUE5HIERBVEE=) 2x), -webkit-image-set(url(data:image/png;base64,UE5HIERBVEE=) 1x, url(data:image/png;base64,MnggUE5HIERBVEE=) 2x);
++ }
++ '''))
++ tmp_dir.CleanUp()
++
++ def testFileResourcesMultipleBackgroundsWithNewline1(self):
++ '''Tests inlined image file resources with line break after first url().'''
++
++ tmp_dir = util.TempDir({
++ 'test.css': '''
++ .image {
++ background: url(test.png),
++ url(test.png);
++ }
++ ''',
++
++ 'test.png': 'PNG DATA',
++
++ '2x/test.png': '2x PNG DATA',
++ })
++
++ html = chrome_html.ChromeHtml(tmp_dir.GetPath('test.css'))
++ html.SetDefines({'scale_factors': '2x'})
++ html.SetAttributes({'flattenhtml': 'true'})
++ html.Parse()
++ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
++ StandardizeHtml('''
++ .image {
++ background: -webkit-image-set(url(data:image/png;base64,UE5HIERBVEE=) 1x, url(data:image/png;base64,MnggUE5HIERBVEE=) 2x),
++ -webkit-image-set(url(data:image/png;base64,UE5HIERBVEE=) 1x, url(data:image/png;base64,MnggUE5HIERBVEE=) 2x);
++ }
++ '''))
++ tmp_dir.CleanUp()
++
++ def testFileResourcesMultipleBackgroundsWithNewline2(self):
++ '''Tests inlined image file resources with line break before first url()
++ and before second url().'''
++
++ tmp_dir = util.TempDir({
++ 'test.css': '''
++ .image {
++ background:
++ url(test.png),
++ url(test.png);
++ }
++ ''',
++
++ 'test.png': 'PNG DATA',
++
++ '2x/test.png': '2x PNG DATA',
++ })
++
++ html = chrome_html.ChromeHtml(tmp_dir.GetPath('test.css'))
++ html.SetDefines({'scale_factors': '2x'})
++ html.SetAttributes({'flattenhtml': 'true'})
++ html.Parse()
++ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
++ StandardizeHtml('''
++ .image {
++ background: -webkit-image-set(url(data:image/png;base64,UE5HIERBVEE=) 1x, url(data:image/png;base64,MnggUE5HIERBVEE=) 2x),
++ -webkit-image-set(url(data:image/png;base64,UE5HIERBVEE=) 1x, url(data:image/png;base64,MnggUE5HIERBVEE=) 2x);
++ }
++ '''))
++ tmp_dir.CleanUp()
++
++ def testFileResourcesCRLF(self):
++ '''Tests inlined image file resource when url() is preceded by a Windows
++ style line break.'''
++
++ tmp_dir = util.TempDir({
++ 'test.css': '''
++ .image {
++ background:\r\nurl(test.png);
++ }
++ ''',
++
++ 'test.png': 'PNG DATA',
++
++ '2x/test.png': '2x PNG DATA',
++ })
++
++ html = chrome_html.ChromeHtml(tmp_dir.GetPath('test.css'))
++ html.SetDefines({'scale_factors': '2x'})
++ html.SetAttributes({'flattenhtml': 'true'})
++ html.Parse()
++ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
++ StandardizeHtml('''
++ .image {
++ background: -webkit-image-set(url(data:image/png;base64,UE5HIERBVEE=) 1x, url(data:image/png;base64,MnggUE5HIERBVEE=) 2x);
++ }
++ '''))
++ tmp_dir.CleanUp()
++
++ def testThemeResources(self):
++ '''Tests inserting high DPI chrome://theme references.'''
++
++ tmp_dir = util.TempDir({
++ 'index.html': '''
++ <!DOCTYPE HTML>
++ <html>
++ <head>
++ <link rel="stylesheet" href="test.css">
++ </head>
++ <body>
++ <!-- Don't need a body. -->
++ </body>
++ </html>
++ ''',
++
++ 'test.css': '''
++ .image {
++ background: url('chrome://theme/IDR_RESOURCE_NAME');
++ content: url('chrome://theme/IDR_RESOURCE_NAME_WITH_Q?$1');
++ }
++ ''',
++ })
++
++ html = chrome_html.ChromeHtml(tmp_dir.GetPath('index.html'))
++ html.SetDefines({'scale_factors': '2x'})
++ html.SetAttributes({'flattenhtml': 'true'})
++ html.Parse()
++ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
++ StandardizeHtml('''
++ <!DOCTYPE HTML>
++ <html>
++ <head>
++ <style>
++ .image {
++ background: -webkit-image-set(url('chrome://theme/IDR_RESOURCE_NAME') 1x, url('chrome://theme/IDR_RESOURCE_NAME@2x') 2x);
++ content: -webkit-image-set(url('chrome://theme/IDR_RESOURCE_NAME_WITH_Q?$1') 1x, url('chrome://theme/IDR_RESOURCE_NAME_WITH_Q@2x?$1') 2x);
++ }
++ </style>
++ </head>
++ <body>
++ <!-- Don't need a body. -->
++ </body>
++ </html>
++ '''))
++ tmp_dir.CleanUp()
++
++ def testRemoveUnsupportedScale(self):
++ '''Tests removing an unsupported scale factor from an explicit image-set.'''
++
++ tmp_dir = util.TempDir({
++ 'index.html': '''
++ <!DOCTYPE HTML>
++ <html>
++ <head>
++ <link rel="stylesheet" href="test.css">
++ </head>
++ <body>
++ <!-- Don't need a body. -->
++ </body>
++ </html>
++ ''',
++
++ 'test.css': '''
++ .image {
++ background: -webkit-image-set(url('test.png') 1x,
++ url('test1.4.png') 1.4x,
++ url('test1.8.png') 1.8x);
++ }
++ ''',
++
++ 'test.png': 'PNG DATA',
++
++ 'test1.4.png': '1.4x PNG DATA',
++
++ 'test1.8.png': '1.8x PNG DATA',
++ })
++
++ html = chrome_html.ChromeHtml(tmp_dir.GetPath('index.html'))
++ html.SetDefines({'scale_factors': '1.8x'})
++ html.SetAttributes({'flattenhtml': 'true'})
++ html.Parse()
++ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
++ StandardizeHtml('''
++ <!DOCTYPE HTML>
++ <html>
++ <head>
++ <style>
++ .image {
++ background: -webkit-image-set(url('data:image/png;base64,UE5HIERBVEE=') 1x,
++ url('data:image/png;base64,MS44eCBQTkcgREFUQQ==') 1.8x);
++ }
++ </style>
++ </head>
++ <body>
++ <!-- Don't need a body. -->
++ </body>
++ </html>
++ '''))
++ tmp_dir.CleanUp()
++
++ def testExpandVariablesInFilename(self):
++ '''
++ Tests variable substitution in filenames while flattening images
++ with multiple scale factors.
++ '''
++
++ tmp_dir = util.TempDir({
++ 'index.html': '''
++ <!DOCTYPE HTML>
++ <html>
++ <head>
++ <link rel="stylesheet" href="test.css">
++ </head>
++ <body>
++ <!-- Don't need a body. -->
++ </body>
++ </html>
++ ''',
++
++ 'test.css': '''
++ .image {
++ background: url('test[WHICH].png');
++ }
++ ''',
++
++ 'test1.png': 'PNG DATA',
++ '1.4x/test1.png': '1.4x PNG DATA',
++ '1.8x/test1.png': '1.8x PNG DATA',
++ })
++
++ def replacer(var, repl):
++ return lambda filename: filename.replace('[%s]' % var, repl)
++
++ html = chrome_html.ChromeHtml(tmp_dir.GetPath('index.html'))
++ html.SetDefines({'scale_factors': '1.4x,1.8x'})
++ html.SetAttributes({'flattenhtml': 'true'})
++ html.SetFilenameExpansionFunction(replacer('WHICH', '1'));
++ html.Parse()
++ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
++ StandardizeHtml('''
++ <!DOCTYPE HTML>
++ <html>
++ <head>
++ <style>
++ .image {
++ background: -webkit-image-set(url('data:image/png;base64,UE5HIERBVEE=') 1x, url('data:image/png;base64,MS40eCBQTkcgREFUQQ==') 1.4x, url('data:image/png;base64,MS44eCBQTkcgREFUQQ==') 1.8x);
++ }
++ </style>
++ </head>
++ <body>
++ <!-- Don't need a body. -->
++ </body>
++ </html>
++ '''))
++ tmp_dir.CleanUp()
++
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/gather/chrome_scaled_image.py b/tools/grit/grit/gather/chrome_scaled_image.py
+new file mode 100644
+index 0000000000..44f98cbcf0
+--- /dev/null
++++ b/tools/grit/grit/gather/chrome_scaled_image.py
+@@ -0,0 +1,157 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Gatherer for <structure type="chrome_scaled_image">.
++'''
++
++from __future__ import print_function
++
++import os
++import struct
++
++from grit import exception
++from grit import lazy_re
++from grit import util
++from grit.gather import interface
++
++
++_PNG_SCALE_CHUNK = b'\0\0\0\0csCl\xc1\x30\x60\x4d'
++
++
++def _RescaleImage(data, from_scale, to_scale):
++ if from_scale != to_scale:
++ assert from_scale == 100
++ # Rather than rescaling the image we add a custom chunk directing Chrome to
++ # rescale it on load. Just append it to the PNG data since
++ # _MoveSpecialChunksToFront will move it later anyway.
++ data += _PNG_SCALE_CHUNK
++ return data
++
++
++_PNG_MAGIC = b'\x89PNG\r\n\x1a\n'
++
++'''Mandatory first chunk in order for the png to be valid.'''
++_FIRST_CHUNK = b'IHDR'
++
++'''Special chunks to move immediately after the IHDR chunk. (so that the PNG
++remains valid.)
++'''
++_SPECIAL_CHUNKS = frozenset(b'csCl npTc'.split())
++
++'''Any ancillary chunk not in this list is deleted from the PNG.'''
++_ANCILLARY_CHUNKS_TO_LEAVE = frozenset(
++ b'bKGD cHRM gAMA iCCP pHYs sBIT sRGB tRNS acTL fcTL fdAT'.split())
++
++
++def _MoveSpecialChunksToFront(data):
++ '''Move special chunks immediately after the IHDR chunk (so that the PNG
++ remains valid). Also delete ancillary chunks that are not on our whitelist.
++ '''
++ first = [_PNG_MAGIC]
++ special_chunks = []
++ rest = []
++ for chunk in _ChunkifyPNG(data):
++ type = chunk[4:8]
++ critical = type < b'a'
++ if type == _FIRST_CHUNK:
++ first.append(chunk)
++ elif type in _SPECIAL_CHUNKS:
++ special_chunks.append(chunk)
++ elif critical or type in _ANCILLARY_CHUNKS_TO_LEAVE:
++ rest.append(chunk)
++ return b''.join(first + special_chunks + rest)
++
++
++def _ChunkifyPNG(data):
++ '''Given a PNG image, yield its chunks in order.'''
++ assert data.startswith(_PNG_MAGIC)
++ pos = 8
++ while pos != len(data):
++ length = 12 + struct.unpack_from('>I', data, pos)[0]
++ assert 12 <= length <= len(data) - pos
++ yield data[pos:pos+length]
++ pos += length
++
++
++def _MakeBraceGlob(strings):
++ '''Given ['foo', 'bar'], return '{foo,bar}', for error reporting.
++ '''
++ if len(strings) == 1:
++ return strings[0]
++ else:
++ return '{' + ','.join(strings) + '}'
++
++
++class ChromeScaledImage(interface.GathererBase):
++ '''Represents an image that exists in multiple layout variants
++ (e.g. "default", "touch") and multiple scale variants
++ (e.g. "100_percent", "200_percent").
++ '''
++
++ split_context_re_ = lazy_re.compile(r'(.+)_(\d+)_percent\Z')
++
++ def _FindInputFile(self):
++ output_context = self.grd_node.GetRoot().output_context
++ match = self.split_context_re_.match(output_context)
++ if not match:
++ raise exception.MissingMandatoryAttribute(
++ 'All <output> nodes must have an appropriate context attribute'
++ ' (e.g. context="touch_200_percent")')
++ req_layout, req_scale = match.group(1), int(match.group(2))
++
++ layouts = [req_layout]
++ try_default_layout = self.grd_node.GetRoot().fallback_to_default_layout
++ if try_default_layout and 'default' not in layouts:
++ layouts.append('default')
++
++ scales = [req_scale]
++ try_low_res = self.grd_node.FindBooleanAttribute(
++ 'fallback_to_low_resolution', default=False, skip_self=False)
++ if try_low_res and 100 not in scales:
++ scales.append(100)
++
++ for layout in layouts:
++ for scale in scales:
++ dir = '%s_%s_percent' % (layout, scale)
++ path = os.path.join(dir, self.rc_file)
++ if os.path.exists(self.grd_node.ToRealPath(path)):
++ return path, scale, req_scale
++
++ if not try_default_layout:
++ # The file was not found in the specified output context and it was
++ # explicitly indicated that the default context should not be searched
++ # as a fallback, so return an empty path.
++ return None, 100, req_scale
++
++ # The file was found in neither the specified context nor the default
++ # context, so raise an exception.
++ dir = "%s_%s_percent" % (_MakeBraceGlob(layouts),
++ _MakeBraceGlob([str(x) for x in scales]))
++ raise exception.FileNotFound(
++ 'Tried ' + self.grd_node.ToRealPath(os.path.join(dir, self.rc_file)))
++
++ def GetInputPath(self):
++ path, scale, req_scale = self._FindInputFile()
++ return path
++
++ def Parse(self):
++ pass
++
++ def GetTextualIds(self):
++ return [self.extkey]
++
++ def GetData(self, lang, encoding):
++ assert encoding == util.BINARY
++
++ path, scale, req_scale = self._FindInputFile()
++ if path is None:
++ return None
++
++ data = util.ReadFile(self.grd_node.ToRealPath(path), util.BINARY)
++ data = _RescaleImage(data, scale, req_scale)
++ data = _MoveSpecialChunksToFront(data)
++ return data
++
++ def Translate(self, *args, **kwargs):
++ return self.GetData()
+diff --git a/tools/grit/grit/gather/chrome_scaled_image_unittest.py b/tools/grit/grit/gather/chrome_scaled_image_unittest.py
+new file mode 100644
+index 0000000000..1cebfc6de2
+--- /dev/null
++++ b/tools/grit/grit/gather/chrome_scaled_image_unittest.py
+@@ -0,0 +1,209 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit tests for ChromeScaledImage.'''
++
++from __future__ import print_function
++
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__),
++ '../..')))
++
++import re
++import struct
++import unittest
++import zlib
++
++from grit import exception
++from grit import util
++from grit.format import data_pack
++from grit.tool import build
++
++
++_OUTFILETYPES = [
++ ('.h', 'rc_header'),
++ ('_map.cc', 'resource_map_source'),
++ ('_map.h', 'resource_map_header'),
++ ('.pak', 'data_package'),
++ ('.rc', 'rc_all'),
++]
++
++
++_PNG_HEADER = (
++ b'\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52'
++ b'\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90\x77\x53'
++ b'\xde')
++_PNG_FOOTER = (
++ b'\x00\x00\x00\x0c\x49\x44\x41\x54\x18\x57\x63\xf8\xff\xff\x3f\x00'
++ b'\x05\xfe\x02\xfe\xa7\x35\x81\x84\x00\x00\x00\x00\x49\x45\x4e\x44'
++ b'\xae\x42\x60\x82')
++
++
++def _MakePNG(chunks):
++ # Python 3 changed the return value of zlib.crc32 to an unsigned int.
++ format = 'i' if sys.version_info.major < 3 else 'I'
++ pack_int32 = struct.Struct('>' + format).pack
++ chunks = [pack_int32(len(payload)) + type + payload +
++ pack_int32(zlib.crc32(type + payload))
++ for type, payload in chunks]
++ return _PNG_HEADER + b''.join(chunks) + _PNG_FOOTER
++
++
++def _GetFilesInPak(pakname):
++ '''Get a set of the files that were actually included in the .pak output.
++ '''
++ return set(data_pack.ReadDataPack(pakname).resources.values())
++
++
++def _GetFilesInRc(rcname, tmp_dir, contents):
++ '''Get a set of the files that were actually included in the .rc output.
++ '''
++ data = util.ReadFile(rcname, util.BINARY).decode('utf-16')
++ contents = dict((tmp_dir.GetPath(k), v) for k, v in contents.items())
++ return set(contents[os.path.normpath(m.group(1))]
++ for m in re.finditer(r'(?m)^\w+\s+BINDATA\s+"([^"]+)"$', data))
++
++
++def _MakeFallbackAttr(fallback):
++ if fallback is None:
++ return ''
++ else:
++ return ' fallback_to_low_resolution="%s"' % ('false', 'true')[fallback]
++
++
++def _Structures(fallback, *body):
++ return '<structures%s>\n%s\n</structures>' % (
++ _MakeFallbackAttr(fallback), '\n'.join(body))
++
++
++def _Structure(name, file, fallback=None):
++ return '<structure name="%s" file="%s" type="chrome_scaled_image"%s />' % (
++ name, file, _MakeFallbackAttr(fallback))
++
++
++def _If(expr, *body):
++ return '<if expr="%s">\n%s\n</if>' % (expr, '\n'.join(body))
++
++
++def _RunBuildTest(self, structures, inputs, expected_outputs, skip_rc=False,
++ layout_fallback=''):
++ outputs = '\n'.join('<output filename="out/%s%s" type="%s" context="%s"%s />'
++ % (context, ext, type, context, layout_fallback)
++ for ext, type in _OUTFILETYPES
++ for context in expected_outputs)
++
++ infiles = {
++ 'in/in.grd': ('''<?xml version="1.0" encoding="UTF-8"?>
++ <grit latest_public_release="0" current_release="1">
++ <outputs>
++ %s
++ </outputs>
++ <release seq="1">
++ %s
++ </release>
++ </grit>
++ ''' % (outputs, structures)).encode('utf-8'),
+ }
++ for pngpath, pngdata in inputs.items():
++ normpath = os.path.normpath('in/' + pngpath)
++ infiles[normpath] = pngdata
++ class Options(object):
++ pass
++
++ with util.TempDir(infiles, mode='wb') as tmp_dir:
++ with tmp_dir.AsCurrentDir():
++ options = Options()
++ options.input = tmp_dir.GetPath('in/in.grd')
++ options.verbose = False
++ options.extra_verbose = False
++ build.RcBuilder().Run(options, [])
++ for context, expected_data in expected_outputs.items():
++ self.assertEquals(expected_data,
++ _GetFilesInPak(tmp_dir.GetPath('out/%s.pak' % context)))
++ if not skip_rc:
++ self.assertEquals(expected_data,
++ _GetFilesInRc(tmp_dir.GetPath('out/%s.rc' % context),
++ tmp_dir, infiles))
++
++
++class ChromeScaledImageUnittest(unittest.TestCase):
++ def testNormalFallback(self):
++ d123a = _MakePNG([(b'AbCd', b'')])
++ t123a = _MakePNG([(b'EfGh', b'')])
++ d123b = _MakePNG([(b'IjKl', b'')])
++ _RunBuildTest(self,
++ _Structures(None,
++ _Structure('IDR_A', 'a.png'),
++ _Structure('IDR_B', 'b.png'),
++ ),
++ {'default_123_percent/a.png': d123a,
++ 'tactile_123_percent/a.png': t123a,
++ 'default_123_percent/b.png': d123b,
++ },
++ {'default_123_percent': set([d123a, d123b]),
++ 'tactile_123_percent': set([t123a, d123b]),
++ })
++
++ def testNormalFallbackFailure(self):
++ self.assertRaises(
++ exception.FileNotFound, _RunBuildTest, self,
++ _Structures(
++ None,
++ _Structure('IDR_A', 'a.png'),
++ ), {
++ 'default_100_percent/a.png': _MakePNG([(b'AbCd', b'')]),
++ 'tactile_100_percent/a.png': _MakePNG([(b'EfGh', b'')]),
++ }, {'tactile_123_percent': 'should fail before using this'})
++
++ def testLowresFallback(self):
++ png = _MakePNG([(b'Abcd', b'')])
++ png_with_csCl = _MakePNG([(b'csCl', b''), (b'Abcd', b'')])
++ for outer in (None, False, True):
++ for inner in (None, False, True):
++ args = (
++ self,
++ _Structures(outer,
++ _Structure('IDR_A', 'a.png', inner),
++ ),
++ {'default_100_percent/a.png': png},
++ {'tactile_200_percent': set([png_with_csCl])})
++ if inner or (inner is None and outer):
++ # should fall back to 100%
++ _RunBuildTest(*args, skip_rc=True)
++ else:
++ # shouldn't fall back
++ self.assertRaises(exception.FileNotFound, _RunBuildTest, *args)
++
++ # Test fallback failure with fallback_to_low_resolution=True
++ self.assertRaises(exception.FileNotFound,
++ _RunBuildTest, self,
++ _Structures(True,
++ _Structure('IDR_A', 'a.png'),
++ ),
++ {}, # no files
++ {'tactile_123_percent': 'should fail before using this'})
++
++ def testNoFallbackToDefaultLayout(self):
++ d123a = _MakePNG([(b'AbCd', b'')])
++ t123a = _MakePNG([(b'EfGh', b'')])
++ d123b = _MakePNG([(b'IjKl', b'')])
++ _RunBuildTest(self,
++ _Structures(None,
++ _Structure('IDR_A', 'a.png'),
++ _Structure('IDR_B', 'b.png'),
++ ),
++ {'default_123_percent/a.png': d123a,
++ 'tactile_123_percent/a.png': t123a,
++ 'default_123_percent/b.png': d123b,
++ },
++ {'default_123_percent': set([d123a, d123b]),
++ 'tactile_123_percent': set([t123a]),
++ },
++ layout_fallback=' fallback_to_default_layout="false"')
+
-+ virtual bool ParseArgs(const CompilerInstance& instance,
-+ const std::vector<std::string>& args) {
-+ bool parsed = true;
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/gather/interface.py b/tools/grit/grit/gather/interface.py
+new file mode 100644
+index 0000000000..15d64f9326
+--- /dev/null
++++ b/tools/grit/grit/gather/interface.py
+@@ -0,0 +1,172 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
+
-+ for (size_t i = 0; i < args.size() && parsed; ++i) {
-+ if (args[i] == "skip-refcounted-dtors") {
-+ check_refcounted_dtors_ = false;
-+ } else if (args[i] == "skip-virtuals-in-implementations") {
-+ check_virtuals_in_implementations_ = false;
-+ } else {
-+ parsed = false;
-+ llvm::errs() << "Unknown argument: " << args[i] << "\n";
++'''Interface for all gatherers.
++'''
++
++from __future__ import print_function
++
++import os.path
++
++import six
++
++from grit import clique
++from grit import util
++
++
++class GathererBase(object):
++ '''Interface for all gatherer implementations. Subclasses must implement
++ all methods that raise NotImplemented.'''
++
++ def __init__(self, rc_file, extkey=None, encoding='cp1252', is_skeleton=False):
++ '''Initializes the gatherer object's attributes, but does not attempt to
++ read the input file.
++
++ Args:
++ rc_file: The 'file' attribute of the <structure> node (usually the
++ relative path to the source file).
++ extkey: e.g. 'ID_MY_DIALOG'
++ encoding: e.g. 'utf-8'
++ is_skeleton: Indicates whether this gatherer is a skeleton gatherer, in
++ which case we should not do some types of processing on the
++ translateable bits.
++ '''
++ self.rc_file = rc_file
++ self.extkey = extkey
++ self.encoding = encoding
++ # A default uberclique that is local to this object. Users can override
++ # this with the uberclique they are using.
++ self.uberclique = clique.UberClique()
++ # Indicates whether this gatherer is a skeleton gatherer, in which case
++ # we should not do some types of processing on the translateable bits.
++ self.is_skeleton = is_skeleton
++ # Stores the grd node on which this gatherer is running. This allows
++ # evaluating expressions.
++ self.grd_node = None
++
++ def SetAttributes(self, attrs):
++ '''Sets node attributes used by the gatherer.
++
++ By default, this does nothing. If special handling is desired, it should be
++ overridden by the child gatherer.
++
++ Args:
++ attrs: The mapping of node attributes.
++ '''
++ pass
++
++ def SetDefines(self, defines):
++ '''Sets global defines used by the gatherer.
++
++ By default, this does nothing. If special handling is desired, it should be
++ overridden by the child gatherer.
++
++ Args:
++ defines: The mapping of define values.
++ '''
++ pass
++
++ def SetGrdNode(self, node):
++ '''Sets the grd node on which this gatherer is running.
++ '''
++ self.grd_node = node
++
++ def SetUberClique(self, uberclique):
++ '''Overrides the default uberclique so that cliques created by this object
++ become part of the uberclique supplied by the user.
++ '''
++ self.uberclique = uberclique
++
++ def Parse(self):
++ '''Reads and parses the contents of what is being gathered.'''
++ raise NotImplementedError()
++
++ def GetData(self, lang, encoding):
++ '''Returns the data to be added to the DataPack for this node or None if
++ this node does not add a DataPack entry.
++ '''
++ return None
++
++ def GetText(self):
++ '''Returns the text of what is being gathered.'''
++ raise NotImplementedError()
++
++ def GetTextualIds(self):
++ '''Returns the mnemonic IDs that need to be defined for the resource
++ being gathered to compile correctly.'''
++ return []
++
++ def GetCliques(self):
++ '''Returns the MessageClique objects for all translateable portions.'''
++ return []
++
++ def GetInputPath(self):
++ return self.rc_file
++
++ def GetHtmlResourceFilenames(self):
++ """Returns a set of all filenames inlined by this gatherer."""
++ return []
++
++ def Translate(self, lang, pseudo_if_not_available=True,
++ skeleton_gatherer=None, fallback_to_english=False):
++ '''Returns the resource being gathered, with translateable portions filled
++ with the translation for language 'lang'.
++
++ If pseudo_if_not_available is true, a pseudotranslation will be used for any
++ message that doesn't have a real translation available.
++
++ If no translation is available and pseudo_if_not_available is false,
++ fallback_to_english controls the behavior. If it is false, throw an error.
++ If it is true, use the English version of the message as its own
++ "translation".
++
++ If skeleton_gatherer is specified, the translation will use the nontranslateable
++ parts from the gatherer 'skeleton_gatherer', which must be of the same type
++ as 'self'.
++
++ If fallback_to_english
++
++ Args:
++ lang: 'en'
++ pseudo_if_not_available: True | False
++ skeleton_gatherer: other_gatherer
++ fallback_to_english: True | False
++
++ Return:
++ e.g. 'ID_THIS_SECTION TYPE\n...BEGIN\n "Translated message"\n......\nEND'
++
++ Raises:
++ grit.exception.NotReady() if used before Parse() has been successfully
++ called.
++ grit.exception.NoSuchTranslation() if 'pseudo_if_not_available' and
++ fallback_to_english are both false and there is no translation for the
++ requested language.
++ '''
++ raise NotImplementedError()
++
++ def SubstituteMessages(self, substituter):
++ '''Applies substitutions to all messages in the gatherer.
++
++ Args:
++ substituter: a grit.util.Substituter object.
++ '''
++ pass
++
++ def SetFilenameExpansionFunction(self, fn):
++ '''Sets a function for rewriting filenames before gathering.'''
++ pass
++
++ # TODO(benrg): Move this elsewhere, since it isn't part of the interface.
++ def _LoadInputFile(self):
++ '''A convenience function for subclasses that loads the contents of the
++ input file.
++ '''
++ if isinstance(self.rc_file, six.string_types):
++ path = self.GetInputPath()
++ # Hack: some unit tests supply an absolute path and no root node.
++ if not os.path.isabs(path):
++ path = self.grd_node.ToRealPath(path)
++ return util.ReadFile(path, self.encoding)
++ else:
++ return self.rc_file.read()
+diff --git a/tools/grit/grit/gather/json_loader.py b/tools/grit/grit/gather/json_loader.py
+new file mode 100644
+index 0000000000..058e5f17ae
+--- /dev/null
++++ b/tools/grit/grit/gather/json_loader.py
+@@ -0,0 +1,27 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++from __future__ import print_function
++
++from grit.gather import interface
++
++
++class JsonLoader(interface.GathererBase):
++ '''A simple gatherer that loads and parses a JSON file.'''
++
++ def Parse(self):
++ '''Reads and parses the text of self._json_text into the data structure in
++ self._data.
++ '''
++ self._json_text = self._LoadInputFile()
++ self._data = None
++
++ globs = {}
++ exec('data = ' + self._json_text, globs)
++ self._data = globs['data']
++
++ def GetData(self, lang, encoding):
++ '''Returns the parsed JSON data.'''
++ assert encoding == 'utf-8'
++ return self._data
+diff --git a/tools/grit/grit/gather/policy_json.py b/tools/grit/grit/gather/policy_json.py
+new file mode 100644
+index 0000000000..6621c5f3c4
+--- /dev/null
++++ b/tools/grit/grit/gather/policy_json.py
+@@ -0,0 +1,325 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Support for "policy_templates.json" format used by the policy template
++generator as a source for generating ADM,ADMX,etc files.'''
++
++from __future__ import print_function
++
++import json
++import sys
++
++import six
++
++from grit.gather import skeleton_gatherer
++from grit import util
++from grit import tclib
++from xml.dom import minidom
++from xml.parsers.expat import ExpatError
++
++
++class PolicyJson(skeleton_gatherer.SkeletonGatherer):
++ '''Collects and translates the following strings from policy_templates.json:
++ - captions, descriptions, labels and Android app support details of policies
++ - captions of enumeration items
++ - misc strings from the 'messages' section
++ Translatable strings may have untranslateable placeholders with the same
++ format that is used in .grd files.
++ '''
++
++ def _AddEndline(self, add_comma):
++ '''Adds an endline to the skeleton tree. If add_comma is true, adds a
++ comma before the endline.
++
++ Args:
++ add_comma: A boolean to add a comma or not.
++ '''
++ self._AddNontranslateableChunk(',\n' if add_comma else '\n')
++
++ def _ParsePlaceholder(self, placeholder, msg):
++ '''Extracts a placeholder from a DOM node and adds it to a tclib Message.
++
++ Args:
++ placeholder: A DOM node of the form:
++ <ph name="PLACEHOLDER_NAME">Placeholder text<ex>Example value</ex></ph>
++ msg: The placeholder is added to this message.
++ '''
++ text = []
++ example_text = []
++ for node1 in placeholder.childNodes:
++ if (node1.nodeType == minidom.Node.TEXT_NODE):
++ text.append(node1.data)
++ elif (node1.nodeType == minidom.Node.ELEMENT_NODE and
++ node1.tagName == 'ex'):
++ for node2 in node1.childNodes:
++ example_text.append(node2.toxml())
++ else:
++ raise Exception('Unexpected element inside a placeholder: ' +
++ node2.toxml())
++ if example_text == []:
++ # In such cases the original text is okay for an example.
++ example_text = text
++
++ replaced_text = self.Escape(''.join(text).strip())
++ replaced_text = replaced_text.replace('$1', self._config['app_name'])
++ replaced_text = replaced_text.replace('$2', self._config['os_name'])
++ replaced_text = replaced_text.replace('$3', self._config['frame_name'])
++
++ msg.AppendPlaceholder(tclib.Placeholder(
++ placeholder.attributes['name'].value,
++ replaced_text,
++ ''.join(example_text).strip()))
++
++ def _ParseMessage(self, string, desc):
++ '''Parses a given string and adds it to the output as a translatable chunk
++ with a given description.
++
++ Args:
++ string: The message string to parse.
++ desc: The description of the message (for the translators).
++ '''
++ msg = tclib.Message(description=desc)
++ xml = '<msg>' + string + '</msg>'
++ try:
++ node = minidom.parseString(xml).childNodes[0]
++ except ExpatError:
++ reason = '''Input isn't valid XML (has < & > been escaped?): ''' + string
++ six.reraise(Exception, reason, sys.exc_info()[2])
++
++ for child in node.childNodes:
++ if child.nodeType == minidom.Node.TEXT_NODE:
++ msg.AppendText(child.data)
++ elif child.nodeType == minidom.Node.ELEMENT_NODE:
++ if child.tagName == 'ph':
++ self._ParsePlaceholder(child, msg)
++ else:
++ raise Exception("Not implemented.")
++ else:
++ raise Exception("Not implemented.")
++ self.skeleton_.append(self.uberclique.MakeClique(msg))
++
++ def _ParseNode(self, node):
++ '''Traverses the subtree of a DOM node, and register a tclib message for
++ all the <message> nodes.
++ '''
++ att_text = []
++ if node.attributes:
++ for key, value in sorted(node.attributes.items()):
++ att_text.append(' %s=\"%s\"' % (key, value))
++ self._AddNontranslateableChunk("<%s%s>" %
++ (node.tagName, ''.join(att_text)))
++ if node.tagName == 'message':
++ msg = tclib.Message(description=node.attributes['desc'])
++ for child in node.childNodes:
++ if child.nodeType == minidom.Node.TEXT_NODE:
++ if msg == None:
++ self._AddNontranslateableChunk(child.data)
++ else:
++ msg.AppendText(child.data)
++ elif child.nodeType == minidom.Node.ELEMENT_NODE:
++ if child.tagName == 'ph':
++ self._ParsePlaceholder(child, msg)
++ else:
++ assert False
++ self.skeleton_.append(self.uberclique.MakeClique(msg))
++ else:
++ for child in node.childNodes:
++ if child.nodeType == minidom.Node.TEXT_NODE:
++ self._AddNontranslateableChunk(child.data)
++ elif node.nodeType == minidom.Node.ELEMENT_NODE:
++ self._ParseNode(child)
++
++ self._AddNontranslateableChunk("</%s>" % node.tagName)
++
++ def _AddIndentedNontranslateableChunk(self, depth, string):
++ '''Adds a nontranslateable chunk of text to the internally stored output.
++
++ Args:
++ depth: The number of double spaces to prepend to the next argument string.
++ string: The chunk of text to add.
++ '''
++ result = []
++ while depth > 0:
++ result.append(' ')
++ depth = depth - 1
++ result.append(string)
++ self._AddNontranslateableChunk(''.join(result))
++
++ def _GetDescription(self, item, item_type, parent_item, key):
++ '''Creates a description for a translatable message. The description gives
++ some context for the person who will translate this message.
++
++ Args:
++ item: A policy or an enumeration item.
++ item_type: 'enum_item' | 'policy'
++ parent_item: The owner of item. (A policy of type group or enum.)
++ key: The name of the key to parse.
++ depth: The level of indentation.
++ '''
++ key_map = {
++ 'desc': 'Description',
++ 'caption': 'Caption',
++ 'label': 'Label',
++ 'arc_support': 'Information about the effect on Android apps'
++ }
++ if item_type == 'policy':
++ return ('%s of the policy named %s [owner(s): %s]' %
++ (key_map[key], item['name'],
++ ','.join(item['owners'] if 'owners' in item else 'unknown')))
++ if item_type == 'enum_item':
++ return ('%s of the option named %s in policy %s [owner(s): %s]' %
++ (key_map[key], item['name'], parent_item['name'],
++ ','.join(parent_item['owners'] if 'owners' in parent_item else 'unknown')))
++ raise Exception('Unexpected type %s' % item_type)
++
++ def _AddSchemaKeys(self, obj, depth):
++ obj_type = type(obj)
++ if obj_type == dict:
++ self._AddNontranslateableChunk('{\n')
++ keys = sorted(obj.keys())
++ for count, (key) in enumerate(keys, 1):
++ json_key = "%s: " % json.dumps(key)
++ self._AddIndentedNontranslateableChunk(depth + 1, json_key)
++ if key == 'description' and type(obj[key]) == str:
++ self._AddNontranslateableChunk("\"")
++ self._ParseMessage(obj[key], 'Description of schema property')
++ self._AddNontranslateableChunk("\"")
++ elif type(obj[key]) in (bool, int, str):
++ self._AddSchemaKeys(obj[key], 0)
++ else:
++ self._AddSchemaKeys(obj[key], depth + 1)
++ self._AddEndline(count < len(keys))
++ self._AddIndentedNontranslateableChunk(depth, '}')
++ elif obj_type == list:
++ self._AddNontranslateableChunk('[\n')
++ for count, (item) in enumerate(obj, 1):
++ self._AddSchemaKeys(item, depth + 1)
++ self._AddEndline(count < len(obj))
++ self._AddIndentedNontranslateableChunk(depth, ']')
++ elif obj_type in (bool, int, str):
++ self._AddIndentedNontranslateableChunk(depth, json.dumps(obj))
++ else:
++ raise Exception('Invalid schema object: %s' % obj)
++
++ def _AddPolicyKey(self, item, item_type, parent_item, key, depth):
++ '''Given a policy/enumeration item and a key, adds that key and its value
++ into the output.
++ E.g.:
++ 'example_value': 123
++ If key indicates that the value is a translatable string, then it is parsed
++ as a translatable string.
++
++ Args:
++ item: A policy or an enumeration item.
++ item_type: 'enum_item' | 'policy'
++ parent_item: The owner of item. (A policy of type group or enum.)
++ key: The name of the key to parse.
++ depth: The level of indentation.
++ '''
++ self._AddIndentedNontranslateableChunk(depth, "%s: " % json.dumps(key))
++ if key in ('desc', 'caption', 'label', 'arc_support'):
++ self._AddNontranslateableChunk("\"")
++ self._ParseMessage(
++ item[key],
++ self._GetDescription(item, item_type, parent_item, key))
++ self._AddNontranslateableChunk("\"")
++ elif key in ('schema', 'validation_schema', 'description_schema'):
++ self._AddSchemaKeys(item[key], depth)
++ else:
++ self._AddNontranslateableChunk(json.dumps(item[key], ensure_ascii=False))
++
++ def _AddItems(self, items, item_type, parent_item, depth):
++ '''Parses and adds a list of items from the JSON file. Items can be policies
++ or parts of an enum policy.
++
++ Args:
++ items: Either a list of policies or a list of dictionaries.
++ item_type: 'enum_item' | 'policy'
++ parent_item: If items contains a list of policies, then this is the policy
++ group that owns them. If items contains a list of enumeration items,
++ then this is the enum policy that holds them.
++ depth: Indicates the depth of our position in the JSON hierarchy. Used to
++ add nice line-indent to the output.
++ '''
++ for item_count, (item1) in enumerate(items, 1):
++ self._AddIndentedNontranslateableChunk(depth, "{\n")
++ keys = sorted(item1.keys())
++ for keys_count, (key) in enumerate(keys, 1):
++ if key == 'items':
++ self._AddIndentedNontranslateableChunk(depth + 1, "\"items\": [\n")
++ self._AddItems(item1['items'], 'enum_item', item1, depth + 2)
++ self._AddIndentedNontranslateableChunk(depth + 1, "]")
++ elif key == 'policies' and all(not isinstance(x, str)
++ for x in item1['policies']):
++ self._AddIndentedNontranslateableChunk(depth + 1, "\"policies\": [\n")
++ self._AddItems(item1['policies'], 'policy', item1, depth + 2)
++ self._AddIndentedNontranslateableChunk(depth + 1, "]")
++ else:
++ self._AddPolicyKey(item1, item_type, parent_item, key, depth + 1)
++ self._AddEndline(keys_count < len(keys))
++ self._AddIndentedNontranslateableChunk(depth, "}")
++ self._AddEndline(item_count < len(items))
++
++ def _AddMessages(self):
++ '''Processed and adds the 'messages' section to the output.'''
++ self._AddNontranslateableChunk(" \"messages\": {\n")
++ messages = self.data['messages'].items()
++ for count, (name, message) in enumerate(messages, 1):
++ self._AddNontranslateableChunk(" %s: {\n" % json.dumps(name))
++ self._AddNontranslateableChunk(" \"text\": \"")
++ self._ParseMessage(message['text'], message['desc'])
++ self._AddNontranslateableChunk("\"\n")
++ self._AddNontranslateableChunk(" }")
++ self._AddEndline(count < len(self.data['messages']))
++ self._AddNontranslateableChunk(" }\n")
++
++ # Although we use the RegexpGatherer base class, we do not use the
++ # _RegExpParse method of that class to implement Parse(). Instead, we
++ # parse using a DOM parser.
++ def Parse(self):
++ if self.have_parsed_:
++ return
++ self.have_parsed_ = True
++
++ self.text_ = self._LoadInputFile()
++ if util.IsExtraVerbose():
++ print(self.text_)
++
++ self.data = eval(self.text_)
++
++ self._AddNontranslateableChunk('{\n')
++ self._AddNontranslateableChunk(" \"policy_definitions\": [\n")
++ self._AddItems(self.data['policy_definitions'], 'policy', None, 2)
++ self._AddNontranslateableChunk(" ],\n")
++ self._AddNontranslateableChunk(" \"policy_atomic_group_definitions\": [\n")
++ if 'policy_atomic_group_definitions' in self.data:
++ self._AddItems(self.data['policy_atomic_group_definitions'],
++ 'policy', None, 2)
++ self._AddNontranslateableChunk(" ],\n")
++ self._AddMessages()
++ self._AddNontranslateableChunk('\n}')
++
++ def Escape(self, text):
++ return json.dumps(text, ensure_ascii=False)[1:-1]
++
++ def SetDefines(self, defines):
++ if not defines:
++ raise Exception('Must pass valid defines')
++
++ if '_chromium' in defines:
++ self._config = {
++ 'build': 'chromium',
++ 'app_name': 'Chromium',
++ 'frame_name': 'Chromium Frame',
++ 'os_name': 'Chromium OS',
+ }
++ elif '_google_chrome' in defines:
++ self._config = {
++ 'build': 'chrome',
++ 'app_name': 'Google Chrome',
++ 'frame_name': 'Google Chrome Frame',
++ 'os_name': 'Google Chrome OS',
++ }
++ else:
++ raise Exception('Unknown build')
+diff --git a/tools/grit/grit/gather/policy_json_unittest.py b/tools/grit/grit/gather/policy_json_unittest.py
+new file mode 100644
+index 0000000000..214cd276aa
+--- /dev/null
++++ b/tools/grit/grit/gather/policy_json_unittest.py
+@@ -0,0 +1,347 @@
++#!/usr/bin/env python
++# Copyright (c) 2011 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit tests for grit.gather.policy_json'''
++
++from __future__ import print_function
++
++import json
++import os
++import re
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++import unittest
++
++from six import StringIO
++
++from grit.gather import policy_json
++
++class PolicyJsonUnittest(unittest.TestCase):
++
++ def GetExpectedOutput(self, original):
++ expected = eval(original)
++ for key, message in expected['messages'].items():
++ del message['desc']
++ return expected
++
++ def testEmpty(self):
++ original = """{
++ 'policy_definitions': [],
++ 'policy_atomic_group_definitions': [],
++ 'messages': {}
++ }"""
++ gatherer = policy_json.PolicyJson(StringIO(original))
++ gatherer.Parse()
++ self.failUnless(len(gatherer.GetCliques()) == 0)
++ self.failUnless(eval(original) == json.loads(gatherer.Translate('en')))
++
++ def testGeneralPolicy(self):
++ original = (
++ "{"
++ " 'policy_definitions': ["
++ " {"
++ " 'name': 'HomepageLocation',"
++ " 'type': 'string',"
++ " 'owners': ['foo@bar.com'],"
++ " 'supported_on': ['chrome.*:8-'],"
++ " 'features': {'dynamic_refresh': 1},"
++ " 'example_value': 'http://chromium.org',"
++ " 'caption': 'nothing special 1',"
++ " 'desc': 'nothing special 2',"
++ " 'label': 'nothing special 3',"
++ " },"
++ " ],"
++ " 'policy_atomic_group_definitions': [],"
++ " 'messages': {"
++ " 'msg_identifier': {"
++ " 'text': 'nothing special 3',"
++ " 'desc': 'nothing special descr 3',"
++ " }"
++ " }"
++ "}")
++ gatherer = policy_json.PolicyJson(StringIO(original))
++ gatherer.Parse()
++ self.failUnless(len(gatherer.GetCliques()) == 4)
++ expected = self.GetExpectedOutput(original)
++ self.failUnless(expected == json.loads(gatherer.Translate('en')))
++
++ def testEnum(self):
++ original = (
++ "{"
++ " 'policy_definitions': ["
++ " {"
++ " 'name': 'Policy1',"
++ " 'owners': ['a@b'],"
++ " 'items': ["
++ " {"
++ " 'name': 'Item1',"
++ " 'caption': 'nothing special',"
++ " }"
++ " ]"
++ " },"
++ " ],"
++ " 'policy_atomic_group_definitions': [],"
++ " 'messages': {}"
++ "}")
++ gatherer = policy_json.PolicyJson(StringIO(original))
++ gatherer.Parse()
++ self.failUnless(len(gatherer.GetCliques()) == 1)
++ expected = self.GetExpectedOutput(original)
++ self.failUnless(expected == json.loads(gatherer.Translate('en')))
++
++ def testSchema(self):
++ original = ("{"
++ " 'policy_definitions': ["
++ " {"
++ " 'name': 'Policy1',"
++ " 'schema': {"
++ " 'type': 'object',"
++ " 'properties': {"
++ " 'outer': {"
++ " 'description': 'outer description',"
++ " 'type': 'object',"
++ " 'inner': {"
++ " 'description': 'inner description',"
++ " 'type': 'integer', 'minimum': 0, 'maximum': 100"
++ " },"
++ " 'inner2': {"
++ " 'description': 'inner2 description',"
++ " 'type': 'integer',"
++ " 'enum': [ 1, 2, 3 ],"
++ " 'sensitiveValue': True"
++ " },"
++ " },"
++ " },"
++ " },"
++ " 'caption': 'nothing special',"
++ " 'owners': ['a@b']"
++ " },"
++ " ],"
++ " 'policy_atomic_group_definitions': [],"
++ " 'messages': {}"
++ "}")
++ gatherer = policy_json.PolicyJson(StringIO(original))
++ gatherer.Parse()
++ self.failUnless(len(gatherer.GetCliques()) == 4)
++ expected = self.GetExpectedOutput(original)
++ self.failUnless(expected == json.loads(gatherer.Translate('en')))
++
++ def testValidationSchema(self):
++ original = ("{"
++ " 'policy_definitions': ["
++ " {"
++ " 'name': 'Policy1',"
++ " 'owners': ['a@b'],"
++ " 'validation_schema': {"
++ " 'type': 'object',"
++ " 'properties': {"
++ " 'description': 'properties description',"
++ " 'type': 'object',"
++ " },"
++ " },"
++ " },"
++ " ],"
++ " 'policy_atomic_group_definitions': [],"
++ " 'messages': {}"
++ "}")
++ gatherer = policy_json.PolicyJson(StringIO(original))
++ gatherer.Parse()
++ self.failUnless(len(gatherer.GetCliques()) == 1)
++ expected = self.GetExpectedOutput(original)
++ self.failUnless(expected == json.loads(gatherer.Translate('en')))
++
++ def testDescriptionSchema(self):
++ original = ("{"
++ " 'policy_definitions': ["
++ " {"
++ " 'name': 'Policy1',"
++ " 'owners': ['a@b'],"
++ " 'description_schema': {"
++ " 'type': 'object',"
++ " 'properties': {"
++ " 'description': 'properties description',"
++ " 'type': 'object',"
++ " },"
++ " },"
++ " },"
++ " ],"
++ " 'policy_atomic_group_definitions': [],"
++ " 'messages': {}"
++ "}")
++ gatherer = policy_json.PolicyJson(StringIO(original))
++ gatherer.Parse()
++ self.failUnless(len(gatherer.GetCliques()) == 1)
++ expected = self.GetExpectedOutput(original)
++ self.failUnless(expected == json.loads(gatherer.Translate('en')))
++
++ # Keeping for backwards compatibility.
++ def testSubPolicyOldFormat(self):
++ original = (
++ "{"
++ " 'policy_definitions': ["
++ " {"
++ " 'type': 'group',"
++ " 'policies': ["
++ " {"
++ " 'name': 'Policy1',"
++ " 'caption': 'nothing special',"
++ " 'owners': ['a@b']"
++ " }"
++ " ]"
++ " }"
++ " ],"
++ " 'policy_atomic_group_definitions': [],"
++ " 'messages': {}"
++ "}")
++ gatherer = policy_json.PolicyJson(StringIO(original))
++ gatherer.Parse()
++ self.failUnless(len(gatherer.GetCliques()) == 1)
++ expected = self.GetExpectedOutput(original)
++ self.failUnless(expected == json.loads(gatherer.Translate('en')))
++
++ def testSubPolicyNewFormat(self):
++ original = (
++ "{"
++ " 'policy_definitions': ["
++ " {"
++ " 'type': 'group',"
++ " 'policies': ['Policy1']"
++ " },"
++ " {"
++ " 'name': 'Policy1',"
++ " 'caption': 'nothing special',"
++ " 'owners': ['a@b']"
++ " }"
++ " ],"
++ " 'policy_atomic_group_definitions': [],"
++ " 'messages': {}"
++ "}")
++ gatherer = policy_json.PolicyJson(StringIO(original))
++ gatherer.Parse()
++ self.failUnless(len(gatherer.GetCliques()) == 1)
++ expected = self.GetExpectedOutput(original)
++ self.failUnless(expected == json.loads(gatherer.Translate('en')))
++
++ def testEscapingAndLineBreaks(self):
++ original = """{
++ 'policy_definitions': [],
++ 'policy_atomic_group_definitions': [],
++ 'messages': {
++ 'msg1': {
++ # The following line will contain two backslash characters when it
++ # ends up in eval().
++ 'text': '''backslashes, Sir? \\\\''',
++ 'desc': ''
++ },
++ 'msg2': {
++ 'text': '''quotes, Madam? "''',
++ 'desc': ''
++ },
++ 'msg3': {
++ # The following line will contain two backslash characters when it
++ # ends up in eval().
++ 'text': 'backslashes, Sir? \\\\',
++ 'desc': ''
++ },
++ 'msg4': {
++ 'text': "quotes, Madam? '",
++ 'desc': ''
++ },
++ 'msg5': {
++ 'text': '''what happens
++with a newline?''',
++ 'desc': ''
++ },
++ 'msg6': {
++ # The following line will contain a backslash+n when it ends up in
++ # eval().
++ 'text': 'what happens\\nwith a newline? (Episode 1)',
++ 'desc': ''
++ }
++ }
++}"""
++ gatherer = policy_json.PolicyJson(StringIO(original))
++ gatherer.Parse()
++ self.failUnless(len(gatherer.GetCliques()) == 6)
++ expected = self.GetExpectedOutput(original)
++ self.failUnless(expected == json.loads(gatherer.Translate('en')))
++
++ def testPlaceholdersChromium(self):
++ original = """{
++ "policy_definitions": [
++ {
++ "name": "Policy1",
++ "caption": "Please install\\n<ph name=\\"PRODUCT_NAME\\">$1<ex>Google Chrome</ex></ph>.",
++ "owners": "a@b"
++ }
++ ],
++ "policy_atomic_group_definitions": [],
++ "messages": {}
++}"""
++ gatherer = policy_json.PolicyJson(StringIO(original))
++ gatherer.SetDefines({'_chromium': True})
++ gatherer.Parse()
++ self.failUnless(len(gatherer.GetCliques()) == 1)
++ expected = json.loads(re.sub('<ph.*ph>', 'Chromium', original))
++ self.failUnless(expected == json.loads(gatherer.Translate('en')))
++ self.failUnless(gatherer.GetCliques()[0].translateable)
++ msg = gatherer.GetCliques()[0].GetMessage()
++ self.failUnless(len(msg.GetPlaceholders()) == 1)
++ ph = msg.GetPlaceholders()[0]
++ self.failUnless(ph.GetOriginal() == 'Chromium')
++ self.failUnless(ph.GetPresentation() == 'PRODUCT_NAME')
++ self.failUnless(ph.GetExample() == 'Google Chrome')
++
++ def testPlaceholdersChrome(self):
++ original = """{
++ "policy_definitions": [
++ {
++ "name": "Policy1",
++ "caption": "Please install\\n<ph name=\\"PRODUCT_NAME\\">$1<ex>Google Chrome</ex></ph>.",
++ "owners": "a@b"
++ }
++ ],
++ "policy_atomic_group_definitions": [],
++ "messages": {}
++}"""
++ gatherer = policy_json.PolicyJson(StringIO(original))
++ gatherer.SetDefines({'_google_chrome': True})
++ gatherer.Parse()
++ self.failUnless(len(gatherer.GetCliques()) == 1)
++ expected = json.loads(re.sub('<ph.*ph>', 'Google Chrome', original))
++ self.failUnless(expected == json.loads(gatherer.Translate('en')))
++ self.failUnless(gatherer.GetCliques()[0].translateable)
++ msg = gatherer.GetCliques()[0].GetMessage()
++ self.failUnless(len(msg.GetPlaceholders()) == 1)
++ ph = msg.GetPlaceholders()[0]
++ self.failUnless(ph.GetOriginal() == 'Google Chrome')
++ self.failUnless(ph.GetPresentation() == 'PRODUCT_NAME')
++ self.failUnless(ph.GetExample() == 'Google Chrome')
++
++ def testGetDescription(self):
++ gatherer = policy_json.PolicyJson({})
++ gatherer.SetDefines({'_google_chrome': True})
++ self.assertEquals(
++ gatherer._GetDescription({'name': 'Policy1', 'owners': ['a@b']},
++ 'policy', None, 'desc'),
++ 'Description of the policy named Policy1 [owner(s): a@b]')
++ self.assertEquals(
++ gatherer._GetDescription({'name': 'Plcy2', 'owners': ['a@b', 'c@d']},
++ 'policy', None, 'caption'),
++ 'Caption of the policy named Plcy2 [owner(s): a@b,c@d]')
++ self.assertEquals(
++ gatherer._GetDescription({'name': 'Plcy3', 'owners': ['a@b']},
++ 'policy', None, 'label'),
++ 'Label of the policy named Plcy3 [owner(s): a@b]')
++ self.assertEquals(
++ gatherer._GetDescription({'name': 'Item'}, 'enum_item',
++ {'name': 'Plcy', 'owners': ['a@b']}, 'caption'),
++ 'Caption of the option named Item in policy Plcy [owner(s): a@b]')
++
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/gather/rc.py b/tools/grit/grit/gather/rc.py
+new file mode 100644
+index 0000000000..dd091d1e18
+--- /dev/null
++++ b/tools/grit/grit/gather/rc.py
+@@ -0,0 +1,343 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Support for gathering resources from RC files.
++'''
++
++from __future__ import print_function
++
++import re
++
++from grit import exception
++from grit import lazy_re
++from grit import tclib
++
++from grit.gather import regexp
++
++
++# Find portions that need unescaping in resource strings. We need to be
++# careful that a \\n is matched _first_ as a \\ rather than matching as
++# a \ followed by a \n.
++# TODO(joi) Handle ampersands if we decide to change them into <ph>
++# TODO(joi) May need to handle other control characters than \n
++_NEED_UNESCAPE = lazy_re.compile(r'""|\\\\|\\n|\\t')
++
++# Find portions that need escaping to encode string as a resource string.
++_NEED_ESCAPE = lazy_re.compile(r'"|\n|\t|\\|\&nbsp\;')
++
++# How to escape certain characters
++_ESCAPE_CHARS = {
++ '"' : '""',
++ '\n' : '\\n',
++ '\t' : '\\t',
++ '\\' : '\\\\',
++ '&nbsp;' : ' '
++}
++
++# How to unescape certain strings
++_UNESCAPE_CHARS = dict([[value, key] for key, value in _ESCAPE_CHARS.items()])
++
++
++
++class Section(regexp.RegexpGatherer):
++ '''A section from a resource file.'''
++
++ @staticmethod
++ def Escape(text):
++ '''Returns a version of 'text' with characters escaped that need to be
++ for inclusion in a resource section.'''
++ def Replace(match):
++ return _ESCAPE_CHARS[match.group()]
++ return _NEED_ESCAPE.sub(Replace, text)
++
++ @staticmethod
++ def UnEscape(text):
++ '''Returns a version of 'text' with escaped characters unescaped.'''
++ def Replace(match):
++ return _UNESCAPE_CHARS[match.group()]
++ return _NEED_UNESCAPE.sub(Replace, text)
++
++ def _RegExpParse(self, rexp, text_to_parse):
++ '''Overrides _RegExpParse to add shortcut group handling. Otherwise
++ the same.
++ '''
++ super(Section, self)._RegExpParse(rexp, text_to_parse)
++
++ if not self.is_skeleton and len(self.GetTextualIds()) > 0:
++ group_name = self.GetTextualIds()[0]
++ for c in self.GetCliques():
++ c.AddToShortcutGroup(group_name)
++
++ def ReadSection(self):
++ rc_text = self._LoadInputFile()
++
++ out = ''
++ begin_count = 0
++ assert self.extkey
++ first_line_re = re.compile(r'\s*' + self.extkey + r'\b')
++ for line in rc_text.splitlines(True):
++ if out or first_line_re.match(line):
++ out += line
++
++ # we stop once we reach the END for the outermost block.
++ begin_count_was = begin_count
++ if len(out) > 0 and line.strip() == 'BEGIN':
++ begin_count += 1
++ elif len(out) > 0 and line.strip() == 'END':
++ begin_count -= 1
++ if begin_count_was == 1 and begin_count == 0:
++ break
++
++ if len(out) == 0:
++ raise exception.SectionNotFound('%s in file %s' % (self.extkey, self.rc_file))
++
++ self.text_ = out.strip()
++
++
++class Dialog(Section):
++ '''A resource section that contains a dialog resource.'''
++
++ # A typical dialog resource section looks like this:
++ #
++ # IDD_ABOUTBOX DIALOGEX 22, 17, 230, 75
++ # STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
++ # CAPTION "About"
++ # FONT 8, "System", 0, 0, 0x0
++ # BEGIN
++ # ICON IDI_KLONK,IDC_MYICON,14,9,20,20
++ # LTEXT "klonk Version ""yibbee"" 1.0",IDC_STATIC,49,10,119,8,
++ # SS_NOPREFIX
++ # LTEXT "Copyright (C) 2005",IDC_STATIC,49,20,119,8
++ # DEFPUSHBUTTON "OK",IDOK,195,6,30,11,WS_GROUP
++ # CONTROL "Jack ""Black"" Daniels",IDC_RADIO1,"Button",
++ # BS_AUTORADIOBUTTON,46,51,84,10
++ # END
++
++ # We are using a sorted set of keys, and we assume that the
++ # group name used for descriptions (type) will come after the "text"
++ # group in alphabetical order. We also assume that there cannot be
++ # more than one description per regular expression match.
++ # If that's not the case some descriptions will be clobbered.
++ dialog_re_ = lazy_re.compile(r'''
++ # The dialog's ID in the first line
++ (?P<id1>[A-Z0-9_]+)\s+DIALOG(EX)?
++ |
++ # The caption of the dialog
++ (?P<type1>CAPTION)\s+"(?P<text1>.*?([^"]|""))"\s
++ |
++ # Lines for controls that have text and an ID
++ \s+(?P<type2>[A-Z]+)\s+"(?P<text2>.*?([^"]|"")?)"\s*,\s*(?P<id2>[A-Z0-9_]+)\s*,
++ |
++ # Lines for controls that have text only
++ \s+(?P<type3>[A-Z]+)\s+"(?P<text3>.*?([^"]|"")?)"\s*,
++ |
++ # Lines for controls that reference other resources
++ \s+[A-Z]+\s+[A-Z0-9_]+\s*,\s*(?P<id3>[A-Z0-9_]*[A-Z][A-Z0-9_]*)
++ |
++ # This matches "NOT SOME_STYLE" so that it gets consumed and doesn't get
++ # matched by the next option (controls that have only an ID and then just
++ # numbers)
++ \s+NOT\s+[A-Z][A-Z0-9_]+
++ |
++ # Lines for controls that have only an ID and then just numbers
++ \s+[A-Z]+\s+(?P<id4>[A-Z0-9_]*[A-Z][A-Z0-9_]*)\s*,
++ ''', re.MULTILINE | re.VERBOSE)
++
++ def Parse(self):
++ '''Knows how to parse dialog resource sections.'''
++ self.ReadSection()
++ self._RegExpParse(self.dialog_re_, self.text_)
++
++
++class Menu(Section):
++ '''A resource section that contains a menu resource.'''
++
++ # A typical menu resource section looks something like this:
++ #
++ # IDC_KLONK MENU
++ # BEGIN
++ # POPUP "&File"
++ # BEGIN
++ # MENUITEM "E&xit", IDM_EXIT
++ # MENUITEM "This be ""Klonk"" me like", ID_FILE_THISBE
++ # POPUP "gonk"
++ # BEGIN
++ # MENUITEM "Klonk && is ""good""", ID_GONK_KLONKIS
++ # END
++ # END
++ # POPUP "&Help"
++ # BEGIN
++ # MENUITEM "&About ...", IDM_ABOUT
++ # END
++ # END
++
++ # Description used for the messages generated for menus, to explain to
++ # the translators how to handle them.
++ MENU_MESSAGE_DESCRIPTION = (
++ 'This message represents a menu. Each of the items appears in sequence '
++ '(some possibly within sub-menus) in the menu. The XX01XX placeholders '
++ 'serve to separate items. Each item contains an & (ampersand) character '
++ 'in front of the keystroke that should be used as a shortcut for that item '
++ 'in the menu. Please make sure that no two items in the same menu share '
++ 'the same shortcut.'
++ )
++
++ # A dandy regexp to suck all the IDs and translateables out of a menu
++ # resource
++ menu_re_ = lazy_re.compile(r'''
++ # Match the MENU ID on the first line
++ ^(?P<id1>[A-Z0-9_]+)\s+MENU
++ |
++ # Match the translateable caption for a popup menu
++ POPUP\s+"(?P<text1>.*?([^"]|""))"\s
++ |
++ # Match the caption & ID of a MENUITEM
++ MENUITEM\s+"(?P<text2>.*?([^"]|""))"\s*,\s*(?P<id2>[A-Z0-9_]+)
++ ''', re.MULTILINE | re.VERBOSE)
++
++ def Parse(self):
++ '''Knows how to parse menu resource sections. Because it is important that
++ menu shortcuts are unique within the menu, we return each menu as a single
++ message with placeholders to break up the different menu items, rather than
++ return a single message per menu item. we also add an automatic description
++ with instructions for the translators.'''
++ self.ReadSection()
++ self.single_message_ = tclib.Message(description=self.MENU_MESSAGE_DESCRIPTION)
++ self._RegExpParse(self.menu_re_, self.text_)
++
++
++class Version(Section):
++ '''A resource section that contains a VERSIONINFO resource.'''
++
++ # A typical version info resource can look like this:
++ #
++ # VS_VERSION_INFO VERSIONINFO
++ # FILEVERSION 1,0,0,1
++ # PRODUCTVERSION 1,0,0,1
++ # FILEFLAGSMASK 0x3fL
++ # #ifdef _DEBUG
++ # FILEFLAGS 0x1L
++ # #else
++ # FILEFLAGS 0x0L
++ # #endif
++ # FILEOS 0x4L
++ # FILETYPE 0x2L
++ # FILESUBTYPE 0x0L
++ # BEGIN
++ # BLOCK "StringFileInfo"
++ # BEGIN
++ # BLOCK "040904e4"
++ # BEGIN
++ # VALUE "CompanyName", "TODO: <Company name>"
++ # VALUE "FileDescription", "TODO: <File description>"
++ # VALUE "FileVersion", "1.0.0.1"
++ # VALUE "LegalCopyright", "TODO: (c) <Company name>. All rights reserved."
++ # VALUE "InternalName", "res_format_test.dll"
++ # VALUE "OriginalFilename", "res_format_test.dll"
++ # VALUE "ProductName", "TODO: <Product name>"
++ # VALUE "ProductVersion", "1.0.0.1"
++ # END
++ # END
++ # BLOCK "VarFileInfo"
++ # BEGIN
++ # VALUE "Translation", 0x409, 1252
++ # END
++ # END
++ #
++ #
++ # In addition to the above fields, VALUE fields named "Comments" and
++ # "LegalTrademarks" may also be translateable.
++
++ version_re_ = lazy_re.compile(r'''
++ # Match the ID on the first line
++ ^(?P<id1>[A-Z0-9_]+)\s+VERSIONINFO
++ |
++ # Match all potentially translateable VALUE sections
++ \s+VALUE\s+"
++ (
++ CompanyName|FileDescription|LegalCopyright|
++ ProductName|Comments|LegalTrademarks
++ )",\s+"(?P<text1>.*?([^"]|""))"\s
++ ''', re.MULTILINE | re.VERBOSE)
++
++ def Parse(self):
++ '''Knows how to parse VERSIONINFO resource sections.'''
++ self.ReadSection()
++ self._RegExpParse(self.version_re_, self.text_)
++
++ # TODO(joi) May need to override the Translate() method to change the
++ # "Translation" VALUE block to indicate the correct language code.
++
++
++class RCData(Section):
++ '''A resource section that contains some data .'''
++
++ # A typical rcdataresource section looks like this:
++ #
++ # IDR_BLAH RCDATA { 1, 2, 3, 4 }
++
++ dialog_re_ = lazy_re.compile(r'''
++ ^(?P<id1>[A-Z0-9_]+)\s+RCDATA\s+(DISCARDABLE)?\s+\{.*?\}
++ ''', re.MULTILINE | re.VERBOSE | re.DOTALL)
++
++ def Parse(self):
++ '''Implementation for resource types w/braces (not BEGIN/END)
++ '''
++ rc_text = self._LoadInputFile()
++
++ out = ''
++ begin_count = 0
++ openbrace_count = 0
++ assert self.extkey
++ first_line_re = re.compile(r'\s*' + self.extkey + r'\b')
++ for line in rc_text.splitlines(True):
++ if out or first_line_re.match(line):
++ out += line
++
++ # We stop once the braces balance (could happen in one line).
++ begin_count_was = begin_count
++ if len(out) > 0:
++ openbrace_count += line.count('{')
++ begin_count += line.count('{')
++ begin_count -= line.count('}')
++ if ((begin_count_was == 1 and begin_count == 0) or
++ (openbrace_count > 0 and begin_count == 0)):
++ break
++
++ if len(out) == 0:
++ raise exception.SectionNotFound('%s in file %s' % (self.extkey, self.rc_file))
++
++ self.text_ = out
++
++ self._RegExpParse(self.dialog_re_, out)
++
++
++class Accelerators(Section):
++ '''An ACCELERATORS table.
++ '''
++
++ # A typical ACCELERATORS section looks like this:
++ #
++ # IDR_ACCELERATOR1 ACCELERATORS
++ # BEGIN
++ # "^C", ID_ACCELERATOR32770, ASCII, NOINVERT
++ # "^V", ID_ACCELERATOR32771, ASCII, NOINVERT
++ # VK_INSERT, ID_ACCELERATOR32772, VIRTKEY, CONTROL, NOINVERT
++ # END
++
++ accelerators_re_ = lazy_re.compile(r'''
++ # Match the ID on the first line
++ ^(?P<id1>[A-Z0-9_]+)\s+ACCELERATORS\s+
++ |
++ # Match accelerators specified as VK_XXX
++ \s+VK_[A-Z0-9_]+,\s*(?P<id2>[A-Z0-9_]+)\s*,
++ |
++ # Match accelerators specified as e.g. "^C"
++ \s+"[^"]*",\s+(?P<id3>[A-Z0-9_]+)\s*,
++ ''', re.MULTILINE | re.VERBOSE)
++
++ def Parse(self):
++ '''Knows how to parse ACCELERATORS resource sections.'''
++ self.ReadSection()
++ self._RegExpParse(self.accelerators_re_, self.text_)
+diff --git a/tools/grit/grit/gather/rc_unittest.py b/tools/grit/grit/gather/rc_unittest.py
+new file mode 100644
+index 0000000000..3c26a4342a
+--- /dev/null
++++ b/tools/grit/grit/gather/rc_unittest.py
+@@ -0,0 +1,372 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit tests for grit.gather.rc'''
++
++from __future__ import print_function
++
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++import unittest
++
++from six import StringIO
++
++from grit.gather import rc
++from grit import util
++
++
++class RcUnittest(unittest.TestCase):
++
++ part_we_want = '''IDC_KLONKACC ACCELERATORS
++BEGIN
++ "?", IDM_ABOUT, ASCII, ALT
++ "/", IDM_ABOUT, ASCII, ALT
++END'''
++
++ def testSectionFromFile(self):
++ buf = '''IDC_SOMETHINGELSE BINGO
++BEGIN
++ BLA BLA
++ BLA BLA
++END
++%s
++
++IDC_KLONK BINGOBONGO
++BEGIN
++ HONGO KONGO
++END
++''' % self.part_we_want
++
++ f = StringIO(buf)
++
++ out = rc.Section(f, 'IDC_KLONKACC')
++ out.ReadSection()
++ self.failUnless(out.GetText() == self.part_we_want)
++
++ out = rc.Section(util.PathFromRoot(r'grit/testdata/klonk.rc'),
++ 'IDC_KLONKACC',
++ encoding='utf-16')
++ out.ReadSection()
++ out_text = out.GetText().replace('\t', '')
++ out_text = out_text.replace(' ', '')
++ self.part_we_want = self.part_we_want.replace(' ', '')
++ self.failUnless(out_text.strip() == self.part_we_want.strip())
++
++
++ def testDialog(self):
++ dlg = rc.Dialog(StringIO('''IDD_ABOUTBOX DIALOGEX 22, 17, 230, 75
++STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
++CAPTION "About"
++FONT 8, "System", 0, 0, 0x0
++BEGIN
++ ICON IDI_KLONK,IDC_MYICON,14,9,20,20
++ LTEXT "klonk Version ""yibbee"" 1.0",IDC_STATIC,49,10,119,8,
++ SS_NOPREFIX
++ LTEXT "Copyright (C) 2005",IDC_STATIC,49,20,119,8
++ DEFPUSHBUTTON "OK",IDOK,195,6,30,11,WS_GROUP
++ CONTROL "Jack ""Black"" Daniels",IDC_RADIO1,"Button",
++ BS_AUTORADIOBUTTON,46,51,84,10
++ // try a line where the ID is on the continuation line
++ LTEXT "blablablabla blablabla blablablablablablablabla blablabla",
++ ID_SMURF, whatever...
++END
++'''), 'IDD_ABOUTBOX')
++ dlg.Parse()
++ self.failUnless(len(dlg.GetTextualIds()) == 7)
++ self.failUnless(len(dlg.GetCliques()) == 6)
++ self.failUnless(dlg.GetCliques()[1].GetMessage().GetRealContent() ==
++ 'klonk Version "yibbee" 1.0')
++
++ transl = dlg.Translate('en')
++ self.failUnless(transl.strip() == dlg.GetText().strip())
++
++ def testAlternateSkeleton(self):
++ dlg = rc.Dialog(StringIO('''IDD_ABOUTBOX DIALOGEX 22, 17, 230, 75
++STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
++CAPTION "About"
++FONT 8, "System", 0, 0, 0x0
++BEGIN
++ LTEXT "Yipee skippy",IDC_STATIC,49,10,119,8,
++ SS_NOPREFIX
++END
++'''), 'IDD_ABOUTBOX')
++ dlg.Parse()
++
++ alt_dlg = rc.Dialog(StringIO('''IDD_ABOUTBOX DIALOGEX 040704, 17, 230, 75
++STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
++CAPTION "XXXXXXXXX"
++FONT 8, "System", 0, 0, 0x0
++BEGIN
++ LTEXT "XXXXXXXXXXXXXXXXX",IDC_STATIC,110978,10,119,8,
++ SS_NOPREFIX
++END
++'''), 'IDD_ABOUTBOX')
++ alt_dlg.Parse()
++
++ transl = dlg.Translate('en', skeleton_gatherer=alt_dlg)
++ self.failUnless(transl.count('040704') and
++ transl.count('110978'))
++ self.failUnless(transl.count('Yipee skippy'))
++
++ def testMenu(self):
++ menu = rc.Menu(StringIO('''IDC_KLONK MENU
++BEGIN
++ POPUP "&File """
++ BEGIN
++ MENUITEM "E&xit", IDM_EXIT
++ MENUITEM "This be ""Klonk"" me like", ID_FILE_THISBE
++ POPUP "gonk"
++ BEGIN
++ MENUITEM "Klonk && is ""good""", ID_GONK_KLONKIS
++ END
++ MENUITEM "This is a very long menu caption to try to see if we can make the ID go to a continuation line, blablabla blablabla bla blabla blablabla blablabla blablabla blablabla...",
++ ID_FILE_THISISAVERYLONGMENUCAPTIONTOTRYTOSEEIFWECANMAKETHEIDGOTOACONTINUATIONLINE
++ END
++ POPUP "&Help"
++ BEGIN
++ MENUITEM "&About ...", IDM_ABOUT
++ END
++END'''), 'IDC_KLONK')
++
++ menu.Parse()
++ self.failUnless(len(menu.GetTextualIds()) == 6)
++ self.failUnless(len(menu.GetCliques()) == 1)
++ self.failUnless(len(menu.GetCliques()[0].GetMessage().GetPlaceholders()) ==
++ 9)
++
++ transl = menu.Translate('en')
++ self.failUnless(transl.strip() == menu.GetText().strip())
++
++ def testVersion(self):
++ version = rc.Version(StringIO('''
++VS_VERSION_INFO VERSIONINFO
++ FILEVERSION 1,0,0,1
++ PRODUCTVERSION 1,0,0,1
++ FILEFLAGSMASK 0x3fL
++#ifdef _DEBUG
++ FILEFLAGS 0x1L
++#else
++ FILEFLAGS 0x0L
++#endif
++ FILEOS 0x4L
++ FILETYPE 0x2L
++ FILESUBTYPE 0x0L
++BEGIN
++ BLOCK "StringFileInfo"
++ BEGIN
++ BLOCK "040904e4"
++ BEGIN
++ VALUE "CompanyName", "TODO: <Company name>"
++ VALUE "FileDescription", "TODO: <File description>"
++ VALUE "FileVersion", "1.0.0.1"
++ VALUE "LegalCopyright", "TODO: (c) <Company name>. All rights reserved."
++ VALUE "InternalName", "res_format_test.dll"
++ VALUE "OriginalFilename", "res_format_test.dll"
++ VALUE "ProductName", "TODO: <Product name>"
++ VALUE "ProductVersion", "1.0.0.1"
++ END
++ END
++ BLOCK "VarFileInfo"
++ BEGIN
++ VALUE "Translation", 0x409, 1252
++ END
++END
++'''.strip()), 'VS_VERSION_INFO')
++ version.Parse()
++ self.failUnless(len(version.GetTextualIds()) == 1)
++ self.failUnless(len(version.GetCliques()) == 4)
++
++ transl = version.Translate('en')
++ self.failUnless(transl.strip() == version.GetText().strip())
++
++
++ def testRegressionDialogBox(self):
++ dialog = rc.Dialog(StringIO('''
++IDD_SIDEBAR_WEATHER_PANEL_PROPPAGE DIALOGEX 0, 0, 205, 157
++STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
++FONT 8, "MS Shell Dlg", 400, 0, 0x1
++BEGIN
++ EDITTEXT IDC_SIDEBAR_WEATHER_NEW_CITY,3,27,112,14,ES_AUTOHSCROLL
++ DEFPUSHBUTTON "Add Location",IDC_SIDEBAR_WEATHER_ADD,119,27,50,14
++ LISTBOX IDC_SIDEBAR_WEATHER_CURR_CITIES,3,48,127,89,
++ LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP
++ PUSHBUTTON "Move Up",IDC_SIDEBAR_WEATHER_MOVE_UP,134,104,50,14
++ PUSHBUTTON "Move Down",IDC_SIDEBAR_WEATHER_MOVE_DOWN,134,121,50,14
++ PUSHBUTTON "Remove",IDC_SIDEBAR_WEATHER_DELETE,134,48,50,14
++ LTEXT "To see current weather conditions and forecasts in the USA, enter the zip code (example: 94043) or city and state (example: Mountain View, CA).",
++ IDC_STATIC,3,0,199,25
++ CONTROL "Fahrenheit",IDC_SIDEBAR_WEATHER_FAHRENHEIT,"Button",
++ BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,3,144,51,10
++ CONTROL "Celsius",IDC_SIDEBAR_WEATHER_CELSIUS,"Button",
++ BS_AUTORADIOBUTTON,57,144,38,10
++END'''.strip()), 'IDD_SIDEBAR_WEATHER_PANEL_PROPPAGE')
++ dialog.Parse()
++ self.failUnless(len(dialog.GetTextualIds()) == 10)
++
++
++ def testRegressionDialogBox2(self):
++ dialog = rc.Dialog(StringIO('''
++IDD_SIDEBAR_EMAIL_PANEL_PROPPAGE DIALOG DISCARDABLE 0, 0, 264, 220
++STYLE WS_CHILD
++FONT 8, "MS Shell Dlg"
++BEGIN
++ GROUPBOX "Email Filters",IDC_STATIC,7,3,250,190
++ LTEXT "Click Add Filter to create the email filter.",IDC_STATIC,16,41,130,9
++ PUSHBUTTON "Add Filter...",IDC_SIDEBAR_EMAIL_ADD_FILTER,196,38,50,14
++ PUSHBUTTON "Remove",IDC_SIDEBAR_EMAIL_REMOVE,196,174,50,14
++ PUSHBUTTON "", IDC_SIDEBAR_EMAIL_HIDDEN, 200, 178, 5, 5, NOT WS_VISIBLE
++ LISTBOX IDC_SIDEBAR_EMAIL_LIST,16,60,230,108,
++ LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP
++ LTEXT "You can prevent certain emails from showing up in the sidebar with a filter.",
++ IDC_STATIC,16,18,234,18
++END'''.strip()), 'IDD_SIDEBAR_EMAIL_PANEL_PROPPAGE')
++ dialog.Parse()
++ self.failUnless('IDC_SIDEBAR_EMAIL_HIDDEN' in dialog.GetTextualIds())
++
++
++ def testRegressionMenuId(self):
++ menu = rc.Menu(StringIO('''
++IDR_HYPERMENU_FOLDER MENU
++BEGIN
++ POPUP "HyperFolder"
++ BEGIN
++ MENUITEM "Open Containing Folder", IDM_OPENFOLDER
++ END
++END'''.strip()), 'IDR_HYPERMENU_FOLDER')
++ menu.Parse()
++ self.failUnless(len(menu.GetTextualIds()) == 2)
++
++ def testRegressionNewlines(self):
++ menu = rc.Menu(StringIO('''
++IDR_HYPERMENU_FOLDER MENU
++BEGIN
++ POPUP "Hyper\\nFolder"
++ BEGIN
++ MENUITEM "Open Containing Folder", IDM_OPENFOLDER
++ END
++END'''.strip()), 'IDR_HYPERMENU_FOLDER')
++ menu.Parse()
++ transl = menu.Translate('en')
++ # Shouldn't find \\n (the \n shouldn't be changed to \\n)
++ self.failUnless(transl.find('\\\\n') == -1)
++
++ def testRegressionTabs(self):
++ menu = rc.Menu(StringIO('''
++IDR_HYPERMENU_FOLDER MENU
++BEGIN
++ POPUP "Hyper\\tFolder"
++ BEGIN
++ MENUITEM "Open Containing Folder", IDM_OPENFOLDER
++ END
++END'''.strip()), 'IDR_HYPERMENU_FOLDER')
++ menu.Parse()
++ transl = menu.Translate('en')
++ # Shouldn't find \\t (the \t shouldn't be changed to \\t)
++ self.failUnless(transl.find('\\\\t') == -1)
++
++ def testEscapeUnescape(self):
++ original = 'Hello "bingo"\n How\\are\\you\\n?'
++ escaped = rc.Section.Escape(original)
++ self.failUnless(escaped == 'Hello ""bingo""\\n How\\\\are\\\\you\\\\n?')
++ unescaped = rc.Section.UnEscape(escaped)
++ self.failUnless(unescaped == original)
++
++ def testRegressionPathsWithSlashN(self):
++ original = '..\\\\..\\\\trs\\\\res\\\\nav_first.gif'
++ unescaped = rc.Section.UnEscape(original)
++ self.failUnless(unescaped == '..\\..\\trs\\res\\nav_first.gif')
++
++ def testRegressionDialogItemsTextOnly(self):
++ dialog = rc.Dialog(StringIO('''IDD_OPTIONS_SEARCH DIALOGEX 0, 0, 280, 292
++STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP |
++ WS_DISABLED | WS_CAPTION | WS_SYSMENU
++CAPTION "Search"
++FONT 8, "MS Shell Dlg", 400, 0, 0x1
++BEGIN
++ GROUPBOX "Select search buttons and options",-1,7,5,266,262
++ CONTROL "",IDC_OPTIONS,"SysTreeView32",TVS_DISABLEDRAGDROP |
++ WS_BORDER | WS_TABSTOP | 0x800,16,19,248,218
++ LTEXT "Use Google site:",-1,26,248,52,8
++ COMBOBOX IDC_GOOGLE_HOME,87,245,177,256,CBS_DROPDOWNLIST |
++ WS_VSCROLL | WS_TABSTOP
++ PUSHBUTTON "Restore Defaults...",IDC_RESET,187,272,86,14
++END'''), 'IDD_OPTIONS_SEARCH')
++ dialog.Parse()
++ translateables = [c.GetMessage().GetRealContent()
++ for c in dialog.GetCliques()]
++ self.failUnless('Select search buttons and options' in translateables)
++ self.failUnless('Use Google site:' in translateables)
++
++ def testAccelerators(self):
++ acc = rc.Accelerators(StringIO('''\
++IDR_ACCELERATOR1 ACCELERATORS
++BEGIN
++ "^C", ID_ACCELERATOR32770, ASCII, NOINVERT
++ "^V", ID_ACCELERATOR32771, ASCII, NOINVERT
++ VK_INSERT, ID_ACCELERATOR32772, VIRTKEY, CONTROL, NOINVERT
++END
++'''), 'IDR_ACCELERATOR1')
++ acc.Parse()
++ self.failUnless(len(acc.GetTextualIds()) == 4)
++ self.failUnless(len(acc.GetCliques()) == 0)
++
++ transl = acc.Translate('en')
++ self.failUnless(transl.strip() == acc.GetText().strip())
++
++
++ def testRegressionEmptyString(self):
++ dlg = rc.Dialog(StringIO('''\
++IDD_CONFIRM_QUIT_GD_DLG DIALOGEX 0, 0, 267, 108
++STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP |
++ WS_CAPTION
++EXSTYLE WS_EX_TOPMOST
++CAPTION "Google Desktop"
++FONT 8, "MS Shell Dlg", 400, 0, 0x1
++BEGIN
++ DEFPUSHBUTTON "&Yes",IDYES,82,87,50,14
++ PUSHBUTTON "&No",IDNO,136,87,50,14
++ ICON 32514,IDC_STATIC,7,9,21,20
++ EDITTEXT IDC_TEXTBOX,34,7,231,60,ES_MULTILINE | ES_READONLY | NOT WS_BORDER
++ CONTROL "",
++ IDC_ENABLE_GD_AUTOSTART,"Button",BS_AUTOCHECKBOX |
++ WS_TABSTOP,33,70,231,10
++END'''), 'IDD_CONFIRM_QUIT_GD_DLG')
++ dlg.Parse()
++
++ def Check():
++ self.failUnless(transl.count('IDC_ENABLE_GD_AUTOSTART'))
++ self.failUnless(transl.count('END'))
++
++ transl = dlg.Translate('de', pseudo_if_not_available=True,
++ fallback_to_english=True)
++ Check()
++ transl = dlg.Translate('de', pseudo_if_not_available=True,
++ fallback_to_english=False)
++ Check()
++ transl = dlg.Translate('de', pseudo_if_not_available=False,
++ fallback_to_english=True)
++ Check()
++ transl = dlg.Translate('de', pseudo_if_not_available=False,
++ fallback_to_english=False)
++ Check()
++ transl = dlg.Translate('en', pseudo_if_not_available=True,
++ fallback_to_english=True)
++ Check()
++ transl = dlg.Translate('en', pseudo_if_not_available=True,
++ fallback_to_english=False)
++ Check()
++ transl = dlg.Translate('en', pseudo_if_not_available=False,
++ fallback_to_english=True)
++ Check()
++ transl = dlg.Translate('en', pseudo_if_not_available=False,
++ fallback_to_english=False)
++ Check()
++
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/gather/regexp.py b/tools/grit/grit/gather/regexp.py
+new file mode 100644
+index 0000000000..97ce2cfbf7
+--- /dev/null
++++ b/tools/grit/grit/gather/regexp.py
+@@ -0,0 +1,82 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''A baseclass for simple gatherers based on regular expressions.
++'''
++
++from __future__ import print_function
++
++from grit.gather import skeleton_gatherer
++
++
++class RegexpGatherer(skeleton_gatherer.SkeletonGatherer):
++ '''Common functionality of gatherers based on parsing using a single
++ regular expression.
++ '''
++
++ DescriptionMapping_ = {
++ 'CAPTION' : 'This is a caption for a dialog',
++ 'CHECKBOX' : 'This is a label for a checkbox',
++ 'CONTROL': 'This is the text on a control',
++ 'CTEXT': 'This is a label for a control',
++ 'DEFPUSHBUTTON': 'This is a button definition',
++ 'GROUPBOX': 'This is a label for a grouping',
++ 'ICON': 'This is a label for an icon',
++ 'LTEXT': 'This is the text for a label',
++ 'PUSHBUTTON': 'This is the text for a button',
+ }
+
-+ return parsed;
-+ }
++ # Contextualization elements. Used for adding additional information
++ # to the message bundle description string from RC files.
++ def AddDescriptionElement(self, string):
++ if string in self.DescriptionMapping_:
++ description = self.DescriptionMapping_[string]
++ else:
++ description = string
++ if self.single_message_:
++ self.single_message_.SetDescription(description)
++ else:
++ if (self.translatable_chunk_):
++ message = self.skeleton_[len(self.skeleton_) - 1].GetMessage()
++ message.SetDescription(description)
++
++ def _RegExpParse(self, regexp, text_to_parse):
++ '''An implementation of Parse() that can be used for resource sections that
++ can be parsed using a single multi-line regular expression.
++
++ All translateables must be in named groups that have names starting with
++ 'text'. All textual IDs must be in named groups that have names starting
++ with 'id'. All type definitions that can be included in the description
++ field for contextualization purposes should have a name that starts with
++ 'type'.
++
++ Args:
++ regexp: re.compile('...', re.MULTILINE)
++ text_to_parse:
++ '''
++ chunk_start = 0
++ for match in regexp.finditer(text_to_parse):
++ groups = match.groupdict()
++ keys = sorted(groups.keys())
++ self.translatable_chunk_ = False
++ for group in keys:
++ if group.startswith('id') and groups[group]:
++ self._AddTextualId(groups[group])
++ elif group.startswith('text') and groups[group]:
++ self._AddNontranslateableChunk(
++ text_to_parse[chunk_start : match.start(group)])
++ chunk_start = match.end(group) # Next chunk will start after the match
++ self._AddTranslateableChunk(groups[group])
++ elif group.startswith('type') and groups[group]:
++ # Add the description to the skeleton_ list. This works because
++ # we are using a sort set of keys, and because we assume that the
++ # group name used for descriptions (type) will come after the "text"
++ # group in alphabetical order. We also assume that there cannot be
++ # more than one description per regular expression match.
++ self.AddDescriptionElement(groups[group])
++
++ self._AddNontranslateableChunk(text_to_parse[chunk_start:])
++
++ if self.single_message_:
++ self.skeleton_.append(self.uberclique.MakeClique(self.single_message_))
+diff --git a/tools/grit/grit/gather/skeleton_gatherer.py b/tools/grit/grit/gather/skeleton_gatherer.py
+new file mode 100644
+index 0000000000..b11862b314
+--- /dev/null
++++ b/tools/grit/grit/gather/skeleton_gatherer.py
+@@ -0,0 +1,149 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
+
-+ private:
-+ bool check_refcounted_dtors_;
-+ bool check_virtuals_in_implementations_;
-+};
++'''A baseclass for simple gatherers that store their gathered resource in a
++list.
++'''
++
++from __future__ import print_function
++
++import six
++
++from grit.gather import interface
++from grit import clique
++from grit import exception
++from grit import tclib
++
++
++class SkeletonGatherer(interface.GathererBase):
++ '''Common functionality of gatherers that parse their input as a skeleton of
++ translatable and nontranslatable chunks.
++ '''
++
++ def __init__(self, *args, **kwargs):
++ super(SkeletonGatherer, self).__init__(*args, **kwargs)
++ # List of parts of the document. Translateable parts are
++ # clique.MessageClique objects, nontranslateable parts are plain strings.
++ # Translated messages are inserted back into the skeleton using the quoting
++ # rules defined by self.Escape()
++ self.skeleton_ = []
++ # A list of the names of IDs that need to be defined for this resource
++ # section to compile correctly.
++ self.ids_ = []
++ # True if Parse() has already been called.
++ self.have_parsed_ = False
++ # True if a translatable chunk has been added
++ self.translatable_chunk_ = False
++ # If not None, all parts of the document will be put into this single
++ # message; otherwise the normal skeleton approach is used.
++ self.single_message_ = None
++ # Number to use for the next placeholder name. Used only if single_message
++ # is not None
++ self.ph_counter_ = 1
++
++ def GetText(self):
++ '''Returns the original text of the section'''
++ return self.text_
++
++ def Escape(self, text):
++ '''Subclasses can override. Base impl is identity.
++ '''
++ return text
++
++ def UnEscape(self, text):
++ '''Subclasses can override. Base impl is identity.
++ '''
++ return text
++
++ def GetTextualIds(self):
++ '''Returns the list of textual IDs that need to be defined for this
++ resource section to compile correctly.'''
++ return self.ids_
++
++ def _AddTextualId(self, id):
++ self.ids_.append(id)
++
++ def GetCliques(self):
++ '''Returns the message cliques for each translateable message in the
++ resource section.'''
++ return [x for x in self.skeleton_ if isinstance(x, clique.MessageClique)]
++
++ def Translate(self, lang, pseudo_if_not_available=True,
++ skeleton_gatherer=None, fallback_to_english=False):
++ if len(self.skeleton_) == 0:
++ raise exception.NotReady()
++ if skeleton_gatherer:
++ assert len(skeleton_gatherer.skeleton_) == len(self.skeleton_)
++
++ out = []
++ for ix in range(len(self.skeleton_)):
++ if isinstance(self.skeleton_[ix], six.string_types):
++ if skeleton_gatherer:
++ # Make sure the skeleton is like the original
++ assert(isinstance(skeleton_gatherer.skeleton_[ix], six.string_types))
++ out.append(skeleton_gatherer.skeleton_[ix])
++ else:
++ out.append(self.skeleton_[ix])
++ else:
++ if skeleton_gatherer: # Make sure the skeleton is like the original
++ assert(not isinstance(skeleton_gatherer.skeleton_[ix],
++ six.string_types))
++ msg = self.skeleton_[ix].MessageForLanguage(lang,
++ pseudo_if_not_available,
++ fallback_to_english)
++
++ def MyEscape(text):
++ return self.Escape(text)
++ text = msg.GetRealContent(escaping_function=MyEscape)
++ out.append(text)
++ return ''.join(out)
++
++ def Parse(self):
++ '''Parses the section. Implemented by subclasses. Idempotent.'''
++ raise NotImplementedError()
++
++ def _AddNontranslateableChunk(self, chunk):
++ '''Adds a nontranslateable chunk.'''
++ if self.single_message_:
++ ph = tclib.Placeholder('XX%02dXX' % self.ph_counter_, chunk, chunk)
++ self.ph_counter_ += 1
++ self.single_message_.AppendPlaceholder(ph)
++ else:
++ self.skeleton_.append(chunk)
++
++ def _AddTranslateableChunk(self, chunk):
++ '''Adds a translateable chunk. It will be unescaped before being added.'''
++ # We don't want empty messages since they are redundant and the TC
++ # doesn't allow them.
++ if chunk == '':
++ return
++
++ unescaped_text = self.UnEscape(chunk)
++ if self.single_message_:
++ self.single_message_.AppendText(unescaped_text)
++ else:
++ self.skeleton_.append(self.uberclique.MakeClique(
++ tclib.Message(text=unescaped_text)))
++ self.translatable_chunk_ = True
++
++ def SubstituteMessages(self, substituter):
++ '''Applies substitutions to all messages in the tree.
++
++ Goes through the skeleton and finds all MessageCliques.
++
++ Args:
++ substituter: a grit.util.Substituter object.
++ '''
++ if self.single_message_:
++ self.single_message_ = substituter.SubstituteMessage(self.single_message_)
++ new_skel = []
++ for chunk in self.skeleton_:
++ if isinstance(chunk, clique.MessageClique):
++ old_message = chunk.GetMessage()
++ new_message = substituter.SubstituteMessage(old_message)
++ if new_message is not old_message:
++ new_skel.append(self.uberclique.MakeClique(new_message))
++ continue
++ new_skel.append(chunk)
++ self.skeleton_ = new_skel
+diff --git a/tools/grit/grit/gather/tr_html.py b/tools/grit/grit/gather/tr_html.py
+new file mode 100644
+index 0000000000..60a9bfaf4e
+--- /dev/null
++++ b/tools/grit/grit/gather/tr_html.py
+@@ -0,0 +1,743 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''A gatherer for the TotalRecall brand of HTML templates with replaceable
++portions. We wanted to reuse extern.tclib.api.handlers.html.TCHTMLParser
++but this proved impossible due to the fact that the TotalRecall HTML templates
++are in general quite far from parseable HTML and the TCHTMLParser derives
++
++from HTMLParser.HTMLParser which requires relatively well-formed HTML. Some
++examples of "HTML" from the TotalRecall HTML templates that wouldn't be
++parseable include things like:
++
++ <a [PARAMS]>blabla</a> (not parseable because attributes are invalid)
++
++ <table><tr><td>[LOTSOFSTUFF]</tr></table> (not parseable because closing
++ </td> is in the HTML [LOTSOFSTUFF]
++ is replaced by)
++
++The other problem with using general parsers (such as TCHTMLParser) is that
++we want to make sure we output the TotalRecall template with as little changes
++as possible in terms of whitespace characters, layout etc. With any parser
++that generates a parse tree, and generates output by dumping the parse tree,
++we would always have little inconsistencies which could cause bugs (the
++TotalRecall template stuff is quite brittle and can break if e.g. a tab
++character is replaced with spaces).
++
++The solution, which may be applicable to some other HTML-like template
++languages floating around Google, is to create a parser with a simple state
++machine that keeps track of what kind of tag it's inside, and whether it's in
++a translateable section or not. Translateable sections are:
++
++a) text (including [BINGO] replaceables) inside of tags that
++ can contain translateable text (which is all tags except
++ for a few)
++
++b) text inside of an 'alt' attribute in an <image> element, or
++ the 'value' attribute of a <submit>, <button> or <text>
++ element.
++
++The parser does not build up a parse tree but rather a "skeleton" which
++is a list of nontranslateable strings intermingled with grit.clique.MessageClique
++objects. This simplifies the parser considerably compared to a regular HTML
++parser. To output a translated document, each item in the skeleton is
++printed out, with the relevant Translation from each MessageCliques being used
++for the requested language.
++
++This implementation borrows some code, constants and ideas from
++extern.tclib.api.handlers.html.TCHTMLParser.
++'''
++
++from __future__ import print_function
++
++import re
++
++import six
++
++from grit import clique
++from grit import exception
++from grit import lazy_re
++from grit import util
++from grit import tclib
++
++from grit.gather import interface
++
++
++# HTML tags which break (separate) chunks.
++_BLOCK_TAGS = ['script', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'br',
++ 'body', 'style', 'head', 'title', 'table', 'tr', 'td', 'th',
++ 'ul', 'ol', 'dl', 'nl', 'li', 'div', 'object', 'center',
++ 'html', 'link', 'form', 'select', 'textarea',
++ 'button', 'option', 'map', 'area', 'blockquote', 'pre',
++ 'meta', 'xmp', 'noscript', 'label', 'tbody', 'thead',
++ 'script', 'style', 'pre', 'iframe', 'img', 'input', 'nowrap',
++ 'fieldset', 'legend']
++
++# HTML tags which may appear within a chunk.
++_INLINE_TAGS = ['b', 'i', 'u', 'tt', 'code', 'font', 'a', 'span', 'small',
++ 'key', 'nobr', 'url', 'em', 's', 'sup', 'strike',
++ 'strong']
++
++# HTML tags within which linebreaks are significant.
++_PREFORMATTED_TAGS = ['textarea', 'xmp', 'pre']
++
++# An array mapping some of the inline HTML tags to more meaningful
++# names for those tags. This will be used when generating placeholders
++# representing these tags.
++_HTML_PLACEHOLDER_NAMES = { 'a' : 'link', 'br' : 'break', 'b' : 'bold',
++ 'i' : 'italic', 'li' : 'item', 'ol' : 'ordered_list', 'p' : 'paragraph',
++ 'ul' : 'unordered_list', 'img' : 'image', 'em' : 'emphasis' }
++
++# We append each of these characters in sequence to distinguish between
++# different placeholders with basically the same name (e.g. BOLD1, BOLD2).
++# Keep in mind that a placeholder name must not be a substring of any other
++# placeholder name in the same message, so we can't simply count (BOLD_1
++# would be a substring of BOLD_10).
++_SUFFIXES = '123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
++
++# Matches whitespace in an HTML document. Also matches HTML comments, which are
++# treated as whitespace.
++_WHITESPACE = lazy_re.compile(r'(\s|&nbsp;|\\n|\\r|<!--\s*desc\s*=.*?-->)+',
++ re.DOTALL)
++
++# Matches whitespace sequences which can be folded into a single whitespace
++# character. This matches single characters so that non-spaces are replaced
++# with spaces.
++_FOLD_WHITESPACE = lazy_re.compile(r'\s+')
++
++# Finds a non-whitespace character
++_NON_WHITESPACE = lazy_re.compile(r'\S')
++
++# Matches two or more &nbsp; in a row (a single &nbsp is not changed into
++# placeholders because different languages require different numbers of spaces
++# and placeholders must match exactly; more than one is probably a "special"
++# whitespace sequence and should be turned into a placeholder).
++_NBSP = lazy_re.compile(r'&nbsp;(&nbsp;)+')
++
++# Matches nontranslateable chunks of the document
++_NONTRANSLATEABLES = lazy_re.compile(r'''
++ <\s*script.+?<\s*/\s*script\s*>
++ |
++ <\s*style.+?<\s*/\s*style\s*>
++ |
++ <!--.+?-->
++ |
++ <\?IMPORT\s.+?> # import tag
++ |
++ <\s*[a-zA-Z_]+:.+?> # custom tag (open)
++ |
++ <\s*/\s*[a-zA-Z_]+:.+?> # custom tag (close)
++ |
++ <!\s*[A-Z]+\s*([^>]+|"[^"]+"|'[^']+')*?>
++ ''', re.MULTILINE | re.DOTALL | re.VERBOSE | re.IGNORECASE)
++
++# Matches a tag and its attributes
++_ELEMENT = lazy_re.compile(r'''
++ # Optional closing /, element name
++ <\s*(?P<closing>/)?\s*(?P<element>[a-zA-Z0-9]+)\s*
++ # Attributes and/or replaceables inside the tag, if any
++ (?P<atts>(
++ \s*([a-zA-Z_][-:.a-zA-Z_0-9]*) # Attribute name
++ (\s*=\s*(\'[^\']*\'|"[^"]*"|[-a-zA-Z0-9./,:;+*%?!&$\(\)_#=~\'"@]*))?
++ |
++ \s*\[(\$?\~)?([A-Z0-9-_]+?)(\~\$?)?\]
++ )*)
++ \s*(?P<empty>/)?\s*> # Optional empty-tag closing /, and tag close
++ ''',
++ re.MULTILINE | re.DOTALL | re.VERBOSE)
++
++# Matches elements that may have translateable attributes. The value of these
++# special attributes is given by group 'value1' or 'value2'. Note that this
++# regexp demands that the attribute value be quoted; this is necessary because
++# the non-tree-building nature of the parser means we don't know when we're
++# writing out attributes, so we wouldn't know to escape spaces.
++_SPECIAL_ELEMENT = lazy_re.compile(r'''
++ <\s*(
++ input[^>]+?value\s*=\s*(\'(?P<value3>[^\']*)\'|"(?P<value4>[^"]*)")
++ [^>]+type\s*=\s*"?'?(button|reset|text|submit)'?"?
++ |
++ (
++ table[^>]+?title\s*=
++ |
++ img[^>]+?alt\s*=
++ |
++ input[^>]+?type\s*=\s*"?'?(button|reset|text|submit)'?"?[^>]+?value\s*=
++ )
++ \s*(\'(?P<value1>[^\']*)\'|"(?P<value2>[^"]*)")
++ )[^>]*?>
++ ''', re.MULTILINE | re.DOTALL | re.VERBOSE | re.IGNORECASE)
++
++# Matches stuff that is translateable if it occurs in the right context
++# (between tags). This includes all characters and character entities.
++# Note that this also matches &nbsp; which needs to be handled as whitespace
++# before this regexp is applied.
++_CHARACTERS = lazy_re.compile(r'''
++ (
++ \w
++ |
++ [\!\@\#\$\%\^\*\(\)\-\=\_\+\[\]\{\}\\\|\;\:\'\"\,\.\/\?\`\~]
++ |
++ &(\#[0-9]+|\#x[0-9a-fA-F]+|[A-Za-z0-9]+);
++ )+
++ ''', re.MULTILINE | re.DOTALL | re.VERBOSE)
++
++# Matches Total Recall's "replaceable" tags, which are just any text
++# in capitals enclosed by delimiters like [] or [~~] or [$~~$] (e.g. [HELLO],
++# [~HELLO~] and [$~HELLO~$]).
++_REPLACEABLE = lazy_re.compile(r'\[(\$?\~)?(?P<name>[A-Z0-9-_]+?)(\~\$?)?\]',
++ re.MULTILINE)
++
++
++# Matches the silly [!]-prefixed "header" that is used in some TotalRecall
++# templates.
++_SILLY_HEADER = lazy_re.compile(r'\[!\]\ntitle\t(?P<title>[^\n]+?)\n.+?\n\n',
++ re.MULTILINE | re.DOTALL)
++
++
++# Matches a comment that provides a description for the message it occurs in.
++_DESCRIPTION_COMMENT = lazy_re.compile(
++ r'<!--\s*desc\s*=\s*(?P<description>.+?)\s*-->', re.DOTALL)
++
++# Matches a comment which is used to break apart multiple messages.
++_MESSAGE_BREAK_COMMENT = lazy_re.compile(r'<!--\s*message-break\s*-->',
++ re.DOTALL)
++
++# Matches a comment which is used to prevent block tags from splitting a message
++_MESSAGE_NO_BREAK_COMMENT = re.compile(r'<!--\s*message-no-break\s*-->',
++ re.DOTALL)
++
++
++_DEBUG = 0
++def _DebugPrint(text):
++ if _DEBUG:
++ print(text.encode('utf-8'))
++
++
++class HtmlChunks(object):
++ '''A parser that knows how to break an HTML-like document into a list of
++ chunks, where each chunk is either translateable or non-translateable.
++ The chunks are unmodified sections of the original document, so concatenating
++ the text of all chunks would result in the original document.'''
++
++ def InTranslateable(self):
++ return self.last_translateable != -1
++
++ def Rest(self):
++ return self.text_[self.current:]
++
++ def StartTranslateable(self):
++ assert not self.InTranslateable()
++ if self.current != 0:
++ # Append a nontranslateable chunk
++ chunk_text = self.text_[self.chunk_start : self.last_nontranslateable + 1]
++ # Needed in the case where document starts with a translateable.
++ if len(chunk_text) > 0:
++ self.AddChunk(False, chunk_text)
++ self.chunk_start = self.last_nontranslateable + 1
++ self.last_translateable = self.current
++ self.last_nontranslateable = -1
++
++ def EndTranslateable(self):
++ assert self.InTranslateable()
++ # Append a translateable chunk
++ self.AddChunk(True,
++ self.text_[self.chunk_start : self.last_translateable + 1])
++ self.chunk_start = self.last_translateable + 1
++ self.last_translateable = -1
++ self.last_nontranslateable = self.current
++
++ def AdvancePast(self, match):
++ self.current += match.end()
++
++ def AddChunk(self, translateable, text):
++ '''Adds a chunk to self, removing linebreaks and duplicate whitespace
++ if appropriate.
++ '''
++ m = _DESCRIPTION_COMMENT.search(text)
++ if m:
++ self.last_description = m.group('description')
++ # Remove the description from the output text
++ text = _DESCRIPTION_COMMENT.sub('', text)
++
++ m = _MESSAGE_BREAK_COMMENT.search(text)
++ if m:
++ # Remove the coment from the output text. It should already effectively
++ # break apart messages.
++ text = _MESSAGE_BREAK_COMMENT.sub('', text)
++
++ if translateable and not self.last_element_ in _PREFORMATTED_TAGS:
++ if self.fold_whitespace_:
++ # Fold whitespace sequences if appropriate. This is optional because it
++ # alters the output strings.
++ text = _FOLD_WHITESPACE.sub(' ', text)
++ else:
++ text = text.replace('\n', ' ')
++ text = text.replace('\r', ' ')
++ # This whitespace folding doesn't work in all cases, thus the
++ # fold_whitespace flag to support backwards compatibility.
++ text = text.replace(' ', ' ')
++ text = text.replace(' ', ' ')
++
++ if translateable:
++ description = self.last_description
++ self.last_description = ''
++ else:
++ description = ''
++
++ if text != '':
++ self.chunks_.append((translateable, text, description))
++
++ def Parse(self, text, fold_whitespace):
++ '''Parses self.text_ into an intermediate format stored in self.chunks_
++ which is translateable and nontranslateable chunks. Also returns
++ self.chunks_
++
++ Args:
++ text: The HTML for parsing.
++ fold_whitespace: Whether whitespace sequences should be folded into a
++ single space.
++
++ Return:
++ [chunk1, chunk2, chunk3, ...] (instances of class Chunk)
++ '''
++ #
++ # Chunker state
++ #
++
++ self.text_ = text
++ self.fold_whitespace_ = fold_whitespace
++
++ # A list of tuples (is_translateable, text) which represents the document
++ # after chunking.
++ self.chunks_ = []
++
++ # Start index of the last chunk, whether translateable or not
++ self.chunk_start = 0
++
++ # Index of the last for-sure translateable character if we are parsing
++ # a translateable chunk, -1 to indicate we are not in a translateable chunk.
++ # This is needed so that we don't include trailing whitespace in the
++ # translateable chunk (whitespace is neutral).
++ self.last_translateable = -1
++
++ # Index of the last for-sure nontranslateable character if we are parsing
++ # a nontranslateable chunk, -1 if we are not in a nontranslateable chunk.
++ # This is needed to make sure we can group e.g. "<b>Hello</b> there"
++ # together instead of just "Hello</b> there" which would be much worse
++ # for translation.
++ self.last_nontranslateable = -1
++
++ # Index of the character we're currently looking at.
++ self.current = 0
++
++ # The name of the last block element parsed.
++ self.last_element_ = ''
++
++ # The last explicit description we found.
++ self.last_description = ''
++
++ # Whether no-break was the last chunk seen
++ self.last_nobreak = False
++
++ while self.current < len(self.text_):
++ _DebugPrint('REST: %s' % self.text_[self.current:self.current+60])
++
++ m = _MESSAGE_NO_BREAK_COMMENT.match(self.Rest())
++ if m:
++ self.AdvancePast(m)
++ self.last_nobreak = True
++ continue
++
++ # Try to match whitespace
++ m = _WHITESPACE.match(self.Rest())
++ if m:
++ # Whitespace is neutral, it just advances 'current' and does not switch
++ # between translateable/nontranslateable. If we are in a
++ # nontranslateable section that extends to the current point, we extend
++ # it to include the whitespace. If we are in a translateable section,
++ # we do not extend it until we find
++ # more translateable parts, because we never want a translateable chunk
++ # to end with whitespace.
++ if (not self.InTranslateable() and
++ self.last_nontranslateable == self.current - 1):
++ self.last_nontranslateable = self.current + m.end() - 1
++ self.AdvancePast(m)
++ continue
++
++ # Then we try to match nontranslateables
++ m = _NONTRANSLATEABLES.match(self.Rest())
++ if m:
++ if self.InTranslateable():
++ self.EndTranslateable()
++ self.last_nontranslateable = self.current + m.end() - 1
++ self.AdvancePast(m)
++ continue
++
++ # Now match all other HTML element tags (opening, closing, or empty, we
++ # don't care).
++ m = _ELEMENT.match(self.Rest())
++ if m:
++ element_name = m.group('element').lower()
++ if element_name in _BLOCK_TAGS:
++ self.last_element_ = element_name
++ if self.InTranslateable():
++ if self.last_nobreak:
++ self.last_nobreak = False
++ else:
++ self.EndTranslateable()
++
++ # Check for "special" elements, i.e. ones that have a translateable
++ # attribute, and handle them correctly. Note that all of the
++ # "special" elements are block tags, so no need to check for this
++ # if the tag is not a block tag.
++ sm = _SPECIAL_ELEMENT.match(self.Rest())
++ if sm:
++ # Get the appropriate group name
++ for group in sm.groupdict():
++ if sm.groupdict()[group]:
++ break
++
++ # First make a nontranslateable chunk up to and including the
++ # quote before the translateable attribute value
++ self.AddChunk(False, self.text_[
++ self.chunk_start : self.current + sm.start(group)])
++ # Then a translateable for the translateable bit
++ self.AddChunk(True, self.Rest()[sm.start(group) : sm.end(group)])
++ # Finally correct the data invariant for the parser
++ self.chunk_start = self.current + sm.end(group)
++
++ self.last_nontranslateable = self.current + m.end() - 1
++ elif self.InTranslateable():
++ # We're in a translateable and the tag is an inline tag, so we
++ # need to include it in the translateable.
++ self.last_translateable = self.current + m.end() - 1
++ self.AdvancePast(m)
++ continue
++
++ # Anything else we find must be translateable, so we advance one character
++ # at a time until one of the above matches.
++ if not self.InTranslateable():
++ self.StartTranslateable()
++ else:
++ self.last_translateable = self.current
++ self.current += 1
++
++ # Close the final chunk
++ if self.InTranslateable():
++ self.AddChunk(True, self.text_[self.chunk_start : ])
++ else:
++ self.AddChunk(False, self.text_[self.chunk_start : ])
++
++ return self.chunks_
++
++
++def HtmlToMessage(html, include_block_tags=False, description=''):
++ '''Takes a bit of HTML, which must contain only "inline" HTML elements,
++ and changes it into a tclib.Message. This involves escaping any entities and
++ replacing any HTML code with placeholders.
++
++ If include_block_tags is true, no error will be given if block tags (e.g.
++ <p> or <br>) are included in the HTML.
++
++ Args:
++ html: 'Hello <b>[USERNAME]</b>, how&nbsp;<i>are</i> you?'
++ include_block_tags: False
++
++ Return:
++ tclib.Message('Hello START_BOLD1USERNAMEEND_BOLD, '
++ 'howNBSPSTART_ITALICareEND_ITALIC you?',
++ [ Placeholder('START_BOLD', '<b>', ''),
++ Placeholder('USERNAME', '[USERNAME]', ''),
++ Placeholder('END_BOLD', '</b>', ''),
++ Placeholder('START_ITALIC', '<i>', ''),
++ Placeholder('END_ITALIC', '</i>', ''), ])
++ '''
++ # Approach is:
++ # - first placeholderize, finding <elements>, [REPLACEABLES] and &nbsp;
++ # - then escape all character entities in text in-between placeholders
++
++ parts = [] # List of strings (for text chunks) and tuples (ID, original)
++ # for placeholders
++
++ count_names = {} # Map of base names to number of times used
++ end_names = {} # Map of base names to stack of end tags (for correct nesting)
++
++ def MakeNameClosure(base, type = ''):
++ '''Returns a closure that can be called once all names have been allocated
++ to return the final name of the placeholder. This allows us to minimally
++ number placeholders for non-overlap.
++
++ Also ensures that END_XXX_Y placeholders have the same Y as the
++ corresponding BEGIN_XXX_Y placeholder when we have nested tags of the same
++ type.
++
++ Args:
++ base: 'phname'
++ type: '' | 'begin' | 'end'
++
++ Return:
++ Closure()
++ '''
++ name = base.upper()
++ if type != '':
++ name = ('%s_%s' % (type, base)).upper()
++
++ count_names.setdefault(name, 0)
++ count_names[name] += 1
++
++ def MakeFinalName(name_ = name, index = count_names[name] - 1):
++ if type.lower() == 'end' and end_names.get(base):
++ return end_names[base].pop(-1) # For correct nesting
++ if count_names[name_] != 1:
++ name_ = '%s_%s' % (name_, _SUFFIXES[index])
++ # We need to use a stack to ensure that the end-tag suffixes match
++ # the begin-tag suffixes. Only needed when more than one tag of the
++ # same type.
++ if type == 'begin':
++ end_name = ('END_%s_%s' % (base, _SUFFIXES[index])).upper()
++ if base in end_names:
++ end_names[base].append(end_name)
++ else:
++ end_names[base] = [end_name]
++
++ return name_
++
++ return MakeFinalName
++
++ current = 0
++ last_nobreak = False
++
++ while current < len(html):
++ m = _MESSAGE_NO_BREAK_COMMENT.match(html[current:])
++ if m:
++ last_nobreak = True
++ current += m.end()
++ continue
++
++ m = _NBSP.match(html[current:])
++ if m:
++ parts.append((MakeNameClosure('SPACE'), m.group()))
++ current += m.end()
++ continue
++
++ m = _REPLACEABLE.match(html[current:])
++ if m:
++ # Replaceables allow - but placeholders don't, so replace - with _
++ ph_name = MakeNameClosure('X_%s_X' % m.group('name').replace('-', '_'))
++ parts.append((ph_name, m.group()))
++ current += m.end()
++ continue
++
++ m = _SPECIAL_ELEMENT.match(html[current:])
++ if m:
++ if not include_block_tags:
++ if last_nobreak:
++ last_nobreak = False
++ else:
++ raise exception.BlockTagInTranslateableChunk(html)
++ element_name = 'block' # for simplification
++ # Get the appropriate group name
++ for group in m.groupdict():
++ if m.groupdict()[group]:
++ break
++ parts.append((MakeNameClosure(element_name, 'begin'),
++ html[current : current + m.start(group)]))
++ parts.append(m.group(group))
++ parts.append((MakeNameClosure(element_name, 'end'),
++ html[current + m.end(group) : current + m.end()]))
++ current += m.end()
++ continue
++
++ m = _ELEMENT.match(html[current:])
++ if m:
++ element_name = m.group('element').lower()
++ if not include_block_tags and not element_name in _INLINE_TAGS:
++ if last_nobreak:
++ last_nobreak = False
++ else:
++ raise exception.BlockTagInTranslateableChunk(html[current:])
++ if element_name in _HTML_PLACEHOLDER_NAMES: # use meaningful names
++ element_name = _HTML_PLACEHOLDER_NAMES[element_name]
++
++ # Make a name for the placeholder
++ type = ''
++ if not m.group('empty'):
++ if m.group('closing'):
++ type = 'end'
++ else:
++ type = 'begin'
++ parts.append((MakeNameClosure(element_name, type), m.group()))
++ current += m.end()
++ continue
++
++ if len(parts) and isinstance(parts[-1], six.string_types):
++ parts[-1] += html[current]
++ else:
++ parts.append(html[current])
++ current += 1
++
++ msg_text = ''
++ placeholders = []
++ for part in parts:
++ if isinstance(part, tuple):
++ final_name = part[0]()
++ original = part[1]
++ msg_text += final_name
++ placeholders.append(tclib.Placeholder(final_name, original, '(HTML code)'))
++ else:
++ msg_text += part
++
++ msg = tclib.Message(text=msg_text, placeholders=placeholders,
++ description=description)
++ content = msg.GetContent()
++ for ix in range(len(content)):
++ if isinstance(content[ix], six.string_types):
++ content[ix] = util.UnescapeHtml(content[ix], replace_nbsp=False)
++
++ return msg
++
++
++class TrHtml(interface.GathererBase):
++ '''Represents a document or message in the template format used by
++ Total Recall for HTML documents.'''
++
++ def __init__(self, *args, **kwargs):
++ super(TrHtml, self).__init__(*args, **kwargs)
++ self.have_parsed_ = False
++ self.skeleton_ = [] # list of strings and MessageClique objects
++ self.fold_whitespace_ = False
++
++ def SetAttributes(self, attrs):
++ '''Sets node attributes used by the gatherer.
++
++ This checks the fold_whitespace attribute.
++
++ Args:
++ attrs: The mapping of node attributes.
++ '''
++ self.fold_whitespace_ = ('fold_whitespace' in attrs and
++ attrs['fold_whitespace'] == 'true')
++
++ def GetText(self):
++ '''Returns the original text of the HTML document'''
++ return self.text_
++
++ def GetTextualIds(self):
++ return [self.extkey]
++
++ def GetCliques(self):
++ '''Returns the message cliques for each translateable message in the
++ document.'''
++ return [x for x in self.skeleton_ if isinstance(x, clique.MessageClique)]
++
++ def Translate(self, lang, pseudo_if_not_available=True,
++ skeleton_gatherer=None, fallback_to_english=False):
++ '''Returns this document with translateable messages filled with
++ the translation for language 'lang'.
++
++ Args:
++ lang: 'en'
++ pseudo_if_not_available: True
++
++ Return:
++ 'ID_THIS_SECTION TYPE\n...BEGIN\n "Translated message"\n......\nEND
++
++ Raises:
++ grit.exception.NotReady() if used before Parse() has been successfully
++ called.
++ grit.exception.NoSuchTranslation() if 'pseudo_if_not_available' is false
++ and there is no translation for the requested language.
++ '''
++ if len(self.skeleton_) == 0:
++ raise exception.NotReady()
++
++ # TODO(joi) Implement support for skeleton gatherers here.
++
++ out = []
++ for item in self.skeleton_:
++ if isinstance(item, six.string_types):
++ out.append(item)
++ else:
++ msg = item.MessageForLanguage(lang,
++ pseudo_if_not_available,
++ fallback_to_english)
++ for content in msg.GetContent():
++ if isinstance(content, tclib.Placeholder):
++ out.append(content.GetOriginal())
++ else:
++ # We escape " characters to increase the chance that attributes
++ # will be properly escaped.
++ out.append(util.EscapeHtml(content, True))
++
++ return ''.join(out)
++
++ def Parse(self):
++ if self.have_parsed_:
++ return
++ self.have_parsed_ = True
++
++ text = self._LoadInputFile()
++
++ # Ignore the BOM character if the document starts with one.
++ if text.startswith(u'\ufeff'):
++ text = text[1:]
++
++ self.text_ = text
++
++ # Parsing is done in two phases: First, we break the document into
++ # translateable and nontranslateable chunks. Second, we run through each
++ # translateable chunk and insert placeholders for any HTML elements,
++ # unescape escaped characters, etc.
++
++ # First handle the silly little [!]-prefixed header because it's not
++ # handled by our HTML parsers.
++ m = _SILLY_HEADER.match(text)
++ if m:
++ self.skeleton_.append(text[:m.start('title')])
++ self.skeleton_.append(self.uberclique.MakeClique(
++ tclib.Message(text=text[m.start('title'):m.end('title')])))
++ self.skeleton_.append(text[m.end('title') : m.end()])
++ text = text[m.end():]
++
++ chunks = HtmlChunks().Parse(text, self.fold_whitespace_)
++
++ for chunk in chunks:
++ if chunk[0]: # Chunk is translateable
++ self.skeleton_.append(self.uberclique.MakeClique(
++ HtmlToMessage(chunk[1], description=chunk[2])))
++ else:
++ self.skeleton_.append(chunk[1])
++
++ # Go through the skeleton and change any messages that consist solely of
++ # placeholders and whitespace into nontranslateable strings.
++ for ix in range(len(self.skeleton_)):
++ got_text = False
++ if isinstance(self.skeleton_[ix], clique.MessageClique):
++ msg = self.skeleton_[ix].GetMessage()
++ for item in msg.GetContent():
++ if (isinstance(item, six.string_types)
++ and _NON_WHITESPACE.search(item) and item != '&nbsp;'):
++ got_text = True
++ break
++ if not got_text:
++ self.skeleton_[ix] = msg.GetRealContent()
++
++ def SubstituteMessages(self, substituter):
++ '''Applies substitutions to all messages in the tree.
++
++ Goes through the skeleton and finds all MessageCliques.
++
++ Args:
++ substituter: a grit.util.Substituter object.
++ '''
++ new_skel = []
++ for chunk in self.skeleton_:
++ if isinstance(chunk, clique.MessageClique):
++ old_message = chunk.GetMessage()
++ new_message = substituter.SubstituteMessage(old_message)
++ if new_message is not old_message:
++ new_skel.append(self.uberclique.MakeClique(new_message))
++ continue
++ new_skel.append(chunk)
++ self.skeleton_ = new_skel
+diff --git a/tools/grit/grit/gather/tr_html_unittest.py b/tools/grit/grit/gather/tr_html_unittest.py
+new file mode 100644
+index 0000000000..1194853d9a
+--- /dev/null
++++ b/tools/grit/grit/gather/tr_html_unittest.py
+@@ -0,0 +1,524 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
+
-+} // namespace
++'''Unit tests for grit.gather.tr_html'''
+
-+static FrontendPluginRegistry::Add<FindBadConstructsAction>
-+X("find-bad-constructs", "Finds bad C++ constructs");
-diff --git a/tools/clang/plugins/Makefile b/tools/clang/plugins/Makefile
++from __future__ import print_function
++
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++import unittest
++
++import six
++from six import StringIO
++
++from grit.gather import tr_html
++from grit import clique
++from grit import util
++
++
++class ParserUnittest(unittest.TestCase):
++ def testChunkingWithoutFoldWhitespace(self):
++ self.VerifyChunking(False)
++
++ def testChunkingWithFoldWhitespace(self):
++ self.VerifyChunking(True)
++
++ def VerifyChunking(self, fold_whitespace):
++ """Use a single function to run all chunking testing.
++
++ This makes it easier to run chunking with fold_whitespace both on and off,
++ to make sure the outputs are the same.
++
++ Args:
++ fold_whitespace: Whether whitespace sequences should be folded into a
++ single space.
++ """
++ self.VerifyChunkingBasic(fold_whitespace)
++ self.VerifyChunkingDescriptions(fold_whitespace)
++ self.VerifyChunkingReplaceables(fold_whitespace)
++ self.VerifyChunkingLineBreaks(fold_whitespace)
++ self.VerifyChunkingMessageBreak(fold_whitespace)
++ self.VerifyChunkingMessageNoBreak(fold_whitespace)
++
++ def VerifyChunkingBasic(self, fold_whitespace):
++ p = tr_html.HtmlChunks()
++ chunks = p.Parse('<p>Hello <b>dear</b> how <i>are</i>you?<p>Fine!',
++ fold_whitespace)
++ self.failUnlessEqual(chunks, [
++ (False, '<p>', ''), (True, 'Hello <b>dear</b> how <i>are</i>you?', ''),
++ (False, '<p>', ''), (True, 'Fine!', '')])
++
++ chunks = p.Parse('<p> Hello <b>dear</b> how <i>are</i>you? <p>Fine!',
++ fold_whitespace)
++ self.failUnlessEqual(chunks, [
++ (False, '<p> ', ''), (True, 'Hello <b>dear</b> how <i>are</i>you?', ''),
++ (False, ' <p>', ''), (True, 'Fine!', '')])
++
++ chunks = p.Parse('<p> Hello <b>dear how <i>are you? <p> Fine!',
++ fold_whitespace)
++ self.failUnlessEqual(chunks, [
++ (False, '<p> ', ''), (True, 'Hello <b>dear how <i>are you?', ''),
++ (False, ' <p> ', ''), (True, 'Fine!', '')])
++
++ # Ensure translateable sections that start with inline tags contain
++ # the starting inline tag.
++ chunks = p.Parse('<b>Hello!</b> how are you?<p><i>I am fine.</i>',
++ fold_whitespace)
++ self.failUnlessEqual(chunks, [
++ (True, '<b>Hello!</b> how are you?', ''), (False, '<p>', ''),
++ (True, '<i>I am fine.</i>', '')])
++
++ # Ensure translateable sections that end with inline tags contain
++ # the ending inline tag.
++ chunks = p.Parse("Hello! How are <b>you?</b><p><i>I'm fine!</i>",
++ fold_whitespace)
++ self.failUnlessEqual(chunks, [
++ (True, 'Hello! How are <b>you?</b>', ''), (False, '<p>', ''),
++ (True, "<i>I'm fine!</i>", '')])
++
++ def VerifyChunkingDescriptions(self, fold_whitespace):
++ p = tr_html.HtmlChunks()
++ # Check capitals and explicit descriptions
++ chunks = p.Parse('<!-- desc=bingo! --><B>Hello!</B> how are you?<P>'
++ '<I>I am fine.</I>', fold_whitespace)
++ self.failUnlessEqual(chunks, [
++ (True, '<B>Hello!</B> how are you?', 'bingo!'), (False, '<P>', ''),
++ (True, '<I>I am fine.</I>', '')])
++ chunks = p.Parse('<B><!-- desc=bingo! -->Hello!</B> how are you?<P>'
++ '<I>I am fine.</I>', fold_whitespace)
++ self.failUnlessEqual(chunks, [
++ (True, '<B>Hello!</B> how are you?', 'bingo!'), (False, '<P>', ''),
++ (True, '<I>I am fine.</I>', '')])
++ # Linebreaks get handled by the tclib message.
++ chunks = p.Parse('<B>Hello!</B> <!-- desc=bi\nngo\n! -->how are you?<P>'
++ '<I>I am fine.</I>', fold_whitespace)
++ self.failUnlessEqual(chunks, [
++ (True, '<B>Hello!</B> how are you?', 'bi\nngo\n!'), (False, '<P>', ''),
++ (True, '<I>I am fine.</I>', '')])
++
++ # In this case, because the explicit description appears after the first
++ # translateable, it will actually apply to the second translateable.
++ chunks = p.Parse('<B>Hello!</B> how are you?<!-- desc=bingo! --><P>'
++ '<I>I am fine.</I>', fold_whitespace)
++ self.failUnlessEqual(chunks, [
++ (True, '<B>Hello!</B> how are you?', ''), (False, '<P>', ''),
++ (True, '<I>I am fine.</I>', 'bingo!')])
++
++ def VerifyChunkingReplaceables(self, fold_whitespace):
++ # Check that replaceables within block tags (where attributes would go) are
++ # handled correctly.
++ p = tr_html.HtmlChunks()
++ chunks = p.Parse('<b>Hello!</b> how are you?<p [BINGO] [$~BONGO~$]>'
++ '<i>I am fine.</i>', fold_whitespace)
++ self.failUnlessEqual(chunks, [
++ (True, '<b>Hello!</b> how are you?', ''),
++ (False, '<p [BINGO] [$~BONGO~$]>', ''),
++ (True, '<i>I am fine.</i>', '')])
++
++ def VerifyChunkingLineBreaks(self, fold_whitespace):
++ # Check that the contents of preformatted tags preserve line breaks.
++ p = tr_html.HtmlChunks()
++ chunks = p.Parse('<textarea>Hello\nthere\nhow\nare\nyou?</textarea>',
++ fold_whitespace)
++ self.failUnlessEqual(chunks, [(False, '<textarea>', ''),
++ (True, 'Hello\nthere\nhow\nare\nyou?', ''), (False, '</textarea>', '')])
++
++ # ...and that other tags' line breaks are converted to spaces
++ chunks = p.Parse('<p>Hello\nthere\nhow\nare\nyou?</p>', fold_whitespace)
++ self.failUnlessEqual(chunks, [(False, '<p>', ''),
++ (True, 'Hello there how are you?', ''), (False, '</p>', '')])
++
++ def VerifyChunkingMessageBreak(self, fold_whitespace):
++ p = tr_html.HtmlChunks()
++ # Make sure that message-break comments work properly.
++ chunks = p.Parse('Break<!-- message-break --> apart '
++ '<!--message-break-->messages', fold_whitespace)
++ self.failUnlessEqual(chunks, [(True, 'Break', ''),
++ (False, ' ', ''),
++ (True, 'apart', ''),
++ (False, ' ', ''),
++ (True, 'messages', '')])
++
++ # Make sure message-break comments work in an inline tag.
++ chunks = p.Parse('<a href=\'google.com\'><!-- message-break -->Google'
++ '<!--message-break--></a>', fold_whitespace)
++ self.failUnlessEqual(chunks, [(False, '<a href=\'google.com\'>', ''),
++ (True, 'Google', ''),
++ (False, '</a>', '')])
++
++ def VerifyChunkingMessageNoBreak(self, fold_whitespace):
++ p = tr_html.HtmlChunks()
++ # Make sure that message-no-break comments work properly.
++ chunks = p.Parse('Please <!-- message-no-break --> <br />don\'t break',
++ fold_whitespace)
++ self.failUnlessEqual(chunks, [(True, 'Please <!-- message-no-break --> '
++ '<br />don\'t break', '')])
++
++ chunks = p.Parse('Please <br /> break. <!-- message-no-break --> <br /> '
++ 'But not this time.', fold_whitespace)
++ self.failUnlessEqual(chunks, [(True, 'Please', ''),
++ (False, ' <br /> ', ''),
++ (True, 'break. <!-- message-no-break --> '
++ '<br /> But not this time.', '')])
++
++ def testTranslateableAttributes(self):
++ p = tr_html.HtmlChunks()
++
++ # Check that the translateable attributes in <img>, <submit>, <button> and
++ # <text> elements buttons are handled correctly.
++ chunks = p.Parse('<img src=bingo.jpg alt="hello there">'
++ '<input type=submit value="hello">'
++ '<input type="button" value="hello">'
++ '<input type=\'text\' value=\'Howdie\'>', False)
++ self.failUnlessEqual(chunks, [
++ (False, '<img src=bingo.jpg alt="', ''), (True, 'hello there', ''),
++ (False, '"><input type=submit value="', ''), (True, 'hello', ''),
++ (False, '"><input type="button" value="', ''), (True, 'hello', ''),
++ (False, '"><input type=\'text\' value=\'', ''), (True, 'Howdie', ''),
++ (False, '\'>', '')])
++
++
++ def testTranslateableHtmlToMessage(self):
++ msg = tr_html.HtmlToMessage(
++ 'Hello <b>[USERNAME]</b>, &lt;how&gt;&nbsp;<i>are</i> you?')
++ pres = msg.GetPresentableContent()
++ self.failUnless(pres ==
++ 'Hello BEGIN_BOLDX_USERNAME_XEND_BOLD, '
++ '<how>&nbsp;BEGIN_ITALICareEND_ITALIC you?')
++
++ msg = tr_html.HtmlToMessage('<b>Hello</b><I>Hello</I><b>Hello</b>')
++ pres = msg.GetPresentableContent()
++ self.failUnless(pres ==
++ 'BEGIN_BOLD_1HelloEND_BOLD_1BEGIN_ITALICHelloEND_ITALIC'
++ 'BEGIN_BOLD_2HelloEND_BOLD_2')
++
++ # Check that nesting (of the <font> tags) is handled correctly - i.e. that
++ # the closing placeholder numbers match the opening placeholders.
++ msg = tr_html.HtmlToMessage(
++ '''<font size=-1><font color=#FF0000>Update!</font> '''
++ '''<a href='http://desktop.google.com/whatsnew.html?hl=[$~LANG~$]'>'''
++ '''New Features</a>: Now search PDFs, MP3s, Firefox web history, and '''
++ '''more</font>''')
++ pres = msg.GetPresentableContent()
++ self.failUnless(pres ==
++ 'BEGIN_FONT_1BEGIN_FONT_2Update!END_FONT_2 BEGIN_LINK'
++ 'New FeaturesEND_LINK: Now search PDFs, MP3s, Firefox '
++ 'web history, and moreEND_FONT_1')
++
++ msg = tr_html.HtmlToMessage('''<a href='[$~URL~$]'><b>[NUM][CAT]</b></a>''')
++ pres = msg.GetPresentableContent()
++ self.failUnless(pres == 'BEGIN_LINKBEGIN_BOLDX_NUM_XX_CAT_XEND_BOLDEND_LINK')
++
++ msg = tr_html.HtmlToMessage(
++ '''<font size=-1><a class=q onClick='return window.qs?qs(this):1' '''
++ '''href='http://[WEBSERVER][SEARCH_URI]'>Desktop</a></font>&nbsp;&nbsp;'''
++ '''&nbsp;&nbsp;''')
++ pres = msg.GetPresentableContent()
++ self.failUnless(pres ==
++ '''BEGIN_FONTBEGIN_LINKDesktopEND_LINKEND_FONTSPACE''')
++
++ msg = tr_html.HtmlToMessage(
++ '''<br><br><center><font size=-2>&copy;2005 Google </font></center>''', 1)
++ pres = msg.GetPresentableContent()
++ self.failUnless(pres ==
++ u'BEGIN_BREAK_1BEGIN_BREAK_2BEGIN_CENTERBEGIN_FONT\xa92005'
++ u' Google END_FONTEND_CENTER')
++
++ msg = tr_html.HtmlToMessage(
++ '''&nbsp;-&nbsp;<a class=c href=[$~CACHE~$]>Cached</a>''')
++ pres = msg.GetPresentableContent()
++ self.failUnless(pres ==
++ '&nbsp;-&nbsp;BEGIN_LINKCachedEND_LINK')
++
++ # Check that upper-case tags are handled correctly.
++ msg = tr_html.HtmlToMessage(
++ '''You can read the <A HREF='http://desktop.google.com/privacypolicy.'''
++ '''html?hl=[LANG_CODE]'>Privacy Policy</A> and <A HREF='http://desktop'''
++ '''.google.com/privacyfaq.html?hl=[LANG_CODE]'>Privacy FAQ</A> online.''')
++ pres = msg.GetPresentableContent()
++ self.failUnless(pres ==
++ 'You can read the BEGIN_LINK_1Privacy PolicyEND_LINK_1 and '
++ 'BEGIN_LINK_2Privacy FAQEND_LINK_2 online.')
++
++ # Check that tags with linebreaks immediately preceding them are handled
++ # correctly.
++ msg = tr_html.HtmlToMessage(
++ '''You can read the
++<A HREF='http://desktop.google.com/privacypolicy.html?hl=[LANG_CODE]'>Privacy Policy</A>
++and <A HREF='http://desktop.google.com/privacyfaq.html?hl=[LANG_CODE]'>Privacy FAQ</A> online.''')
++ pres = msg.GetPresentableContent()
++ self.failUnless(pres == '''You can read the
++BEGIN_LINK_1Privacy PolicyEND_LINK_1
++and BEGIN_LINK_2Privacy FAQEND_LINK_2 online.''')
++
++ # Check that message-no-break comments are handled correctly.
++ msg = tr_html.HtmlToMessage('''Please <!-- message-no-break --><br /> don't break''')
++ pres = msg.GetPresentableContent()
++ self.failUnlessEqual(pres, '''Please BREAK don't break''')
++
++class TrHtmlUnittest(unittest.TestCase):
++ def testSetAttributes(self):
++ html = tr_html.TrHtml(StringIO(''))
++ self.failUnlessEqual(html.fold_whitespace_, False)
++ html.SetAttributes({})
++ self.failUnlessEqual(html.fold_whitespace_, False)
++ html.SetAttributes({'fold_whitespace': 'false'})
++ self.failUnlessEqual(html.fold_whitespace_, False)
++ html.SetAttributes({'fold_whitespace': 'true'})
++ self.failUnlessEqual(html.fold_whitespace_, True)
++
++ def testFoldWhitespace(self):
++ text = '<td> Test Message </td>'
++
++ html = tr_html.TrHtml(StringIO(text))
++ html.Parse()
++ self.failUnlessEqual(html.skeleton_[1].GetMessage().GetPresentableContent(),
++ 'Test Message')
++
++ html = tr_html.TrHtml(StringIO(text))
++ html.fold_whitespace_ = True
++ html.Parse()
++ self.failUnlessEqual(html.skeleton_[1].GetMessage().GetPresentableContent(),
++ 'Test Message')
++
++ def testTable(self):
++ html = tr_html.TrHtml(StringIO('''<table class="shaded-header"><tr>
++<td class="header-element b expand">Preferences</td>
++<td class="header-element s">
++<a href="http://desktop.google.com/preferences.html">Preferences&nbsp;Help</a>
++</td>
++</tr></table>'''))
++ html.Parse()
++ self.failUnless(html.skeleton_[3].GetMessage().GetPresentableContent() ==
++ 'BEGIN_LINKPreferences&nbsp;HelpEND_LINK')
++
++ def testSubmitAttribute(self):
++ html = tr_html.TrHtml(StringIO('''</td>
++<td class="header-element"><input type=submit value="Save Preferences"
++name=submit2></td>
++</tr></table>'''))
++ html.Parse()
++ self.failUnless(html.skeleton_[1].GetMessage().GetPresentableContent() ==
++ 'Save Preferences')
++
++ def testWhitespaceAfterInlineTag(self):
++ '''Test that even if there is whitespace after an inline tag at the start
++ of a translateable section the inline tag will be included.
++ '''
++ html = tr_html.TrHtml(
++ StringIO('''<label for=DISPLAYNONE><font size=-1> Hello</font>'''))
++ html.Parse()
++ self.failUnless(html.skeleton_[1].GetMessage().GetRealContent() ==
++ '<font size=-1> Hello</font>')
++
++ def testSillyHeader(self):
++ html = tr_html.TrHtml(StringIO('''[!]
++title\tHello
++bingo
++bongo
++bla
++
++<p>Other stuff</p>'''))
++ html.Parse()
++ content = html.skeleton_[1].GetMessage().GetRealContent()
++ self.failUnless(content == 'Hello')
++ self.failUnless(html.skeleton_[-1] == '</p>')
++ # Right after the translateable the nontranslateable should start with
++ # a linebreak (this catches a bug we had).
++ self.failUnless(html.skeleton_[2][0] == '\n')
++
++
++ def testExplicitDescriptions(self):
++ html = tr_html.TrHtml(
++ StringIO('Hello [USER]<br/><!-- desc=explicit -->'
++ '<input type="button">Go!</input>'))
++ html.Parse()
++ msg = html.GetCliques()[1].GetMessage()
++ self.failUnlessEqual(msg.GetDescription(), 'explicit')
++ self.failUnlessEqual(msg.GetRealContent(), 'Go!')
++
++ html = tr_html.TrHtml(
++ StringIO('Hello [USER]<br/><!-- desc=explicit\nmultiline -->'
++ '<input type="button">Go!</input>'))
++ html.Parse()
++ msg = html.GetCliques()[1].GetMessage()
++ self.failUnlessEqual(msg.GetDescription(), 'explicit multiline')
++ self.failUnlessEqual(msg.GetRealContent(), 'Go!')
++
++
++ def testRegressionInToolbarAbout(self):
++ html = tr_html.TrHtml(util.PathFromRoot(r'grit/testdata/toolbar_about.html'))
++ html.Parse()
++ cliques = html.GetCliques()
++ for cl in cliques:
++ content = cl.GetMessage().GetRealContent()
++ if content.count('De parvis grandis acervus erit'):
++ self.failIf(content.count('$/translate'))
++
++
++ def HtmlFromFileWithManualCheck(self, f):
++ html = tr_html.TrHtml(f)
++ html.Parse()
++
++ # For manual results inspection only...
++ list = []
++ for item in html.skeleton_:
++ if isinstance(item, six.string_types):
++ list.append(item)
++ else:
++ list.append(item.GetMessage().GetPresentableContent())
++
++ return html
++
++
++ def testPrivacyHtml(self):
++ html = self.HtmlFromFileWithManualCheck(
++ util.PathFromRoot(r'grit/testdata/privacy.html'))
++
++ self.failUnless(html.skeleton_[1].GetMessage().GetRealContent() ==
++ 'Privacy and Google Desktop Search')
++ self.failUnless(html.skeleton_[3].startswith('<'))
++ self.failUnless(len(html.skeleton_) > 10)
++
++
++ def testPreferencesHtml(self):
++ html = self.HtmlFromFileWithManualCheck(
++ util.PathFromRoot(r'grit/testdata/preferences.html'))
++
++ # Verify that we don't get '[STATUS-MESSAGE]' as the original content of
++ # one of the MessageClique objects (it would be a placeholder-only message
++ # and we're supposed to have stripped those).
++
++ for item in [x for x in html.skeleton_
++ if isinstance(x, clique.MessageClique)]:
++ if (item.GetMessage().GetRealContent() == '[STATUS-MESSAGE]' or
++ item.GetMessage().GetRealContent() == '[ADDIN-DO] [ADDIN-OPTIONS]'):
++ self.fail()
++
++ self.failUnless(len(html.skeleton_) > 100)
++
++ def AssertNumberOfTranslateables(self, files, num):
++ '''Fails if any of the files in files don't have exactly
++ num translateable sections.
++
++ Args:
++ files: ['file1', 'file2']
++ num: 3
++ '''
++ for f in files:
++ f = util.PathFromRoot(r'grit/testdata/%s' % f)
++ html = self.HtmlFromFileWithManualCheck(f)
++ self.failUnless(len(html.GetCliques()) == num)
++
++ def testFewTranslateables(self):
++ self.AssertNumberOfTranslateables(['browser.html', 'email_thread.html',
++ 'header.html', 'mini.html',
++ 'oneclick.html', 'script.html',
++ 'time_related.html', 'versions.html'], 0)
++ self.AssertNumberOfTranslateables(['footer.html', 'hover.html'], 1)
++
++ def testOtherHtmlFilesForManualInspection(self):
++ files = [
++ 'about.html', 'bad_browser.html', 'cache_prefix.html',
++ 'cache_prefix_file.html', 'chat_result.html', 'del_footer.html',
++ 'del_header.html', 'deleted.html', 'details.html', 'email_result.html',
++ 'error.html', 'explicit_web.html', 'footer.html',
++ 'homepage.html', 'indexing_speed.html',
++ 'install_prefs.html', 'install_prefs2.html',
++ 'oem_enable.html', 'oem_non_admin.html', 'onebox.html',
++ 'password.html', 'quit_apps.html', 'recrawl.html',
++ 'searchbox.html', 'sidebar_h.html', 'sidebar_v.html', 'status.html',
++ ]
++ for f in files:
++ self.HtmlFromFileWithManualCheck(
++ util.PathFromRoot(r'grit/testdata/%s' % f))
++
++ def testTranslate(self):
++ # Note that the English translation of documents that use character
++ # literals (e.g. &copy;) will not be the same as the original document
++ # because the character literal will be transformed into the Unicode
++ # character itself. So for this test we choose some relatively complex
++ # HTML without character entities (but with &nbsp; because that's handled
++ # specially).
++ html = tr_html.TrHtml(StringIO(''' <script>
++ <!--
++ function checkOffice() { var w = document.getElementById("h7");
++ var e = document.getElementById("h8"); var o = document.getElementById("h10");
++ if (!(w.checked || e.checked)) { o.checked=0;o.disabled=1;} else {o.disabled=0;} }
++ // -->
++ </script>
++ <input type=checkbox [CHECK-DOC] name=DOC id=h7 onclick='checkOffice()'>
++ <label for=h7> Word</label><br>
++ <input type=checkbox [CHECK-XLS] name=XLS id=h8 onclick='checkOffice()'>
++ <label for=h8> Excel</label><br>
++ <input type=checkbox [CHECK-PPT] name=PPT id=h9>
++ <label for=h9> PowerPoint</label><br>
++ </span></td><td nowrap valign=top><span class="s">
++ <input type=checkbox [CHECK-PDF] name=PDF id=hpdf>
++ <label for=hpdf> PDF</label><br>
++ <input type=checkbox [CHECK-TXT] name=TXT id=h6>
++ <label for=h6> Text, media, and other files</label><br>
++ </tr>&nbsp;&nbsp;
++ <tr><td nowrap valign=top colspan=3><span class="s"><br />
++ <input type=checkbox [CHECK-SECUREOFFICE] name=SECUREOFFICE id=h10>
++ <label for=h10> Password-protected Office documents (Word, Excel)</label><br />
++ <input type=checkbox [DISABLED-HTTPS] [CHECK-HTTPS] name=HTTPS id=h12><label
++ for=h12> Secure pages (HTTPS) in web history</label></span></td></tr>
++ </table>'''))
++ html.Parse()
++ trans = html.Translate('en')
++ if (html.GetText() != trans):
++ self.fail()
++
++
++ def testHtmlToMessageWithBlockTags(self):
++ msg = tr_html.HtmlToMessage(
++ 'Hello<p>Howdie<img alt="bingo" src="image.gif">', True)
++ result = msg.GetPresentableContent()
++ self.failUnless(
++ result == 'HelloBEGIN_PARAGRAPHHowdieBEGIN_BLOCKbingoEND_BLOCK')
++
++ msg = tr_html.HtmlToMessage(
++ 'Hello<p>Howdie<input type="button" value="bingo">', True)
++ result = msg.GetPresentableContent()
++ self.failUnless(
++ result == 'HelloBEGIN_PARAGRAPHHowdieBEGIN_BLOCKbingoEND_BLOCK')
++
++
++ def testHtmlToMessageRegressions(self):
++ msg = tr_html.HtmlToMessage(' - ', True)
++ result = msg.GetPresentableContent()
++ self.failUnless(result == ' - ')
++
++
++ def testEscapeUnescaped(self):
++ text = '&copy;&nbsp; & &quot;&lt;hello&gt;&quot;'
++ unescaped = util.UnescapeHtml(text)
++ self.failUnless(unescaped == u'\u00a9\u00a0 & "<hello>"')
++ escaped_unescaped = util.EscapeHtml(unescaped, True)
++ self.failUnless(escaped_unescaped ==
++ u'\u00a9\u00a0 &amp; &quot;&lt;hello&gt;&quot;')
++
++ def testRegressionCjkHtmlFile(self):
++ # TODO(joi) Fix this problem where unquoted attributes that
++ # have a value that is CJK characters causes the regular expression
++ # match never to return. (culprit is the _ELEMENT regexp(
++ if False:
++ html = self.HtmlFromFileWithManualCheck(util.PathFromRoot(
++ r'grit/testdata/ko_oem_enable_bug.html'))
++ self.failUnless(True)
++
++ def testRegressionCpuHang(self):
++ # If this regression occurs, the unit test will never return
++ html = tr_html.TrHtml(StringIO(
++ '''<input type=text size=12 id=advFileTypeEntry [~SHOW-FILETYPE-BOX~] value="[EXT]" name=ext>'''))
++ html.Parse()
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/gather/txt.py b/tools/grit/grit/gather/txt.py
new file mode 100644
-index 0000000000..0cfec71159
+index 0000000000..e5c10abc28
--- /dev/null
-+++ b/tools/clang/plugins/Makefile
-@@ -0,0 +1,19 @@
-+# This file requires the clang build system, at least for now. So to use this
-+# Makefile, you should execute the following commands to copy this directory
-+# into a clang checkout:
-+#
-+# cp -R <this directory> third_party/llvm/tools/clang/tools/chrome-plugin
-+# cd third_party/llvm/tools/clang/tools/chrome-plugin
-+# make
++++ b/tools/grit/grit/gather/txt.py
+@@ -0,0 +1,38 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
+
-+CLANG_LEVEL := ../..
-+LIBRARYNAME = FindBadConstructs
++'''Supports making amessage from a text file.
++'''
+
-+LINK_LIBS_IN_SHARED = 0
-+SHARED_LIBRARY = 1
++from __future__ import print_function
+
-+include $(CLANG_LEVEL)/Makefile
++from grit.gather import interface
++from grit import tclib
+
-+ifeq ($(OS),Darwin)
-+ LDFLAGS=-Wl,-undefined,dynamic_lookup
-+endif
-diff --git a/tools/clang/plugins/OWNERS b/tools/clang/plugins/OWNERS
++
++class TxtFile(interface.GathererBase):
++ '''A text file gatherer. Very simple, all text from the file becomes a
++ single clique.
++ '''
++
++ def Parse(self):
++ self.text_ = self._LoadInputFile()
++ self.clique_ = self.uberclique.MakeClique(tclib.Message(text=self.text_))
++
++ def GetText(self):
++ '''Returns the text of what is being gathered.'''
++ return self.text_
++
++ def GetTextualIds(self):
++ return [self.extkey]
++
++ def GetCliques(self):
++ '''Returns the MessageClique objects for all translateable portions.'''
++ return [self.clique_]
++
++ def Translate(self, lang, pseudo_if_not_available=True,
++ skeleton_gatherer=None, fallback_to_english=False):
++ return self.clique_.MessageForLanguage(lang,
++ pseudo_if_not_available,
++ fallback_to_english).GetRealContent()
+diff --git a/tools/grit/grit/gather/txt_unittest.py b/tools/grit/grit/gather/txt_unittest.py
new file mode 100644
-index 0000000000..4733a4f06b
+index 0000000000..abb9ed98d7
--- /dev/null
-+++ b/tools/clang/plugins/OWNERS
-@@ -0,0 +1 @@
-+erg@chromium.org
-diff --git a/tools/clang/plugins/README.chromium b/tools/clang/plugins/README.chromium
++++ b/tools/grit/grit/gather/txt_unittest.py
+@@ -0,0 +1,35 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit tests for TxtFile gatherer'''
++
++from __future__ import print_function
++
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++
++import unittest
++
++from six import StringIO
++
++from grit.gather import txt
++
++
++class TxtUnittest(unittest.TestCase):
++ def testGather(self):
++ input = StringIO('Hello there\nHow are you?')
++ gatherer = txt.TxtFile(input)
++ gatherer.Parse()
++ self.failUnless(gatherer.GetText() == input.getvalue())
++ self.failUnless(len(gatherer.GetCliques()) == 1)
++ self.failUnless(gatherer.GetCliques()[0].GetMessage().GetRealContent() ==
++ input.getvalue())
++
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/grd_reader.py b/tools/grit/grit/grd_reader.py
new file mode 100644
-index 0000000000..a2ce0ff557
+index 0000000000..b7bb782977
--- /dev/null
-+++ b/tools/clang/plugins/README.chromium
-@@ -0,0 +1,4 @@
-+Documentation for this code is:
++++ b/tools/grit/grit/grd_reader.py
+@@ -0,0 +1,238 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Class for reading GRD files into memory, without processing them.
++'''
++
++from __future__ import print_function
++
++import os.path
++import sys
++import xml.sax
++import xml.sax.handler
++
++import six
++
++from grit import exception
++from grit import util
++from grit.node import mapping
++from grit.node import misc
++
++
++class StopParsingException(Exception):
++ '''An exception used to stop parsing.'''
++ pass
++
++
++class GrdContentHandler(xml.sax.handler.ContentHandler):
++ def __init__(self, stop_after, debug, dir, defines, tags_to_ignore,
++ target_platform, source):
++ # Invariant of data:
++ # 'root' is the root of the parse tree being created, or None if we haven't
++ # parsed out any elements.
++ # 'stack' is the a stack of elements that we push new nodes onto and
++ # pop from when they finish parsing, or [] if we are not currently parsing.
++ # 'stack[-1]' is the top of the stack.
++ self.root = None
++ self.stack = []
++ self.stop_after = stop_after
++ self.debug = debug
++ self.dir = dir
++ self.defines = defines
++ self.tags_to_ignore = tags_to_ignore or set()
++ self.ignore_depth = 0
++ self.target_platform = target_platform
++ self.source = source
++
++ def startElement(self, name, attrs):
++ if self.ignore_depth or name in self.tags_to_ignore:
++ if self.debug and self.ignore_depth == 0:
++ print("Ignoring element %s and its children" % name)
++ self.ignore_depth += 1
++ return
++
++ if self.debug:
++ attr_list = ' '.join('%s="%s"' % kv for kv in attrs.items())
++ print("Starting parsing of element %s with attributes %r" %
++ (name, attr_list or '(none)'))
++
++ typeattr = attrs.get('type')
++ node = mapping.ElementToClass(name, typeattr)()
++ node.source = self.source
++
++ if self.stack:
++ self.stack[-1].AddChild(node)
++ node.StartParsing(name, self.stack[-1])
++ else:
++ assert self.root is None
++ self.root = node
++ if isinstance(self.root, misc.GritNode):
++ if self.target_platform:
++ self.root.SetTargetPlatform(self.target_platform)
++ node.StartParsing(name, None)
++ if self.defines:
++ node.SetDefines(self.defines)
++ self.stack.append(node)
++
++ for attr, attrval in attrs.items():
++ node.HandleAttribute(attr, attrval)
++
++ def endElement(self, name):
++ if self.ignore_depth:
++ self.ignore_depth -= 1
++ return
++
++ if name == 'part':
++ partnode = self.stack[-1]
++ partnode.started_inclusion = True
++ # Add the contents of the sub-grd file as children of the <part> node.
++ partname = os.path.join(self.dir, partnode.GetInputPath())
++ # Check the GRDP file exists.
++ if not os.path.exists(partname):
++ raise exception.FileNotFound(partname)
++ # Exceptions propagate to the handler in grd_reader.Parse().
++ oldsource = self.source
++ try:
++ self.source = partname
++ xml.sax.parse(partname, GrdPartContentHandler(self))
++ finally:
++ self.source = oldsource
++
++ if self.debug:
++ print("End parsing of element %s" % name)
++ self.stack.pop().EndParsing()
++
++ if name == self.stop_after:
++ raise StopParsingException()
++
++ def characters(self, content):
++ if self.ignore_depth == 0:
++ if self.stack[-1]:
++ self.stack[-1].AppendContent(content)
++
++ def ignorableWhitespace(self, whitespace):
++ # TODO(joi): This is not supported by expat. Should use a different XML
++ # parser?
++ pass
++
++
++class GrdPartContentHandler(xml.sax.handler.ContentHandler):
++ def __init__(self, parent):
++ self.parent = parent
++ self.depth = 0
++
++ def startElement(self, name, attrs):
++ if self.depth:
++ self.parent.startElement(name, attrs)
++ else:
++ if name != 'grit-part':
++ raise exception.MissingElement("root tag must be <grit-part>")
++ if attrs:
++ raise exception.UnexpectedAttribute(
++ "<grit-part> tag must not have attributes")
++ self.depth += 1
++
++ def endElement(self, name):
++ self.depth -= 1
++ if self.depth:
++ self.parent.endElement(name)
++
++ def characters(self, content):
++ self.parent.characters(content)
++
++ def ignorableWhitespace(self, whitespace):
++ self.parent.ignorableWhitespace(whitespace)
++
++
++def Parse(filename_or_stream, dir=None, stop_after=None, first_ids_file=None,
++ debug=False, defines=None, tags_to_ignore=None, target_platform=None,
++ predetermined_ids_file=None):
++ '''Parses a GRD file into a tree of nodes (from grit.node).
++
++ If filename_or_stream is a stream, 'dir' should point to the directory
++ notionally containing the stream (this feature is only used in unit tests).
++
++ If 'stop_after' is provided, the parsing will stop once the first node
++ with this name has been fully parsed (including all its contents).
++
++ If 'debug' is true, lots of information about the parsing events will be
++ printed out during parsing of the file.
++
++ If 'first_ids_file' is non-empty, it is used to override the setting for the
++ first_ids_file attribute of the <grit> root node. Note that the first_ids_file
++ parameter should be relative to the cwd, even though the first_ids_file
++ attribute of the <grit> node is relative to the grd file.
++
++ If 'target_platform' is set, this is used to determine the target
++ platform of builds, instead of using |sys.platform|.
++
++ Args:
++ filename_or_stream: './bla.xml'
++ dir: None (if filename_or_stream is a filename) or '.'
++ stop_after: 'inputs'
++ first_ids_file: 'GRIT_DIR/../gritsettings/resource_ids'
++ debug: False
++ defines: dictionary of defines, like {'chromeos': '1'}
++ target_platform: None or the value that would be returned by sys.platform
++ on your target platform.
++ predetermined_ids_file: File path to a file containing a pre-determined
++ mapping from resource names to resource ids which will be used to assign
++ resource ids to those resources.
++
++ Return:
++ Subclass of grit.node.base.Node
++
++ Throws:
++ grit.exception.Parsing
++ '''
++
++ if isinstance(filename_or_stream, six.string_types):
++ source = filename_or_stream
++ if dir is None:
++ dir = util.dirname(filename_or_stream)
++ else:
++ source = None
++
++ handler = GrdContentHandler(stop_after=stop_after, debug=debug, dir=dir,
++ defines=defines, tags_to_ignore=tags_to_ignore,
++ target_platform=target_platform, source=source)
++ try:
++ xml.sax.parse(filename_or_stream, handler)
++ except StopParsingException:
++ assert stop_after
++ pass
++ except:
++ if not debug:
++ print("parse exception: run GRIT with the -x flag to debug .grd problems")
++ raise
++
++ if handler.root.name != 'grit':
++ raise exception.MissingElement("root tag must be <grit>")
++
++ if hasattr(handler.root, 'SetOwnDir'):
++ # Fix up the base_dir so it is relative to the input file.
++ assert dir is not None
++ handler.root.SetOwnDir(dir)
++
++ if isinstance(handler.root, misc.GritNode):
++ handler.root.SetPredeterminedIdsFile(predetermined_ids_file)
++ if first_ids_file:
++ # Make the path to the first_ids_file relative to the grd file,
++ # unless it begins with GRIT_DIR.
++ GRIT_DIR_PREFIX = 'GRIT_DIR'
++ if not (first_ids_file.startswith(GRIT_DIR_PREFIX)
++ and first_ids_file[len(GRIT_DIR_PREFIX)] in ['/', '\\']):
++ rel_dir = os.path.relpath(os.getcwd(), dir)
++ first_ids_file = util.normpath(os.path.join(rel_dir, first_ids_file))
++ handler.root.attrs['first_ids_file'] = first_ids_file
++ # Assign first ids to the nodes that don't have them.
++ handler.root.AssignFirstIds(filename_or_stream, defines)
++
++ return handler.root
+
-+- http://code.google.com/p/chromium/wiki/Clang
-+- http://code.google.com/p/chromium/wiki/WritingClangPlugins
-diff --git a/tools/clang/plugins/tests/base_refcounted.cpp b/tools/clang/plugins/tests/base_refcounted.cpp
++
++if __name__ == '__main__':
++ util.ChangeStdoutEncoding()
++ print(six.text_type(Parse(sys.argv[1])))
+diff --git a/tools/grit/grit/grd_reader_unittest.py b/tools/grit/grit/grd_reader_unittest.py
new file mode 100644
-index 0000000000..364a3e888c
+index 0000000000..920a92f9c0
--- /dev/null
-+++ b/tools/clang/plugins/tests/base_refcounted.cpp
-@@ -0,0 +1,72 @@
-+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+// Use of this source code is governed by a BSD-style license that can be
-+// found in the LICENSE file.
++++ b/tools/grit/grit/grd_reader_unittest.py
+@@ -0,0 +1,346 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
+
-+#include "base_refcounted.h"
++'''Unit tests for grd_reader package'''
+
-+#include <cstddef>
++from __future__ import print_function
+
-+namespace {
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
++
++import unittest
++
++import six
++from six import StringIO
++
++from grit import exception
++from grit import grd_reader
++from grit import util
++from grit.node import empty
++from grit.node import message
++
++
++class GrdReaderUnittest(unittest.TestCase):
++ def testParsingAndXmlOutput(self):
++ input = u'''<?xml version="1.0" encoding="UTF-8"?>
++<grit base_dir="." current_release="3" latest_public_release="2" source_lang_id="en-US">
++ <release seq="3">
++ <includes>
++ <include file="images/logo.gif" name="ID_LOGO" type="gif" />
++ </includes>
++ <messages>
++ <if expr="True">
++ <message desc="Printed to greet the currently logged in user" name="IDS_GREETING">
++ Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, how are you doing today?
++ </message>
++ </if>
++ </messages>
++ <structures>
++ <structure file="rc_files/dialogs.rc" name="IDD_NARROW_DIALOG" type="dialog">
++ <skeleton expr="lang == 'fr-FR'" file="bla.rc" variant_of_revision="3" />
++ </structure>
++ <structure file="rc_files/version.rc" name="VS_VERSION_INFO" type="version" />
++ </structures>
++ </release>
++ <translations>
++ <file lang="nl" path="nl_translations.xtb" />
++ </translations>
++ <outputs>
++ <output filename="resource.h" type="rc_header" />
++ <output filename="resource.rc" lang="en-US" type="rc_all" />
++ </outputs>
++</grit>'''
++ pseudo_file = StringIO(input)
++ tree = grd_reader.Parse(pseudo_file, '.')
++ output = six.text_type(tree)
++ expected_output = input.replace(u' base_dir="."', u'')
++ self.assertEqual(expected_output, output)
++ self.failUnless(tree.GetNodeById('IDS_GREETING'))
++
++
++ def testStopAfter(self):
++ input = u'''<?xml version="1.0" encoding="UTF-8"?>
++<grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
++ <outputs>
++ <output filename="resource.h" type="rc_header" />
++ <output filename="resource.rc" lang="en-US" type="rc_all" />
++ </outputs>
++ <release seq="3">
++ <includes>
++ <include type="gif" name="ID_LOGO" file="images/logo.gif"/>
++ </includes>
++ </release>
++</grit>'''
++ pseudo_file = StringIO(input)
++ tree = grd_reader.Parse(pseudo_file, '.', stop_after='outputs')
++ # only an <outputs> child
++ self.failUnless(len(tree.children) == 1)
++ self.failUnless(tree.children[0].name == 'outputs')
++
++ def testLongLinesWithComments(self):
++ input = u'''<?xml version="1.0" encoding="UTF-8"?>
++<grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
++ <release seq="3">
++ <messages>
++ <message name="IDS_GREETING" desc="Printed to greet the currently logged in user">
++ This is a very long line with no linebreaks yes yes it stretches on <!--
++ -->and on <!--
++ -->and on!
++ </message>
++ </messages>
++ </release>
++</grit>'''
++ pseudo_file = StringIO(input)
++ tree = grd_reader.Parse(pseudo_file, '.')
++
++ greeting = tree.GetNodeById('IDS_GREETING')
++ self.failUnless(greeting.GetCliques()[0].GetMessage().GetRealContent() ==
++ 'This is a very long line with no linebreaks yes yes it '
++ 'stretches on and on and on!')
++
++ def doTestAssignFirstIds(self, first_ids_path):
++ input = u'''<?xml version="1.0" encoding="UTF-8"?>
++<grit latest_public_release="2" source_lang_id="en-US" current_release="3"
++ base_dir="." first_ids_file="%s">
++ <release seq="3">
++ <messages>
++ <message name="IDS_TEST" desc="test">
++ test
++ </message>
++ </messages>
++ </release>
++</grit>''' % first_ids_path
++ pseudo_file = StringIO(input)
++ grit_root_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)),
++ '..')
++ fake_input_path = os.path.join(
++ grit_root_dir, "grit/testdata/chrome/app/generated_resources.grd")
++ root = grd_reader.Parse(pseudo_file, os.path.split(fake_input_path)[0])
++ root.AssignFirstIds(fake_input_path, {})
++ messages_node = root.children[0].children[0]
++ self.failUnless(isinstance(messages_node, empty.MessagesNode))
++ self.failUnless(messages_node.attrs["first_id"] !=
++ empty.MessagesNode().DefaultAttributes()["first_id"])
++
++ def testAssignFirstIds(self):
++ self.doTestAssignFirstIds("../../tools/grit/resource_ids")
++
++ def testAssignFirstIdsUseGritDir(self):
++ self.doTestAssignFirstIds("GRIT_DIR/grit/testdata/tools/grit/resource_ids")
++
++ def testAssignFirstIdsMultipleMessages(self):
++ """If there are multiple messages sections, the resource_ids file
++ needs to list multiple first_id values."""
++ input = u'''<?xml version="1.0" encoding="UTF-8"?>
++<grit latest_public_release="2" source_lang_id="en-US" current_release="3"
++ base_dir="." first_ids_file="resource_ids">
++ <release seq="3">
++ <messages>
++ <message name="IDS_TEST" desc="test">
++ test
++ </message>
++ </messages>
++ <messages>
++ <message name="IDS_TEST2" desc="test">
++ test2
++ </message>
++ </messages>
++ </release>
++</grit>'''
++ pseudo_file = StringIO(input)
++ grit_root_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)),
++ '..')
++ fake_input_path = os.path.join(grit_root_dir, "grit/testdata/test.grd")
++
++ root = grd_reader.Parse(pseudo_file, os.path.split(fake_input_path)[0])
++ root.AssignFirstIds(fake_input_path, {})
++ messages_node = root.children[0].children[0]
++ self.assertTrue(isinstance(messages_node, empty.MessagesNode))
++ self.assertEqual('100', messages_node.attrs["first_id"])
++ messages_node = root.children[0].children[1]
++ self.assertTrue(isinstance(messages_node, empty.MessagesNode))
++ self.assertEqual('10000', messages_node.attrs["first_id"])
++
++ def testUseNameForIdAndPpIfdef(self):
++ input = u'''<?xml version="1.0" encoding="UTF-8"?>
++<grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
++ <release seq="3">
++ <messages>
++ <if expr="pp_ifdef('hello')">
++ <message name="IDS_HELLO" use_name_for_id="true">
++ Hello!
++ </message>
++ </if>
++ </messages>
++ </release>
++</grit>'''
++ pseudo_file = StringIO(input)
++ root = grd_reader.Parse(pseudo_file, '.', defines={'hello': '1'})
++
++ # Check if the ID is set to the name. In the past, there was a bug
++ # that caused the ID to be a generated number.
++ hello = root.GetNodeById('IDS_HELLO')
++ self.failUnless(hello.GetCliques()[0].GetId() == 'IDS_HELLO')
++
++ def testUseNameForIdWithIfElse(self):
++ input = u'''<?xml version="1.0" encoding="UTF-8"?>
++<grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
++ <release seq="3">
++ <messages>
++ <if expr="pp_ifdef('hello')">
++ <then>
++ <message name="IDS_HELLO" use_name_for_id="true">
++ Hello!
++ </message>
++ </then>
++ <else>
++ <message name="IDS_HELLO" use_name_for_id="true">
++ Yellow!
++ </message>
++ </else>
++ </if>
++ </messages>
++ </release>
++</grit>'''
++ pseudo_file = StringIO(input)
++ root = grd_reader.Parse(pseudo_file, '.', defines={'hello': '1'})
++
++ # Check if the ID is set to the name. In the past, there was a bug
++ # that caused the ID to be a generated number.
++ hello = root.GetNodeById('IDS_HELLO')
++ self.failUnless(hello.GetCliques()[0].GetId() == 'IDS_HELLO')
++
++ def testPartInclusionAndCorrectSource(self):
++ arbitrary_path_grd = u'''\
++ <grit-part>
++ <message name="IDS_TEST5" desc="test5">test5</message>
++ </grit-part>'''
++ tmp_dir = util.TempDir({'arbitrary_path.grp': arbitrary_path_grd})
++ arbitrary_path_grd_file = tmp_dir.GetPath('arbitrary_path.grp')
++ top_grd = u'''\
++ <grit latest_public_release="2" current_release="3">
++ <release seq="3">
++ <messages>
++ <message name="IDS_TEST" desc="test">
++ test
++ </message>
++ <part file="sub.grp" />
++ <part file="%s" />
++ </messages>
++ </release>
++ </grit>''' % arbitrary_path_grd_file
++ sub_grd = u'''\
++ <grit-part>
++ <message name="IDS_TEST2" desc="test2">test2</message>
++ <part file="subsub.grp" />
++ <message name="IDS_TEST3" desc="test3">test3</message>
++ </grit-part>'''
++ subsub_grd = u'''\
++ <grit-part>
++ <message name="IDS_TEST4" desc="test4">test4</message>
++ </grit-part>'''
++ expected_output = u'''\
++ <grit current_release="3" latest_public_release="2">
++ <release seq="3">
++ <messages>
++ <message desc="test" name="IDS_TEST">
++ test
++ </message>
++ <part file="sub.grp">
++ <message desc="test2" name="IDS_TEST2">
++ test2
++ </message>
++ <part file="subsub.grp">
++ <message desc="test4" name="IDS_TEST4">
++ test4
++ </message>
++ </part>
++ <message desc="test3" name="IDS_TEST3">
++ test3
++ </message>
++ </part>
++ <part file="%s">
++ <message desc="test5" name="IDS_TEST5">
++ test5
++ </message>
++ </part>
++ </messages>
++ </release>
++ </grit>''' % arbitrary_path_grd_file
++
++ with util.TempDir({'sub.grp': sub_grd,
++ 'subsub.grp': subsub_grd}) as tmp_sub_dir:
++ output = grd_reader.Parse(StringIO(top_grd),
++ tmp_sub_dir.GetPath())
++ correct_sources = {
++ 'IDS_TEST': None,
++ 'IDS_TEST2': tmp_sub_dir.GetPath('sub.grp'),
++ 'IDS_TEST3': tmp_sub_dir.GetPath('sub.grp'),
++ 'IDS_TEST4': tmp_sub_dir.GetPath('subsub.grp'),
++ 'IDS_TEST5': arbitrary_path_grd_file,
++ }
+
-+// Unsafe; should error.
-+class AnonymousDerivedProtectedToPublicInImpl
-+ : public ProtectedRefCountedDtorInHeader {
-+ public:
-+ AnonymousDerivedProtectedToPublicInImpl() {}
-+ ~AnonymousDerivedProtectedToPublicInImpl() {}
-+};
++ for node in output.ActiveDescendants():
++ with node:
++ if isinstance(node, message.MessageNode):
++ self.assertEqual(correct_sources[node.attrs.get('name')], node.source)
++ self.assertEqual(expected_output.split(), output.FormatXml().split())
++ tmp_dir.CleanUp()
++
++ def testPartInclusionFailure(self):
++ template = u'''
++ <grit latest_public_release="2" current_release="3">
++ <outputs>
++ %s
++ </outputs>
++ </grit>'''
++
++ part_failures = [
++ (exception.UnexpectedContent, u'<part file="x">fnord</part>'),
++ (exception.UnexpectedChild,
++ u'<part file="x"><output filename="x" type="y" /></part>'),
++ (exception.FileNotFound, u'<part file="yet_created_x" />'),
++ ]
++ for raises, data in part_failures:
++ data = StringIO(template % data)
++ self.assertRaises(raises, grd_reader.Parse, data, '.')
++
++ gritpart_failures = [
++ (exception.UnexpectedAttribute, u'<grit-part file="xyz"></grit-part>'),
++ (exception.MissingElement, u'<output filename="x" type="y" />'),
++ ]
++ for raises, data in gritpart_failures:
++ top_grd = StringIO(template % u'<part file="bad.grp" />')
++ with util.TempDir({'bad.grp': data}) as temp_dir:
++ self.assertRaises(raises, grd_reader.Parse, top_grd, temp_dir.GetPath())
++
++ def testEarlyEnoughPlatformSpecification(self):
++ # This is a regression test for issue
++ # https://code.google.com/p/grit-i18n/issues/detail?id=23
++ grd_text = u'''<?xml version="1.0" encoding="UTF-8"?>
++ <grit latest_public_release="1" current_release="1">
++ <release seq="1">
++ <messages>
++ <if expr="not pp_ifdef('use_titlecase')">
++ <message name="IDS_XYZ">foo</message>
++ </if>
++ <!-- The assumption is that use_titlecase is never true for
++ this platform. When the platform isn't set to 'android'
++ early enough, we get a duplicate message name. -->
++ <if expr="os == '%s'">
++ <message name="IDS_XYZ">boo</message>
++ </if>
++ </messages>
++ </release>
++ </grit>''' % sys.platform
++ with util.TempDir({}) as temp_dir:
++ grd_reader.Parse(StringIO(grd_text), temp_dir.GetPath(),
++ target_platform='android')
+
-+} // namespace
+
-+// Unsafe; should error.
-+class PublicRefCountedDtorInImpl
-+ : public base::RefCounted<PublicRefCountedDtorInImpl> {
-+ public:
-+ PublicRefCountedDtorInImpl() {}
-+ ~PublicRefCountedDtorInImpl() {}
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/grit-todo.xml b/tools/grit/grit/grit-todo.xml
+new file mode 100644
+index 0000000000..b8c20fdfad
+--- /dev/null
++++ b/tools/grit/grit/grit-todo.xml
+@@ -0,0 +1,62 @@
++<?xml version="1.0" encoding="windows-1252"?>
++<TODOLIST FILEFORMAT="6" PROJECTNAME="GRIT" NEXTUNIQUEID="56" FILEVERSION="69" LASTMODIFIED="2005-08-19">
++ <TASK STARTDATESTRING="2005-04-08" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38453.49975694" TITLE="check 'name' attribute is unique" TIMEESTUNITS="H" ID="2" PERCENTDONE="100" STARTDATE="38450.00000000" DONEDATESTRING="2005-04-11" POS="22" DONEDATE="38453.00000000"/>
++ <TASK STARTDATESTRING="2005-04-08" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38488.48189815" TITLE="import id-calculating code" TIMEESTUNITS="H" ID="3" PERCENTDONE="100" STARTDATE="38450.00000000" DONEDATESTRING="2005-05-16" POS="13" DONEDATE="38488.00000000"/>
++ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38488.48209491" TITLE="Import tool for existing translations" TIMEESTUNITS="H" ID="6" PERCENTDONE="100" STARTDATE="38488.00000000" DONEDATESTRING="2005-06-16" POS="12" DONEDATE="38519.00000000"/>
++ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38511.00805556" TITLE="Export XMBs" TIMEESTUNITS="H" ID="8" PERCENTDONE="100" STARTDATE="38488.00000000" DONEDATESTRING="2005-06-08" POS="20" DONEDATE="38511.00000000"/>
++ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38511.00924769" TITLE="Initial Integration" TIMEESTUNITS="H" ID="10" PERCENTDONE="100" STARTDATE="38488.00000000" DONEDATESTRING="2005-06-08" POS="10" DONEDATE="38511.00000000">
++ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38496.54048611" TITLE="parser for %s strings" TIMEESTUNITS="H" ID="4" PERCENTDONE="100" STARTDATE="38488.00000000" DONEDATESTRING="2005-05-24" POS="2" DONEDATE="38496.00000000"/>
++ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38497.00261574" TITLE="import tool for existing RC files" TIMEESTUNITS="H" ID="5" PERCENTDONE="100" STARTDATE="38488.00000000" DONEDATESTRING="2005-05-25" POS="4" DONEDATE="38497.00000000">
++ <TASK STARTDATESTRING="2005-05-24" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38496.92990741" TITLE="handle button value= and img alt= in message HTML text" TIMEESTUNITS="H" ID="22" PERCENTDONE="100" STARTDATE="38496.00000000" DONEDATESTRING="2005-05-24" POS="1" DONEDATE="38496.00000000"/>
++ <TASK STARTDATESTRING="2005-05-24" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38497.00258102" TITLE="&amp;nbsp; bug" TIMEESTUNITS="H" ID="23" PERCENTDONE="100" STARTDATE="38496.00000000" DONEDATESTRING="2005-05-25" POS="2" DONEDATE="38497.00000000"/>
++ </TASK>
++ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38490.61171296" TITLE="grit build" TIMEESTUNITS="H" ID="7" PERCENTDONE="100" STARTDATE="38488.00000000" DONEDATESTRING="2005-05-18" POS="6" DONEDATE="38490.00000000">
++ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38490.61168981" TITLE="use IDs gathered from gatherers for .h file" TIMEESTUNITS="H" ID="20" PERCENTDONE="100" STARTDATE="38488.00000000" DONEDATESTRING="2005-05-18" POS="1" DONEDATE="38490.00000000"/>
++ </TASK>
++ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38504.55199074" TITLE="SCons Integration" TIMEESTUNITS="H" ID="9" PERCENTDONE="100" STARTDATE="38488.00000000" DONEDATESTRING="2005-06-01" POS="1" DONEDATE="38504.00000000"/>
++ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38490.61181713" TITLE="handle includes" TIMEESTUNITS="H" ID="12" PERCENTDONE="100" STARTDATE="38488.00000000" DONEDATESTRING="2005-05-18" POS="5" DONEDATE="38490.00000000"/>
++ <TASK STARTDATESTRING="2005-05-24" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38507.98567130" TITLE="output translated HTML templates" TIMEESTUNITS="H" ID="25" PERCENTDONE="100" STARTDATE="38496.00000000" DONEDATESTRING="2005-06-04" POS="3" DONEDATE="38507.00000000"/>
++ <TASK STARTDATESTRING="2005-05-31" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38507.99394676" TITLE="bug: re-escape too much in RC dialogs etc." TIMEESTUNITS="H" ID="38" PERCENTDONE="100" STARTDATE="38503.00000000" DONEDATESTRING="2005-06-04" POS="7" DONEDATE="38507.00000000"/>
++ </TASK>
++ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38489.46444444" TITLE="handle structure variants" TIMEESTUNITS="H" ID="11" PERCENTDONE="100" STARTDATE="38488.00000000" DONEDATESTRING="2005-06-16" POS="15" DONEDATE="38519.00000000"/>
++ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38489.46456019" TITLE="handle include variants" TIMEESTUNITS="H" ID="13" PERCENTDONE="100" STARTDATE="38488.00000000" DONEDATESTRING="2005-06-16" POS="17" DONEDATE="38519.00000000"/>
++ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38489.46537037" TITLE="handle translateable text for includes (e.g. image text)" TIMEESTUNITS="H" ID="14" PERCENTDONE="100" STARTDATE="38488.00000000" DONEDATESTRING="2005-06-16" POS="14" DONEDATE="38519.00000000"/>
++ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38489.46712963" TITLE="ddoc" TIMEESTUNITS="H" ID="15" STARTDATE="38488.00000000" POS="4">
++ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38489.46718750" TITLE="review comments miket" TIMEESTUNITS="H" ID="16" STARTDATE="38488.00000000" POS="2"/>
++ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38489.46722222" TITLE="review comments pdoyle" TIMEESTUNITS="H" ID="17" STARTDATE="38488.00000000" POS="1"/>
++ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38489.46732639" TITLE="remove 'extkey' from structure" TIMEESTUNITS="H" ID="18" STARTDATE="38488.00000000" POS="3"/>
++ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38489.53537037" TITLE="add 'encoding' to structure" TIMEESTUNITS="H" ID="19" STARTDATE="38488.00000000" POS="6"/>
++ <TASK STARTDATESTRING="2005-05-31" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38503.55304398" TITLE="document limitation: emitter doesn't emit the translated HTML templates" TIMEESTUNITS="H" ID="30" STARTDATE="38503.00000000" POS="4"/>
++ <TASK STARTDATESTRING="2005-05-31" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38504.58541667" TITLE="add 'internal_comment' to &lt;message&gt;" TIMEESTUNITS="H" ID="32" STARTDATE="38503.00000000" POS="5"/>
++ <TASK STARTDATESTRING="2005-05-31" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38505.73391204" TITLE="&lt;outputs&gt; can not have paths (because of SCons integration - goes to build dir)" TIMEESTUNITS="H" ID="36" STARTDATE="38503.00000000" POS="9"/>
++ <TASK STARTDATESTRING="2005-05-31" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38506.64265046" TITLE="&lt;identifers&gt; and &lt;identifier&gt; nodes" TIMEESTUNITS="H" ID="37" STARTDATE="38503.00000000" POS="10"/>
++ <TASK STARTDATESTRING="2005-06-23" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38526.62344907" TITLE="&lt;structure&gt; can have 'exclude_from_rc' attribute (default false)" TIMEESTUNITS="H" ID="47" STARTDATE="38526.00000000" POS="8"/>
++ <TASK STARTDATESTRING="2005-06-23" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38531.94135417" TITLE="add 'enc_check' to &lt;grit&gt;" TIMEESTUNITS="H" ID="48" STARTDATE="38526.00000000" POS="7"/>
++ </TASK>
++ <TASK STARTDATESTRING="2005-05-18" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38492.51549769" TITLE="handle nontranslateable messages (in MessageClique?)" TIMEESTUNITS="H" ID="21" PERCENTDONE="100" STARTDATE="38490.00000000" DONEDATESTRING="2005-06-16" POS="16" DONEDATE="38519.00000000"/>
++ <TASK STARTDATESTRING="2005-05-24" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38505.70454861" TITLE="ask cprince about SCons builder in new mk system" TIMEESTUNITS="H" ID="24" PERCENTDONE="100" STARTDATE="38496.00000000" DONEDATESTRING="2005-06-02" POS="25" DONEDATE="38505.00000000"/>
++ <TASK STARTDATESTRING="2005-05-24" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38504.57436343" TITLE="fix AOL resource in trunk (&quot;???????&quot;)" TIMEESTUNITS="H" ID="26" PERCENTDONE="100" STARTDATE="38496.00000000" DONEDATESTRING="2005-06-01" POS="19" DONEDATE="38504.00000000"/>
++ <TASK STARTDATESTRING="2005-05-24" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38498.53893519" TITLE="rc_all vs. rc_translateable vs. rc_nontranslateable" TIMEESTUNITS="H" ID="27" PERCENTDONE="100" STARTDATE="38496.00000000" DONEDATESTRING="2005-06-16" POS="6" DONEDATE="38519.00000000"/>
++ <TASK STARTDATESTRING="2005-05-31" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38509.45532407" TITLE="make separate .grb &quot;outputs&quot; file (and change SCons integ) (??)" TIMEESTUNITS="H" ID="28" PERCENTDONE="100" STARTDATE="38503.00000000" DONEDATESTRING="2005-06-06" POS="8" DONEDATE="38509.00000000"/>
++ <TASK STARTDATESTRING="2005-05-31" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38511.00939815" TITLE="fix unit tests so they run from any directory" TIMEESTUNITS="H" ID="33" PERCENTDONE="100" STARTDATE="38503.00000000" DONEDATESTRING="2005-06-08" POS="18" DONEDATE="38511.00000000"/>
++ <TASK STARTDATESTRING="2005-05-31" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38508.96640046" TITLE="Change R4 tool to CC correct team(s) on GRIT changes" TIMEESTUNITS="H" ID="39" PERCENTDONE="100" STARTDATE="38503.00000000" DONEDATESTRING="2005-06-05" POS="23" DONEDATE="38508.00000000"/>
++ <TASK STARTDATESTRING="2005-06-07" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38511.00881944" TITLE="Document why wrapper.rc" TIMEESTUNITS="H" ID="40" PERCENTDONE="100" STARTDATE="38510.00000000" DONEDATESTRING="2005-06-08" POS="21" DONEDATE="38511.00000000"/>
++ <TASK STARTDATESTRING="2005-06-08" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38511.00804398" TITLE="import XTBs" TIMEESTUNITS="H" ID="41" PERCENTDONE="100" STARTDATE="38511.00000000" DONEDATESTRING="2005-06-16" POS="11" DONEDATE="38519.00000000"/>
++ <TASK STARTDATESTRING="2005-06-08" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38511.00875000" TITLE="Nightly build integration" TIMEESTUNITS="H" ID="42" STARTDATE="38511.00000000" POS="3"/>
++ <TASK STARTDATESTRING="2005-06-08" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38511.00891204" TITLE="BUGS" TIMEESTUNITS="H" ID="43" STARTDATE="38511.00000000" POS="24">
++ <TASK STARTDATESTRING="2005-06-08" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38513.03375000" TITLE="Should report error if RC-section structure refers to does not exist" TIMEESTUNITS="H" ID="44" PERCENTDONE="100" STARTDATE="38511.00000000" DONEDATESTRING="2005-06-10" POS="1" DONEDATE="38513.00000000"/>
++ </TASK>
++ <TASK STARTDATESTRING="2005-06-08" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38511.00981481" TITLE="NEW FEATURES" TIMEESTUNITS="H" ID="45" PERCENTDONE="100" STARTDATE="38511.00000000" DONEDATESTRING="2005-06-16" POS="7" DONEDATE="38519.00000000">
++ <TASK STARTDATESTRING="2005-05-31" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38505.70077546" TITLE="Implement line-continuation feature (\ at end of line?)" TIMEESTUNITS="H" ID="34" PERCENTDONE="100" STARTDATE="38503.00000000" DONEDATESTRING="2005-06-16" POS="1" DONEDATE="38519.00000000"/>
++ <TASK STARTDATESTRING="2005-05-31" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38505.70262731" TITLE="Implement conditional inclusion &amp; reflect the conditionals from R3 RC file" TIMEESTUNITS="H" ID="35" PERCENTDONE="100" STARTDATE="38503.00000000" DONEDATESTRING="2005-06-16" POS="2" DONEDATE="38519.00000000"/>
++ </TASK>
++ <TASK STARTDATESTRING="2005-06-08" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38511.01046296" TITLE="TC integration (one-way TO the TC)" TIMEESTUNITS="H" ID="46" PERCENTDONE="100" STARTDATE="38511.00000000" DONEDATESTRING="2005-06-16" POS="5" DONEDATE="38519.00000000"/>
++ <TASK STARTDATESTRING="2005-06-30" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38533.59072917" TITLE="bazaar20 ad for GRIT help" TIMEESTUNITS="H" ID="49" STARTDATE="38533.00000000" POS="2">
++ <TASK STARTDATESTRING="2005-08-19" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38583.72346065" TITLE="bazaar20 ideas" TIMEESTUNITS="H" ID="51" STARTDATE="38583.00000000" POS="1">
++ <TASK STARTDATESTRING="2005-08-19" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38583.72354167" TITLE="GUI for adding/editing messages" TIMEESTUNITS="H" ID="52" STARTDATE="38583.00000000" POS="2"/>
++ <TASK STARTDATESTRING="2005-08-19" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38583.72365741" TITLE="XLIFF import/export" TIMEESTUNITS="H" ID="54" STARTDATE="38583.00000000" POS="1"/>
++ </TASK>
++ </TASK>
++ <TASK STARTDATESTRING="2005-06-30" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38583.73721065" TITLE="internal_comment for all resource nodes (not just &lt;message&gt;)" TIMEESTUNITS="H" ID="50" PERCENTDONE="100" STARTDATE="38533.00000000" DONEDATESTRING="2005-08-19" POS="9" DONEDATE="38583.73721065"/>
++ <TASK STARTDATESTRING="2005-08-19" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38583.73743056" TITLE="Preserve XML comments - this gives us line continuation and more" TIMEESTUNITS="H" ID="55" STARTDATE="38583.72326389" POS="1"/>
++</TODOLIST>
+diff --git a/tools/grit/grit/grit_runner.py b/tools/grit/grit/grit_runner.py
+new file mode 100644
+index 0000000000..26aa0d58c4
+--- /dev/null
++++ b/tools/grit/grit/grit_runner.py
+@@ -0,0 +1,334 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
+
-+ private:
-+ friend class base::RefCounted<PublicRefCountedDtorInImpl>;
-+};
++"""Command processor for GRIT. This is the script you invoke to run the various
++GRIT tools.
++"""
+
-+class Foo {
-+ public:
-+ class BarInterface {
-+ protected:
-+ virtual ~BarInterface() {}
-+ };
++from __future__ import print_function
+
-+ typedef base::RefCounted<BarInterface> RefCountedBar;
-+ typedef RefCountedBar AnotherTypedef;
-+};
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
++
++import getopt
++
++from grit import util
++
++import grit.extern.FP
++
++# Tool info factories; these import only within each factory to avoid
++# importing most of the GRIT code until required.
++def ToolFactoryBuild():
++ import grit.tool.build
++ return grit.tool.build.RcBuilder()
++
++def ToolFactoryBuildInfo():
++ import grit.tool.buildinfo
++ return grit.tool.buildinfo.DetermineBuildInfo()
++
++def ToolFactoryCount():
++ import grit.tool.count
++ return grit.tool.count.CountMessage()
++
++def ToolFactoryDiffStructures():
++ import grit.tool.diff_structures
++ return grit.tool.diff_structures.DiffStructures()
++
++def ToolFactoryMenuTranslationsFromParts():
++ import grit.tool.menu_from_parts
++ return grit.tool.menu_from_parts.MenuTranslationsFromParts()
++
++def ToolFactoryNewGrd():
++ import grit.tool.newgrd
++ return grit.tool.newgrd.NewGrd()
++
++def ToolFactoryResizeDialog():
++ import grit.tool.resize
++ return grit.tool.resize.ResizeDialog()
++
++def ToolFactoryRc2Grd():
++ import grit.tool.rc2grd
++ return grit.tool.rc2grd.Rc2Grd()
++
++def ToolFactoryTest():
++ import grit.tool.test
++ return grit.tool.test.TestTool()
++
++def ToolFactoryTranslationToTc():
++ import grit.tool.transl2tc
++ return grit.tool.transl2tc.TranslationToTc()
++
++def ToolFactoryUnit():
++ import grit.tool.unit
++ return grit.tool.unit.UnitTestTool()
++
++
++def ToolFactoryUpdateResourceIds():
++ import grit.tool.update_resource_ids
++ return grit.tool.update_resource_ids.UpdateResourceIds()
++
++
++def ToolFactoryXmb():
++ import grit.tool.xmb
++ return grit.tool.xmb.OutputXmb()
++
++def ToolAndroid2Grd():
++ import grit.tool.android2grd
++ return grit.tool.android2grd.Android2Grd()
++
++# Keys for the following map
++_FACTORY = 1
++_REQUIRES_INPUT = 2
++_HIDDEN = 3 # optional key - presence indicates tool is hidden
++
++# Maps tool names to the tool's module. Done as a list of (key, value) tuples
++# instead of a map to preserve ordering.
++_TOOLS = [
++ ['android2grd', {
++ _FACTORY: ToolAndroid2Grd,
++ _REQUIRES_INPUT: False
++ }],
++ ['build', {
++ _FACTORY: ToolFactoryBuild,
++ _REQUIRES_INPUT: True
++ }],
++ ['buildinfo', {
++ _FACTORY: ToolFactoryBuildInfo,
++ _REQUIRES_INPUT: True
++ }],
++ ['count', {
++ _FACTORY: ToolFactoryCount,
++ _REQUIRES_INPUT: True
++ }],
++ [
++ 'menufromparts',
++ {
++ _FACTORY: ToolFactoryMenuTranslationsFromParts,
++ _REQUIRES_INPUT: True,
++ _HIDDEN: True
++ }
++ ],
++ ['newgrd', {
++ _FACTORY: ToolFactoryNewGrd,
++ _REQUIRES_INPUT: False
++ }],
++ ['rc2grd', {
++ _FACTORY: ToolFactoryRc2Grd,
++ _REQUIRES_INPUT: False
++ }],
++ ['resize', {
++ _FACTORY: ToolFactoryResizeDialog,
++ _REQUIRES_INPUT: True
++ }],
++ ['sdiff', {
++ _FACTORY: ToolFactoryDiffStructures,
++ _REQUIRES_INPUT: False
++ }],
++ ['test', {
++ _FACTORY: ToolFactoryTest,
++ _REQUIRES_INPUT: True,
++ _HIDDEN: True
++ }],
++ [
++ 'transl2tc',
++ {
++ _FACTORY: ToolFactoryTranslationToTc,
++ _REQUIRES_INPUT: False
++ }
++ ],
++ ['unit', {
++ _FACTORY: ToolFactoryUnit,
++ _REQUIRES_INPUT: False
++ }],
++ [
++ 'update_resource_ids',
++ {
++ _FACTORY: ToolFactoryUpdateResourceIds,
++ _REQUIRES_INPUT: False
++ }
++ ],
++ ['xmb', {
++ _FACTORY: ToolFactoryXmb,
++ _REQUIRES_INPUT: True
++ }],
++]
++
++
++def PrintUsage():
++ tool_list = ''
++ for (tool, info) in _TOOLS:
++ if not _HIDDEN in info:
++ tool_list += ' %-12s %s\n' % (
++ tool, info[_FACTORY]().ShortDescription())
++
++ print("""GRIT - the Google Resource and Internationalization Tool
++
++Usage: grit [GLOBALOPTIONS] TOOL [args to tool]
++
++Global options:
++
++ -i INPUT Specifies the INPUT file to use (a .grd file). If this is not
++ specified, GRIT will look for the environment variable GRIT_INPUT.
++ If it is not present either, GRIT will try to find an input file
++ named 'resource.grd' in the current working directory.
++
++ -h MODULE Causes GRIT to use MODULE.UnsignedFingerPrint instead of
++ grit.extern.FP.UnsignedFingerprint. MODULE must be
++ available somewhere in the PYTHONPATH search path.
++
++ -v Print more verbose runtime information.
++
++ -x Print extremely verbose runtime information. Implies -v
++
++ -p FNAME Specifies that GRIT should profile its execution and output the
++ results to the file FNAME.
++
++Tools:
++
++ TOOL can be one of the following:
++%s
++ For more information on how to use a particular tool, and the specific
++ arguments you can send to that tool, execute 'grit help TOOL'
++""" % (tool_list))
++
++
++class Options(object):
++ """Option storage and parsing."""
++
++ def __init__(self):
++ self.hash = None
++ self.input = None
++ self.verbose = False
++ self.extra_verbose = False
++ self.output_stream = sys.stdout
++ self.profile_dest = None
++
++ def ReadOptions(self, args):
++ """Reads options from the start of args and returns the remainder."""
++ (opts, args) = getopt.getopt(args, 'vxi:p:h:', ('help',))
++ for (key, val) in opts:
++ if key == '-h': self.hash = val
++ elif key == '-i': self.input = val
++ elif key == '-v':
++ self.verbose = True
++ util.verbose = True
++ elif key == '-x':
++ self.verbose = True
++ util.verbose = True
++ self.extra_verbose = True
++ util.extra_verbose = True
++ elif key == '-p': self.profile_dest = val
++ elif key == '--help':
++ PrintUsage()
++ sys.exit(0)
++
++ if not self.input:
++ if 'GRIT_INPUT' in os.environ:
++ self.input = os.environ['GRIT_INPUT']
++ else:
++ self.input = 'resource.grd'
++
++ return args
++
++ def __repr__(self):
++ return '(verbose: %d, input: %s)' % (
++ self.verbose, self.input)
++
++
++def _GetToolInfo(tool):
++ """Returns the info map for the tool named 'tool' or None if there is no
++ such tool."""
++ matches = [t for t in _TOOLS if t[0] == tool]
++ if not matches:
++ return None
++ else:
++ return matches[0][1]
++
++
++def Main(args=None):
++ """Parses arguments and does the appropriate thing."""
++ util.ChangeStdoutEncoding()
++
++ # Support for setuptools console wrappers.
++ if args is None:
++ args = sys.argv[1:]
++
++ options = Options()
++ try:
++ args = options.ReadOptions(args) # args may be shorter after this
++ except getopt.GetoptError as e:
++ print("grit:", str(e))
++ print("Try running 'grit help' for valid options.")
++ return 1
++ if not args:
++ print("No tool provided. Try running 'grit help' for a list of tools.")
++ return 2
++
++ tool = args[0]
++ if tool == 'help':
++ if len(args) == 1:
++ PrintUsage()
++ return 0
++ else:
++ tool = args[1]
++ if not _GetToolInfo(tool):
++ print("No such tool. Try running 'grit help' for a list of tools.")
++ return 2
++
++ print("Help for 'grit %s' (for general help, run 'grit help'):\n" %
++ (tool,))
++ _GetToolInfo(tool)[_FACTORY]().ShowUsage()
++ return 0
++ if not _GetToolInfo(tool):
++ print("No such tool. Try running 'grit help' for a list of tools.")
++ return 2
++
++ try:
++ if _GetToolInfo(tool)[_REQUIRES_INPUT]:
++ os.stat(options.input)
++ except OSError:
++ print('Input file %s not found.\n'
++ 'To specify a different input file:\n'
++ ' 1. Use the GRIT_INPUT environment variable.\n'
++ ' 2. Use the -i command-line option. This overrides '
++ 'GRIT_INPUT.\n'
++ ' 3. Specify neither GRIT_INPUT or -i and GRIT will try to load '
++ "'resource.grd'\n"
++ ' from the current directory.' % options.input)
++ return 2
++
++ if options.hash:
++ grit.extern.FP.UseUnsignedFingerPrintFromModule(options.hash)
++
++ try:
++ toolobject = _GetToolInfo(tool)[_FACTORY]()
++ if options.profile_dest:
++ import hotshot
++ prof = hotshot.Profile(options.profile_dest)
++ return prof.runcall(toolobject.Run, options, args[1:])
++ else:
++ return toolobject.Run(options, args[1:])
++ except getopt.GetoptError as e:
++ print("grit: %s: %s" % (tool, str(e)))
++ print("Try running 'grit help %s' for valid options." % (tool,))
++ return 1
+
-+class Baz {
-+ public:
-+ typedef typename Foo::AnotherTypedef MyLocalTypedef;
-+};
+
-+// Unsafe; should error.
-+class UnsafeTypedefChainInImpl : public Baz::MyLocalTypedef {
-+ public:
-+ UnsafeTypedefChainInImpl() {}
-+ ~UnsafeTypedefChainInImpl() {}
-+};
++if __name__ == '__main__':
++ sys.path.append(
++ os.path.join(
++ os.path.dirname(
++ os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
++ 'diagnosis'))
++ try:
++ import crbug_1001171
++ with crbug_1001171.DumpStateOnLookupError():
++ sys.exit(Main(sys.argv[1:]))
++ except ImportError:
++ pass
++
++ sys.exit(Main(sys.argv[1:]))
+diff --git a/tools/grit/grit/grit_runner_unittest.py b/tools/grit/grit/grit_runner_unittest.py
+new file mode 100644
+index 0000000000..1487001d81
+--- /dev/null
++++ b/tools/grit/grit/grit_runner_unittest.py
+@@ -0,0 +1,42 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit tests for grit.py'''
++
++from __future__ import print_function
+
-+int main() {
-+ PublicRefCountedDtorInHeader bad;
-+ PublicRefCountedDtorInImpl also_bad;
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
+
-+ ProtectedRefCountedDtorInHeader* protected_ok = NULL;
-+ PrivateRefCountedDtorInHeader* private_ok = NULL;
++import unittest
+
-+ DerivedProtectedToPublicInHeader still_bad;
-+ PublicRefCountedThreadSafeDtorInHeader another_bad_variation;
-+ AnonymousDerivedProtectedToPublicInImpl and_this_is_bad_too;
-+ ImplicitDerivedProtectedToPublicInHeader bad_yet_again;
-+ UnsafeTypedefChainInImpl and_again_this_is_bad;
++from six import StringIO
+
-+ WebKitPublicDtorInHeader ignored;
-+ WebKitDerivedPublicDtorInHeader still_ignored;
++from grit import util
++import grit.grit_runner
+
-+ return 0;
-+}
-diff --git a/tools/clang/plugins/tests/base_refcounted.h b/tools/clang/plugins/tests/base_refcounted.h
++class OptionArgsUnittest(unittest.TestCase):
++ def setUp(self):
++ self.buf = StringIO()
++ self.old_stdout = sys.stdout
++ sys.stdout = self.buf
++
++ def tearDown(self):
++ sys.stdout = self.old_stdout
++
++ def testSimple(self):
++ grit.grit_runner.Main(['-i',
++ util.PathFromRoot('grit/testdata/simple-input.xml'),
++ 'test', 'bla', 'voff', 'ga'])
++ output = self.buf.getvalue()
++ self.failUnless(output.count("'test'") == 0) # tool name doesn't occur
++ self.failUnless(output.count('bla'))
++ self.failUnless(output.count('simple-input.xml'))
++
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/lazy_re.py b/tools/grit/grit/lazy_re.py
new file mode 100644
-index 0000000000..1e53215997
+index 0000000000..5c461e87e7
--- /dev/null
-+++ b/tools/clang/plugins/tests/base_refcounted.h
-@@ -0,0 +1,121 @@
-+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+// Use of this source code is governed by a BSD-style license that can be
-+// found in the LICENSE file.
++++ b/tools/grit/grit/lazy_re.py
+@@ -0,0 +1,46 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
+
-+#ifndef BASE_REFCOUNTED_H_
-+#define BASE_REFCOUNTED_H_
++'''In GRIT, we used to compile a lot of regular expressions at parse
++time. Since many of them never get used, we use lazy_re to compile
++them on demand the first time they are used, thus speeding up startup
++time in some cases.
++'''
++
++from __future__ import print_function
++
++import re
++
++
++class LazyRegexObject(object):
++ '''This object creates a RegexObject with the arguments passed in
++ its constructor, the first time any attribute except the several on
++ the class itself is accessed. This accomplishes lazy compilation of
++ the regular expression while maintaining a nearly-identical
++ interface.
++ '''
++
++ def __init__(self, *args, **kwargs):
++ self._stash_args = args
++ self._stash_kwargs = kwargs
++ self._lazy_re = None
++
++ def _LazyInit(self):
++ if not self._lazy_re:
++ self._lazy_re = re.compile(*self._stash_args, **self._stash_kwargs)
++
++ def __getattribute__(self, name):
++ if name in ('_LazyInit', '_lazy_re', '_stash_args', '_stash_kwargs'):
++ return object.__getattribute__(self, name)
++ else:
++ self._LazyInit()
++ return getattr(self._lazy_re, name)
++
++
++def compile(*args, **kwargs):
++ '''Creates a LazyRegexObject that, when invoked on, will compile a
++ re.RegexObject (via re.compile) with the same arguments passed to
++ this function, and delegate almost all of its methods to it.
++ '''
++ return LazyRegexObject(*args, **kwargs)
+diff --git a/tools/grit/grit/lazy_re_unittest.py b/tools/grit/grit/lazy_re_unittest.py
+new file mode 100644
+index 0000000000..8488b454ee
+--- /dev/null
++++ b/tools/grit/grit/lazy_re_unittest.py
+@@ -0,0 +1,40 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
+
-+namespace base {
++'''Unit test for lazy_re.
++'''
+
-+template <typename T>
-+class RefCounted {
-+ public:
-+ RefCounted() {}
-+ ~RefCounted() {}
-+};
++from __future__ import print_function
+
-+template <typename T>
-+class RefCountedThreadSafe {
-+ public:
-+ RefCountedThreadSafe() {}
-+ ~RefCountedThreadSafe() {}
-+};
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
+
-+} // namespace base
++import re
++import unittest
+
-+// Ignore classes whose inheritance tree ends in WebKit's RefCounted base
-+// class. Though prone to error, this pattern is very prevalent in WebKit
-+// code, so do not issue any warnings.
-+namespace WebKit {
++from grit import lazy_re
+
-+template <typename T>
-+class RefCounted {
-+ public:
-+ RefCounted() {}
-+ ~RefCounted() {}
-+};
+
-+} // namespace WebKit
++class LazyReUnittest(unittest.TestCase):
+
-+// Unsafe; should error.
-+class PublicRefCountedDtorInHeader
-+ : public base::RefCounted<PublicRefCountedDtorInHeader> {
-+ public:
-+ PublicRefCountedDtorInHeader() {}
-+ ~PublicRefCountedDtorInHeader() {}
++ def testCreatedOnlyOnDemand(self):
++ rex = lazy_re.compile('bingo')
++ self.assertEqual(None, rex._lazy_re)
++ self.assertTrue(rex.match('bingo'))
++ self.assertNotEqual(None, rex._lazy_re)
+
-+ private:
-+ friend class base::RefCounted<PublicRefCountedDtorInHeader>;
-+};
++ def testJustKwargsWork(self):
++ rex = lazy_re.compile(flags=re.I, pattern='BiNgO')
++ self.assertTrue(rex.match('bingo'))
++
++ def testPositionalAndKwargsWork(self):
++ rex = lazy_re.compile('BiNgO', flags=re.I)
++ self.assertTrue(rex.match('bingo'))
+
-+// Unsafe; should error.
-+class PublicRefCountedThreadSafeDtorInHeader
-+ : public base::RefCountedThreadSafe<
-+ PublicRefCountedThreadSafeDtorInHeader> {
-+ public:
-+ PublicRefCountedThreadSafeDtorInHeader() {}
-+ ~PublicRefCountedThreadSafeDtorInHeader() {}
-+
-+ private:
-+ friend class base::RefCountedThreadSafe<
-+ PublicRefCountedThreadSafeDtorInHeader>;
-+};
+
-+// Safe; should not have errors.
-+class ProtectedRefCountedDtorInHeader
-+ : public base::RefCounted<ProtectedRefCountedDtorInHeader> {
-+ public:
-+ ProtectedRefCountedDtorInHeader() {}
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/node/__init__.py b/tools/grit/grit/node/__init__.py
+new file mode 100644
+index 0000000000..2fc0d3360c
+--- /dev/null
++++ b/tools/grit/grit/node/__init__.py
+@@ -0,0 +1,8 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
+
-+ protected:
-+ ~ProtectedRefCountedDtorInHeader() {}
++'''Package 'grit.node'
++'''
+
-+ private:
-+ friend class base::RefCounted<ProtectedRefCountedDtorInHeader>;
-+};
++pass
+diff --git a/tools/grit/grit/node/base.py b/tools/grit/grit/node/base.py
+new file mode 100644
+index 0000000000..40859d301d
+--- /dev/null
++++ b/tools/grit/grit/node/base.py
+@@ -0,0 +1,670 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
+
-+// Safe; should not have errors.
-+class PrivateRefCountedDtorInHeader
-+ : public base::RefCounted<PrivateRefCountedDtorInHeader> {
-+ public:
-+ PrivateRefCountedDtorInHeader() {}
++'''Base types for nodes in a GRIT resource tree.
++'''
+
-+ private:
-+ ~PrivateRefCountedDtorInHeader() {}
-+ friend class base::RefCounted<PrivateRefCountedDtorInHeader>;
-+};
++from __future__ import print_function
+
-+// Unsafe; A grandchild class ends up exposing their parent and grandparent's
-+// destructors.
-+class DerivedProtectedToPublicInHeader
-+ : public ProtectedRefCountedDtorInHeader {
-+ public:
-+ DerivedProtectedToPublicInHeader() {}
-+ ~DerivedProtectedToPublicInHeader() {}
-+};
++import ast
++import os
++import struct
++import sys
++from xml.sax import saxutils
++
++import six
++
++from grit import constants
++from grit import clique
++from grit import exception
++from grit import util
++from grit.node import brotli_util
++import grit.format.gzip_string
++
++
++class Node(object):
++ '''An item in the tree that has children.'''
++
++ # Valid content types that can be returned by _ContentType()
++ _CONTENT_TYPE_NONE = 0 # No CDATA content but may have children
++ _CONTENT_TYPE_CDATA = 1 # Only CDATA, no children.
++ _CONTENT_TYPE_MIXED = 2 # CDATA and children, possibly intermingled
++
++ # Types of files to be compressed by default.
++ _COMPRESS_BY_DEFAULT_EXTENSIONS = ('.js', '.html', '.css', '.svg')
++
++ # Default nodes to not whitelist skipped
++ _whitelist_marked_as_skip = False
++
++ # A class-static cache to speed up EvaluateExpression().
++ # Keys are expressions (e.g. 'is_ios and lang == "fr"'). Values are tuples
++ # (code, variables_in_expr) where code is the compiled expression and can be
++ # directly eval'd, and variables_in_expr is the list of variable and method
++ # names used in the expression (e.g. ['is_ios', 'lang']).
++ eval_expr_cache = {}
++
++ def __init__(self):
++ self.children = [] # A list of child elements
++ self.mixed_content = [] # A list of u'' and/or child elements (this
++ # duplicates 'children' but
++ # is needed to preserve markup-type content).
++ self.name = u'' # The name of this element
++ self.attrs = {} # The set of attributes (keys to values)
++ self.parent = None # Our parent unless we are the root element.
++ self.uberclique = None # Allows overriding uberclique for parts of tree
++ self.source = None # File that this node was parsed from
++
++ # This context handler allows you to write "with node:" and get a
++ # line identifying the offending node if an exception escapes from the body
++ # of the with statement.
++ def __enter__(self):
++ return self
++
++ def __exit__(self, exc_type, exc_value, traceback):
++ if exc_type is not None:
++ print(u'Error processing node %s: %s' % (six.text_type(self), exc_value))
++
++ def __iter__(self):
++ '''A preorder iteration through the tree that this node is the root of.'''
++ return self.Preorder()
++
++ def Preorder(self):
++ '''Generator that generates first this node, then the same generator for
++ any child nodes.'''
++ yield self
++ for child in self.children:
++ for iterchild in child.Preorder():
++ yield iterchild
++
++ def ActiveChildren(self):
++ '''Returns the children of this node that should be included in the current
++ configuration. Overridden by <if>.'''
++ return [node for node in self.children if not node.WhitelistMarkedAsSkip()]
++
++ def ActiveDescendants(self):
++ '''Yields the current node and all descendants that should be included in
++ the current configuration, in preorder.'''
++ yield self
++ for child in self.ActiveChildren():
++ for descendant in child.ActiveDescendants():
++ yield descendant
++
++ def GetRoot(self):
++ '''Returns the root Node in the tree this Node belongs to.'''
++ curr = self
++ while curr.parent:
++ curr = curr.parent
++ return curr
++
++ # TODO(joi) Use this (currently untested) optimization?:
++ #if hasattr(self, '_root'):
++ # return self._root
++ #curr = self
++ #while curr.parent and not hasattr(curr, '_root'):
++ # curr = curr.parent
++ #if curr.parent:
++ # self._root = curr._root
++ #else:
++ # self._root = curr
++ #return self._root
++
++ def StartParsing(self, name, parent):
++ '''Called at the start of parsing.
++
++ Args:
++ name: u'elementname'
++ parent: grit.node.base.Node or subclass or None
++ '''
++ assert isinstance(name, six.string_types)
++ assert not parent or isinstance(parent, Node)
++ self.name = name
++ self.parent = parent
++
++ def AddChild(self, child):
++ '''Adds a child to the list of children of this node, if it is a valid
++ child for the node.'''
++ assert isinstance(child, Node)
++ if (not self._IsValidChild(child) or
++ self._ContentType() == self._CONTENT_TYPE_CDATA):
++ explanation = 'invalid child %s for parent %s' % (str(child), self.name)
++ raise exception.UnexpectedChild(explanation)
++ self.children.append(child)
++ self.mixed_content.append(child)
++
++ def RemoveChild(self, child_id):
++ '''Removes the first node that has a "name" attribute which
++ matches "child_id" in the list of immediate children of
++ this node.
++
++ Args:
++ child_id: String identifying the child to be removed
++ '''
++ index = 0
++ # Safe not to copy since we only remove the first element found
++ for child in self.children:
++ name_attr = child.attrs['name']
++ if name_attr == child_id:
++ self.children.pop(index)
++ self.mixed_content.pop(index)
++ break
++ index += 1
++
++ def AppendContent(self, content):
++ '''Appends a chunk of text as content of this node.
++
++ Args:
++ content: u'hello'
++
++ Return:
++ None
++ '''
++ assert isinstance(content, six.string_types)
++ if self._ContentType() != self._CONTENT_TYPE_NONE:
++ self.mixed_content.append(content)
++ elif content.strip() != '':
++ raise exception.UnexpectedContent()
++
++ def HandleAttribute(self, attrib, value):
++ '''Informs the node of an attribute that was parsed out of the GRD file
++ for it.
++
++ Args:
++ attrib: 'name'
++ value: 'fooblat'
++
++ Return:
++ None
++ '''
++ assert isinstance(attrib, six.string_types)
++ assert isinstance(value, six.string_types)
++ if self._IsValidAttribute(attrib, value):
++ self.attrs[attrib] = value
++ else:
++ raise exception.UnexpectedAttribute(attrib)
++
++ def EndParsing(self):
++ '''Called at the end of parsing.'''
++
++ # TODO(joi) Rewrite this, it's extremely ugly!
++ if len(self.mixed_content):
++ if isinstance(self.mixed_content[0], six.string_types):
++ # Remove leading and trailing chunks of pure whitespace.
++ while (len(self.mixed_content) and
++ isinstance(self.mixed_content[0], six.string_types) and
++ self.mixed_content[0].strip() == ''):
++ self.mixed_content = self.mixed_content[1:]
++ # Strip leading and trailing whitespace from mixed content chunks
++ # at front and back.
++ if (len(self.mixed_content) and
++ isinstance(self.mixed_content[0], six.string_types)):
++ self.mixed_content[0] = self.mixed_content[0].lstrip()
++ # Remove leading and trailing ''' (used to demarcate whitespace)
++ if (len(self.mixed_content) and
++ isinstance(self.mixed_content[0], six.string_types)):
++ if self.mixed_content[0].startswith("'''"):
++ self.mixed_content[0] = self.mixed_content[0][3:]
++ if len(self.mixed_content):
++ if isinstance(self.mixed_content[-1], six.string_types):
++ # Same stuff all over again for the tail end.
++ while (len(self.mixed_content) and
++ isinstance(self.mixed_content[-1], six.string_types) and
++ self.mixed_content[-1].strip() == ''):
++ self.mixed_content = self.mixed_content[:-1]
++ if (len(self.mixed_content) and
++ isinstance(self.mixed_content[-1], six.string_types)):
++ self.mixed_content[-1] = self.mixed_content[-1].rstrip()
++ if (len(self.mixed_content) and
++ isinstance(self.mixed_content[-1], six.string_types)):
++ if self.mixed_content[-1].endswith("'''"):
++ self.mixed_content[-1] = self.mixed_content[-1][:-3]
++
++ # Check that all mandatory attributes are there.
++ for node_mandatt in self.MandatoryAttributes():
++ mandatt_list = []
++ if node_mandatt.find('|') >= 0:
++ mandatt_list = node_mandatt.split('|')
++ else:
++ mandatt_list.append(node_mandatt)
++
++ mandatt_option_found = False
++ for mandatt in mandatt_list:
++ assert mandatt not in self.DefaultAttributes()
++ if mandatt in self.attrs:
++ if not mandatt_option_found:
++ mandatt_option_found = True
++ else:
++ raise exception.MutuallyExclusiveMandatoryAttribute(mandatt)
++
++ if not mandatt_option_found:
++ raise exception.MissingMandatoryAttribute(mandatt)
++
++ # Add default attributes if not specified in input file.
++ for defattr in self.DefaultAttributes():
++ if not defattr in self.attrs:
++ self.attrs[defattr] = self.DefaultAttributes()[defattr]
++
++ def GetCdata(self):
++ '''Returns all CDATA of this element, concatenated into a single
++ string. Note that this ignores any elements embedded in CDATA.'''
++ return ''.join([c for c in self.mixed_content
++ if isinstance(c, six.string_types)])
++
++ def __str__(self):
++ '''Returns this node and all nodes below it as an XML document in a Unicode
++ string.'''
++ header = u'<?xml version="1.0" encoding="UTF-8"?>\n'
++ return header + self.FormatXml()
++
++ # Some Python 2 glue.
++ __unicode__ = __str__
++
++ def FormatXml(self, indent = u'', one_line = False):
++ '''Returns this node and all nodes below it as an XML
++ element in a Unicode string. This differs from __unicode__ in that it does
++ not include the <?xml> stuff at the top of the string. If one_line is true,
++ children and CDATA are layed out in a way that preserves internal
++ whitespace.
++ '''
++ assert isinstance(indent, six.string_types)
++
++ content_one_line = (one_line or
++ self._ContentType() == self._CONTENT_TYPE_MIXED)
++ inside_content = self.ContentsAsXml(indent, content_one_line)
++
++ # Then the attributes for this node.
++ attribs = u''
++ default_attribs = self.DefaultAttributes()
++ for attrib, value in sorted(self.attrs.items()):
++ # Only print an attribute if it is other than the default value.
++ if attrib not in default_attribs or value != default_attribs[attrib]:
++ attribs += u' %s=%s' % (attrib, saxutils.quoteattr(value))
++
++ # Finally build the XML for our node and return it
++ if len(inside_content) > 0:
++ if one_line:
++ return u'<%s%s>%s</%s>' % (self.name, attribs, inside_content,
++ self.name)
++ elif content_one_line:
++ return u'%s<%s%s>\n%s %s\n%s</%s>' % (
++ indent, self.name, attribs,
++ indent, inside_content,
++ indent, self.name)
++ else:
++ return u'%s<%s%s>\n%s\n%s</%s>' % (
++ indent, self.name, attribs,
++ inside_content,
++ indent, self.name)
++ else:
++ return u'%s<%s%s />' % (indent, self.name, attribs)
++
++ def ContentsAsXml(self, indent, one_line):
++ '''Returns the contents of this node (CDATA and child elements) in XML
++ format. If 'one_line' is true, the content will be laid out on one line.'''
++ assert isinstance(indent, six.string_types)
++
++ # Build the contents of the element.
++ inside_parts = []
++ last_item = None
++ for mixed_item in self.mixed_content:
++ if isinstance(mixed_item, Node):
++ inside_parts.append(mixed_item.FormatXml(indent + u' ', one_line))
++ if not one_line:
++ inside_parts.append(u'\n')
++ else:
++ message = mixed_item
++ # If this is the first item and it starts with whitespace, we add
++ # the ''' delimiter.
++ if not last_item and message.lstrip() != message:
++ message = u"'''" + message
++ inside_parts.append(util.EncodeCdata(message))
++ last_item = mixed_item
++
++ # If there are only child nodes and no cdata, there will be a spurious
++ # trailing \n
++ if len(inside_parts) and inside_parts[-1] == '\n':
++ inside_parts = inside_parts[:-1]
++
++ # If the last item is a string (not a node) and ends with whitespace,
++ # we need to add the ''' delimiter.
++ if (isinstance(last_item, six.string_types) and
++ last_item.rstrip() != last_item):
++ inside_parts[-1] = inside_parts[-1] + u"'''"
++
++ return u''.join(inside_parts)
++
++ def SubstituteMessages(self, substituter):
++ '''Applies substitutions to all messages in the tree.
++
++ Called as a final step of RunGatherers.
++
++ Args:
++ substituter: a grit.util.Substituter object.
++ '''
++ for child in self.children:
++ child.SubstituteMessages(substituter)
++
++ def _IsValidChild(self, child):
++ '''Returns true if 'child' is a valid child of this node.
++ Overridden by subclasses.'''
++ return False
++
++ def _IsValidAttribute(self, name, value):
++ '''Returns true if 'name' is the name of a valid attribute of this element
++ and 'value' is a valid value for that attribute. Overriden by
++ subclasses unless they have only mandatory attributes.'''
++ return (name in self.MandatoryAttributes() or
++ name in self.DefaultAttributes())
++
++ def _ContentType(self):
++ '''Returns the type of content this element can have. Overridden by
++ subclasses. The content type can be one of the _CONTENT_TYPE_XXX constants
++ above.'''
++ return self._CONTENT_TYPE_NONE
++
++ def MandatoryAttributes(self):
++ '''Returns a list of attribute names that are mandatory (non-optional)
++ on the current element. One can specify a list of
++ "mutually exclusive mandatory" attributes by specifying them as one
++ element in the list, separated by a "|" character.
++ '''
++ return []
++
++ def DefaultAttributes(self):
++ '''Returns a dictionary of attribute names that have defaults, mapped to
++ the default value. Overridden by subclasses.'''
++ return {}
++
++ def GetCliques(self):
++ '''Returns all MessageClique objects belonging to this node. Overridden
++ by subclasses.
++
++ Return:
++ [clique1, clique2] or []
++ '''
++ return []
++
++ def ToRealPath(self, path_from_basedir):
++ '''Returns a real path (which can be absolute or relative to the current
++ working directory), given a path that is relative to the base directory
++ set for the GRIT input file.
++
++ Args:
++ path_from_basedir: '..'
++
++ Return:
++ 'resource'
++ '''
++ return util.normpath(os.path.join(self.GetRoot().GetBaseDir(),
++ os.path.expandvars(path_from_basedir)))
++
++ def GetInputPath(self):
++ '''Returns a path, relative to the base directory set for the grd file,
++ that points to the file the node refers to.
++ '''
++ # This implementation works for most nodes that have an input file.
++ return self.attrs['file']
++
++ def UberClique(self):
++ '''Returns the uberclique that should be used for messages originating in
++ a given node. If the node itself has its uberclique set, that is what we
++ use, otherwise we search upwards until we find one. If we do not find one
++ even at the root node, we set the root node's uberclique to a new
++ uberclique instance.
++ '''
++ node = self
++ while not node.uberclique and node.parent:
++ node = node.parent
++ if not node.uberclique:
++ node.uberclique = clique.UberClique()
++ return node.uberclique
++
++ def IsTranslateable(self):
++ '''Returns false if the node has contents that should not be translated,
++ otherwise returns false (even if the node has no contents).
++ '''
++ if not 'translateable' in self.attrs:
++ return True
++ else:
++ return self.attrs['translateable'] == 'true'
++
++ def IsAccessibilityWithNoUI(self):
++ '''Returns true if the node is marked as an accessibility label and the
++ message isn't shown in the UI. Otherwise returns false. This label is
++ used to determine if the text requires screenshots.'''
++ if not 'is_accessibility_with_no_ui' in self.attrs:
++ return False
++ else:
++ return self.attrs['is_accessibility_with_no_ui'] == 'true'
++
++ def GetNodeById(self, id):
++ '''Returns the node in the subtree parented by this node that has a 'name'
++ attribute matching 'id'. Returns None if no such node is found.
++ '''
++ for node in self:
++ if 'name' in node.attrs and node.attrs['name'] == id:
++ return node
++ return None
++
++ def GetChildrenOfType(self, type):
++ '''Returns a list of all subnodes (recursing to all leaves) of this node
++ that are of the indicated type (or tuple of types).
++
++ Args:
++ type: A type you could use with isinstance().
++
++ Return:
++ A list, possibly empty.
++ '''
++ return [child for child in self if isinstance(child, type)]
++
++ def GetTextualIds(self):
++ '''Returns a list of the textual ids of this node.
++ '''
++ if 'name' in self.attrs:
++ return [self.attrs['name']]
++ return []
++
++ @classmethod
++ def EvaluateExpression(cls, expr, defs, target_platform, extra_variables={}):
++ '''Worker for EvaluateCondition (below) and conditions in XTB files.'''
++ if expr in cls.eval_expr_cache:
++ code, variables_in_expr = cls.eval_expr_cache[expr]
++ else:
++ # Get a list of all variable and method names used in the expression.
++ syntax_tree = ast.parse(expr, mode='eval')
++ variables_in_expr = [node.id for node in ast.walk(syntax_tree) if
++ isinstance(node, ast.Name) and node.id not in ('True', 'False')]
++ code = compile(syntax_tree, filename='<string>', mode='eval')
++ cls.eval_expr_cache[expr] = code, variables_in_expr
++
++ # Set values only for variables that are needed to eval the expression.
++ variable_map = {}
++ for name in variables_in_expr:
++ if name == 'os':
++ value = target_platform
++ elif name == 'defs':
++ value = defs
++
++ elif name == 'is_linux':
++ value = target_platform.startswith('linux')
++ elif name == 'is_macosx':
++ value = target_platform == 'darwin'
++ elif name == 'is_win':
++ value = target_platform in ('cygwin', 'win32')
++ elif name == 'is_android':
++ value = target_platform == 'android'
++ elif name == 'is_ios':
++ value = target_platform == 'ios'
++ elif name == 'is_bsd':
++ value = 'bsd' in target_platform
++ elif name == 'is_posix':
++ value = (target_platform in ('darwin', 'linux2', 'linux3', 'sunos5',
++ 'android', 'ios')
++ or 'bsd' in target_platform)
++
++ elif name == 'pp_ifdef':
++ def pp_ifdef(symbol):
++ return symbol in defs
++ value = pp_ifdef
++ elif name == 'pp_if':
++ def pp_if(symbol):
++ return defs.get(symbol, False)
++ value = pp_if
++
++ elif name in defs:
++ value = defs[name]
++ elif name in extra_variables:
++ value = extra_variables[name]
++ else:
++ # Undefined variables default to False.
++ value = False
++
++ variable_map[name] = value
++
++ eval_result = eval(code, {}, variable_map)
++ assert isinstance(eval_result, bool)
++ return eval_result
++
++ def EvaluateCondition(self, expr):
++ '''Returns true if and only if the Python expression 'expr' evaluates
++ to true.
++
++ The expression is given a few local variables:
++ - 'lang' is the language currently being output
++ (the 'lang' attribute of the <output> element).
++ - 'context' is the current output context
++ (the 'context' attribute of the <output> element).
++ - 'defs' is a map of C preprocessor-style symbol names to their values.
++ - 'os' is the current platform (likely 'linux2', 'win32' or 'darwin').
++ - 'pp_ifdef(symbol)' is a shorthand for "symbol in defs".
++ - 'pp_if(symbol)' is a shorthand for "symbol in defs and defs[symbol]".
++ - 'is_linux', 'is_macosx', 'is_win', 'is_posix' are true if 'os'
++ matches the given platform.
++ '''
++ root = self.GetRoot()
++ lang = getattr(root, 'output_language', '')
++ context = getattr(root, 'output_context', '')
++ defs = getattr(root, 'defines', {})
++ target_platform = getattr(root, 'target_platform', '')
++ extra_variables = {
++ 'lang': lang,
++ 'context': context,
++ }
++ return Node.EvaluateExpression(
++ expr, defs, target_platform, extra_variables)
++
++ def OnlyTheseTranslations(self, languages):
++ '''Turns off loading of translations for languages not in the provided list.
++
++ Attrs:
++ languages: ['fr', 'zh_cn']
++ '''
++ for node in self:
++ if (hasattr(node, 'IsTranslation') and
++ node.IsTranslation() and
++ node.GetLang() not in languages):
++ node.DisableLoading()
++
++ def FindBooleanAttribute(self, attr, default, skip_self):
++ '''Searches all ancestors of the current node for the nearest enclosing
++ definition of the given boolean attribute.
++
++ Args:
++ attr: 'fallback_to_english'
++ default: What to return if no node defines the attribute.
++ skip_self: Don't check the current node, only its parents.
++ '''
++ p = self.parent if skip_self else self
++ while p:
++ value = p.attrs.get(attr, 'default').lower()
++ if value != 'default':
++ return (value == 'true')
++ p = p.parent
++ return default
++
++ def PseudoIsAllowed(self):
++ '''Returns true if this node is allowed to use pseudo-translations. This
++ is true by default, unless this node is within a <release> node that has
++ the allow_pseudo attribute set to false.
++ '''
++ return self.FindBooleanAttribute('allow_pseudo',
++ default=True, skip_self=True)
++
++ def ShouldFallbackToEnglish(self):
++ '''Returns true iff this node should fall back to English when
++ pseudotranslations are disabled and no translation is available for a
++ given message.
++ '''
++ return self.FindBooleanAttribute('fallback_to_english',
++ default=False, skip_self=True)
++
++ def WhitelistMarkedAsSkip(self):
++ '''Returns true if the node is marked to be skipped in the output by a
++ whitelist.
++ '''
++ return self._whitelist_marked_as_skip
++
++ def SetWhitelistMarkedAsSkip(self, mark_skipped):
++ '''Sets WhitelistMarkedAsSkip.
++ '''
++ self._whitelist_marked_as_skip = mark_skipped
++
++ def ExpandVariables(self):
++ '''Whether we need to expand variables on a given node.'''
++ return False
++
++ def IsResourceMapSource(self):
++ '''Whether this node is a resource map source.'''
++ return False
++
++ def CompressDataIfNeeded(self, data):
++ '''Compress data using the format specified in the compress attribute.
++
++ Args:
++ data: The data to compressed.
++ Returns:
++ The data in gzipped or brotli compressed format. If the format is
++ unspecified then this returns the data uncompressed.
++ '''
++
++ compress = self.attrs.get('compress')
++
++ # Compress JS, HTML, CSS and SVG files by default (gzip), unless |compress|
++ # is explicitly specified.
++ compress_by_default = (compress == 'default'
++ and self.attrs.get('file').endswith(
++ self._COMPRESS_BY_DEFAULT_EXTENSIONS))
++
++ if compress == 'gzip' or compress_by_default:
++ # We only use rsyncable compression on Linux.
++ # We exclude ChromeOS since ChromeOS bots are Linux based but do not have
++ # the --rsyncable option built in for gzip. See crbug.com/617950.
++ if sys.platform == 'linux2' and 'chromeos' not in self.GetRoot().defines:
++ return grit.format.gzip_string.GzipStringRsyncable(data)
++ return grit.format.gzip_string.GzipString(data)
++
++ if compress == 'brotli':
++ # The length of the uncompressed data as 8 bytes little-endian.
++ size_bytes = struct.pack("<q", len(data))
++ data = brotli_util.BrotliCompress(data)
++ # BROTLI_CONST is prepended to brotli decompressed data in order to
++ # easily check if a resource has been brotli compressed.
++ # The length of the uncompressed data is also appended to the start,
++ # truncated to 6 bytes, little-endian. size_bytes is 8 bytes,
++ # need to truncate further to 6.
++ formatter = b'%ds %dx %ds' % (6, 2, len(size_bytes) - 8)
++ return (constants.BROTLI_CONST +
++ b''.join(struct.unpack(formatter, size_bytes)) +
++ data)
++
++ if compress == 'false' or compress == 'default':
++ return data
++
++ raise Exception('Invalid value for compression')
++
++
++class ContentNode(Node):
++ '''Convenience baseclass for nodes that can have content.'''
++ def _ContentType(self):
++ return self._CONTENT_TYPE_MIXED
+diff --git a/tools/grit/grit/node/base_unittest.py b/tools/grit/grit/node/base_unittest.py
+new file mode 100644
+index 0000000000..32a5a0ca59
+--- /dev/null
++++ b/tools/grit/grit/node/base_unittest.py
+@@ -0,0 +1,259 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
+
-+// Unsafe; A grandchild ends up implicitly exposing their parent and
-+// grantparent's destructors.
-+class ImplicitDerivedProtectedToPublicInHeader
-+ : public ProtectedRefCountedDtorInHeader {
-+ public:
-+ ImplicitDerivedProtectedToPublicInHeader() {}
-+};
++'''Unit tests for base.Node functionality (as used in various subclasses)'''
+
-+// Unsafe-but-ignored; should not have errors.
-+class WebKitPublicDtorInHeader
-+ : public WebKit::RefCounted<WebKitPublicDtorInHeader> {
-+ public:
-+ WebKitPublicDtorInHeader() {}
-+ ~WebKitPublicDtorInHeader() {}
-+};
++from __future__ import print_function
+
-+// Unsafe-but-ignored; should not have errors.
-+class WebKitDerivedPublicDtorInHeader
-+ : public WebKitPublicDtorInHeader {
-+ public:
-+ WebKitDerivedPublicDtorInHeader() {}
-+ ~WebKitDerivedPublicDtorInHeader() {}
-+};
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++import unittest
++
++from six import StringIO
++
++from grit import grd_reader
++from grit import util
++from grit.node import base
++from grit.node import message
++
++
++def MakePlaceholder(phname='BINGO'):
++ ph = message.PhNode()
++ ph.StartParsing(u'ph', None)
++ ph.HandleAttribute(u'name', phname)
++ ph.AppendContent(u'bongo')
++ ph.EndParsing()
++ return ph
++
++
++class NodeUnittest(unittest.TestCase):
++ def testWhitespaceHandling(self):
++ # We test using the Message node type.
++ node = message.MessageNode()
++ node.StartParsing(u'hello', None)
++ node.HandleAttribute(u'name', u'bla')
++ node.AppendContent(u" ''' two spaces ")
++ node.EndParsing()
++ self.failUnless(node.GetCdata() == u' two spaces')
++
++ node = message.MessageNode()
++ node.StartParsing(u'message', None)
++ node.HandleAttribute(u'name', u'bla')
++ node.AppendContent(u" two spaces ''' ")
++ node.EndParsing()
++ self.failUnless(node.GetCdata() == u'two spaces ')
++
++ def testWhitespaceHandlingWithChildren(self):
++ # We test using the Message node type.
++ node = message.MessageNode()
++ node.StartParsing(u'message', None)
++ node.HandleAttribute(u'name', u'bla')
++ node.AppendContent(u" ''' two spaces ")
++ node.AddChild(MakePlaceholder())
++ node.AppendContent(u' space before and after ')
++ node.AddChild(MakePlaceholder('BONGO'))
++ node.AppendContent(u" space before two after '''")
++ node.EndParsing()
++ self.failUnless(node.mixed_content[0] == u' two spaces ')
++ self.failUnless(node.mixed_content[2] == u' space before and after ')
++ self.failUnless(node.mixed_content[-1] == u' space before two after ')
++
++ def testXmlFormatMixedContent(self):
++ # Again test using the Message node type, because it is the only mixed
++ # content node.
++ node = message.MessageNode()
++ node.StartParsing(u'message', None)
++ node.HandleAttribute(u'name', u'name')
++ node.AppendContent(u'Hello <young> ')
++
++ ph = message.PhNode()
++ ph.StartParsing(u'ph', None)
++ ph.HandleAttribute(u'name', u'USERNAME')
++ ph.AppendContent(u'$1')
++ ex = message.ExNode()
++ ex.StartParsing(u'ex', None)
++ ex.AppendContent(u'Joi')
++ ex.EndParsing()
++ ph.AddChild(ex)
++ ph.EndParsing()
++
++ node.AddChild(ph)
++ node.EndParsing()
++
++ non_indented_xml = node.FormatXml()
++ self.failUnless(non_indented_xml == u'<message name="name">\n Hello '
++ u'&lt;young&gt; <ph name="USERNAME">$1<ex>Joi</ex></ph>'
++ u'\n</message>')
++
++ indented_xml = node.FormatXml(u' ')
++ self.failUnless(indented_xml == u' <message name="name">\n Hello '
++ u'&lt;young&gt; <ph name="USERNAME">$1<ex>Joi</ex></ph>'
++ u'\n </message>')
++
++ def testXmlFormatMixedContentWithLeadingWhitespace(self):
++ # Again test using the Message node type, because it is the only mixed
++ # content node.
++ node = message.MessageNode()
++ node.StartParsing(u'message', None)
++ node.HandleAttribute(u'name', u'name')
++ node.AppendContent(u"''' Hello <young> ")
++
++ ph = message.PhNode()
++ ph.StartParsing(u'ph', None)
++ ph.HandleAttribute(u'name', u'USERNAME')
++ ph.AppendContent(u'$1')
++ ex = message.ExNode()
++ ex.StartParsing(u'ex', None)
++ ex.AppendContent(u'Joi')
++ ex.EndParsing()
++ ph.AddChild(ex)
++ ph.EndParsing()
++
++ node.AddChild(ph)
++ node.AppendContent(u" yessiree '''")
++ node.EndParsing()
++
++ non_indented_xml = node.FormatXml()
++ self.failUnless(non_indented_xml ==
++ u"<message name=\"name\">\n ''' Hello"
++ u' &lt;young&gt; <ph name="USERNAME">$1<ex>Joi</ex></ph>'
++ u" yessiree '''\n</message>")
++
++ indented_xml = node.FormatXml(u' ')
++ self.failUnless(indented_xml ==
++ u" <message name=\"name\">\n ''' Hello"
++ u' &lt;young&gt; <ph name="USERNAME">$1<ex>Joi</ex></ph>'
++ u" yessiree '''\n </message>")
++
++ self.failUnless(node.GetNodeById('name'))
++
++ def testXmlFormatContentWithEntities(self):
++ '''Tests a bug where &nbsp; would not be escaped correctly.'''
++ from grit import tclib
++ msg_node = message.MessageNode.Construct(None, tclib.Message(
++ text = 'BEGIN_BOLDHelloWHITESPACEthere!END_BOLD Bingo!',
++ placeholders = [
++ tclib.Placeholder('BEGIN_BOLD', '<b>', 'bla'),
++ tclib.Placeholder('WHITESPACE', '&nbsp;', 'bla'),
++ tclib.Placeholder('END_BOLD', '</b>', 'bla')]),
++ 'BINGOBONGO')
++ xml = msg_node.FormatXml()
++ self.failUnless(xml.find('&nbsp;') == -1, 'should have no entities')
++
++ def testIter(self):
++ # First build a little tree of message and ph nodes.
++ node = message.MessageNode()
++ node.StartParsing(u'message', None)
++ node.HandleAttribute(u'name', u'bla')
++ node.AppendContent(u" ''' two spaces ")
++ node.AppendContent(u' space before and after ')
++ ph = message.PhNode()
++ ph.StartParsing(u'ph', None)
++ ph.AddChild(message.ExNode())
++ ph.HandleAttribute(u'name', u'BINGO')
++ ph.AppendContent(u'bongo')
++ node.AddChild(ph)
++ node.AddChild(message.PhNode())
++ node.AppendContent(u" space before two after '''")
++
++ order = [message.MessageNode, message.PhNode, message.ExNode, message.PhNode]
++ for n in node:
++ self.failUnless(type(n) == order[0])
++ order = order[1:]
++ self.failUnless(len(order) == 0)
++
++ def testGetChildrenOfType(self):
++ xml = '''<?xml version="1.0" encoding="UTF-8"?>
++ <grit latest_public_release="2" source_lang_id="en-US"
++ current_release="3" base_dir=".">
++ <outputs>
++ <output filename="resource.h" type="rc_header" />
++ <output filename="en/generated_resources.rc" type="rc_all"
++ lang="en" />
++ <if expr="pp_if('NOT_TRUE')">
++ <output filename="de/generated_resources.rc" type="rc_all"
++ lang="de" />
++ </if>
++ </outputs>
++ <release seq="3">
++ <messages>
++ <message name="ID_HELLO">Hello!</message>
++ </messages>
++ </release>
++ </grit>'''
++ grd = grd_reader.Parse(StringIO(xml),
++ util.PathFromRoot('grit/test/data'))
++ from grit.node import node_io
++ output_nodes = grd.GetChildrenOfType(node_io.OutputNode)
++ self.failUnlessEqual(len(output_nodes), 3)
++ self.failUnlessEqual(output_nodes[2].attrs['filename'],
++ 'de/generated_resources.rc')
++
++ def testEvaluateExpression(self):
++ def AssertExpr(expected_value, expr, defs, target_platform,
++ extra_variables):
++ self.failUnlessEqual(expected_value, base.Node.EvaluateExpression(
++ expr, defs, target_platform, extra_variables))
++
++ AssertExpr(True, "True", {}, 'linux', {})
++ AssertExpr(False, "False", {}, 'linux', {})
++ AssertExpr(True, "True or False", {}, 'linux', {})
++ AssertExpr(False, "True and False", {}, 'linux', {})
++ AssertExpr(True, "os == 'linux'", {}, 'linux', {})
++ AssertExpr(False, "os == 'linux'", {}, 'ios', {})
++ AssertExpr(True, "'foo' in defs", {'foo': 'bar'}, 'ios', {})
++ AssertExpr(False, "'foo' in defs", {'baz': 'bar'}, 'ios', {})
++ AssertExpr(False, "'foo' in defs", {}, 'ios', {})
++ AssertExpr(True, "is_linux", {}, 'linux2', {})
++ AssertExpr(False, "is_linux", {}, 'win32', {})
++ AssertExpr(True, "is_macosx", {}, 'darwin', {})
++ AssertExpr(False, "is_macosx", {}, 'ios', {})
++ AssertExpr(True, "is_win", {}, 'win32', {})
++ AssertExpr(False, "is_win", {}, 'darwin', {})
++ AssertExpr(True, "is_android", {}, 'android', {})
++ AssertExpr(False, "is_android", {}, 'linux3', {})
++ AssertExpr(True, "is_ios", {}, 'ios', {})
++ AssertExpr(False, "is_ios", {}, 'darwin', {})
++ AssertExpr(True, "is_posix", {}, 'linux2', {})
++ AssertExpr(True, "is_posix", {}, 'darwin', {})
++ AssertExpr(True, "is_posix", {}, 'android', {})
++ AssertExpr(True, "is_posix", {}, 'ios', {})
++ AssertExpr(True, "is_posix", {}, 'freebsd7', {})
++ AssertExpr(False, "is_posix", {}, 'win32', {})
++ AssertExpr(True, "pp_ifdef('foo')", {'foo': True}, 'win32', {})
++ AssertExpr(True, "pp_ifdef('foo')", {'foo': False}, 'win32', {})
++ AssertExpr(False, "pp_ifdef('foo')", {'bar': True}, 'win32', {})
++ AssertExpr(True, "pp_if('foo')", {'foo': True}, 'win32', {})
++ AssertExpr(False, "pp_if('foo')", {'foo': False}, 'win32', {})
++ AssertExpr(False, "pp_if('foo')", {'bar': True}, 'win32', {})
++ AssertExpr(True, "foo", {'foo': True}, 'win32', {})
++ AssertExpr(False, "foo", {'foo': False}, 'win32', {})
++ AssertExpr(False, "foo", {'bar': True}, 'win32', {})
++ AssertExpr(True, "foo == 'baz'", {'foo': 'baz'}, 'win32', {})
++ AssertExpr(False, "foo == 'baz'", {'foo': True}, 'win32', {})
++ AssertExpr(False, "foo == 'baz'", {}, 'win32', {})
++ AssertExpr(True, "lang == 'de'", {}, 'win32', {'lang': 'de'})
++ AssertExpr(False, "lang == 'de'", {}, 'win32', {'lang': 'fr'})
++ AssertExpr(False, "lang == 'de'", {}, 'win32', {})
++
++ # Test a couple more complex expressions for good measure.
++ AssertExpr(True, "is_ios and (lang in ['de', 'fr'] or foo)",
++ {'foo': 'bar'}, 'ios', {'lang': 'fr', 'context': 'today'})
++ AssertExpr(False, "is_ios and (lang in ['de', 'fr'] or foo)",
++ {'foo': False}, 'linux2', {'lang': 'fr', 'context': 'today'})
++ AssertExpr(False, "is_ios and (lang in ['de', 'fr'] or foo)",
++ {'baz': 'bar'}, 'ios', {'lang': 'he', 'context': 'today'})
++ AssertExpr(True, "foo == 'bar' or not baz",
++ {'foo': 'bar', 'fun': True}, 'ios', {'lang': 'en'})
++ AssertExpr(True, "foo == 'bar' or not baz",
++ {}, 'ios', {'lang': 'en', 'context': 'java'})
++ AssertExpr(False, "foo == 'bar' or not baz",
++ {'foo': 'ruz', 'baz': True}, 'ios', {'lang': 'en'})
+
-+#endif // BASE_REFCOUNTED_H_
-diff --git a/tools/clang/plugins/tests/base_refcounted.txt b/tools/clang/plugins/tests/base_refcounted.txt
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/node/brotli_util.py b/tools/grit/grit/node/brotli_util.py
new file mode 100644
-index 0000000000..4626424177
+index 0000000000..77f70e49d5
--- /dev/null
-+++ b/tools/clang/plugins/tests/base_refcounted.txt
-@@ -0,0 +1,23 @@
-+In file included from base_refcounted.cpp:5:
-+./base_refcounted.h:45:3: warning: [chromium-style] Classes that are ref-counted should not have public destructors.
-+ ~PublicRefCountedDtorInHeader() {}
-+ ^
-+./base_refcounted.h:57:3: warning: [chromium-style] Classes that are ref-counted should not have public destructors.
-+ ~PublicRefCountedThreadSafeDtorInHeader() {}
-+ ^
-+./base_refcounted.h:94:3: warning: [chromium-style] Classes that are ref-counted should not have public destructors.
-+ ~DerivedProtectedToPublicInHeader() {}
-+ ^
-+./base_refcounted.h:99:1: warning: [chromium-style] Classes that are ref-counted should have explicit destructors that are protected or private.
-+class ImplicitDerivedProtectedToPublicInHeader
-+^
-+base_refcounted.cpp:16:3: warning: [chromium-style] Classes that are ref-counted should not have public destructors.
-+ ~AnonymousDerivedProtectedToPublicInImpl() {}
-+ ^
-+base_refcounted.cpp:26:3: warning: [chromium-style] Classes that are ref-counted should not have public destructors.
-+ ~PublicRefCountedDtorInImpl() {}
-+ ^
-+base_refcounted.cpp:52:3: warning: [chromium-style] Classes that are ref-counted should not have public destructors.
-+ ~UnsafeTypedefChainInImpl() {}
-+ ^
-+7 warnings generated.
-diff --git a/tools/clang/plugins/tests/inline_copy_ctor.cpp b/tools/clang/plugins/tests/inline_copy_ctor.cpp
-new file mode 100644
-index 0000000000..dcd90020c5
---- /dev/null
-+++ b/tools/clang/plugins/tests/inline_copy_ctor.cpp
-@@ -0,0 +1,5 @@
-+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+// Use of this source code is governed by a BSD-style license that can be
-+// found in the LICENSE file.
++++ b/tools/grit/grit/node/brotli_util.py
+@@ -0,0 +1,29 @@
++# Copyright 2019 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++"""Framework for compressing resources using Brotli."""
++
++import subprocess
++
++__brotli_executable = None
+
-+#include "inline_copy_ctor.h"
-diff --git a/tools/clang/plugins/tests/inline_copy_ctor.h b/tools/clang/plugins/tests/inline_copy_ctor.h
++
++def SetBrotliCommand(brotli):
++ # brotli is a list. In production it contains the path to the Brotli executable.
++ # During testing it contains [python, mock_brotli.py] for testing on Windows.
++ global __brotli_executable
++ __brotli_executable = brotli
++
++
++def BrotliCompress(data):
++ if not __brotli_executable:
++ raise Exception('Add "use_brotli = true" to you GN grit(...) target ' +
++ 'if you want to use brotli.')
++ compress = subprocess.Popen(__brotli_executable + ['-', '-f'],
++ stdin=subprocess.PIPE, stdout=subprocess.PIPE)
++ return compress.communicate(data)[0]
++
++def IsInitialized():
++ global __brotli_executable
++ return __brotli_executable is not None
+diff --git a/tools/grit/grit/node/custom/__init__.py b/tools/grit/grit/node/custom/__init__.py
new file mode 100644
-index 0000000000..619a18392b
+index 0000000000..e179cf7730
--- /dev/null
-+++ b/tools/clang/plugins/tests/inline_copy_ctor.h
-@@ -0,0 +1,12 @@
-+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+// Use of this source code is governed by a BSD-style license that can be
-+// found in the LICENSE file.
++++ b/tools/grit/grit/node/custom/__init__.py
+@@ -0,0 +1,8 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
+
-+struct C {
-+ C();
-+ ~C();
++'''Package 'grit.node.custom'
++'''
+
-+ static C foo() { return C(); }
++pass
+diff --git a/tools/grit/grit/node/custom/filename.py b/tools/grit/grit/node/custom/filename.py
+new file mode 100644
+index 0000000000..55a27e58c1
+--- /dev/null
++++ b/tools/grit/grit/node/custom/filename.py
+@@ -0,0 +1,29 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
+
-+ int a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p , q, r, s, t, u, v, w, x;
-+};
-diff --git a/tools/clang/plugins/tests/inline_copy_ctor.txt b/tools/clang/plugins/tests/inline_copy_ctor.txt
++'''A CustomType for filenames.'''
++
++from __future__ import print_function
++
++from grit import clique
++from grit import lazy_re
++
++
++class WindowsFilename(clique.CustomType):
++ '''Validates that messages can be used as Windows filenames, and strips
++ illegal characters out of translations.
++ '''
++
++ BANNED = lazy_re.compile(r'\+|:|\/|\\\\|\*|\?|\"|\<|\>|\|')
++
++ def Validate(self, message):
++ return not self.BANNED.search(message.GetPresentableContent())
++
++ def ValidateAndModify(self, lang, translation):
++ is_ok = self.Validate(translation)
++ self.ModifyEachTextPart(lang, translation)
++ return is_ok
++
++ def ModifyTextPart(self, lang, text):
++ return self.BANNED.sub(' ', text)
+diff --git a/tools/grit/grit/node/custom/filename_unittest.py b/tools/grit/grit/node/custom/filename_unittest.py
new file mode 100644
-index 0000000000..bc4bd8911e
+index 0000000000..8e2a6dd64a
--- /dev/null
-+++ b/tools/clang/plugins/tests/inline_copy_ctor.txt
-@@ -0,0 +1,5 @@
-+In file included from inline_copy_ctor.cpp:5:
-+./inline_copy_ctor.h:5:1: warning: [chromium-style] Complex class/struct needs an explicit out-of-line copy constructor.
-+struct C {
-+^
-+1 warning generated.
-diff --git a/tools/clang/plugins/tests/inline_ctor.cpp b/tools/clang/plugins/tests/inline_ctor.cpp
++++ b/tools/grit/grit/node/custom/filename_unittest.py
+@@ -0,0 +1,34 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit tests for grit.node.custom.filename'''
++
++from __future__ import print_function
++
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../../..'))
++
++import unittest
++from grit.node.custom import filename
++from grit import clique
++from grit import tclib
++
++
++class WindowsFilenameUnittest(unittest.TestCase):
++
++ def testValidate(self):
++ factory = clique.UberClique()
++ msg = tclib.Message(text='Bingo bongo')
++ c = factory.MakeClique(msg)
++ c.SetCustomType(filename.WindowsFilename())
++ translation = tclib.Translation(id=msg.GetId(), text='Bilingo bolongo:')
++ c.AddTranslation(translation, 'fr')
++ self.failUnless(c.MessageForLanguage('fr').GetRealContent() == 'Bilingo bolongo ')
++
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/node/empty.py b/tools/grit/grit/node/empty.py
new file mode 100644
-index 0000000000..6a751fb405
+index 0000000000..e19d2c4ddb
--- /dev/null
-+++ b/tools/clang/plugins/tests/inline_ctor.cpp
-@@ -0,0 +1,25 @@
-+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-+// Use of this source code is governed by a BSD-style license that can be
-+// found in the LICENSE file.
++++ b/tools/grit/grit/node/empty.py
+@@ -0,0 +1,64 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
+
-+#include "inline_ctor.h"
++'''Container nodes that don't have any logic.
++'''
+
-+#include <string>
-+#include <vector>
++from __future__ import print_function
+
-+// We don't warn on classes that are in CPP files.
-+class InlineInCPPOK {
-+ public:
-+ InlineInCPPOK() {}
-+ ~InlineInCPPOK() {}
++from grit.node import base
++from grit.node import include
++from grit.node import message
++from grit.node import misc
++from grit.node import node_io
++from grit.node import structure
+
-+ private:
-+ std::vector<int> one_;
-+ std::vector<std::string> two_;
-+};
+
-+int main() {
-+ InlineInCPPOK one;
-+ InlineCtorsArentOKInHeader two;
-+ return 0;
++class GroupingNode(base.Node):
++ '''Base class for all the grouping elements (<structures>, <includes>,
++ <messages> and <identifiers>).'''
++ def DefaultAttributes(self):
++ return {
++ 'first_id' : '',
++ 'comment' : '',
++ 'fallback_to_english' : 'false',
++ 'fallback_to_low_resolution' : 'false',
++ }
++
++
++class IncludesNode(GroupingNode):
++ '''The <includes> element.'''
++ def _IsValidChild(self, child):
++ return isinstance(child, (include.IncludeNode, misc.IfNode, misc.PartNode))
++
++
++class MessagesNode(GroupingNode):
++ '''The <messages> element.'''
++ def _IsValidChild(self, child):
++ return isinstance(child, (message.MessageNode, misc.IfNode, misc.PartNode))
++
++
++class StructuresNode(GroupingNode):
++ '''The <structures> element.'''
++ def _IsValidChild(self, child):
++ return isinstance(child, (structure.StructureNode,
++ misc.IfNode, misc.PartNode))
++
++
++class TranslationsNode(base.Node):
++ '''The <translations> element.'''
++ def _IsValidChild(self, child):
++ return isinstance(child, (node_io.FileNode, misc.IfNode, misc.PartNode))
++
++
++class OutputsNode(base.Node):
++ '''The <outputs> element.'''
++ def _IsValidChild(self, child):
++ return isinstance(child, (node_io.OutputNode, misc.IfNode, misc.PartNode))
++
++
++class IdentifiersNode(GroupingNode):
++ '''The <identifiers> element.'''
++ def _IsValidChild(self, child):
++ return isinstance(child, misc.IdentifierNode)
+diff --git a/tools/grit/grit/node/include.py b/tools/grit/grit/node/include.py
+new file mode 100644
+index 0000000000..b06b9889bb
+--- /dev/null
++++ b/tools/grit/grit/node/include.py
+@@ -0,0 +1,170 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++"""Handling of the <include> element.
++"""
++
++from __future__ import print_function
++
++import os
++
++from grit import util
++import grit.format.html_inline
++import grit.format.rc
++from grit.format import minifier
++from grit.node import base
++
++class IncludeNode(base.Node):
++ """An <include> element."""
++
++ def __init__(self):
++ super(IncludeNode, self).__init__()
++
++ # Cache flattened data so that we don't flatten the same file
++ # multiple times.
++ self._flattened_data = None
++ # Also keep track of the last filename we flattened to, so we can
++ # avoid doing it more than once.
++ self._last_flat_filename = None
++
++ def _IsValidChild(self, child):
++ return False
++
++ def _GetFlattenedData(
++ self, allow_external_script=False, preprocess_only=False):
++ if not self._flattened_data:
++ filename = self.ToRealPath(self.GetInputPath())
++ self._flattened_data = (
++ grit.format.html_inline.InlineToString(filename, self,
++ preprocess_only=preprocess_only,
++ allow_external_script=allow_external_script))
++ return self._flattened_data.encode('utf-8')
++
++ def MandatoryAttributes(self):
++ return ['name', 'type', 'file']
++
++ def DefaultAttributes(self):
++ """Attributes:
++ translateable: False if the node has contents that should not be
++ translated.
++ preprocess: Takes the same code path as flattenhtml, but it
++ disables any processing/inlining outside of <if>
++ and <include>.
++ compress: The format to compress the data with, e.g. 'gzip'
++ or 'false' if data should not be compressed.
++ skip_minify: If true, skips minifying the node's contents.
++ skip_in_resource_map: If true, do not add to the resource map.
++ """
++ return {
++ 'translateable': 'true',
++ 'generateid': 'true',
++ 'filenameonly': 'false',
++ 'mkoutput': 'false',
++ 'preprocess': 'false',
++ 'flattenhtml': 'false',
++ 'compress': 'default',
++ 'allowexternalscript': 'false',
++ 'relativepath': 'false',
++ 'use_base_dir': 'true',
++ 'skip_minify': 'false',
++ 'skip_in_resource_map': 'false',
++ }
++
++ def GetInputPath(self):
++ # Do not mess with absolute paths, that would make them invalid.
++ if os.path.isabs(os.path.expandvars(self.attrs['file'])):
++ return self.attrs['file']
++
++ # We have no control over code that calls ToRealPath later, so convert
++ # the path to be relative against our basedir.
++ if self.attrs.get('use_base_dir', 'true') != 'true':
++ # Normalize the directory path to use the appropriate OS separator.
++ # GetBaseDir() may return paths\like\this or paths/like/this, since it is
++ # read from the base_dir attribute in the grd file.
++ norm_base_dir = util.normpath(self.GetRoot().GetBaseDir())
++ return os.path.relpath(self.attrs['file'], norm_base_dir)
++
++ return self.attrs['file']
++
++ def FileForLanguage(self, lang, output_dir):
++ """Returns the file for the specified language. This allows us to return
++ different files for different language variants of the include file.
++ """
++ input_path = self.GetInputPath()
++ if input_path is None:
++ return None
++
++ return self.ToRealPath(input_path)
++
++ def GetDataPackValue(self, lang, encoding):
++ '''Returns bytes or a str represenation for a data_pack entry.'''
++ filename = self.ToRealPath(self.GetInputPath())
++ if self.attrs['flattenhtml'] == 'true':
++ allow_external_script = self.attrs['allowexternalscript'] == 'true'
++ data = self._GetFlattenedData(allow_external_script=allow_external_script)
++ elif self.attrs['preprocess'] == 'true':
++ data = self._GetFlattenedData(preprocess_only=True)
++ else:
++ data = util.ReadFile(filename, util.BINARY)
++
++ if self.attrs['skip_minify'] != 'true':
++ # Note that the minifier will only do anything if a minifier command
++ # has been set in the command line.
++ data = minifier.Minify(data, filename)
++
++ # Include does not care about the encoding, because it only returns binary
++ # data.
++ return self.CompressDataIfNeeded(data)
++
++ def Process(self, output_dir):
++ """Rewrite file references to be base64 encoded data URLs. The new file
++ will be written to output_dir and the name of the new file is returned."""
++ filename = self.ToRealPath(self.GetInputPath())
++ flat_filename = os.path.join(output_dir,
++ self.attrs['name'] + '_' + os.path.basename(filename))
++
++ if self._last_flat_filename == flat_filename:
++ return
++
++ with open(flat_filename, 'wb') as outfile:
++ outfile.write(self._GetFlattenedData())
++
++ self._last_flat_filename = flat_filename
++ return os.path.basename(flat_filename)
++
++ def GetHtmlResourceFilenames(self):
++ """Returns a set of all filenames inlined by this file."""
++ allow_external_script = self.attrs['allowexternalscript'] == 'true'
++ return grit.format.html_inline.GetResourceFilenames(
++ self.ToRealPath(self.GetInputPath()),
++ self,
++ allow_external_script=allow_external_script)
++
++ def IsResourceMapSource(self):
++ skip = self.attrs.get('skip_in_resource_map', 'false') == 'true'
++ return not skip
++
++ @staticmethod
++ def Construct(parent, name, type, file, translateable=True,
++ filenameonly=False, mkoutput=False, relativepath=False):
++ """Creates a new node which is a child of 'parent', with attributes set
++ by parameters of the same name.
++ """
++ # Convert types to appropriate strings
++ translateable = util.BoolToString(translateable)
++ filenameonly = util.BoolToString(filenameonly)
++ mkoutput = util.BoolToString(mkoutput)
++ relativepath = util.BoolToString(relativepath)
++
++ node = IncludeNode()
++ node.StartParsing('include', parent)
++ node.HandleAttribute('name', name)
++ node.HandleAttribute('type', type)
++ node.HandleAttribute('file', file)
++ node.HandleAttribute('translateable', translateable)
++ node.HandleAttribute('filenameonly', filenameonly)
++ node.HandleAttribute('mkoutput', mkoutput)
++ node.HandleAttribute('relativepath', relativepath)
++ node.EndParsing()
++ return node
+diff --git a/tools/grit/grit/node/include_unittest.py b/tools/grit/grit/node/include_unittest.py
+new file mode 100644
+index 0000000000..4c658f1ffe
+--- /dev/null
++++ b/tools/grit/grit/node/include_unittest.py
+@@ -0,0 +1,134 @@
++#!/usr/bin/env python
++# Copyright (c) 2013 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit tests for include.IncludeNode'''
++
++from __future__ import print_function
++
++import os
++import sys
++import unittest
++import zlib
++
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++from grit.node import misc
++from grit.node import include
++from grit.node import empty
++from grit import util
++
++
++def checkIsGzipped(filename, compress_attr):
++ test_data_root = util.PathFromRoot('grit/testdata')
++ root = util.ParseGrdForUnittest(
++ '''
++ <includes>
++ <include name="TEST_TXT" file="%s" %s type="BINDATA"/>
++ </includes>''' % (filename, compress_attr),
++ base_dir=test_data_root)
++ node, = root.GetChildrenOfType(include.IncludeNode)
++ compressed = node.GetDataPackValue(lang='en', encoding=util.BINARY)
++
++ decompressed_data = zlib.decompress(compressed, 16 + zlib.MAX_WBITS)
++ expected = util.ReadFile(os.path.join(test_data_root, filename), util.BINARY)
++ return expected == decompressed_data
++
++
++class IncludeNodeUnittest(unittest.TestCase):
++ def testGetPath(self):
++ root = misc.GritNode()
++ root.StartParsing(u'grit', None)
++ root.HandleAttribute(u'latest_public_release', u'0')
++ root.HandleAttribute(u'current_release', u'1')
++ root.HandleAttribute(u'base_dir', r'..\resource')
++ release = misc.ReleaseNode()
++ release.StartParsing(u'release', root)
++ release.HandleAttribute(u'seq', u'1')
++ root.AddChild(release)
++ includes = empty.IncludesNode()
++ includes.StartParsing(u'includes', release)
++ release.AddChild(includes)
++ include_node = include.IncludeNode()
++ include_node.StartParsing(u'include', includes)
++ include_node.HandleAttribute(u'file', r'flugel\kugel.pdf')
++ includes.AddChild(include_node)
++ root.EndParsing()
++
++ self.assertEqual(root.ToRealPath(include_node.GetInputPath()),
++ util.normpath(
++ os.path.join(r'../resource', r'flugel/kugel.pdf')))
++
++ def testGetPathNoBasedir(self):
++ root = misc.GritNode()
++ root.StartParsing(u'grit', None)
++ root.HandleAttribute(u'latest_public_release', u'0')
++ root.HandleAttribute(u'current_release', u'1')
++ root.HandleAttribute(u'base_dir', r'..\resource')
++ release = misc.ReleaseNode()
++ release.StartParsing(u'release', root)
++ release.HandleAttribute(u'seq', u'1')
++ root.AddChild(release)
++ includes = empty.IncludesNode()
++ includes.StartParsing(u'includes', release)
++ release.AddChild(includes)
++ include_node = include.IncludeNode()
++ include_node.StartParsing(u'include', includes)
++ include_node.HandleAttribute(u'file', r'flugel\kugel.pdf')
++ include_node.HandleAttribute(u'use_base_dir', u'false')
++ includes.AddChild(include_node)
++ root.EndParsing()
++
++ last_dir = os.path.basename(os.getcwd())
++ expected_path = util.normpath(os.path.join(
++ u'..', last_dir, u'flugel/kugel.pdf'))
++ self.assertEqual(root.ToRealPath(include_node.GetInputPath()),
++ expected_path)
++
++ def testCompressGzip(self):
++ self.assertTrue(checkIsGzipped('test_text.txt', 'compress="gzip"'))
++
++ def testCompressGzipByDefault(self):
++ self.assertTrue(checkIsGzipped('test_html.html', ''))
++ self.assertTrue(checkIsGzipped('test_js.js', ''))
++ self.assertTrue(checkIsGzipped('test_css.css', ''))
++ self.assertTrue(checkIsGzipped('test_svg.svg', ''))
++
++ self.assertTrue(checkIsGzipped('test_html.html', 'compress="default"'))
++ self.assertTrue(checkIsGzipped('test_js.js', 'compress="default"'))
++ self.assertTrue(checkIsGzipped('test_css.css', 'compress="default"'))
++ self.assertTrue(checkIsGzipped('test_svg.svg', 'compress="default"'))
++
++ def testSkipInResourceMap(self):
++ root = util.ParseGrdForUnittest('''
++ <includes>
++ <include name="TEST1_TXT" file="test1_text.txt" type="BINDATA"/>
++ <include name="TEST2_TXT" file="test1_text.txt" type="BINDATA"
++ skip_in_resource_map="true"/>
++ <include name="TEST3_TXT" file="test1_text.txt" type="BINDATA"
++ skip_in_resource_map="false"/>
++ </includes>''', base_dir = util.PathFromRoot('grit/testdata'))
++ inc = root.GetChildrenOfType(include.IncludeNode)
++ self.assertTrue(inc[0].IsResourceMapSource())
++ self.assertFalse(inc[1].IsResourceMapSource())
++ self.assertTrue(inc[2].IsResourceMapSource())
++
++ def testAcceptsPreprocess(self):
++ root = util.ParseGrdForUnittest(
++ '''
++ <includes>
++ <include name="PREPROCESS_TEST" file="preprocess_test.html"
++ preprocess="true" compress="false" type="chrome_html"/>
++ </includes>''',
++ base_dir=util.PathFromRoot('grit/testdata'))
++ inc, = root.GetChildrenOfType(include.IncludeNode)
++ result = inc.GetDataPackValue(lang='en', encoding=util.BINARY)
++ self.assertIn(b'should be kept', result)
++ self.assertIn(b'in the middle...', result)
++ self.assertNotIn(b'should be removed', result)
++
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/node/mapping.py b/tools/grit/grit/node/mapping.py
+new file mode 100644
+index 0000000000..6297f0b666
+--- /dev/null
++++ b/tools/grit/grit/node/mapping.py
+@@ -0,0 +1,60 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Maps each node type to an implementation class.
++When adding a new node type, you add to this mapping.
++'''
++
++from __future__ import print_function
++
++from grit import exception
++
++from grit.node import empty
++from grit.node import include
++from grit.node import message
++from grit.node import misc
++from grit.node import node_io
++from grit.node import structure
++from grit.node import variant
++
++
++_ELEMENT_TO_CLASS = {
++ 'identifiers' : empty.IdentifiersNode,
++ 'includes' : empty.IncludesNode,
++ 'messages' : empty.MessagesNode,
++ 'outputs' : empty.OutputsNode,
++ 'structures' : empty.StructuresNode,
++ 'translations' : empty.TranslationsNode,
++ 'include' : include.IncludeNode,
++ 'emit' : node_io.EmitNode,
++ 'file' : node_io.FileNode,
++ 'output' : node_io.OutputNode,
++ 'ex' : message.ExNode,
++ 'message' : message.MessageNode,
++ 'ph' : message.PhNode,
++ 'else' : misc.ElseNode,
++ 'grit' : misc.GritNode,
++ 'identifier' : misc.IdentifierNode,
++ 'if' : misc.IfNode,
++ 'part' : misc.PartNode,
++ 'release' : misc.ReleaseNode,
++ 'then' : misc.ThenNode,
++ 'structure' : structure.StructureNode,
++ 'skeleton' : variant.SkeletonNode,
+}
-diff --git a/tools/clang/plugins/tests/inline_ctor.h b/tools/clang/plugins/tests/inline_ctor.h
++
++
++def ElementToClass(name, typeattr):
++ '''Maps an element to a class that handles the element.
++
++ Args:
++ name: 'element' (the name of the element)
++ typeattr: 'type' (the value of the type attribute, if present, else None)
++
++ Return:
++ type
++ '''
++ if name not in _ELEMENT_TO_CLASS:
++ raise exception.UnknownElement()
++ return _ELEMENT_TO_CLASS[name]
+diff --git a/tools/grit/grit/node/message.py b/tools/grit/grit/node/message.py
new file mode 100644
-index 0000000000..d053b2f57d
+index 0000000000..4fa83cf26b
--- /dev/null
-+++ b/tools/clang/plugins/tests/inline_ctor.h
-@@ -0,0 +1,21 @@
-+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+// Use of this source code is governed by a BSD-style license that can be
-+// found in the LICENSE file.
++++ b/tools/grit/grit/node/message.py
+@@ -0,0 +1,362 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
+
-+#ifndef INLINE_CTOR_H_
-+#define INLINE_CTOR_H_
++'''Handling of the <message> element.
++'''
++
++from __future__ import print_function
++
++import re
++
++import six
++
++from grit.node import base
++
++from grit import clique
++from grit import exception
++from grit import lazy_re
++from grit import tclib
++from grit import util
++
++
++# Matches exactly three dots ending a line or followed by whitespace.
++_ELLIPSIS_PATTERN = lazy_re.compile(r'(?<!\.)\.\.\.(?=$|\s)')
++_ELLIPSIS_SYMBOL = u'\u2026' # Ellipsis
++
++# Finds whitespace at the start and end of a string which can be multiline.
++_WHITESPACE = lazy_re.compile(r'(?P<start>\s*)(?P<body>.+?)(?P<end>\s*)\Z',
++ re.DOTALL | re.MULTILINE)
++
++# <ph> placeholder elements should contain the special character formatters
++# used to format <ph> element content.
++# Android format.
++_ANDROID_FORMAT = (r'%[1-9]+\$'
++ r'([-#+ 0,(]*)([0-9]+)?(\.[0-9]+)?'
++ r'([bBhHsScCdoxXeEfgGaAtT%n])')
++# Chrome l10n format.
++_CHROME_FORMAT = r'\$+\d'
++# Windows EWT numeric and GRIT %s %d formats.
++_OTHER_FORMAT = r'%[0-9sd]'
++
++# Finds formatters that must be in a placeholder (<ph>) element.
++_FORMATTERS = lazy_re.compile(
++ '(%s)|(%s)|(%s)' % (_ANDROID_FORMAT, _CHROME_FORMAT, _OTHER_FORMAT))
++_BAD_PLACEHOLDER_MSG = ('ERROR: Placeholder formatter found outside of <ph> '
++ 'tag in message "%s" in %s.')
++_INVALID_PH_CHAR_MSG = ('ERROR: Invalid format characters found in message '
++ '"%s" <ph> tag in %s.')
++
++# Finds HTML tag tokens.
++_HTMLTOKEN = lazy_re.compile(r'<[/]?[a-z][a-z0-9]*[^>]*>', re.I)
++
++# Finds HTML entities.
++_HTMLENTITY = lazy_re.compile(r'&[^\s]*;')
++
++
++class MessageNode(base.ContentNode):
++ '''A <message> element.'''
++
++ # For splitting a list of things that can be separated by commas or
++ # whitespace
++ _SPLIT_RE = lazy_re.compile(r'\s*,\s*|\s+')
++
++ def __init__(self):
++ super(MessageNode, self).__init__()
++ # Valid after EndParsing, this is the MessageClique that contains the
++ # source message and any translations of it that have been loaded.
++ self.clique = None
++
++ # We don't send leading and trailing whitespace into the translation
++ # console, but rather tack it onto the source message and any
++ # translations when formatting them into RC files or what have you.
++ self.ws_at_start = '' # Any whitespace characters at the start of the text
++ self.ws_at_end = '' # --"-- at the end of the text
++
++ # A list of "shortcut groups" this message is in. We check to make sure
++ # that shortcut keys (e.g. &J) within each shortcut group are unique.
++ self.shortcut_groups_ = []
++
++ # Formatter-specific data used to control the output of individual strings.
++ # formatter_data is a space separated list of C preprocessor-style
++ # definitions. Names without values are given the empty string value.
++ # Example: "foo=5 bar baz=100"
++ self.formatter_data = {}
++
++ # Whether or not to convert ... -> U+2026 within Translate().
++ self._replace_ellipsis = False
++
++ def _IsValidChild(self, child):
++ return isinstance(child, (PhNode))
++
++ def _IsValidAttribute(self, name, value):
++ if name not in [
++ 'name', 'offset', 'translateable', 'desc', 'meaning',
++ 'internal_comment', 'shortcut_groups', 'custom_type', 'validation_expr',
++ 'use_name_for_id', 'sub_variable', 'formatter_data',
++ 'is_accessibility_with_no_ui'
++ ]:
++ return False
++ if (name in ('translateable', 'sub_variable') and
++ value not in ['true', 'false']):
++ return False
++ return True
++
++ def SetReplaceEllipsis(self, value):
++ r'''Sets whether to replace ... with \u2026.
++ '''
++ self._replace_ellipsis = value
++
++ def MandatoryAttributes(self):
++ return ['name|offset']
++
++ def DefaultAttributes(self):
++ return {
++ 'custom_type': '',
++ 'desc': '',
++ 'formatter_data': '',
++ 'internal_comment': '',
++ 'is_accessibility_with_no_ui': 'false',
++ 'meaning': '',
++ 'shortcut_groups': '',
++ 'sub_variable': 'false',
++ 'translateable': 'true',
++ 'use_name_for_id': 'false',
++ 'validation_expr': '',
++ }
+
-+#include <string>
-+#include <vector>
++ def HandleAttribute(self, attrib, value):
++ base.ContentNode.HandleAttribute(self, attrib, value)
++ if attrib != 'formatter_data':
++ return
++
++ # Parse value, a space-separated list of defines, into a dict.
++ # Example: "foo=5 bar" -> {'foo':'5', 'bar':''}
++ for item in value.split():
++ name, _, val = item.partition('=')
++ self.formatter_data[name] = val
++
++ def GetTextualIds(self):
++ '''
++ Returns the concatenation of the parent's node first_id and
++ this node's offset if it has one, otherwise just call the
++ superclass' implementation
++ '''
++ if 'offset' not in self.attrs:
++ return super(MessageNode, self).GetTextualIds()
++
++ # we search for the first grouping node in the parents' list
++ # to take care of the case where the first parent is an <if> node
++ grouping_parent = self.parent
++ import grit.node.empty
++ while grouping_parent and not isinstance(grouping_parent,
++ grit.node.empty.GroupingNode):
++ grouping_parent = grouping_parent.parent
++
++ assert 'first_id' in grouping_parent.attrs
++ return [grouping_parent.attrs['first_id'] + '_' + self.attrs['offset']]
++
++ def IsTranslateable(self):
++ return self.attrs['translateable'] == 'true'
++
++ def EndParsing(self):
++ super(MessageNode, self).EndParsing()
++
++ # Make the text (including placeholder references) and list of placeholders,
++ # verify placeholder formats, then strip and store leading and trailing
++ # whitespace and create the tclib.Message() and a clique to contain it.
++
++ text = ''
++ placeholders = []
++
++ for item in self.mixed_content:
++ if isinstance(item, six.string_types):
++ # Not a <ph> element: fail if any <ph> formatters are detected.
++ if _FORMATTERS.search(item):
++ print(_BAD_PLACEHOLDER_MSG % (item, self.source))
++ raise exception.PlaceholderNotInsidePhNode
++ text += item
++ else:
++ # Extract the <ph> element components.
++ presentation = item.attrs['name'].upper()
++ text += presentation
++ ex = ' ' # <ex> example element cdata if present.
++ if len(item.children):
++ ex = item.children[0].GetCdata()
++ original = item.GetCdata()
++
++ # Sanity check the <ph> element content.
++ cdata = original
++ # Replace all HTML tag tokens in cdata.
++ match = _HTMLTOKEN.search(cdata)
++ while match:
++ cdata = cdata.replace(match.group(0), '_')
++ match = _HTMLTOKEN.search(cdata)
++ # Replace all HTML entities in cdata.
++ match = _HTMLENTITY.search(cdata)
++ while match:
++ cdata = cdata.replace(match.group(0), '_')
++ match = _HTMLENTITY.search(cdata)
++ # Remove first matching formatter from cdata.
++ match = _FORMATTERS.search(cdata)
++ if match:
++ cdata = cdata.replace(match.group(0), '')
++ # Fail if <ph> special chars remain in cdata.
++ if re.search(r'[%\$]', cdata):
++ message_id = self.attrs['name'] + ' ' + original;
++ print(_INVALID_PH_CHAR_MSG % (message_id, self.source))
++ raise exception.InvalidCharactersInsidePhNode
++
++ # Otherwise, accept this <ph> placeholder.
++ placeholders.append(tclib.Placeholder(presentation, original, ex))
++
++ m = _WHITESPACE.match(text)
++ if m:
++ self.ws_at_start = m.group('start')
++ self.ws_at_end = m.group('end')
++ text = m.group('body')
++
++ self.shortcut_groups_ = self._SPLIT_RE.split(self.attrs['shortcut_groups'])
++ self.shortcut_groups_ = [i for i in self.shortcut_groups_ if i != '']
++
++ description_or_id = self.attrs['desc']
++ if description_or_id == '' and 'name' in self.attrs:
++ description_or_id = 'ID: %s' % self.attrs['name']
++
++ assigned_id = None
++ if self.attrs['use_name_for_id'] == 'true':
++ assigned_id = self.attrs['name']
++ message = tclib.Message(text=text, placeholders=placeholders,
++ description=description_or_id,
++ meaning=self.attrs['meaning'],
++ assigned_id=assigned_id)
++ self.InstallMessage(message)
++
++ def InstallMessage(self, message):
++ '''Sets this node's clique from a tclib.Message instance.
++
++ Args:
++ message: A tclib.Message.
++ '''
++ self.clique = self.UberClique().MakeClique(message, self.IsTranslateable())
++ for group in self.shortcut_groups_:
++ self.clique.AddToShortcutGroup(group)
++ if self.attrs['custom_type'] != '':
++ self.clique.SetCustomType(util.NewClassInstance(self.attrs['custom_type'],
++ clique.CustomType))
++ elif self.attrs['validation_expr'] != '':
++ self.clique.SetCustomType(
++ clique.OneOffCustomType(self.attrs['validation_expr']))
++
++ def SubstituteMessages(self, substituter):
++ '''Applies substitution to this message.
++
++ Args:
++ substituter: a grit.util.Substituter object.
++ '''
++ message = substituter.SubstituteMessage(self.clique.GetMessage())
++ if message is not self.clique.GetMessage():
++ self.InstallMessage(message)
++
++ def GetCliques(self):
++ return [self.clique] if self.clique else []
++
++ def Translate(self, lang):
++ '''Returns a translated version of this message.
++ '''
++ assert self.clique
++ msg = self.clique.MessageForLanguage(lang,
++ self.PseudoIsAllowed(),
++ self.ShouldFallbackToEnglish()
++ ).GetRealContent()
++ if self._replace_ellipsis:
++ msg = _ELLIPSIS_PATTERN.sub(_ELLIPSIS_SYMBOL, msg)
++ # Always remove all byte order marks (\uFEFF) https://crbug.com/1033305
++ msg = msg.replace(u'\uFEFF','')
++ return msg.replace('[GRITLANGCODE]', lang)
++
++ def NameOrOffset(self):
++ key = 'name' if 'name' in self.attrs else 'offset'
++ return self.attrs[key]
++
++ def ExpandVariables(self):
++ '''We always expand variables on Messages.'''
++ return True
++
++ def GetDataPackValue(self, lang, encoding):
++ '''Returns a str represenation for a data_pack entry.'''
++ message = self.ws_at_start + self.Translate(lang) + self.ws_at_end
++ return util.Encode(message, encoding)
++
++ def IsResourceMapSource(self):
++ return True
++
++ @staticmethod
++ def Construct(parent, message, name, desc='', meaning='', translateable=True):
++ '''Constructs a new message node that is a child of 'parent', with the
++ name, desc, meaning and translateable attributes set using the same-named
++ parameters and the text of the message and any placeholders taken from
++ 'message', which must be a tclib.Message() object.'''
++ # Convert type to appropriate string
++ translateable = 'true' if translateable else 'false'
++
++ node = MessageNode()
++ node.StartParsing('message', parent)
++ node.HandleAttribute('name', name)
++ node.HandleAttribute('desc', desc)
++ node.HandleAttribute('meaning', meaning)
++ node.HandleAttribute('translateable', translateable)
++
++ items = message.GetContent()
++ for ix, item in enumerate(items):
++ if isinstance(item, six.string_types):
++ # Ensure whitespace at front and back of message is correctly handled.
++ if ix == 0:
++ item = "'''" + item
++ if ix == len(items) - 1:
++ item = item + "'''"
++
++ node.AppendContent(item)
++ else:
++ phnode = PhNode()
++ phnode.StartParsing('ph', node)
++ phnode.HandleAttribute('name', item.GetPresentation())
++ phnode.AppendContent(item.GetOriginal())
++
++ if len(item.GetExample()) and item.GetExample() != ' ':
++ exnode = ExNode()
++ exnode.StartParsing('ex', phnode)
++ exnode.AppendContent(item.GetExample())
++ exnode.EndParsing()
++ phnode.AddChild(exnode)
++
++ phnode.EndParsing()
++ node.AddChild(phnode)
++
++ node.EndParsing()
++ return node
++
++
++class PhNode(base.ContentNode):
++ '''A <ph> element.'''
++
++ def _IsValidChild(self, child):
++ return isinstance(child, ExNode)
++
++ def MandatoryAttributes(self):
++ return ['name']
++
++ def EndParsing(self):
++ super(PhNode, self).EndParsing()
++ # We only allow a single example for each placeholder
++ if len(self.children) > 1:
++ raise exception.TooManyExamples()
++
++ def GetTextualIds(self):
++ # The 'name' attribute is not an ID.
++ return []
++
++
++class ExNode(base.ContentNode):
++ '''An <ex> element.'''
++ pass
+diff --git a/tools/grit/grit/node/message_unittest.py b/tools/grit/grit/node/message_unittest.py
+new file mode 100644
+index 0000000000..7a4cbbedc2
+--- /dev/null
++++ b/tools/grit/grit/node/message_unittest.py
+@@ -0,0 +1,380 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
+
-+class InlineCtorsArentOKInHeader {
-+ public:
-+ InlineCtorsArentOKInHeader() {}
-+ ~InlineCtorsArentOKInHeader() {}
++'''Unit tests for grit.node.message'''
+
-+ private:
-+ std::vector<int> one_;
-+ std::vector<std::string> two_;
-+};
++from __future__ import print_function
+
-+#endif // INLINE_CTOR_H_
-diff --git a/tools/clang/plugins/tests/inline_ctor.txt b/tools/clang/plugins/tests/inline_ctor.txt
++import os
++import sys
++import unittest
++
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++from grit import exception
++from grit import tclib
++from grit import util
++from grit.node import message
++
++class MessageUnittest(unittest.TestCase):
++ def testMessage(self):
++ root = util.ParseGrdForUnittest('''
++ <messages>
++ <message name="IDS_GREETING"
++ desc="Printed to greet the currently logged in user">
++ Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, how are you doing today?
++ </message>
++ </messages>''')
++ msg, = root.GetChildrenOfType(message.MessageNode)
++ cliques = msg.GetCliques()
++ content = cliques[0].GetMessage().GetPresentableContent()
++ self.failUnless(content == 'Hello USERNAME, how are you doing today?')
++
++ def testMessageWithWhitespace(self):
++ root = util.ParseGrdForUnittest("""\
++ <messages>
++ <message name="IDS_BLA" desc="">
++ ''' Hello there <ph name="USERNAME">%s</ph> '''
++ </message>
++ </messages>""")
++ msg, = root.GetChildrenOfType(message.MessageNode)
++ content = msg.GetCliques()[0].GetMessage().GetPresentableContent()
++ self.failUnless(content == 'Hello there USERNAME')
++ self.failUnless(msg.ws_at_start == ' ')
++ self.failUnless(msg.ws_at_end == ' ')
++
++ def testConstruct(self):
++ msg = tclib.Message(text=" Hello USERNAME, how are you? BINGO\t\t",
++ placeholders=[tclib.Placeholder('USERNAME', '%s', 'Joi'),
++ tclib.Placeholder('BINGO', '%d', '11')])
++ msg_node = message.MessageNode.Construct(None, msg, 'BINGOBONGO')
++ self.failUnless(msg_node.children[0].name == 'ph')
++ self.failUnless(msg_node.children[0].children[0].name == 'ex')
++ self.failUnless(msg_node.children[0].children[0].GetCdata() == 'Joi')
++ self.failUnless(msg_node.children[1].children[0].GetCdata() == '11')
++ self.failUnless(msg_node.ws_at_start == ' ')
++ self.failUnless(msg_node.ws_at_end == '\t\t')
++
++ def testUnicodeConstruct(self):
++ text = u'Howdie \u00fe'
++ msg = tclib.Message(text=text)
++ msg_node = message.MessageNode.Construct(None, msg, 'BINGOBONGO')
++ msg_from_node = msg_node.GetCdata()
++ self.failUnless(msg_from_node == text)
++
++ def testFormatterData(self):
++ root = util.ParseGrdForUnittest("""\
++ <messages>
++ <message name="IDS_BLA" desc="" formatter_data=" foo=123 bar qux=low">
++ Text
++ </message>
++ </messages>""")
++ msg, = root.GetChildrenOfType(message.MessageNode)
++ expected_formatter_data = {
++ 'foo': '123',
++ 'bar': '',
++ 'qux': 'low'}
++
++ # Can't use assertDictEqual, not available in Python 2.6, so do it
++ # by hand.
++ self.failUnlessEqual(len(expected_formatter_data),
++ len(msg.formatter_data))
++ for key in expected_formatter_data:
++ self.failUnlessEqual(expected_formatter_data[key],
++ msg.formatter_data[key])
++
++ def testReplaceEllipsis(self):
++ root = util.ParseGrdForUnittest('''
++ <messages>
++ <message name="IDS_GREETING" desc="">
++ A...B.... <ph name="PH">%s<ex>A</ex></ph>... B... C...
++ </message>
++ </messages>''')
++ msg, = root.GetChildrenOfType(message.MessageNode)
++ msg.SetReplaceEllipsis(True)
++ content = msg.Translate('en')
++ self.failUnlessEqual(u'A...B.... %s\u2026 B\u2026 C\u2026', content)
++
++ def testRemoveByteOrderMark(self):
++ root = util.ParseGrdForUnittest(u'''
++ <messages>
++ <message name="IDS_HAS_BOM" desc="">
++ \uFEFFThis\uFEFF i\uFEFFs OK\uFEFF
++ </message>
++ </messages>''')
++ msg, = root.GetChildrenOfType(message.MessageNode)
++ content = msg.Translate('en')
++ self.failUnlessEqual(u'This is OK', content)
++
++ def testPlaceholderHasTooManyExamples(self):
++ try:
++ util.ParseGrdForUnittest("""\
++ <messages>
++ <message name="IDS_FOO" desc="foo">
++ Hi <ph name="NAME">$1<ex>Joi</ex><ex>Joy</ex></ph>
++ </message>
++ </messages>""")
++ except exception.TooManyExamples:
++ return
++ self.fail('Should have gotten exception')
++
++ def testPlaceholderHasInvalidName(self):
++ try:
++ util.ParseGrdForUnittest("""\
++ <messages>
++ <message name="IDS_FOO" desc="foo">
++ Hi <ph name="ABC!">$1</ph>
++ </message>
++ </messages>""")
++ except exception.InvalidPlaceholderName:
++ return
++ self.fail('Should have gotten exception')
++
++ def testChromeLocalizedFormatIsInsidePhNode(self):
++ try:
++ util.ParseGrdForUnittest("""\
++ <messages>
++ <message name="IDS_CHROME_L10N" desc="l10n format">
++ This message is missing the ph node: $1
++ </message>
++ </messages>""")
++ except exception.PlaceholderNotInsidePhNode:
++ return
++ self.fail('Should have gotten exception')
++
++ def testAndroidStringFormatIsInsidePhNode(self):
++ try:
++ util.ParseGrdForUnittest("""\
++ <messages>
++ <message name="IDS_ANDROID" desc="string format">
++ This message is missing a ph node: %1$s
++ </message>
++ </messages>""")
++ except exception.PlaceholderNotInsidePhNode:
++ return
++ self.fail('Should have gotten exception')
++
++ def testAndroidIntegerFormatIsInsidePhNode(self):
++ try:
++ util.ParseGrdForUnittest("""\
++ <messages>
++ <message name="IDS_ANDROID" desc="integer format">
++ This message is missing a ph node: %2$d
++ </message>
++ </messages>""")
++ except exception.PlaceholderNotInsidePhNode:
++ return
++ self.fail('Should have gotten exception')
++
++ def testAndroidIntegerWidthFormatIsInsidePhNode(self):
++ try:
++ util.ParseGrdForUnittest("""\
++ <messages>
++ <message name="IDS_ANDROID" desc="integer width format">
++ This message is missing a ph node: %2$3d
++ </message>
++ </messages>""")
++ except exception.PlaceholderNotInsidePhNode:
++ return
++ self.fail('Should have gotten exception')
++
++ def testValidAndroidIntegerWidthFormatInPhNode(self):
++ try:
++ util.ParseGrdForUnittest("""\
++ <messages>
++ <message name="IDS_ANDROID_WIDTH">
++ <ph name="VALID">%2$3d<ex>042</ex></ph>
++ </message>
++ </messages>""")
++ except:
++ self.fail('Should not have gotten exception')
++
++ def testAndroidFloatFormatIsInsidePhNode(self):
++ try:
++ util.ParseGrdForUnittest("""\
++ <messages>
++ <message name="IDS_ANDROID" desc="float number format">
++ This message is missing a ph node: %3$4.5f
++ </message>
++ </messages>""")
++ except exception.PlaceholderNotInsidePhNode:
++ return
++ self.fail('Should have gotten exception')
++
++ def testGritStringFormatIsInsidePhNode(self):
++ try:
++ util.ParseGrdForUnittest("""\
++ <messages>
++ <message name="IDS_GRIT_STRING" desc="grit string format">
++ This message is missing the ph node: %s
++ </message>
++ </messages>""")
++ except exception.PlaceholderNotInsidePhNode:
++ return
++ self.fail('Should have gotten exception')
++
++ def testGritIntegerFormatIsInsidePhNode(self):
++ try:
++ util.ParseGrdForUnittest("""\
++ <messages>
++ <message name="IDS_GRIT_INTEGER" desc="grit integer format">
++ This message is missing the ph node: %d
++ </message>
++ </messages>""")
++ except exception.PlaceholderNotInsidePhNode:
++ return
++ self.fail('Should have gotten exception')
++
++ def testWindowsETWIntegerFormatIsInsidePhNode(self):
++ try:
++ util.ParseGrdForUnittest("""\
++ <messages>
++ <message name="IDS_WINDOWS_ETW" desc="ETW tracing integer">
++ This message is missing the ph node: %1
++ </message>
++ </messages>""")
++ except exception.PlaceholderNotInsidePhNode:
++ return
++ self.fail('Should have gotten exception')
++
++ def testValidMultipleFormattersInsidePhNodes(self):
++ root = util.ParseGrdForUnittest("""\
++ <messages>
++ <message name="IDS_MULTIPLE_FORMATTERS">
++ <ph name="ERROR_COUNT">%1$d<ex>1</ex></ph> error, <ph name="WARNING_COUNT">%2$d<ex>1</ex></ph> warning
++ </message>
++ </messages>""")
++ msg, = root.GetChildrenOfType(message.MessageNode)
++ cliques = msg.GetCliques()
++ content = cliques[0].GetMessage().GetPresentableContent()
++ self.failUnless(content == 'ERROR_COUNT error, WARNING_COUNT warning')
++
++ def testMultipleFormattersAreInsidePhNodes(self):
++ failed = True
++ try:
++ util.ParseGrdForUnittest("""\
++ <messages>
++ <message name="IDS_MULTIPLE_FORMATTERS">
++ %1$d error, %2$d warning
++ </message>
++ </messages>""")
++ except exception.PlaceholderNotInsidePhNode:
++ failed = False
++ if failed:
++ self.fail('Should have gotten exception')
++ return
++
++ failed = True
++ try:
++ util.ParseGrdForUnittest("""\
++ <messages>
++ <message name="IDS_MULTIPLE_FORMATTERS">
++ <ph name="ERROR_COUNT">%1$d<ex>1</ex></ph> error, %2$d warning
++ </message>
++ </messages>""")
++ except exception.PlaceholderNotInsidePhNode:
++ failed = False
++ if failed:
++ self.fail('Should have gotten exception')
++ return
++
++ failed = True
++ try:
++ util.ParseGrdForUnittest("""\
++ <messages>
++ <message name="IDS_MULTIPLE_FORMATTERS">
++ <ph name="INVALID">%1$d %2$d</ph>
++ </message>
++ </messages>""")
++ except exception.InvalidCharactersInsidePhNode:
++ failed = False
++ if failed:
++ self.fail('Should have gotten exception')
++ return
++
++ def testValidHTMLFormatInsidePhNode(self):
++ try:
++ util.ParseGrdForUnittest("""\
++ <messages>
++ <message name="IDS_HTML">
++ <ph name="VALID">&lt;span&gt;$1&lt;/span&gt;<ex>1</ex></ph>
++ </message>
++ </messages>""")
++ except:
++ self.fail('Should not have gotten exception')
++
++ def testValidHTMLWithAttributesFormatInsidePhNode(self):
++ try:
++ util.ParseGrdForUnittest("""\
++ <messages>
++ <message name="IDS_HTML_ATTRIBUTE">
++ <ph name="VALID">&lt;span attribute="js:$this %"&gt;$2&lt;/span&gt;<ex>2</ex></ph>
++ </message>
++ </messages>""")
++ except:
++ self.fail('Should not have gotten exception')
++
++ def testValidHTMLEntityFormatInsidePhNode(self):
++ try:
++ util.ParseGrdForUnittest("""\
++ <messages>
++ <message name="IDS_ENTITY">
++ <ph name="VALID">&gt;%1$d&lt;<ex>1</ex></ph>
++ </message>
++ </messages>""")
++ except:
++ self.fail('Should not have gotten exception')
++
++ def testValidMultipleDollarFormatInsidePhNode(self):
++ try:
++ util.ParseGrdForUnittest("""\
++ <messages>
++ <message name="IDS_DOLLARS" desc="l10n dollars format">
++ <ph name="VALID">$$1</ph>
++ </message>
++ </messages>""")
++ except:
++ self.fail('Should not have gotten exception')
++
++ def testInvalidDollarCharacterInsidePhNode(self):
++ try:
++ util.ParseGrdForUnittest("""\
++ <messages>
++ <message name="IDS_BAD_DOLLAR">
++ <ph name="INVALID">%1$d $</ph>
++ </message>
++ </messages>""")
++ except exception.InvalidCharactersInsidePhNode:
++ return
++ self.fail('Should have gotten exception')
++
++ def testInvalidPercentCharacterInsidePhNode(self):
++ try:
++ util.ParseGrdForUnittest("""\
++ <messages>
++ <message name="IDS_BAD_PERCENT">
++ <ph name="INVALID">%1$d %</ph>
++ </message>
++ </messages>""")
++ except exception.InvalidCharactersInsidePhNode:
++ return
++ self.fail('Should have gotten exception')
++
++ def testInvalidMixedFormatCharactersInsidePhNode(self):
++ try:
++ util.ParseGrdForUnittest("""\
++ <messages>
++ <message name="IDS_MIXED_FORMATS">
++ <ph name="INVALID">%1$2</ph>
++ </message>
++ </messages>""")
++ except exception.InvalidCharactersInsidePhNode:
++ return
++ self.fail('Should have gotten exception')
++
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/node/misc.py b/tools/grit/grit/node/misc.py
new file mode 100644
-index 0000000000..caa0cb4e3b
+index 0000000000..2d8b06d6a5
--- /dev/null
-+++ b/tools/clang/plugins/tests/inline_ctor.txt
-@@ -0,0 +1,8 @@
-+In file included from inline_ctor.cpp:5:
-+./inline_ctor.h:13:3: warning: [chromium-style] Complex constructor has an inlined body.
-+ InlineCtorsArentOKInHeader() {}
-+ ^
-+./inline_ctor.h:14:3: warning: [chromium-style] Complex destructor has an inline body.
-+ ~InlineCtorsArentOKInHeader() {}
-+ ^
-+2 warnings generated.
-diff --git a/tools/clang/plugins/tests/missing_ctor.cpp b/tools/clang/plugins/tests/missing_ctor.cpp
-new file mode 100644
-index 0000000000..8ee2fb2ac8
---- /dev/null
-+++ b/tools/clang/plugins/tests/missing_ctor.cpp
-@@ -0,0 +1,23 @@
-+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-+// Use of this source code is governed by a BSD-style license that can be
-+// found in the LICENSE file.
++++ b/tools/grit/grit/node/misc.py
+@@ -0,0 +1,707 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
+
-+#include "missing_ctor.h"
++"""Miscellaneous node types.
++"""
+
-+#include <string>
-+#include <vector>
++from __future__ import print_function
+
-+// We don't warn on classes that use default ctors in cpp files.
-+class MissingInCPPOK {
-+ public:
++import os.path
++import re
++import sys
+
-+ private:
-+ std::vector<int> one_;
-+ std::vector<std::string> two_;
-+};
++import six
++
++from grit import constants
++from grit import exception
++from grit import util
++from grit.extern import FP
++from grit.node import base
++from grit.node import message
++from grit.node import node_io
++
++
++# Python 3 doesn't have long() as int() works everywhere. But we really do need
++# the long() behavior on Python 2 as our ids are much too large for int().
++try:
++ long
++except NameError:
++ long = int
++
++
++# RTL languages
++# TODO(jennyz): remove this fixed set of RTL language array
++# now that generic expand_variable code exists.
++_RTL_LANGS = (
++ 'ar', # Arabic
++ 'fa', # Farsi
++ 'iw', # Hebrew
++ 'ks', # Kashmiri
++ 'ku', # Kurdish
++ 'ps', # Pashto
++ 'ur', # Urdu
++ 'yi', # Yiddish
++)
++
++
++def _ReadFirstIdsFromFile(filename, defines):
++ """Read the starting resource id values from |filename|. We also
++ expand variables of the form <(FOO) based on defines passed in on
++ the command line.
++
++ Returns a tuple, the absolute path of SRCDIR followed by the
++ first_ids dictionary.
++ """
++ first_ids_dict = eval(util.ReadFile(filename, 'utf-8'))
++ src_root_dir = os.path.abspath(os.path.join(os.path.dirname(filename),
++ first_ids_dict['SRCDIR']))
++
++ def ReplaceVariable(matchobj):
++ for key, value in defines.items():
++ if matchobj.group(1) == key:
++ return value
++ return ''
++
++ renames = []
++ for grd_filename in first_ids_dict:
++ new_grd_filename = re.sub(r'<\(([A-Za-z_]+)\)', ReplaceVariable,
++ grd_filename)
++ if new_grd_filename != grd_filename:
++ abs_grd_filename = os.path.abspath(new_grd_filename)
++ if abs_grd_filename[:len(src_root_dir)] != src_root_dir:
++ new_grd_filename = os.path.basename(abs_grd_filename)
++ else:
++ new_grd_filename = abs_grd_filename[len(src_root_dir) + 1:]
++ new_grd_filename = new_grd_filename.replace('\\', '/')
++ renames.append((grd_filename, new_grd_filename))
++
++ for grd_filename, new_grd_filename in renames:
++ first_ids_dict[new_grd_filename] = first_ids_dict[grd_filename]
++ del(first_ids_dict[grd_filename])
++
++ return (src_root_dir, first_ids_dict)
++
++
++def _ComputeIds(root, predetermined_tids):
++ """Returns a dict of textual id -> numeric id for all nodes in root.
++
++ IDs are mostly assigned sequentially, but will vary based on:
++ * first_id node attribute (from first_ids_file)
++ * hash of textual id (if not first_id is defined)
++ * offset node attribute
++ * whether the textual id matches a system id
++ * whether the node generates its own ID via GetId()
++
++ Args:
++ predetermined_tids: Dict of textual id -> numeric id to use in return dict.
++ """
++ from grit.node import empty, include, misc, structure
++
++ ids = {} # Maps numeric id to textual id
++ tids = {} # Maps textual id to numeric id
++ id_reasons = {} # Maps numeric id to text id and a human-readable explanation
++ group = None
++ last_id = None
++ predetermined_ids = {value: key
++ for key, value in predetermined_tids.items()}
++
++ for item in root:
++ if isinstance(item, empty.GroupingNode):
++ # Note: this won't work if any GroupingNode can be contained inside
++ # another.
++ group = item
++ last_id = None
++ continue
++
++ assert not item.GetTextualIds() or isinstance(item,
++ (include.IncludeNode, message.MessageNode,
++ misc.IdentifierNode, structure.StructureNode))
++
++ # Resources that use the RES protocol don't need
++ # any numerical ids generated, so we skip them altogether.
++ # This is accomplished by setting the flag 'generateid' to false
++ # in the GRD file.
++ if item.attrs.get('generateid', 'true') == 'false':
++ continue
++
++ for tid in item.GetTextualIds():
++ if util.SYSTEM_IDENTIFIERS.match(tid):
++ # Don't emit a new ID for predefined IDs
++ continue
++
++ if tid in tids:
++ continue
++
++ if predetermined_tids and tid in predetermined_tids:
++ id = predetermined_tids[tid]
++ reason = "from predetermined_tids map"
++
++ # Some identifier nodes can provide their own id,
++ # and we use that id in the generated header in that case.
++ elif hasattr(item, 'GetId') and item.GetId():
++ id = long(item.GetId())
++ reason = 'returned by GetId() method'
++
++ elif ('offset' in item.attrs and group and
++ group.attrs.get('first_id', '') != ''):
++ offset_text = item.attrs['offset']
++ parent_text = group.attrs['first_id']
++
++ try:
++ offset_id = long(offset_text)
++ except ValueError:
++ offset_id = tids[offset_text]
++
++ try:
++ parent_id = long(parent_text)
++ except ValueError:
++ parent_id = tids[parent_text]
++
++ id = parent_id + offset_id
++ reason = 'first_id %d + offset %d' % (parent_id, offset_id)
++
++ # We try to allocate IDs sequentially for blocks of items that might
++ # be related, for instance strings in a stringtable (as their IDs might be
++ # used e.g. as IDs for some radio buttons, in which case the IDs must
++ # be sequential).
++ #
++ # We do this by having the first item in a section store its computed ID
++ # (computed from a fingerprint) in its parent object. Subsequent children
++ # of the same parent will then try to get IDs that sequentially follow
++ # the currently stored ID (on the parent) and increment it.
++ elif last_id is None:
++ # First check if the starting ID is explicitly specified by the parent.
++ if group and group.attrs.get('first_id', '') != '':
++ id = long(group.attrs['first_id'])
++ reason = "from parent's first_id attribute"
++ else:
++ # Automatically generate the ID based on the first clique from the
++ # first child of the first child node of our parent (i.e. when we
++ # first get to this location in the code).
++
++ # According to
++ # http://msdn.microsoft.com/en-us/library/t2zechd4(VS.71).aspx
++ # the safe usable range for resource IDs in Windows is from decimal
++ # 101 to 0x7FFF.
++
++ id = FP.UnsignedFingerPrint(tid)
++ id = id % (0x7FFF - 101) + 101
++ reason = 'chosen by random fingerprint -- use first_id to override'
++
++ last_id = id
++ else:
++ id = last_id = last_id + 1
++ reason = 'sequentially assigned'
++
++ reason = "%s (%s)" % (tid, reason)
++ # Don't fail when 'offset' is specified, as the base and the 0th
++ # offset will have the same ID.
++ if id in id_reasons and not 'offset' in item.attrs:
++ raise exception.IdRangeOverlap('ID %d was assigned to both %s and %s.'
++ % (id, id_reasons[id], reason))
++
++ if id < 101:
++ print('WARNING: Numeric resource IDs should be greater than 100 to\n'
++ 'avoid conflicts with system-defined resource IDs.')
++
++ if tid not in predetermined_tids and id in predetermined_ids:
++ raise exception.IdRangeOverlap('ID %d overlaps between %s and %s'
++ % (id, tid, predetermined_ids[tid]))
++
++ ids[id] = tid
++ tids[tid] = id
++ id_reasons[id] = reason
++
++ return tids
++
++class SplicingNode(base.Node):
++ """A node whose children should be considered to be at the same level as
++ its siblings for most purposes. This includes <if> and <part> nodes.
++ """
++
++ def _IsValidChild(self, child):
++ assert self.parent, '<%s> node should never be root.' % self.name
++ if isinstance(child, SplicingNode):
++ return True # avoid O(n^2) behavior
++ return self.parent._IsValidChild(child)
++
++
++class IfNode(SplicingNode):
++ """A node for conditional inclusion of resources.
++ """
++
++ def MandatoryAttributes(self):
++ return ['expr']
++
++ def _IsValidChild(self, child):
++ return (isinstance(child, (ThenNode, ElseNode)) or
++ super(IfNode, self)._IsValidChild(child))
++
++ def EndParsing(self):
++ children = self.children
++ self.if_then_else = False
++ if any(isinstance(node, (ThenNode, ElseNode)) for node in children):
++ if (len(children) != 2 or not isinstance(children[0], ThenNode) or
++ not isinstance(children[1], ElseNode)):
++ raise exception.UnexpectedChild(
++ '<if> element must be <if><then>...</then><else>...</else></if>')
++ self.if_then_else = True
++
++ def ActiveChildren(self):
++ cond = self.EvaluateCondition(self.attrs['expr'])
++ if self.if_then_else:
++ return self.children[0 if cond else 1].ActiveChildren()
++ else:
++ # Equivalent to having all children inside <then> with an empty <else>
++ return super(IfNode, self).ActiveChildren() if cond else []
++
++
++class ThenNode(SplicingNode):
++ """A <then> node. Can only appear directly inside an <if> node."""
++ pass
++
++
++class ElseNode(SplicingNode):
++ """An <else> node. Can only appear directly inside an <if> node."""
++ pass
++
++
++class PartNode(SplicingNode):
++ """A node for inclusion of sub-grd (*.grp) files.
++ """
++
++ def __init__(self):
++ super(PartNode, self).__init__()
++ self.started_inclusion = False
++
++ def MandatoryAttributes(self):
++ return ['file']
++
++ def _IsValidChild(self, child):
++ return self.started_inclusion and super(PartNode, self)._IsValidChild(child)
++
++
++class ReleaseNode(base.Node):
++ """The <release> element."""
++
++ def _IsValidChild(self, child):
++ from grit.node import empty
++ return isinstance(child, (empty.IncludesNode, empty.MessagesNode,
++ empty.StructuresNode, empty.IdentifiersNode))
++
++ def _IsValidAttribute(self, name, value):
++ return (
++ (name == 'seq' and int(value) <= self.GetRoot().GetCurrentRelease()) or
++ name == 'allow_pseudo'
++ )
++
++ def MandatoryAttributes(self):
++ return ['seq']
++
++ def DefaultAttributes(self):
++ return { 'allow_pseudo' : 'true' }
++
++
++class GritNode(base.Node):
++ """The <grit> root element."""
++
++ def __init__(self):
++ super(GritNode, self).__init__()
++ self.output_language = ''
++ self.defines = {}
++ self.substituter = None
++ self.target_platform = sys.platform
++ self.whitelist_support = False
++ self._predetermined_ids_file = None
++ self._id_map = None # Dict of textual_id -> numeric_id.
++
++ def _IsValidChild(self, child):
++ from grit.node import empty
++ return isinstance(child, (ReleaseNode, empty.TranslationsNode,
++ empty.OutputsNode))
++
++ def _IsValidAttribute(self, name, value):
++ if name not in ['base_dir', 'first_ids_file', 'source_lang_id',
++ 'latest_public_release', 'current_release',
++ 'enc_check', 'tc_project', 'grit_version',
++ 'output_all_resource_defines']:
++ return False
++ if name in ['latest_public_release', 'current_release'] and value.strip(
++ '0123456789') != '':
++ return False
++ return True
++
++ def MandatoryAttributes(self):
++ return ['latest_public_release', 'current_release']
++
++ def DefaultAttributes(self):
++ return {
++ 'base_dir' : '.',
++ 'first_ids_file': '',
++ 'grit_version': 1,
++ 'source_lang_id' : 'en',
++ 'enc_check' : constants.ENCODING_CHECK,
++ 'tc_project' : 'NEED_TO_SET_tc_project_ATTRIBUTE',
++ }
++
++ def EndParsing(self):
++ super(GritNode, self).EndParsing()
++ if (int(self.attrs['latest_public_release'])
++ > int(self.attrs['current_release'])):
++ raise exception.Parsing('latest_public_release cannot have a greater '
++ 'value than current_release')
++
++ self.ValidateUniqueIds()
++
++ # Add the encoding check if it's not present (should ensure that it's always
++ # present in all .grd files generated by GRIT). If it's present, assert if
++ # it's not correct.
++ if 'enc_check' not in self.attrs or self.attrs['enc_check'] == '':
++ self.attrs['enc_check'] = constants.ENCODING_CHECK
++ else:
++ assert self.attrs['enc_check'] == constants.ENCODING_CHECK, (
++ 'Are you sure your .grd file is in the correct encoding (UTF-8)?')
++
++ def ValidateUniqueIds(self):
++ """Validate that 'name' attribute is unique in all nodes in this tree
++ except for nodes that are children of <if> nodes.
++ """
++ unique_names = {}
++ duplicate_names = []
++ # To avoid false positives from mutually exclusive <if> clauses, check
++ # against whatever the output condition happens to be right now.
++ # TODO(benrg): do something better.
++ for node in self.ActiveDescendants():
++ if node.attrs.get('generateid', 'true') == 'false':
++ continue # Duplication not relevant in that case
++
++ for node_id in node.GetTextualIds():
++ if util.SYSTEM_IDENTIFIERS.match(node_id):
++ continue # predefined IDs are sometimes used more than once
++
++ if node_id in unique_names and node_id not in duplicate_names:
++ duplicate_names.append(node_id)
++ unique_names[node_id] = 1
++
++ if len(duplicate_names):
++ raise exception.DuplicateKey(', '.join(duplicate_names))
++
++
++ def GetCurrentRelease(self):
++ """Returns the current release number."""
++ return int(self.attrs['current_release'])
++
++ def GetLatestPublicRelease(self):
++ """Returns the latest public release number."""
++ return int(self.attrs['latest_public_release'])
++
++ def GetSourceLanguage(self):
++ """Returns the language code of the source language."""
++ return self.attrs['source_lang_id']
++
++ def GetTcProject(self):
++ """Returns the name of this project in the TranslationConsole, or
++ 'NEED_TO_SET_tc_project_ATTRIBUTE' if it is not defined."""
++ return self.attrs['tc_project']
++
++ def SetOwnDir(self, dir):
++ """Informs the 'grit' element of the directory the file it is in resides.
++ This allows it to calculate relative paths from the input file, which is
++ what we desire (rather than from the current path).
++
++ Args:
++ dir: r'c:\bla'
++
++ Return:
++ None
++ """
++ assert dir
++ self.base_dir = os.path.normpath(os.path.join(dir, self.attrs['base_dir']))
++
++ def GetBaseDir(self):
++ """Returns the base directory, relative to the working directory. To get
++ the base directory as set in the .grd file, use GetOriginalBaseDir()
++ """
++ if hasattr(self, 'base_dir'):
++ return self.base_dir
++ else:
++ return self.GetOriginalBaseDir()
++
++ def GetOriginalBaseDir(self):
++ """Returns the base directory, as set in the .grd file.
++ """
++ return self.attrs['base_dir']
++
++ def IsWhitelistSupportEnabled(self):
++ return self.whitelist_support
++
++ def SetWhitelistSupportEnabled(self, whitelist_support):
++ self.whitelist_support = whitelist_support
++
++ def GetInputFiles(self):
++ """Returns the list of files that are read to produce the output."""
++
++ # Importing this here avoids a circular dependency in the imports.
++ # pylint: disable-msg=C6204
++ from grit.node import include
++ from grit.node import misc
++ from grit.node import structure
++ from grit.node import variant
++
++ # Check if the input is required for any output configuration.
++ input_files = set()
++ # Collect even inactive PartNodes since they affect ID assignments.
++ for node in self:
++ if isinstance(node, misc.PartNode):
++ input_files.add(self.ToRealPath(node.GetInputPath()))
++
++ old_output_language = self.output_language
++ for lang, ctx, fallback in self.GetConfigurations():
++ self.SetOutputLanguage(lang or self.GetSourceLanguage())
++ self.SetOutputContext(ctx)
++ self.SetFallbackToDefaultLayout(fallback)
++
++ for node in self.ActiveDescendants():
++ if isinstance(node, (node_io.FileNode, include.IncludeNode,
++ structure.StructureNode, variant.SkeletonNode)):
++ input_path = node.GetInputPath()
++ if input_path is not None:
++ input_files.add(self.ToRealPath(input_path))
++
++ # If it's a flattened node, grab inlined resources too.
++ if ((node.name == 'structure' or node.name == 'include')
++ and node.attrs['flattenhtml'] == 'true'):
++ if node.name == 'structure':
++ node.RunPreSubstitutionGatherer()
++ input_files.update(node.GetHtmlResourceFilenames())
++
++ self.SetOutputLanguage(old_output_language)
++ return sorted(input_files)
++
++ def GetFirstIdsFile(self):
++ """Returns a usable path to the first_ids file, if set, otherwise
++ returns None.
++
++ The first_ids_file attribute is by default relative to the
++ base_dir of the .grd file, but may be prefixed by GRIT_DIR/,
++ which makes it relative to the directory of grit.py
++ (e.g. GRIT_DIR/../gritsettings/resource_ids).
++ """
++ if not self.attrs['first_ids_file']:
++ return None
++
++ path = self.attrs['first_ids_file']
++ GRIT_DIR_PREFIX = 'GRIT_DIR'
++ if (path.startswith(GRIT_DIR_PREFIX)
++ and path[len(GRIT_DIR_PREFIX)] in ['/', '\\']):
++ return util.PathFromRoot(path[len(GRIT_DIR_PREFIX) + 1:])
++ else:
++ return self.ToRealPath(path)
++
++ def GetOutputFiles(self):
++ """Returns the list of <output> nodes that are descendants of this node's
++ <outputs> child and are not enclosed by unsatisfied <if> conditionals.
++ """
++ for child in self.children:
++ if child.name == 'outputs':
++ return [node for node in child.ActiveDescendants()
++ if node.name == 'output']
++ raise exception.MissingElement()
++
++ def GetConfigurations(self):
++ """Returns the distinct (language, context, fallback_to_default_layout)
++ triples from the output nodes.
++ """
++ return set((n.GetLanguage(), n.GetContext(), n.GetFallbackToDefaultLayout())
++ for n in self.GetOutputFiles())
++
++ def GetSubstitutionMessages(self):
++ """Returns the list of <message sub_variable="true"> nodes."""
++ return [n for n in self.ActiveDescendants()
++ if isinstance(n, message.MessageNode)
++ and n.attrs['sub_variable'] == 'true']
++
++ def SetOutputLanguage(self, output_language):
++ """Set the output language. Prepares substitutions.
++
++ The substitutions are reset every time the language is changed.
++ They include messages designated as variables, and language codes for html
++ and rc files.
++
++ Args:
++ output_language: a two-letter language code (eg: 'en', 'ar'...) or ''
++ """
++ if not output_language:
++ # We do not specify the output language for .grh files,
++ # so we get an empty string as the default.
++ # The value should match grit.clique.MessageClique.source_language.
++ output_language = self.GetSourceLanguage()
++ if output_language != self.output_language:
++ self.output_language = output_language
++ self.substituter = None # force recalculate
++
++ def SetOutputContext(self, output_context):
++ self.output_context = output_context
++ self.substituter = None # force recalculate
++
++ def SetFallbackToDefaultLayout(self, fallback_to_default_layout):
++ self.fallback_to_default_layout = fallback_to_default_layout
++ self.substituter = None # force recalculate
++
++ def SetDefines(self, defines):
++ self.defines = defines
++ self.substituter = None # force recalculate
++
++ def SetTargetPlatform(self, target_platform):
++ self.target_platform = target_platform
++
++ def GetSubstituter(self):
++ if self.substituter is None:
++ self.substituter = util.Substituter()
++ self.substituter.AddMessages(self.GetSubstitutionMessages(),
++ self.output_language)
++ if self.output_language in _RTL_LANGS:
++ direction = 'dir="RTL"'
++ else:
++ direction = 'dir="LTR"'
++ self.substituter.AddSubstitutions({
++ 'GRITLANGCODE': self.output_language,
++ 'GRITDIR': direction,
++ })
++ from grit.format import rc # avoid circular dep
++ rc.RcSubstitutions(self.substituter, self.output_language)
++ return self.substituter
++
++ def AssignFirstIds(self, filename_or_stream, defines):
++ """Assign first ids to each grouping node based on values from the
++ first_ids file (if specified on the <grit> node).
++ """
++ assert self._id_map is None, 'AssignFirstIds() after InitializeIds()'
++ # If the input is a stream, then we're probably in a unit test and
++ # should skip this step.
++ if not isinstance(filename_or_stream, six.string_types):
++ return
++
++ # Nothing to do if the first_ids_filename attribute isn't set.
++ first_ids_filename = self.GetFirstIdsFile()
++ if not first_ids_filename:
++ return
++
++ src_root_dir, first_ids = _ReadFirstIdsFromFile(first_ids_filename,
++ defines)
++ from grit.node import empty
++ for node in self.Preorder():
++ if isinstance(node, empty.GroupingNode):
++ abs_filename = os.path.abspath(filename_or_stream)
++ if abs_filename[:len(src_root_dir)] != src_root_dir:
++ filename = os.path.basename(filename_or_stream)
++ else:
++ filename = abs_filename[len(src_root_dir) + 1:]
++ filename = filename.replace('\\', '/')
++
++ if node.attrs['first_id'] != '':
++ raise Exception(
++ "Don't set the first_id attribute when using the first_ids_file "
++ "attribute on the <grit> node, update %s instead." %
++ first_ids_filename)
++
++ try:
++ id_list = first_ids[filename][node.name]
++ except KeyError as e:
++ print('-' * 78)
++ print('Resource id not set for %s (%s)!' % (filename, node.name))
++ print('Please update %s to include an entry for %s. See the '
++ 'comments in resource_ids for information on why you need to '
++ 'update that file.' % (first_ids_filename, filename))
++ print('-' * 78)
++ raise e
++
++ try:
++ node.attrs['first_id'] = str(id_list.pop(0))
++ except IndexError as e:
++ raise Exception('Please update %s and add a first id for %s (%s).'
++ % (first_ids_filename, filename, node.name))
++
++ def GetIdMap(self):
++ '''Return a dictionary mapping textual ids to numeric ids.'''
++ return self._id_map
++
++ def SetPredeterminedIdsFile(self, predetermined_ids_file):
++ assert self._id_map is None, (
++ 'SetPredeterminedIdsFile() after InitializeIds()')
++ self._predetermined_ids_file = predetermined_ids_file
++
++ def InitializeIds(self):
++ '''Initializes the text ID -> numeric ID mapping.'''
++ predetermined_id_map = {}
++ if self._predetermined_ids_file:
++ with open(self._predetermined_ids_file) as f:
++ for line in f:
++ tid, nid = line.split()
++ predetermined_id_map[tid] = int(nid)
++ self._id_map = _ComputeIds(self, predetermined_id_map)
++
++ def RunGatherers(self, debug=False):
++ '''Call RunPreSubstitutionGatherer() on every node of the tree, then apply
++ substitutions, then call RunPostSubstitutionGatherer() on every node.
++
++ The substitutions step requires that the output language has been set.
++ Locally, get the Substitution messages and add them to the substituter.
++ Also add substitutions for language codes in the Rc.
++
++ Args:
++ debug: will print information while running gatherers.
++ '''
++ for node in self.ActiveDescendants():
++ if hasattr(node, 'RunPreSubstitutionGatherer'):
++ with node:
++ node.RunPreSubstitutionGatherer(debug=debug)
++
++ assert self.output_language
++ self.SubstituteMessages(self.GetSubstituter())
++
++ for node in self.ActiveDescendants():
++ if hasattr(node, 'RunPostSubstitutionGatherer'):
++ with node:
++ node.RunPostSubstitutionGatherer(debug=debug)
++
++
++class IdentifierNode(base.Node):
++ """A node for specifying identifiers that should appear in the resource
++ header file, and be unique amongst all other resource identifiers, but don't
++ have any other attributes or reference any resources.
++ """
++
++ def MandatoryAttributes(self):
++ return ['name']
++
++ def DefaultAttributes(self):
++ return { 'comment' : '', 'id' : '', 'systemid': 'false' }
++
++ def GetId(self):
++ """Returns the id of this identifier if it has one, None otherwise
++ """
++ if 'id' in self.attrs:
++ return self.attrs['id']
++ return None
++
++ def EndParsing(self):
++ """Handles system identifiers."""
++ super(IdentifierNode, self).EndParsing()
++ if self.attrs['systemid'] == 'true':
++ util.SetupSystemIdentifiers((self.attrs['name'],))
++
++ @staticmethod
++ def Construct(parent, name, id, comment, systemid='false'):
++ """Creates a new node which is a child of 'parent', with attributes set
++ by parameters of the same name.
++ """
++ node = IdentifierNode()
++ node.StartParsing('identifier', parent)
++ node.HandleAttribute('name', name)
++ node.HandleAttribute('id', id)
++ node.HandleAttribute('comment', comment)
++ node.HandleAttribute('systemid', systemid)
++ node.EndParsing()
++ return node
+diff --git a/tools/grit/grit/node/misc_unittest.py b/tools/grit/grit/node/misc_unittest.py
+new file mode 100644
+index 0000000000..c192b096f4
+--- /dev/null
++++ b/tools/grit/grit/node/misc_unittest.py
+@@ -0,0 +1,590 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit tests for misc.GritNode'''
++
++from __future__ import print_function
++
++import contextlib
++import os
++import sys
++import tempfile
++import unittest
++
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++from six import StringIO
++
++from grit import grd_reader
++import grit.exception
++from grit import util
++from grit.format import rc
++from grit.format import rc_header
++from grit.node import misc
++
++
++@contextlib.contextmanager
++def _MakeTempPredeterminedIdsFile(content):
++ """Write the |content| string to a temporary file.
++
++ The temporary file must be deleted by the caller.
++
++ Example:
++ with _MakeTempPredeterminedIdsFile('foo') as path:
++ ...
++ os.remove(path)
++
++ Args:
++ content: The string to write.
++
++ Yields:
++ The name of the temporary file.
++ """
++ with tempfile.NamedTemporaryFile(mode='w', delete=False) as f:
++ f.write(content)
++ f.flush()
++ f.close()
++ yield f.name
++
++
++class GritNodeUnittest(unittest.TestCase):
++ def testUniqueNameAttribute(self):
++ try:
++ restree = grd_reader.Parse(
++ util.PathFromRoot('grit/testdata/duplicate-name-input.xml'))
++ self.fail('Expected parsing exception because of duplicate names.')
++ except grit.exception.Parsing:
++ pass # Expected case
++
++ def testReadFirstIdsFromFile(self):
++ test_resource_ids = os.path.join(os.path.dirname(__file__), '..',
++ 'testdata', 'resource_ids')
++ base_dir = os.path.dirname(test_resource_ids)
++
++ src_dir, id_dict = misc._ReadFirstIdsFromFile(
++ test_resource_ids,
++ {
++ 'FOO': os.path.join(base_dir, 'bar'),
++ 'SHARED_INTERMEDIATE_DIR': os.path.join(base_dir,
++ 'out/Release/obj/gen'),
++ })
++ self.assertEqual({}, id_dict.get('bar/file.grd', None))
++ self.assertEqual({},
++ id_dict.get('out/Release/obj/gen/devtools/devtools.grd', None))
++
++ src_dir, id_dict = misc._ReadFirstIdsFromFile(
++ test_resource_ids,
++ {
++ 'SHARED_INTERMEDIATE_DIR': '/outside/src_dir',
++ })
++ self.assertEqual({}, id_dict.get('devtools.grd', None))
++
++ # Verifies that GetInputFiles() returns the correct list of files
++ # corresponding to ChromeScaledImage nodes when assets are missing.
++ def testGetInputFilesChromeScaledImage(self):
++ chrome_html_path = util.PathFromRoot('grit/testdata/chrome_html.html')
++ xml = '''<?xml version="1.0" encoding="utf-8"?>
++ <grit latest_public_release="0" current_release="1">
++ <outputs>
++ <output filename="default.pak" type="data_package" context="default_100_percent" />
++ <output filename="special.pak" type="data_package" context="special_100_percent" fallback_to_default_layout="false" />
++ </outputs>
++ <release seq="1">
++ <structures fallback_to_low_resolution="true">
++ <structure type="chrome_scaled_image" name="IDR_A" file="a.png" />
++ <structure type="chrome_scaled_image" name="IDR_B" file="b.png" />
++ <structure type="chrome_html" name="HTML_FILE1" file="%s" flattenhtml="true" />
++ </structures>
++ </release>
++ </grit>''' % chrome_html_path
++
++ grd = grd_reader.Parse(StringIO(xml),
++ util.PathFromRoot('grit/testdata'))
++ expected = ['chrome_html.html', 'default_100_percent/a.png',
++ 'default_100_percent/b.png', 'included_sample.html',
++ 'special_100_percent/a.png']
++ actual = [os.path.relpath(path, util.PathFromRoot('grit/testdata')) for
++ path in grd.GetInputFiles()]
++ # Convert path separator for Windows paths.
++ actual = [path.replace('\\', '/') for path in actual]
++ self.assertEquals(expected, actual)
++
++ # Verifies that GetInputFiles() returns the correct list of files
++ # when files include other files.
++ def testGetInputFilesFromIncludes(self):
++ chrome_html_path = util.PathFromRoot('grit/testdata/chrome_html.html')
++ xml = '''<?xml version="1.0" encoding="utf-8"?>
++ <grit latest_public_release="0" current_release="1">
++ <outputs>
++ <output filename="default.pak" type="data_package" context="default_100_percent" />
++ <output filename="special.pak" type="data_package" context="special_100_percent" fallback_to_default_layout="false" />
++ </outputs>
++ <release seq="1">
++ <includes>
++ <include name="IDR_TESTDATA_CHROME_HTML" file="%s" flattenhtml="true"
++ allowexternalscript="true" type="BINDATA" />
++ </includes>
++ </release>
++ </grit>''' % chrome_html_path
++
++ grd = grd_reader.Parse(StringIO(xml), util.PathFromRoot('grit/testdata'))
++ expected = ['chrome_html.html', 'included_sample.html']
++ actual = [os.path.relpath(path, util.PathFromRoot('grit/testdata')) for
++ path in grd.GetInputFiles()]
++ # Convert path separator for Windows paths.
++ actual = [path.replace('\\', '/') for path in actual]
++ self.assertEquals(expected, actual)
++
++ def testNonDefaultEntry(self):
++ grd = util.ParseGrdForUnittest('''
++ <messages>
++ <message name="IDS_A" desc="foo">bar</message>
++ <if expr="lang == 'fr'">
++ <message name="IDS_B" desc="foo">bar</message>
++ </if>
++ </messages>''')
++ grd.SetOutputLanguage('fr')
++ output = ''.join(rc_header.Format(grd, 'fr', '.'))
++ self.assertIn('#define IDS_A 2378\n#define IDS_B 2379', output)
++
++ def testExplicitFirstIdOverlaps(self):
++ # second first_id will overlap preexisting range
++ self.assertRaises(grit.exception.IdRangeOverlap,
++ util.ParseGrdForUnittest, '''
++ <includes first_id="300" comment="bingo">
++ <include type="gif" name="ID_LOGO" file="images/logo.gif" />
++ <include type="gif" name="ID_LOGO2" file="images/logo2.gif" />
++ </includes>
++ <messages first_id="301">
++ <message name="IDS_GREETING" desc="Printed to greet the currently logged in user">
++ Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, how are you doing today?
++ </message>
++ <message name="IDS_SMURFGEBURF">Frubegfrums</message>
++ </messages>''')
++
++ def testImplicitOverlapsPreexisting(self):
++ # second message in <messages> will overlap preexisting range
++ self.assertRaises(grit.exception.IdRangeOverlap,
++ util.ParseGrdForUnittest, '''
++ <includes first_id="301" comment="bingo">
++ <include type="gif" name="ID_LOGO" file="images/logo.gif" />
++ <include type="gif" name="ID_LOGO2" file="images/logo2.gif" />
++ </includes>
++ <messages first_id="300">
++ <message name="IDS_GREETING" desc="Printed to greet the currently logged in user">
++ Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, how are you doing today?
++ </message>
++ <message name="IDS_SMURFGEBURF">Frubegfrums</message>
++ </messages>''')
++
++ def testPredeterminedIds(self):
++ with _MakeTempPredeterminedIdsFile('IDS_A 101\nIDS_B 102') as ids_file:
++ grd = util.ParseGrdForUnittest('''
++ <includes first_id="300" comment="bingo">
++ <include type="gif" name="IDS_B" file="images/logo.gif" />
++ </includes>
++ <messages first_id="10000">
++ <message name="IDS_GREETING" desc="Printed to greet the currently logged in user">
++ Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, how are you doing today?
++ </message>
++ <message name="IDS_A">
++ Bongo!
++ </message>
++ </messages>''', predetermined_ids_file=ids_file)
++ output = rc_header.FormatDefines(grd)
++ self.assertEqual(('#define IDS_B 102\n'
++ '#define IDS_GREETING 10000\n'
++ '#define IDS_A 101\n'), ''.join(output))
++ os.remove(ids_file)
++
++ def testPredeterminedIdsOverlap(self):
++ with _MakeTempPredeterminedIdsFile('ID_LOGO 10000') as ids_file:
++ self.assertRaises(grit.exception.IdRangeOverlap,
++ util.ParseGrdForUnittest, '''
++ <includes first_id="300" comment="bingo">
++ <include type="gif" name="ID_LOGO" file="images/logo.gif" />
++ </includes>
++ <messages first_id="10000">
++ <message name="IDS_GREETING" desc="Printed to greet the currently logged in user">
++ Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, how are you doing today?
++ </message>
++ <message name="IDS_BONGO">
++ Bongo!
++ </message>
++ </messages>''', predetermined_ids_file=ids_file)
++ os.remove(ids_file)
++
++
++class IfNodeUnittest(unittest.TestCase):
++ def testIffyness(self):
++ grd = grd_reader.Parse(StringIO('''
++ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
++ <release seq="3">
++ <messages>
++ <if expr="'bingo' in defs">
++ <message name="IDS_BINGO">
++ Bingo!
++ </message>
++ </if>
++ <if expr="'hello' in defs">
++ <message name="IDS_HELLO">
++ Hello!
++ </message>
++ </if>
++ <if expr="lang == 'fr' or 'FORCE_FRENCH' in defs">
++ <message name="IDS_HELLO" internal_comment="French version">
++ Good morning
++ </message>
++ </if>
++ <if expr="is_win">
++ <message name="IDS_ISWIN">is_win</message>
++ </if>
++ </messages>
++ </release>
++ </grit>'''), dir='.')
++
++ messages_node = grd.children[0].children[0]
++ bingo_message = messages_node.children[0].children[0]
++ hello_message = messages_node.children[1].children[0]
++ french_message = messages_node.children[2].children[0]
++ is_win_message = messages_node.children[3].children[0]
++
++ self.assertTrue(bingo_message.name == 'message')
++ self.assertTrue(hello_message.name == 'message')
++ self.assertTrue(french_message.name == 'message')
++
++ grd.SetOutputLanguage('fr')
++ grd.SetDefines({'hello': '1'})
++ active = set(grd.ActiveDescendants())
++ self.failUnless(bingo_message not in active)
++ self.failUnless(hello_message in active)
++ self.failUnless(french_message in active)
++
++ grd.SetOutputLanguage('en')
++ grd.SetDefines({'bingo': 1})
++ active = set(grd.ActiveDescendants())
++ self.failUnless(bingo_message in active)
++ self.failUnless(hello_message not in active)
++ self.failUnless(french_message not in active)
++
++ grd.SetOutputLanguage('en')
++ grd.SetDefines({'FORCE_FRENCH': '1', 'bingo': '1'})
++ active = set(grd.ActiveDescendants())
++ self.failUnless(bingo_message in active)
++ self.failUnless(hello_message not in active)
++ self.failUnless(french_message in active)
++
++ grd.SetOutputLanguage('en')
++ grd.SetDefines({})
++ self.failUnless(grd.target_platform == sys.platform)
++ grd.SetTargetPlatform('darwin')
++ active = set(grd.ActiveDescendants())
++ self.failUnless(is_win_message not in active)
++ grd.SetTargetPlatform('win32')
++ active = set(grd.ActiveDescendants())
++ self.failUnless(is_win_message in active)
++
++ def testElsiness(self):
++ grd = util.ParseGrdForUnittest('''
++ <messages>
++ <if expr="True">
++ <then> <message name="IDS_YES1"></message> </then>
++ <else> <message name="IDS_NO1"></message> </else>
++ </if>
++ <if expr="True">
++ <then> <message name="IDS_YES2"></message> </then>
++ <else> </else>
++ </if>
++ <if expr="True">
++ <then> </then>
++ <else> <message name="IDS_NO2"></message> </else>
++ </if>
++ <if expr="True">
++ <then> </then>
++ <else> </else>
++ </if>
++ <if expr="False">
++ <then> <message name="IDS_NO3"></message> </then>
++ <else> <message name="IDS_YES3"></message> </else>
++ </if>
++ <if expr="False">
++ <then> <message name="IDS_NO4"></message> </then>
++ <else> </else>
++ </if>
++ <if expr="False">
++ <then> </then>
++ <else> <message name="IDS_YES4"></message> </else>
++ </if>
++ <if expr="False">
++ <then> </then>
++ <else> </else>
++ </if>
++ </messages>''')
++ included = [msg.attrs['name'] for msg in grd.ActiveDescendants()
++ if msg.name == 'message']
++ self.assertEqual(['IDS_YES1', 'IDS_YES2', 'IDS_YES3', 'IDS_YES4'], included)
++
++ def testIffynessWithOutputNodes(self):
++ grd = grd_reader.Parse(StringIO('''
++ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
++ <outputs>
++ <output filename="uncond1.rc" type="rc_data" />
++ <if expr="lang == 'fr' or 'hello' in defs">
++ <output filename="only_fr.adm" type="adm" />
++ <output filename="only_fr.plist" type="plist" />
++ </if>
++ <if expr="lang == 'ru'">
++ <output filename="doc.html" type="document" />
++ </if>
++ <output filename="uncond2.adm" type="adm" />
++ <output filename="iftest.h" type="rc_header">
++ <emit emit_type='prepend'></emit>
++ </output>
++ </outputs>
++ </grit>'''), dir='.')
++
++ outputs_node = grd.children[0]
++ uncond1_output = outputs_node.children[0]
++ only_fr_adm_output = outputs_node.children[1].children[0]
++ only_fr_plist_output = outputs_node.children[1].children[1]
++ doc_output = outputs_node.children[2].children[0]
++ uncond2_output = outputs_node.children[0]
++ self.assertTrue(uncond1_output.name == 'output')
++ self.assertTrue(only_fr_adm_output.name == 'output')
++ self.assertTrue(only_fr_plist_output.name == 'output')
++ self.assertTrue(doc_output.name == 'output')
++ self.assertTrue(uncond2_output.name == 'output')
++
++ grd.SetOutputLanguage('ru')
++ grd.SetDefines({'hello': '1'})
++ outputs = [output.GetFilename() for output in grd.GetOutputFiles()]
++ self.assertEquals(
++ outputs,
++ ['uncond1.rc', 'only_fr.adm', 'only_fr.plist', 'doc.html',
++ 'uncond2.adm', 'iftest.h'])
++
++ grd.SetOutputLanguage('ru')
++ grd.SetDefines({'bingo': '2'})
++ outputs = [output.GetFilename() for output in grd.GetOutputFiles()]
++ self.assertEquals(
++ outputs,
++ ['uncond1.rc', 'doc.html', 'uncond2.adm', 'iftest.h'])
++
++ grd.SetOutputLanguage('fr')
++ grd.SetDefines({'hello': '1'})
++ outputs = [output.GetFilename() for output in grd.GetOutputFiles()]
++ self.assertEquals(
++ outputs,
++ ['uncond1.rc', 'only_fr.adm', 'only_fr.plist', 'uncond2.adm',
++ 'iftest.h'])
++
++ grd.SetOutputLanguage('en')
++ grd.SetDefines({'bingo': '1'})
++ outputs = [output.GetFilename() for output in grd.GetOutputFiles()]
++ self.assertEquals(outputs, ['uncond1.rc', 'uncond2.adm', 'iftest.h'])
++
++ grd.SetOutputLanguage('fr')
++ grd.SetDefines({'bingo': '1'})
++ outputs = [output.GetFilename() for output in grd.GetOutputFiles()]
++ self.assertNotEquals(outputs, ['uncond1.rc', 'uncond2.adm', 'iftest.h'])
++
++ def testChildrenAccepted(self):
++ grd_reader.Parse(StringIO(r'''<?xml version="1.0"?>
++ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
++ <release seq="3">
++ <includes>
++ <if expr="'bingo' in defs">
++ <include type="gif" name="ID_LOGO2" file="images/logo2.gif" />
++ </if>
++ <if expr="'bingo' in defs">
++ <if expr="'hello' in defs">
++ <include type="gif" name="ID_LOGO2" file="images/logo2.gif" />
++ </if>
++ </if>
++ </includes>
++ <structures>
++ <if expr="'bingo' in defs">
++ <structure type="dialog" name="IDD_ABOUTBOX" file="grit\test\data\klonk.rc" encoding="utf-16" />
++ </if>
++ <if expr="'bingo' in defs">
++ <if expr="'hello' in defs">
++ <structure type="dialog" name="IDD_ABOUTBOX" file="grit\test\data\klonk.rc" encoding="utf-16" />
++ </if>
++ </if>
++ </structures>
++ <messages>
++ <if expr="'bingo' in defs">
++ <message name="IDS_BINGO">Bingo!</message>
++ </if>
++ <if expr="'bingo' in defs">
++ <if expr="'hello' in defs">
++ <message name="IDS_BINGO">Bingo!</message>
++ </if>
++ </if>
++ </messages>
++ </release>
++ <translations>
++ <if expr="'bingo' in defs">
++ <file lang="nl" path="nl_translations.xtb" />
++ </if>
++ <if expr="'bingo' in defs">
++ <if expr="'hello' in defs">
++ <file lang="nl" path="nl_translations.xtb" />
++ </if>
++ </if>
++ </translations>
++ </grit>'''), dir='.')
++
++ def testIfBadChildrenNesting(self):
++ # includes
++ xml = StringIO(r'''<?xml version="1.0"?>
++ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
++ <release seq="3">
++ <includes>
++ <if expr="'bingo' in defs">
++ <structure type="dialog" name="IDD_ABOUTBOX" file="grit\test\data\klonk.rc" encoding="utf-16" />
++ </if>
++ </includes>
++ </release>
++ </grit>''')
++ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml)
++ # messages
++ xml = StringIO(r'''<?xml version="1.0"?>
++ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
++ <release seq="3">
++ <messages>
++ <if expr="'bingo' in defs">
++ <structure type="dialog" name="IDD_ABOUTBOX" file="grit\test\data\klonk.rc" encoding="utf-16" />
++ </if>
++ </messages>
++ </release>
++ </grit>''')
++ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml)
++ # structures
++ xml = StringIO('''<?xml version="1.0"?>
++ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
++ <release seq="3">
++ <structures>
++ <if expr="'bingo' in defs">
++ <message name="IDS_BINGO">Bingo!</message>
++ </if>
++ </structures>
++ </release>
++ </grit>''')
++ # translations
++ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml)
++ xml = StringIO('''<?xml version="1.0"?>
++ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
++ <translations>
++ <if expr="'bingo' in defs">
++ <message name="IDS_BINGO">Bingo!</message>
++ </if>
++ </translations>
++ </grit>''')
++ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml)
++ # same with nesting
++ xml = StringIO(r'''<?xml version="1.0"?>
++ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
++ <release seq="3">
++ <includes>
++ <if expr="'bingo' in defs">
++ <if expr="'hello' in defs">
++ <structure type="dialog" name="IDD_ABOUTBOX" file="grit\test\data\klonk.rc" encoding="utf-16" />
++ </if>
++ </if>
++ </includes>
++ </release>
++ </grit>''')
++ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml)
++ xml = StringIO(r'''<?xml version="1.0"?>
++ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
++ <release seq="3">
++ <messages>
++ <if expr="'bingo' in defs">
++ <if expr="'hello' in defs">
++ <structure type="dialog" name="IDD_ABOUTBOX" file="grit\test\data\klonk.rc" encoding="utf-16" />
++ </if>
++ </if>
++ </messages>
++ </release>
++ </grit>''')
++ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml)
++ xml = StringIO('''<?xml version="1.0"?>
++ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
++ <release seq="3">
++ <structures>
++ <if expr="'bingo' in defs">
++ <if expr="'hello' in defs">
++ <message name="IDS_BINGO">Bingo!</message>
++ </if>
++ </if>
++ </structures>
++ </release>
++ </grit>''')
++ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml)
++ xml = StringIO('''<?xml version="1.0"?>
++ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
++ <translations>
++ <if expr="'bingo' in defs">
++ <if expr="'hello' in defs">
++ <message name="IDS_BINGO">Bingo!</message>
++ </if>
++ </if>
++ </translations>
++ </grit>''')
++ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml)
++
++
++class ReleaseNodeUnittest(unittest.TestCase):
++ def testPseudoControl(self):
++ grd = grd_reader.Parse(StringIO('''<?xml version="1.0" encoding="UTF-8"?>
++ <grit latest_public_release="1" source_lang_id="en-US" current_release="2" base_dir=".">
++ <release seq="1" allow_pseudo="false">
++ <messages>
++ <message name="IDS_HELLO">
++ Hello
++ </message>
++ </messages>
++ <structures>
++ <structure type="dialog" name="IDD_ABOUTBOX" encoding="utf-16" file="klonk.rc" />
++ </structures>
++ </release>
++ <release seq="2">
++ <messages>
++ <message name="IDS_BINGO">
++ Bingo
++ </message>
++ </messages>
++ <structures>
++ <structure type="menu" name="IDC_KLONKMENU" encoding="utf-16" file="klonk.rc" />
++ </structures>
++ </release>
++ </grit>'''), util.PathFromRoot('grit/testdata'))
++ grd.SetOutputLanguage('en')
++ grd.RunGatherers()
++
++ hello = grd.GetNodeById('IDS_HELLO')
++ aboutbox = grd.GetNodeById('IDD_ABOUTBOX')
++ bingo = grd.GetNodeById('IDS_BINGO')
++ menu = grd.GetNodeById('IDC_KLONKMENU')
++
++ for node in [hello, aboutbox]:
++ self.failUnless(not node.PseudoIsAllowed())
++
++ for node in [bingo, menu]:
++ self.failUnless(node.PseudoIsAllowed())
++
++ # TODO(benrg): There was a test here that formatting hello and aboutbox with
++ # a pseudo language should fail, but they do not fail and the test was
++ # broken and failed to catch it. Fix this.
++
++ # Should not raise an exception since pseudo is allowed
++ rc.FormatMessage(bingo, 'xyz-pseudo')
++ rc.FormatStructure(menu, 'xyz-pseudo', '.')
++
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/node/mock_brotli.py b/tools/grit/grit/node/mock_brotli.py
+new file mode 100644
+index 0000000000..14237aab20
+--- /dev/null
++++ b/tools/grit/grit/node/mock_brotli.py
+@@ -0,0 +1,10 @@
++#!/usr/bin/env python
++# Copyright 2019 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++"""Mock Brotli Executable for testing purposes."""
+
-+int main() {
-+ MissingInCPPOK one;
-+ MissingCtorsArentOKInHeader two;
-+ return 0;
++import sys
++
++sys.stdout.write('This has been mock compressed!')
+diff --git a/tools/grit/grit/node/node_io.py b/tools/grit/grit/node/node_io.py
+new file mode 100644
+index 0000000000..ccbc2c0647
+--- /dev/null
++++ b/tools/grit/grit/node/node_io.py
+@@ -0,0 +1,117 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''The <output> and <file> elements.
++'''
++
++from __future__ import print_function
++
++import os
++
++from grit import xtb_reader
++from grit.node import base
++
++
++class FileNode(base.Node):
++ '''A <file> element.'''
++
++ def __init__(self):
++ super(FileNode, self).__init__()
++ self.re = None
++ self.should_load_ = True
++
++ def IsTranslation(self):
++ return True
++
++ def GetLang(self):
++ return self.attrs['lang']
++
++ def DisableLoading(self):
++ self.should_load_ = False
++
++ def MandatoryAttributes(self):
++ return ['path', 'lang']
++
++ def RunPostSubstitutionGatherer(self, debug=False):
++ if not self.should_load_:
++ return
++
++ root = self.GetRoot()
++ defs = getattr(root, 'defines', {})
++ target_platform = getattr(root, 'target_platform', '')
++
++ xtb_file = open(self.ToRealPath(self.GetInputPath()), 'rb')
++ try:
++ lang = xtb_reader.Parse(xtb_file,
++ self.UberClique().GenerateXtbParserCallback(
++ self.attrs['lang'], debug=debug),
++ defs=defs,
++ target_platform=target_platform)
++ except:
++ print("Exception during parsing of %s" % self.GetInputPath())
++ raise
++ # Translation console uses non-standard language codes 'iw' and 'no' for
++ # Hebrew and Norwegian Bokmal instead of 'he' and 'nb' used in Chrome.
++ # Note that some Chrome's .grd still use 'no' instead of 'nb', but 'nb' is
++ # always used for generated .pak files.
++ ALTERNATIVE_LANG_CODE_MAP = { 'he': 'iw', 'nb': 'no' }
++ assert (lang == self.attrs['lang'] or
++ lang == ALTERNATIVE_LANG_CODE_MAP[self.attrs['lang']]), (
++ 'The XTB file you reference must contain messages in the language '
++ 'specified\nby the \'lang\' attribute.')
++
++ def GetInputPath(self):
++ return os.path.expandvars(self.attrs['path'])
++
++
++class OutputNode(base.Node):
++ '''An <output> element.'''
++
++ def MandatoryAttributes(self):
++ return ['filename', 'type']
++
++ def DefaultAttributes(self):
++ return {
++ 'lang' : '', # empty lang indicates all languages
++ 'language_section' : 'neutral', # defines a language neutral section
++ 'context' : '',
++ 'fallback_to_default_layout' : 'true',
++ }
++
++ def GetType(self):
++ return self.attrs['type']
++
++ def GetLanguage(self):
++ '''Returns the language ID, default 'en'.'''
++ return self.attrs['lang']
++
++ def GetContext(self):
++ return self.attrs['context']
++
++ def GetFilename(self):
++ return self.attrs['filename']
++
++ def GetOutputFilename(self):
++ path = None
++ if hasattr(self, 'output_filename'):
++ path = self.output_filename
++ else:
++ path = self.attrs['filename']
++ return os.path.expandvars(path)
++
++ def GetFallbackToDefaultLayout(self):
++ return self.attrs['fallback_to_default_layout'].lower() == 'true'
++
++ def _IsValidChild(self, child):
++ return isinstance(child, EmitNode)
++
++class EmitNode(base.ContentNode):
++ ''' An <emit> element.'''
++
++ def DefaultAttributes(self):
++ return { 'emit_type' : 'prepend'}
++
++ def GetEmitType(self):
++ '''Returns the emit_type for this node. Default is 'append'.'''
++ return self.attrs['emit_type']
+diff --git a/tools/grit/grit/node/node_io_unittest.py b/tools/grit/grit/node/node_io_unittest.py
+new file mode 100644
+index 0000000000..1f45e51af8
+--- /dev/null
++++ b/tools/grit/grit/node/node_io_unittest.py
+@@ -0,0 +1,182 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit tests for node_io.FileNode'''
++
++from __future__ import print_function
++
++import os
++import sys
++import unittest
++
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++from six import StringIO
++
++from grit.node import misc
++from grit.node import node_io
++from grit.node import empty
++from grit import grd_reader
++from grit import util
++
++
++def _GetAllCliques(root_node):
++ """Return all cliques in the |root_node| tree."""
++ ret = []
++ for node in root_node:
++ ret.extend(node.GetCliques())
++ return ret
++
++
++class FileNodeUnittest(unittest.TestCase):
++ def testGetPath(self):
++ root = misc.GritNode()
++ root.StartParsing(u'grit', None)
++ root.HandleAttribute(u'latest_public_release', u'0')
++ root.HandleAttribute(u'current_release', u'1')
++ root.HandleAttribute(u'base_dir', r'..\resource')
++ translations = empty.TranslationsNode()
++ translations.StartParsing(u'translations', root)
++ root.AddChild(translations)
++ file_node = node_io.FileNode()
++ file_node.StartParsing(u'file', translations)
++ file_node.HandleAttribute(u'path', r'flugel\kugel.pdf')
++ translations.AddChild(file_node)
++ root.EndParsing()
++
++ self.failUnless(root.ToRealPath(file_node.GetInputPath()) ==
++ util.normpath(
++ os.path.join(r'../resource', r'flugel/kugel.pdf')))
++
++ def VerifyCliquesContainEnglishAndFrenchAndNothingElse(self, cliques):
++ self.assertEqual(2, len(cliques))
++ for clique in cliques:
++ self.assertEqual({'en', 'fr'}, set(clique.clique.keys()))
++
++ def testLoadTranslations(self):
++ xml = '''<?xml version="1.0" encoding="UTF-8"?>
++ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
++ <translations>
++ <file path="generated_resources_fr.xtb" lang="fr" />
++ </translations>
++ <release seq="3">
++ <messages>
++ <message name="ID_HELLO">Hello!</message>
++ <message name="ID_HELLO_USER">Hello <ph name="USERNAME">%s<ex>Joi</ex></ph></message>
++ </messages>
++ </release>
++ </grit>'''
++ grd = grd_reader.Parse(StringIO(xml),
++ util.PathFromRoot('grit/testdata'))
++ grd.SetOutputLanguage('en')
++ grd.RunGatherers()
++ self.VerifyCliquesContainEnglishAndFrenchAndNothingElse(_GetAllCliques(grd))
++
++ def testIffyness(self):
++ grd = grd_reader.Parse(StringIO('''<?xml version="1.0" encoding="UTF-8"?>
++ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
++ <translations>
++ <if expr="lang == 'fr'">
++ <file path="generated_resources_fr.xtb" lang="fr" />
++ </if>
++ </translations>
++ <release seq="3">
++ <messages>
++ <message name="ID_HELLO">Hello!</message>
++ <message name="ID_HELLO_USER">Hello <ph name="USERNAME">%s<ex>Joi</ex></ph></message>
++ </messages>
++ </release>
++ </grit>'''), util.PathFromRoot('grit/testdata'))
++ grd.SetOutputLanguage('en')
++ grd.RunGatherers()
++ cliques = _GetAllCliques(grd)
++ self.assertEqual(2, len(cliques))
++ for clique in cliques:
++ self.assertEqual({'en'}, set(clique.clique.keys()))
++
++ grd.SetOutputLanguage('fr')
++ grd.RunGatherers()
++ self.VerifyCliquesContainEnglishAndFrenchAndNothingElse(_GetAllCliques(grd))
++
++ def testConditionalLoadTranslations(self):
++ xml = '''<?xml version="1.0" encoding="UTF-8"?>
++ <grit latest_public_release="2" source_lang_id="en-US" current_release="3"
++ base_dir=".">
++ <translations>
++ <if expr="True">
++ <file path="generated_resources_fr.xtb" lang="fr" />
++ </if>
++ <if expr="False">
++ <file path="no_such_file.xtb" lang="de" />
++ </if>
++ </translations>
++ <release seq="3">
++ <messages>
++ <message name="ID_HELLO">Hello!</message>
++ <message name="ID_HELLO_USER">Hello <ph name="USERNAME">%s<ex>
++ Joi</ex></ph></message>
++ </messages>
++ </release>
++ </grit>'''
++ grd = grd_reader.Parse(StringIO(xml),
++ util.PathFromRoot('grit/testdata'))
++ grd.SetOutputLanguage('en')
++ grd.RunGatherers()
++ self.VerifyCliquesContainEnglishAndFrenchAndNothingElse(_GetAllCliques(grd))
++
++ def testConditionalOutput(self):
++ xml = '''<?xml version="1.0" encoding="UTF-8"?>
++ <grit latest_public_release="2" source_lang_id="en-US" current_release="3"
++ base_dir=".">
++ <outputs>
++ <output filename="resource.h" type="rc_header" />
++ <output filename="en/generated_resources.rc" type="rc_all"
++ lang="en" />
++ <if expr="pp_if('NOT_TRUE')">
++ <output filename="de/generated_resources.rc" type="rc_all"
++ lang="de" />
++ </if>
++ </outputs>
++ <release seq="3">
++ <messages>
++ <message name="ID_HELLO">Hello!</message>
++ </messages>
++ </release>
++ </grit>'''
++ grd = grd_reader.Parse(StringIO(xml),
++ util.PathFromRoot('grit/test/data'),
++ defines={})
++ grd.SetOutputLanguage('en')
++ grd.RunGatherers()
++ outputs = grd.GetChildrenOfType(node_io.OutputNode)
++ active = set(grd.ActiveDescendants())
++ self.failUnless(outputs[0] in active)
++ self.failUnless(outputs[0].GetType() == 'rc_header')
++ self.failUnless(outputs[1] in active)
++ self.failUnless(outputs[1].GetType() == 'rc_all')
++ self.failUnless(outputs[2] not in active)
++ self.failUnless(outputs[2].GetType() == 'rc_all')
++
++ # Verify that 'iw' and 'no' language codes in xtb files are mapped to 'he' and
++ # 'nb'.
++ def testLangCodeMapping(self):
++ grd = grd_reader.Parse(StringIO('''<?xml version="1.0" encoding="UTF-8"?>
++ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
++ <translations>
++ <file path="generated_resources_no.xtb" lang="nb" />
++ <file path="generated_resources_iw.xtb" lang="he" />
++ </translations>
++ <release seq="3">
++ <messages></messages>
++ </release>
++ </grit>'''), util.PathFromRoot('grit/testdata'))
++ grd.SetOutputLanguage('en')
++ grd.RunGatherers()
++ self.assertEqual([], _GetAllCliques(grd))
++
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/node/structure.py b/tools/grit/grit/node/structure.py
+new file mode 100644
+index 0000000000..ec170faebb
+--- /dev/null
++++ b/tools/grit/grit/node/structure.py
+@@ -0,0 +1,375 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''The <structure> element.
++'''
++
++from __future__ import print_function
++
++import os
++import platform
++import re
++
++from grit import exception
++from grit import util
++from grit.node import base
++from grit.node import variant
++
++import grit.gather.admin_template
++import grit.gather.chrome_html
++import grit.gather.chrome_scaled_image
++import grit.gather.policy_json
++import grit.gather.rc
++import grit.gather.tr_html
++import grit.gather.txt
++
++import grit.format.rc
++
++# Type of the gatherer to use for each type attribute
++_GATHERERS = {
++ 'accelerators' : grit.gather.rc.Accelerators,
++ 'admin_template' : grit.gather.admin_template.AdmGatherer,
++ 'chrome_html' : grit.gather.chrome_html.ChromeHtml,
++ 'chrome_scaled_image' : grit.gather.chrome_scaled_image.ChromeScaledImage,
++ 'dialog' : grit.gather.rc.Dialog,
++ 'menu' : grit.gather.rc.Menu,
++ 'rcdata' : grit.gather.rc.RCData,
++ 'tr_html' : grit.gather.tr_html.TrHtml,
++ 'txt' : grit.gather.txt.TxtFile,
++ 'version' : grit.gather.rc.Version,
++ 'policy_template_metafile' : grit.gather.policy_json.PolicyJson,
+}
-diff --git a/tools/clang/plugins/tests/missing_ctor.h b/tools/clang/plugins/tests/missing_ctor.h
++
++
++# TODO(joi) Print a warning if the 'variant_of_revision' attribute indicates
++# that a skeleton variant is older than the original file.
++
++
++class StructureNode(base.Node):
++ '''A <structure> element.'''
++
++ # Regular expression for a local variable definition. Each definition
++ # is of the form NAME=VALUE, where NAME cannot contain '=' or ',' and
++ # VALUE must escape all commas: ',' -> ',,'. Each variable definition
++ # should be separated by a comma with no extra whitespace.
++ # Example: THING1=foo,THING2=bar
++ variable_pattern = re.compile(r'([^,=\s]+)=((?:,,|[^,])*)')
++
++ def __init__(self):
++ super(StructureNode, self).__init__()
++
++ # Keep track of the last filename we flattened to, so we can
++ # avoid doing it more than once.
++ self._last_flat_filename = None
++
++ # See _Substitute; this substituter is used for local variables and
++ # the root substituter is used for global variables.
++ self.substituter = None
++
++ def _IsValidChild(self, child):
++ return isinstance(child, variant.SkeletonNode)
++
++ def _ParseVariables(self, variables):
++ '''Parse a variable string into a dictionary.'''
++ matches = StructureNode.variable_pattern.findall(variables)
++ return dict((name, value.replace(',,', ',')) for name, value in matches)
++
++ def EndParsing(self):
++ super(StructureNode, self).EndParsing()
++
++ # Now that we have attributes and children, instantiate the gatherers.
++ gathertype = _GATHERERS[self.attrs['type']]
++
++ self.gatherer = gathertype(self.attrs['file'],
++ self.attrs['name'],
++ self.attrs['encoding'])
++ self.gatherer.SetGrdNode(self)
++ self.gatherer.SetUberClique(self.UberClique())
++ if hasattr(self.GetRoot(), 'defines'):
++ self.gatherer.SetDefines(self.GetRoot().defines)
++ self.gatherer.SetAttributes(self.attrs)
++ if self.ExpandVariables():
++ self.gatherer.SetFilenameExpansionFunction(self._Substitute)
++
++ # Parse local variables and instantiate the substituter.
++ if self.attrs['variables']:
++ variables = self.attrs['variables']
++ self.substituter = util.Substituter()
++ self.substituter.AddSubstitutions(self._ParseVariables(variables))
++
++ self.skeletons = {} # Maps expressions to skeleton gatherers
++ for child in self.children:
++ assert isinstance(child, variant.SkeletonNode)
++ skel = gathertype(child.attrs['file'],
++ self.attrs['name'],
++ child.GetEncodingToUse(),
++ is_skeleton=True)
++ skel.SetGrdNode(self) # TODO(benrg): Or child? Only used for ToRealPath
++ skel.SetUberClique(self.UberClique())
++ if hasattr(self.GetRoot(), 'defines'):
++ skel.SetDefines(self.GetRoot().defines)
++ if self.ExpandVariables():
++ skel.SetFilenameExpansionFunction(self._Substitute)
++ self.skeletons[child.attrs['expr']] = skel
++
++ def MandatoryAttributes(self):
++ return ['type', 'name', 'file']
++
++ def DefaultAttributes(self):
++ return {
++ 'encoding': 'cp1252',
++ 'exclude_from_rc': 'false',
++ 'line_end': 'unix',
++ 'output_encoding': 'utf-8',
++ 'generateid': 'true',
++ 'expand_variables': 'false',
++ 'output_filename': '',
++ 'fold_whitespace': 'false',
++ # Run an arbitrary command after translation is complete
++ # so that it doesn't interfere with what's in translation
++ # console.
++ 'run_command': '',
++ # Leave empty to run on all platforms, comma-separated
++ # for one or more specific platforms. Values must match
++ # output of platform.system().
++ 'run_command_on_platforms': '',
++ 'allowexternalscript': 'false',
++ # preprocess takes the same code path as flattenhtml, but it
++ # disables any processing/inlining outside of <if> and <include>.
++ 'preprocess': 'false',
++ 'flattenhtml': 'false',
++ 'fallback_to_low_resolution': 'default',
++ 'variables': '',
++ 'compress': 'default',
++ 'use_base_dir': 'true',
++ }
++
++ def IsExcludedFromRc(self):
++ return self.attrs['exclude_from_rc'] == 'true'
++
++ def Process(self, output_dir):
++ """Writes the processed data to output_dir. In the case of a chrome_html
++ structure this will add references to other scale factors. If flattening
++ this will also write file references to be base64 encoded data URLs. The
++ name of the new file is returned."""
++ filename = self.ToRealPath(self.GetInputPath())
++ flat_filename = os.path.join(output_dir,
++ self.attrs['name'] + '_' + os.path.basename(filename))
++
++ if self._last_flat_filename == flat_filename:
++ return
++
++ with open(flat_filename, 'wb') as outfile:
++ if self.ExpandVariables():
++ text = self.gatherer.GetText()
++ file_contents = self._Substitute(text)
++ else:
++ file_contents = self.gatherer.GetData('', 'utf-8')
++ outfile.write(file_contents.encode('utf-8'))
++
++ self._last_flat_filename = flat_filename
++ return os.path.basename(flat_filename)
++
++ def GetLineEnd(self):
++ '''Returns the end-of-line character or characters for files output because
++ of this node ('\r\n', '\n', or '\r' depending on the 'line_end' attribute).
++ '''
++ if self.attrs['line_end'] == 'unix':
++ return '\n'
++ elif self.attrs['line_end'] == 'windows':
++ return '\r\n'
++ elif self.attrs['line_end'] == 'mac':
++ return '\r'
++ else:
++ raise exception.UnexpectedAttribute(
++ "Attribute 'line_end' must be one of 'unix' (default), 'windows' or "
++ "'mac'")
++
++ def GetCliques(self):
++ return self.gatherer.GetCliques()
++
++ def GetDataPackValue(self, lang, encoding):
++ """Returns a bytes representation for a data_pack entry."""
++ if self.ExpandVariables():
++ text = self.gatherer.GetText()
++ data = util.Encode(self._Substitute(text), encoding)
++ else:
++ data = self.gatherer.GetData(lang, encoding)
++ if encoding != util.BINARY:
++ data = data.encode(encoding)
++ return self.CompressDataIfNeeded(data)
++
++ def GetHtmlResourceFilenames(self):
++ """Returns a set of all filenames inlined by this node."""
++ return self.gatherer.GetHtmlResourceFilenames()
++
++ def GetInputPath(self):
++ path = self.gatherer.GetInputPath()
++ if path is None:
++ return path
++
++ # Do not mess with absolute paths, that would make them invalid.
++ if os.path.isabs(os.path.expandvars(path)):
++ return path
++
++ # We have no control over code that calls ToRealPath later, so convert
++ # the path to be relative against our basedir.
++ if self.attrs.get('use_base_dir', 'true') != 'true':
++ # Normalize the directory path to use the appropriate OS separator.
++ # GetBaseDir() may return paths\like\this or paths/like/this, since it is
++ # read from the base_dir attribute in the grd file.
++ norm_base_dir = util.normpath(self.GetRoot().GetBaseDir())
++ return os.path.relpath(path, norm_base_dir)
++
++ return path
++
++ def GetTextualIds(self):
++ if not hasattr(self, 'gatherer'):
++ # This case is needed because this method is called by
++ # GritNode.ValidateUniqueIds before RunGatherers has been called.
++ # TODO(benrg): Fix this?
++ return [self.attrs['name']]
++ return self.gatherer.GetTextualIds()
++
++ def RunPreSubstitutionGatherer(self, debug=False):
++ if debug:
++ print('Running gatherer %s for file %s' %
++ (type(self.gatherer), self.GetInputPath()))
++
++ # Note: Parse() is idempotent, therefore this method is also.
++ self.gatherer.Parse()
++ for skel in self.skeletons.values():
++ skel.Parse()
++
++ def GetSkeletonGatherer(self):
++ '''Returns the gatherer for the alternate skeleton that should be used,
++ based on the expressions for selecting skeletons, or None if the skeleton
++ from the English version of the structure should be used.
++ '''
++ for expr in self.skeletons:
++ if self.EvaluateCondition(expr):
++ return self.skeletons[expr]
++ return None
++
++ def HasFileForLanguage(self):
++ return self.attrs['type'] in ['tr_html', 'admin_template', 'txt',
++ 'chrome_scaled_image',
++ 'chrome_html']
++
++ def ExpandVariables(self):
++ '''Variable expansion on structures is controlled by an XML attribute.
++
++ However, old files assume that expansion is always on for Rc files.
++
++ Returns:
++ A boolean.
++ '''
++ attrs = self.GetRoot().attrs
++ if 'grit_version' in attrs and attrs['grit_version'] > 1:
++ return self.attrs['expand_variables'] == 'true'
++ else:
++ return (self.attrs['expand_variables'] == 'true' or
++ self.attrs['file'].lower().endswith('.rc'))
++
++ def _Substitute(self, text):
++ '''Perform local and global variable substitution.'''
++ if self.substituter:
++ text = self.substituter.Substitute(text)
++ return self.GetRoot().GetSubstituter().Substitute(text)
++
++ def RunCommandOnCurrentPlatform(self):
++ if self.attrs['run_command_on_platforms'] == '':
++ return True
++ else:
++ target_platforms = self.attrs['run_command_on_platforms'].split(',')
++ return platform.system() in target_platforms
++
++ def FileForLanguage(self, lang, output_dir, create_file=True,
++ return_if_not_generated=True):
++ '''Returns the filename of the file associated with this structure,
++ for the specified language.
++
++ Args:
++ lang: 'fr'
++ output_dir: 'c:\temp'
++ create_file: True
++ '''
++ assert self.HasFileForLanguage()
++ # If the source language is requested, and no extra changes are requested,
++ # use the existing file.
++ if ((not lang or lang == self.GetRoot().GetSourceLanguage()) and
++ self.attrs['expand_variables'] != 'true' and
++ (not self.attrs['run_command'] or
++ not self.RunCommandOnCurrentPlatform())):
++ if return_if_not_generated:
++ input_path = self.GetInputPath()
++ if input_path is None:
++ return None
++ return self.ToRealPath(input_path)
++ else:
++ return None
++
++ if self.attrs['output_filename'] != '':
++ filename = self.attrs['output_filename']
++ else:
++ filename = os.path.basename(self.attrs['file'])
++ assert len(filename)
++ filename = '%s_%s' % (lang, filename)
++ filename = os.path.join(output_dir, filename)
++
++ # Only create the output if it was requested by the call.
++ if create_file:
++ text = self.gatherer.Translate(
++ lang,
++ pseudo_if_not_available=self.PseudoIsAllowed(),
++ fallback_to_english=self.ShouldFallbackToEnglish(),
++ skeleton_gatherer=self.GetSkeletonGatherer())
++
++ file_contents = util.FixLineEnd(text, self.GetLineEnd())
++ if self.ExpandVariables():
++ # Note that we reapply substitution a second time here.
++ # This is because a) we need to look inside placeholders
++ # b) the substitution values are language-dependent
++ file_contents = self._Substitute(file_contents)
++
++ with open(filename, 'wb') as file_object:
++ output_stream = util.WrapOutputStream(file_object,
++ self.attrs['output_encoding'])
++ output_stream.write(file_contents)
++
++ if self.attrs['run_command'] and self.RunCommandOnCurrentPlatform():
++ # Run arbitrary commands after translation is complete so that it
++ # doesn't interfere with what's in translation console.
++ command = self.attrs['run_command'] % {'filename': filename}
++ result = os.system(command)
++ assert result == 0, '"%s" failed.' % command
++
++ return filename
++
++ def IsResourceMapSource(self):
++ return True
++
++ @staticmethod
++ def Construct(parent, name, type, file, encoding='cp1252'):
++ '''Creates a new node which is a child of 'parent', with attributes set
++ by parameters of the same name.
++ '''
++ node = StructureNode()
++ node.StartParsing('structure', parent)
++ node.HandleAttribute('name', name)
++ node.HandleAttribute('type', type)
++ node.HandleAttribute('file', file)
++ node.HandleAttribute('encoding', encoding)
++ node.EndParsing()
++ return node
++
++ def SubstituteMessages(self, substituter):
++ '''Propagates substitution to gatherer.
++
++ Args:
++ substituter: a grit.util.Substituter object.
++ '''
++ assert hasattr(self, 'gatherer')
++ if self.ExpandVariables():
++ self.gatherer.SubstituteMessages(substituter)
+diff --git a/tools/grit/grit/node/structure_unittest.py b/tools/grit/grit/node/structure_unittest.py
new file mode 100644
-index 0000000000..1050457a1a
+index 0000000000..0e66dce37a
--- /dev/null
-+++ b/tools/clang/plugins/tests/missing_ctor.h
-@@ -0,0 +1,19 @@
-+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-+// Use of this source code is governed by a BSD-style license that can be
-+// found in the LICENSE file.
++++ b/tools/grit/grit/node/structure_unittest.py
+@@ -0,0 +1,178 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit tests for <structure> nodes.
++'''
+
-+#ifndef MISSING_CTOR_H_
-+#define MISSING_CTOR_H_
++from __future__ import print_function
+
-+#include <string>
-+#include <vector>
++import os
++import os.path
++import sys
++import zlib
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++import platform
++import tempfile
++import unittest
++import struct
++
++from grit import constants
++from grit import util
++from grit.node import brotli_util
++from grit.node import structure
++from grit.format import rc
++
++
++def checkIsGzipped(filename, compress_attr):
++ test_data_root = util.PathFromRoot('grit/testdata')
++ root = util.ParseGrdForUnittest(
++ '''
++ <structures>
++ <structure name="TEST_TXT" file="%s" %s type="chrome_html"/>
++ </structures>''' % (filename, compress_attr),
++ base_dir=test_data_root)
++ node, = root.GetChildrenOfType(structure.StructureNode)
++ node.RunPreSubstitutionGatherer()
++ compressed = node.GetDataPackValue(lang='en', encoding=util.BINARY)
++
++ decompressed_data = zlib.decompress(compressed, 16 + zlib.MAX_WBITS)
++ expected = util.ReadFile(os.path.join(test_data_root, filename), util.BINARY)
++ return expected == decompressed_data
++
++
++class StructureUnittest(unittest.TestCase):
++ def testSkeleton(self):
++ grd = util.ParseGrdForUnittest('''
++ <structures>
++ <structure type="dialog" name="IDD_ABOUTBOX" file="klonk.rc" encoding="utf-16-le">
++ <skeleton expr="lang == 'fr'" variant_of_revision="1" file="klonk-alternate-skeleton.rc" />
++ </structure>
++ </structures>''', base_dir=util.PathFromRoot('grit/testdata'))
++ grd.SetOutputLanguage('fr')
++ grd.RunGatherers()
++ transl = ''.join(rc.Format(grd, 'fr', '.'))
++ self.failUnless(transl.count('040704') and transl.count('110978'))
++ self.failUnless(transl.count('2005",IDC_STATIC'))
++
++ def testRunCommandOnCurrentPlatform(self):
++ node = structure.StructureNode()
++ node.attrs = node.DefaultAttributes()
++ self.failUnless(node.RunCommandOnCurrentPlatform())
++ node.attrs['run_command_on_platforms'] = 'Nosuch'
++ self.failIf(node.RunCommandOnCurrentPlatform())
++ node.attrs['run_command_on_platforms'] = (
++ 'Nosuch,%s,Othernot' % platform.system())
++ self.failUnless(node.RunCommandOnCurrentPlatform())
++
++ def testVariables(self):
++ grd = util.ParseGrdForUnittest('''
++ <structures>
++ <structure type="chrome_html" name="hello_tmpl" file="structure_variables.html" expand_variables="true" variables="GREETING=Hello,THINGS=foo,, bar,, baz,EQUATION=2+2==4,filename=simple" flattenhtml="true"></structure>
++ </structures>''', base_dir=util.PathFromRoot('grit/testdata'))
++ grd.SetOutputLanguage('en')
++ grd.RunGatherers()
++ node, = grd.GetChildrenOfType(structure.StructureNode)
++ filename = node.Process(tempfile.gettempdir())
++ filepath = os.path.join(tempfile.gettempdir(), filename)
++ with open(filepath) as f:
++ result = f.read()
++ self.failUnlessEqual(('<h1>Hello!</h1>\n'
++ 'Some cool things are foo, bar, baz.\n'
++ 'Did you know that 2+2==4?\n'
++ '<p>\n'
++ ' Hello!\n'
++ '</p>\n'), result)
++ os.remove(filepath)
++
++ def testGetPath(self):
++ base_dir = util.PathFromRoot('grit/testdata')
++ grd = util.ParseGrdForUnittest('''
++ <structures>
++ <structure type="chrome_html" name="hello_tmpl" file="structure_variables.html" expand_variables="true" variables="GREETING=Hello,THINGS=foo,, bar,, baz,EQUATION=2+2==4,filename=simple" flattenhtml="true" use_base_dir="true"></structure>
++ </structures>''', base_dir)
++ grd.SetOutputLanguage('en')
++ grd.RunGatherers()
++ node, = grd.GetChildrenOfType(structure.StructureNode)
++ self.assertEqual(grd.ToRealPath(node.GetInputPath()),
++ os.path.abspath(os.path.join(
++ base_dir, r'structure_variables.html')))
++
++ def testGetPathNoBasedir(self):
++ base_dir = util.PathFromRoot('grit/testdata')
++ abs_path = os.path.join(base_dir, r'structure_variables.html')
++ rel_path = os.path.relpath(abs_path, os.getcwd())
++ grd = util.ParseGrdForUnittest('''
++ <structures>
++ <structure type="chrome_html" name="hello_tmpl" file="''' + rel_path + '''" expand_variables="true" variables="GREETING=Hello,THINGS=foo,, bar,, baz,EQUATION=2+2==4,filename=simple" flattenhtml="true" use_base_dir="false"></structure>
++ </structures>''', util.PathFromRoot('grit/testdata'))
++ grd.SetOutputLanguage('en')
++ grd.RunGatherers()
++ node, = grd.GetChildrenOfType(structure.StructureNode)
++ self.assertEqual(grd.ToRealPath(node.GetInputPath()),
++ os.path.abspath(os.path.join(
++ base_dir, r'structure_variables.html')))
++
++ def testCompressGzip(self):
++ self.assertTrue(checkIsGzipped('test_text.txt', 'compress="gzip"'))
++
++ def testCompressGzipByDefault(self):
++ self.assertTrue(checkIsGzipped('test_html.html', ''))
++ self.assertTrue(checkIsGzipped('test_js.js', ''))
++ self.assertTrue(checkIsGzipped('test_css.css', ''))
++ self.assertTrue(checkIsGzipped('test_svg.svg', ''))
++
++ self.assertTrue(checkIsGzipped('test_html.html', 'compress="default"'))
++ self.assertTrue(checkIsGzipped('test_js.js', 'compress="default"'))
++ self.assertTrue(checkIsGzipped('test_css.css', 'compress="default"'))
++ self.assertTrue(checkIsGzipped('test_svg.svg', 'compress="default"'))
++
++ def testCompressBrotli(self):
++ test_data_root = util.PathFromRoot('grit/testdata')
++ root = util.ParseGrdForUnittest(
++ '''
++ <structures>
++ <structure name="TEST_TXT" file="test_text.txt"
++ compress="brotli" type="chrome_html" />
++ </structures>''',
++ base_dir=test_data_root)
++ node, = root.GetChildrenOfType(structure.StructureNode)
++ node.RunPreSubstitutionGatherer()
++
++ # Using the mock brotli decompression executable.
++ brotli_util.SetBrotliCommand([sys.executable,
++ os.path.join(os.path.dirname(__file__),
++ 'mock_brotli.py')])
++ compressed = node.GetDataPackValue(lang='en', encoding=util.BINARY)
++ # Assert that the first two bytes in compressed format is BROTLI_CONST.
++ self.assertEqual(constants.BROTLI_CONST, compressed[0:2])
++
++ # Compare the actual size of the uncompressed test data with
++ # the size appended during compression.
++ actual_size = len(util.ReadFile(
++ os.path.join(test_data_root, 'test_text.txt'), util.BINARY))
++ uncompress_size = struct.unpack('<i', compressed[2:6])[0]
++ uncompress_size += struct.unpack('<h', compressed[6:8])[0] << 4*8
++ self.assertEqual(actual_size, uncompress_size)
++
++ self.assertEqual(b'This has been mock compressed!', compressed[8:])
++
++ def testNotCompressed(self):
++ test_data_root = util.PathFromRoot('grit/testdata')
++ root = util.ParseGrdForUnittest('''
++ <structures>
++ <structure name="TEST_TXT" file="test_text.txt" type="chrome_html" />
++ </structures>''', base_dir=test_data_root)
++ node, = root.GetChildrenOfType(structure.StructureNode)
++ node.RunPreSubstitutionGatherer()
++ data = node.GetDataPackValue(lang='en', encoding=util.BINARY)
++
++ self.assertEqual(util.ReadFile(
++ os.path.join(test_data_root, 'test_text.txt'), util.BINARY), data)
+
-+class MissingCtorsArentOKInHeader {
-+ public:
+
-+ private:
-+ std::vector<int> one_;
-+ std::vector<std::string> two_;
-+};
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/node/variant.py b/tools/grit/grit/node/variant.py
+new file mode 100644
+index 0000000000..9f5845f954
+--- /dev/null
++++ b/tools/grit/grit/node/variant.py
+@@ -0,0 +1,41 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''The <skeleton> element.
++'''
++
++from __future__ import print_function
++
++from grit.node import base
++
++
++class SkeletonNode(base.Node):
++ '''A <skeleton> element.'''
++
++ # TODO(joi) Support inline skeleton variants as CDATA instead of requiring
++ # a 'file' attribute.
++
++ def MandatoryAttributes(self):
++ return ['expr', 'variant_of_revision', 'file']
++
++ def DefaultAttributes(self):
++ '''If not specified, 'encoding' will actually default to the parent node's
++ encoding.
++ '''
++ return {'encoding' : ''}
++
++ def _ContentType(self):
++ if 'file' in self.attrs:
++ return self._CONTENT_TYPE_NONE
++ else:
++ return self._CONTENT_TYPE_CDATA
++
++ def GetEncodingToUse(self):
++ if self.attrs['encoding'] == '':
++ return self.parent.attrs['encoding']
++ else:
++ return self.attrs['encoding']
++
++ def GetInputPath(self):
++ return self.attrs['file']
+diff --git a/tools/grit/grit/pseudo.py b/tools/grit/grit/pseudo.py
+new file mode 100644
+index 0000000000..b607bfc6bb
+--- /dev/null
++++ b/tools/grit/grit/pseudo.py
+@@ -0,0 +1,129 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Pseudotranslation support. Our pseudotranslations are based on the
++P-language, which is a simple vowel-extending language. Examples of P:
++ - "hello" becomes "hepellopo"
++ - "howdie" becomes "hopowdiepie"
++ - "because" becomes "bepecaupause" (but in our implementation we don't
++ handle the silent e at the end so it actually would return "bepecaupausepe"
++
++The P-language has the excellent quality of increasing the length of text
++by around 30-50% which is great for pseudotranslations, to stress test any
++GUI layouts etc.
++
++To make the pseudotranslations more obviously "not a translation" and to make
++them exercise any code that deals with encodings, we also transform all English
++vowels into equivalent vowels with diacriticals on them (rings, acutes,
++diaresis, and circumflex), and we write the "p" in the P-language as a Hebrew
++character Qof. It looks sort of like a latin character "p" but it is outside
++the latin-1 character set which will stress character encoding bugs.
++'''
++
++from __future__ import print_function
++
++from grit import lazy_re
++from grit import tclib
++
++
++# An RFC language code for the P pseudolanguage.
++PSEUDO_LANG = 'x-P-pseudo'
++
++# Hebrew character Qof. It looks kind of like a 'p' but is outside
++# the latin-1 character set which is good for our purposes.
++# TODO(joi) For now using P instead of Qof, because of some bugs it used. Find
++# a better solution, i.e. one that introduces a non-latin1 character into the
++# pseudotranslation.
++#_QOF = u'\u05e7'
++_QOF = u'P'
++
++# How we map each vowel.
++_VOWELS = {
++ u'a' : u'\u00e5', # a with ring
++ u'e' : u'\u00e9', # e acute
++ u'i' : u'\u00ef', # i diaresis
++ u'o' : u'\u00f4', # o circumflex
++ u'u' : u'\u00fc', # u diaresis
++ u'y' : u'\u00fd', # y acute
++ u'A' : u'\u00c5', # A with ring
++ u'E' : u'\u00c9', # E acute
++ u'I' : u'\u00cf', # I diaresis
++ u'O' : u'\u00d4', # O circumflex
++ u'U' : u'\u00dc', # U diaresis
++ u'Y' : u'\u00dd', # Y acute
++}
++_VOWELS_KEYS = set(_VOWELS.keys())
++
++# Matches vowels and P
++_PSUB_RE = lazy_re.compile("(%s)" % '|'.join(_VOWELS_KEYS | {'P'}))
++
++
++# Pseudotranslations previously created. This is important for performance
++# reasons, especially since we routinely pseudotranslate the whole project
++# several or many different times for each build.
++_existing_translations = {}
++
++
++def MapVowels(str, also_p = False):
++ '''Returns a copy of 'str' where characters that exist as keys in _VOWELS
++ have been replaced with the corresponding value. If also_p is true, this
++ function will also change capital P characters into a Hebrew character Qof.
++ '''
++ def Repl(match):
++ if match.group() == 'p':
++ if also_p:
++ return _QOF
++ else:
++ return 'p'
++ else:
++ return _VOWELS[match.group()]
++ return _PSUB_RE.sub(Repl, str)
++
++
++def PseudoString(str):
++ '''Returns a pseudotranslation of the provided string, in our enhanced
++ P-language.'''
++ if str in _existing_translations:
++ return _existing_translations[str]
++
++ outstr = u''
++ ix = 0
++ while ix < len(str):
++ if str[ix] not in _VOWELS_KEYS:
++ outstr += str[ix]
++ ix += 1
++ else:
++ # We want to treat consecutive vowels as one composite vowel. This is not
++ # always accurate e.g. in composite words but good enough.
++ consecutive_vowels = u''
++ while ix < len(str) and str[ix] in _VOWELS_KEYS:
++ consecutive_vowels += str[ix]
++ ix += 1
++ changed_vowels = MapVowels(consecutive_vowels)
++ outstr += changed_vowels
++ outstr += _QOF
++ outstr += changed_vowels
++
++ _existing_translations[str] = outstr
++ return outstr
++
++
++def PseudoMessage(message):
++ '''Returns a pseudotranslation of the provided message.
++
++ Args:
++ message: tclib.Message()
++
++ Return:
++ tclib.Translation()
++ '''
++ transl = tclib.Translation()
++
++ for part in message.GetContent():
++ if isinstance(part, tclib.Placeholder):
++ transl.AppendPlaceholder(part)
++ else:
++ transl.AppendText(PseudoString(part))
++
++ return transl
+diff --git a/tools/grit/grit/pseudo_rtl.py b/tools/grit/grit/pseudo_rtl.py
+new file mode 100644
+index 0000000000..2240b571de
+--- /dev/null
++++ b/tools/grit/grit/pseudo_rtl.py
+@@ -0,0 +1,104 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Pseudo RTL, (aka Fake Bidi) support. It simply wraps each word with
++Unicode RTL overrides.
++More info at https://sites.google.com/a/chromium.org/dev/Home/fake-bidi
++'''
++
++from __future__ import print_function
++
++import re
++
++from grit import lazy_re
++from grit import tclib
++
++ACCENTED_STRINGS = {
++ 'a': u"\u00e5", 'e': u"\u00e9", 'i': u"\u00ee", 'o': u"\u00f6",
++ 'u': u"\u00fb", 'A': u"\u00c5", 'E': u"\u00c9", 'I': u"\u00ce",
++ 'O': u"\u00d6", 'U': u"\u00db", 'c': u"\u00e7", 'd': u"\u00f0",
++ 'n': u"\u00f1", 'p': u"\u00fe", 'y': u"\u00fd", 'C': u"\u00c7",
++ 'D': u"\u00d0", 'N': u"\u00d1", 'P': u"\u00de", 'Y': u"\u00dd",
++ 'f': u"\u0192", 's': u"\u0161", 'S': u"\u0160", 'z': u"\u017e",
++ 'Z': u"\u017d", 'g': u"\u011d", 'G': u"\u011c", 'h': u"\u0125",
++ 'H': u"\u0124", 'j': u"\u0135", 'J': u"\u0134", 'k': u"\u0137",
++ 'K': u"\u0136", 'l': u"\u013c", 'L': u"\u013b", 't': u"\u0163",
++ 'T': u"\u0162", 'w': u"\u0175", 'W': u"\u0174",
++ '$': u"\u20ac", '?': u"\u00bf", 'R': u"\u00ae", r'!': u"\u00a1",
++}
++
++# a character set containing the keys in ACCENTED_STRINGS
++# We should not accent characters in an escape sequence such as "\n".
++# To be safe, we assume every character following a backslash is an escaped
++# character. We also need to consider the case like "\\n", which means
++# a blackslash and a character "n", we will accent the character "n".
++TO_ACCENT = lazy_re.compile(
++ r'[%s]|\\[a-z\\]' % ''.join(ACCENTED_STRINGS.keys()))
++
++# Lex text so that we don't interfere with html tokens and entities.
++# This lexing scheme will handle all well formed tags and entities, html or
++# xhtml. It will not handle comments, CDATA sections, or the unescaping tags:
++# script, style, xmp or listing. If any of those appear in messages,
++# something is wrong.
++TOKENS = [ lazy_re.compile(
++ '^%s' % pattern, # match at the beginning of input
++ re.I | re.S # html tokens are case-insensitive
++ )
++ for pattern in
++ (
++ # a run of non html special characters
++ r'[^<&]+',
++ # a tag
++ (r'</?[a-z]\w*' # beginning of tag
++ r'(?:\s+\w+(?:\s*=\s*' # attribute start
++ r'(?:[^\s"\'>]+|"[^\"]*"|\'[^\']*\'))?' # attribute value
++ r')*\s*/?>'),
++ # an entity
++ r'&(?:[a-z]\w+|#\d+|#x[\da-f]+);',
++ # an html special character not part of a special sequence
++ r'.'
++ ) ]
++
++ALPHABETIC_RUN = lazy_re.compile(r'([^\W0-9_]+)')
++
++RLO = u'\u202e'
++PDF = u'\u202c'
++
++def PseudoRTLString(text):
++ '''Returns a fake bidirectional version of the source string. This code is
++ based on accentString above, in turn copied from Frank Tang.
++ '''
++ parts = []
++ while text:
++ m = None
++ for token in TOKENS:
++ m = token.search(text)
++ if m:
++ part = m.group(0)
++ text = text[len(part):]
++ if part[0] not in ('<', '&'):
++ # not a tag or entity, so accent
++ part = ALPHABETIC_RUN.sub(lambda run: RLO + run.group() + PDF, part)
++ parts.append(part)
++ break
++ return ''.join(parts)
++
++
++def PseudoRTLMessage(message):
++ '''Returns a pseudo-RTL (aka Fake-Bidi) translation of the provided message.
++
++ Args:
++ message: tclib.Message()
++
++ Return:
++ tclib.Translation()
++ '''
++ transl = tclib.Translation()
++ for part in message.GetContent():
++ if isinstance(part, tclib.Placeholder):
++ transl.AppendPlaceholder(part)
++ else:
++ transl.AppendText(PseudoRTLString(part))
++
++ return transl
+diff --git a/tools/grit/grit/pseudo_unittest.py b/tools/grit/grit/pseudo_unittest.py
+new file mode 100644
+index 0000000000..b1d53ff401
+--- /dev/null
++++ b/tools/grit/grit/pseudo_unittest.py
+@@ -0,0 +1,55 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit tests for grit.pseudo'''
++
++from __future__ import print_function
++
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
++
++import unittest
++
++from grit import pseudo
++from grit import tclib
++
++
++class PseudoUnittest(unittest.TestCase):
++ def testVowelMapping(self):
++ self.failUnless(pseudo.MapVowels('abebibobuby') ==
++ u'\u00e5b\u00e9b\u00efb\u00f4b\u00fcb\u00fd')
++ self.failUnless(pseudo.MapVowels('ABEBIBOBUBY') ==
++ u'\u00c5B\u00c9B\u00cfB\u00d4B\u00dcB\u00dd')
++
++ def testPseudoString(self):
++ out = pseudo.PseudoString('hello')
++ self.failUnless(out == pseudo.MapVowels(u'hePelloPo', True))
++
++ def testConsecutiveVowels(self):
++ out = pseudo.PseudoString("beautiful weather, ain't it?")
++ self.failUnless(out == pseudo.MapVowels(
++ u"beauPeautiPifuPul weaPeathePer, aiPain't iPit?", 1))
++
++ def testCapitals(self):
++ out = pseudo.PseudoString("HOWDIE DOODIE, DR. JONES")
++ self.failUnless(out == pseudo.MapVowels(
++ u"HOPOWDIEPIE DOOPOODIEPIE, DR. JOPONEPES", 1))
++
++ def testPseudoMessage(self):
++ msg = tclib.Message(text='Hello USERNAME, how are you?',
++ placeholders=[
++ tclib.Placeholder('USERNAME', '%s', 'Joi')])
++ trans = pseudo.PseudoMessage(msg)
++ # TODO(joi) It would be nicer if 'you' -> 'youPou' instead of
++ # 'you' -> 'youPyou' and if we handled the silent e in 'are'
++ self.failUnless(trans.GetPresentableContent() ==
++ pseudo.MapVowels(
++ u'HePelloPo USERNAME, hoPow aParePe youPyou?', 1))
++
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/shortcuts.py b/tools/grit/grit/shortcuts.py
+new file mode 100644
+index 0000000000..0db2ce436c
+--- /dev/null
++++ b/tools/grit/grit/shortcuts.py
+@@ -0,0 +1,93 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Stuff to prevent conflicting shortcuts.
++'''
++
++from __future__ import print_function
++
++from grit import lazy_re
++
++
++class ShortcutGroup(object):
++ '''Manages a list of cliques that belong together in a single shortcut
++ group. Knows how to detect conflicting shortcut keys.
++ '''
++
++ # Matches shortcut keys, e.g. &J
++ SHORTCUT_RE = lazy_re.compile('([^&]|^)(&[A-Za-z])')
++
++ def __init__(self, name):
++ self.name = name
++ # Map of language codes to shortcut keys used (which is a map of
++ # shortcut keys to counts).
++ self.keys_by_lang = {}
++ # List of cliques in this group
++ self.cliques = []
++
++ def AddClique(self, c):
++ for existing_clique in self.cliques:
++ if existing_clique.GetId() == c.GetId():
++ # This happens e.g. when we have e.g.
++ # <if expr1><structure 1></if> <if expr2><structure 2></if>
++ # where only one will really be included in the output.
++ return
++
++ self.cliques.append(c)
++ for (lang, msg) in c.clique.items():
++ if lang not in self.keys_by_lang:
++ self.keys_by_lang[lang] = {}
++ keymap = self.keys_by_lang[lang]
++
++ content = msg.GetRealContent()
++ keys = [groups[1] for groups in self.SHORTCUT_RE.findall(content)]
++ for key in keys:
++ key = key.upper()
++ if key in keymap:
++ keymap[key] += 1
++ else:
++ keymap[key] = 1
++
++ def GenerateWarnings(self, tc_project):
++ # For any language that has more than one occurrence of any shortcut,
++ # make a list of the conflicting shortcuts.
++ problem_langs = {}
++ for (lang, keys) in self.keys_by_lang.items():
++ for (key, count) in keys.items():
++ if count > 1:
++ if lang not in problem_langs:
++ problem_langs[lang] = []
++ problem_langs[lang].append(key)
++
++ warnings = []
++ if len(problem_langs):
++ warnings.append("WARNING - duplicate keys exist in shortcut group %s" %
++ self.name)
++ for (lang,keys) in problem_langs.items():
++ warnings.append(" %6s duplicates: %s" % (lang, ', '.join(keys)))
++ return warnings
++
++
++def GenerateDuplicateShortcutsWarnings(uberclique, tc_project):
++ '''Given an UberClique and a project name, will print out helpful warnings
++ if there are conflicting shortcuts within shortcut groups in the provided
++ UberClique.
++
++ Args:
++ uberclique: clique.UberClique()
++ tc_project: 'MyProjectNameInTheTranslationConsole'
++
++ Returns:
++ ['warning line 1', 'warning line 2', ...]
++ '''
++ warnings = []
++ groups = {}
++ for c in uberclique.AllCliques():
++ for group in c.shortcut_groups:
++ if group not in groups:
++ groups[group] = ShortcutGroup(group)
++ groups[group].AddClique(c)
++ for group in groups.values():
++ warnings += group.GenerateWarnings(tc_project)
++ return warnings
+diff --git a/tools/grit/grit/shortcuts_unittest.py b/tools/grit/grit/shortcuts_unittest.py
+new file mode 100644
+index 0000000000..30e7c4f758
+--- /dev/null
++++ b/tools/grit/grit/shortcuts_unittest.py
+@@ -0,0 +1,79 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit tests for grit.shortcuts
++'''
++
++from __future__ import print_function
++
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
++
++import unittest
++
++from six import StringIO
++
++from grit import shortcuts
++from grit import clique
++from grit import tclib
++from grit.gather import rc
++
++class ShortcutsUnittest(unittest.TestCase):
++
++ def setUp(self):
++ self.uq = clique.UberClique()
++
++ def testFunctionality(self):
++ c = self.uq.MakeClique(tclib.Message(text="Hello &there"))
++ c.AddToShortcutGroup('group_name')
++ c = self.uq.MakeClique(tclib.Message(text="Howdie &there partner"))
++ c.AddToShortcutGroup('group_name')
++
++ warnings = shortcuts.GenerateDuplicateShortcutsWarnings(self.uq, 'PROJECT')
++ self.failUnless(warnings)
++
++ def testAmpersandEscaping(self):
++ c = self.uq.MakeClique(tclib.Message(text="Hello &there"))
++ c.AddToShortcutGroup('group_name')
++ c = self.uq.MakeClique(tclib.Message(text="S&&T are the &letters S and T"))
++ c.AddToShortcutGroup('group_name')
++
++ warnings = shortcuts.GenerateDuplicateShortcutsWarnings(self.uq, 'PROJECT')
++ self.failUnless(len(warnings) == 0)
++
++ def testDialog(self):
++ dlg = rc.Dialog(StringIO('''\
++IDD_SIDEBAR_RSS_PANEL_PROPPAGE DIALOGEX 0, 0, 239, 221
++STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
++FONT 8, "MS Shell Dlg", 400, 0, 0x1
++BEGIN
++ PUSHBUTTON "Add &URL",IDC_SIDEBAR_RSS_ADD_URL,182,53,57,14
++ EDITTEXT IDC_SIDEBAR_RSS_NEW_URL,0,53,178,15,ES_AUTOHSCROLL
++ PUSHBUTTON "&Remove",IDC_SIDEBAR_RSS_REMOVE,183,200,56,14
++ PUSHBUTTON "&Edit",IDC_SIDEBAR_RSS_EDIT,123,200,56,14
++ CONTROL "&Automatically add commonly viewed clips",
++ IDC_SIDEBAR_RSS_AUTO_ADD,"Button",BS_AUTOCHECKBOX |
++ BS_MULTILINE | WS_TABSTOP,0,200,120,17
++ PUSHBUTTON "",IDC_SIDEBAR_RSS_HIDDEN,179,208,6,6,NOT WS_VISIBLE
++ LTEXT "You can display clips from blogs, news sites, and other online sources.",
++ IDC_STATIC,0,0,239,10
++ LISTBOX IDC_SIDEBAR_DISPLAYED_FEED_LIST,0,69,239,127,LBS_SORT |
++ LBS_OWNERDRAWFIXED | LBS_HASSTRINGS |
++ LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_HSCROLL |
++ WS_TABSTOP
++ LTEXT "Add a clip from a recently viewed website by clicking Add Recent Clips.",
++ IDC_STATIC,0,13,141,19
++ LTEXT "Or, if you know a site supports RSS or Atom, you can enter the RSS or Atom URL below and add it to your list of Web Clips.",
++ IDC_STATIC,0,33,239,18
++ PUSHBUTTON "Add Recent &Clips (10)...",
++ IDC_SIDEBAR_RSS_ADD_RECENT_CLIPS,146,14,93,14
++END'''), 'IDD_SIDEBAR_RSS_PANEL_PROPPAGE')
++ dlg.SetUberClique(self.uq)
++ dlg.Parse()
++
++ warnings = shortcuts.GenerateDuplicateShortcutsWarnings(self.uq, 'PROJECT')
++ self.failUnless(len(warnings) == 0)
++
+diff --git a/tools/grit/grit/tclib.py b/tools/grit/grit/tclib.py
+new file mode 100644
+index 0000000000..27ba366924
+--- /dev/null
++++ b/tools/grit/grit/tclib.py
+@@ -0,0 +1,246 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Adaptation of the extern.tclib classes for our needs.
++'''
++
++from __future__ import print_function
++
++import functools
++import re
++
++import six
++
++from grit import exception
++from grit import lazy_re
++import grit.extern.tclib
++
++
++# Matches whitespace sequences which can be folded into a single whitespace
++# character. This matches single characters so that non-spaces are replaced
++# with spaces.
++_FOLD_WHITESPACE = re.compile(r'\s+')
++
++# Caches compiled regexp used to split tags in BaseMessage.__init__()
++_RE_CACHE = {}
++
++def Identity(i):
++ return i
++
++
++class BaseMessage(object):
++ '''Base class with methods shared by Message and Translation.
++ '''
++
++ def __init__(self, text='', placeholders=[], description='', meaning=''):
++ self.parts = []
++ self.placeholders = []
++ self.meaning = meaning
++ self.dirty = True # True if self.id is (or might be) wrong
++ self.id = 0
++ self.SetDescription(description)
++
++ if text != '':
++ if not placeholders or placeholders == []:
++ self.AppendText(text)
++ else:
++ tag_map = {}
++ for placeholder in placeholders:
++ tag_map[placeholder.GetPresentation()] = [placeholder, 0]
++ # This creates a regexp like '(TAG1|TAG2|TAG3)'.
++ # The tags have to be sorted in order of decreasing length, so that
++ # longer tags are substituted before shorter tags that happen to be
++ # substrings of the longer tag.
++ # E.g. "EXAMPLE_FOO_NAME" must be matched before "EXAMPLE_FOO",
++ # otherwise "EXAMPLE_FOO" splits "EXAMPLE_FOO_NAME" too.
++ tags = sorted(tag_map.keys(),
++ key=functools.cmp_to_key(
++ lambda x, y: len(x) - len(y) or ((x > y) - (x < y))),
++ reverse=True)
++ tag_re = '(' + '|'.join(tags) + ')'
++
++ # This caching improves the time to build
++ # chrome/app:generated_resources from 21.562s to 17.672s on Linux.
++ compiled_re = _RE_CACHE.get(tag_re, None)
++ if compiled_re is None:
++ compiled_re = re.compile(tag_re)
++ _RE_CACHE[tag_re] = compiled_re
++
++ chunked_text = compiled_re.split(text)
++
++ for chunk in chunked_text:
++ if chunk: # ignore empty chunk
++ if chunk in tag_map:
++ self.AppendPlaceholder(tag_map[chunk][0])
++ tag_map[chunk][1] += 1 # increase placeholder use count
++ else:
++ self.AppendText(chunk)
++ for key in tag_map:
++ assert tag_map[key][1] != 0
++
++ def GetRealContent(self, escaping_function=Identity):
++ '''Returns the original content, i.e. what your application and users
++ will see.
++
++ Specify a function to escape each translateable bit, if you like.
++ '''
++ bits = []
++ for item in self.parts:
++ if isinstance(item, six.string_types):
++ bits.append(escaping_function(item))
++ else:
++ bits.append(item.GetOriginal())
++ return ''.join(bits)
++
++ def GetPresentableContent(self):
++ presentable_content = []
++ for part in self.parts:
++ if isinstance(part, Placeholder):
++ presentable_content.append(part.GetPresentation())
++ else:
++ presentable_content.append(part)
++ return ''.join(presentable_content)
++
++ def AppendPlaceholder(self, placeholder):
++ assert isinstance(placeholder, Placeholder)
++ dup = False
++ for other in self.GetPlaceholders():
++ if other.presentation == placeholder.presentation:
++ assert other.original == placeholder.original
++ dup = True
++
++ if not dup:
++ self.placeholders.append(placeholder)
++ self.parts.append(placeholder)
++ self.dirty = True
++
++ def AppendText(self, text):
++ assert isinstance(text, six.string_types)
++ assert text != ''
++
++ self.parts.append(text)
++ self.dirty = True
++
++ def GetContent(self):
++ '''Returns the parts of the message. You may modify parts if you wish.
++ Note that you must not call GetId() on this object until you have finished
++ modifying the contents.
++ '''
++ self.dirty = True # user might modify content
++ return self.parts
++
++ def GetDescription(self):
++ return self.description
++
++ def SetDescription(self, description):
++ self.description = _FOLD_WHITESPACE.sub(' ', description)
++
++ def GetMeaning(self):
++ return self.meaning
++
++ def GetId(self):
++ if self.dirty:
++ self.id = self.GenerateId()
++ self.dirty = False
++ return self.id
++
++ def GenerateId(self):
++ return grit.extern.tclib.GenerateMessageId(self.GetPresentableContent(),
++ self.meaning)
++
++ def GetPlaceholders(self):
++ return self.placeholders
++
++ def FillTclibBaseMessage(self, msg):
++ msg.SetDescription(self.description.encode('utf-8'))
++
++ for part in self.parts:
++ if isinstance(part, Placeholder):
++ ph = grit.extern.tclib.Placeholder(
++ part.presentation.encode('utf-8'),
++ part.original.encode('utf-8'),
++ part.example.encode('utf-8'))
++ msg.AppendPlaceholder(ph)
++ else:
++ msg.AppendText(part.encode('utf-8'))
++
++
++class Message(BaseMessage):
++ '''A message.'''
++
++ def __init__(self, text='', placeholders=[], description='', meaning='',
++ assigned_id=None):
++ super(Message, self).__init__(text, placeholders, description, meaning)
++ self.assigned_id = assigned_id
++
++ def ToTclibMessage(self):
++ msg = grit.extern.tclib.Message('utf-8', meaning=self.meaning)
++ self.FillTclibBaseMessage(msg)
++ return msg
++
++ def GetId(self):
++ '''Use the assigned id if we have one.'''
++ if self.assigned_id:
++ return self.assigned_id
++
++ return super(Message, self).GetId()
++
++ def HasAssignedId(self):
++ '''Returns True if this message has an assigned id.'''
++ return bool(self.assigned_id)
++
++
++class Translation(BaseMessage):
++ '''A translation.'''
++
++ def __init__(self, text='', id='', placeholders=[], description='', meaning=''):
++ super(Translation, self).__init__(text, placeholders, description, meaning)
++ self.id = id
++
++ def GetId(self):
++ assert id != '', "ID has not been set."
++ return self.id
++
++ def SetId(self, id):
++ self.id = id
++
++ def ToTclibMessage(self):
++ msg = grit.extern.tclib.Message(
++ 'utf-8', id=self.id, meaning=self.meaning)
++ self.FillTclibBaseMessage(msg)
++ return msg
++
++
++class Placeholder(grit.extern.tclib.Placeholder):
++ '''Modifies constructor to accept a Unicode string
++ '''
++
++ # Must match placeholder presentation names
++ _NAME_RE = lazy_re.compile('^[A-Za-z0-9_]+$')
++
++ def __init__(self, presentation, original, example):
++ '''Creates a new placeholder.
++
++ Args:
++ presentation: 'USERNAME'
++ original: '%s'
++ example: 'Joi'
++ '''
++ assert presentation != ''
++ assert original != ''
++ assert example != ''
++ if not self._NAME_RE.match(presentation):
++ raise exception.InvalidPlaceholderName(presentation)
++ self.presentation = presentation
++ self.original = original
++ self.example = example
++
++ def GetPresentation(self):
++ return self.presentation
++
++ def GetOriginal(self):
++ return self.original
++
++ def GetExample(self):
++ return self.example
+diff --git a/tools/grit/grit/tclib_unittest.py b/tools/grit/grit/tclib_unittest.py
+new file mode 100644
+index 0000000000..7a08654e1b
+--- /dev/null
++++ b/tools/grit/grit/tclib_unittest.py
+@@ -0,0 +1,180 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit tests for grit.tclib'''
++
++from __future__ import print_function
++
++import sys
++import os.path
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
++
++import unittest
++
++import six
++
++from grit import tclib
++
++from grit import exception
++import grit.extern.tclib
++
++
++class TclibUnittest(unittest.TestCase):
++ def testInit(self):
++ msg = tclib.Message(text=u'Hello Earthlings',
++ description='Greetings\n\t message')
++ self.failUnlessEqual(msg.GetPresentableContent(), 'Hello Earthlings')
++ self.failUnless(isinstance(msg.GetPresentableContent(), six.string_types))
++ self.failUnlessEqual(msg.GetDescription(), 'Greetings message')
++
++ def testGetAttr(self):
++ msg = tclib.Message()
++ msg.AppendText(u'Hello') # Tests __getattr__
++ self.failUnless(msg.GetPresentableContent() == 'Hello')
++ self.failUnless(isinstance(msg.GetPresentableContent(), six.string_types))
++
++ def testAll(self):
++ text = u'Howdie USERNAME'
++ phs = [tclib.Placeholder(u'USERNAME', u'%s', 'Joi')]
++ msg = tclib.Message(text=text, placeholders=phs)
++ self.failUnless(msg.GetPresentableContent() == 'Howdie USERNAME')
++
++ trans = tclib.Translation(text=text, placeholders=phs)
++ self.failUnless(trans.GetPresentableContent() == 'Howdie USERNAME')
++ self.failUnless(isinstance(trans.GetPresentableContent(), six.string_types))
++
++ def testUnicodeReturn(self):
++ text = u'\u00fe'
++ msg = tclib.Message(text=text)
++ self.failUnless(msg.GetPresentableContent() == text)
++ from_list = msg.GetContent()[0]
++ self.failUnless(from_list == text)
++
++ def testRegressionTranslationInherited(self):
++ '''Regression tests a bug that was caused by grit.tclib.Translation
++ inheriting from the translation console's Translation object
++ instead of only owning an instance of it.
++ '''
++ msg = tclib.Message(text=u"BLA1\r\nFrom: BLA2 \u00fe BLA3",
++ placeholders=[
++ tclib.Placeholder('BLA1', '%s', '%s'),
++ tclib.Placeholder('BLA2', '%s', '%s'),
++ tclib.Placeholder('BLA3', '%s', '%s')])
++ transl = tclib.Translation(text=msg.GetPresentableContent(),
++ placeholders=msg.GetPlaceholders())
++ content = transl.GetContent()
++ self.failUnless(isinstance(content[3], six.string_types))
++
++ def testFingerprint(self):
++ # This has Windows line endings. That is on purpose.
++ id = grit.extern.tclib.GenerateMessageId(
++ 'Google Desktop for Enterprise\r\n'
++ 'All Rights Reserved\r\n'
++ '\r\n'
++ '---------\r\n'
++ 'Contents\r\n'
++ '---------\r\n'
++ 'This distribution contains the following files:\r\n'
++ '\r\n'
++ 'GoogleDesktopSetup.msi - Installation and setup program\r\n'
++ 'GoogleDesktop.adm - Group Policy administrative template file\r\n'
++ 'AdminGuide.pdf - Google Desktop for Enterprise administrative guide\r\n'
++ '\r\n'
++ '\r\n'
++ '--------------\r\n'
++ 'Documentation\r\n'
++ '--------------\r\n'
++ 'Full documentation and installation instructions are in the \r\n'
++ 'administrative guide, and also online at \r\n'
++ 'http://desktop.google.com/enterprise/adminguide.html.\r\n'
++ '\r\n'
++ '\r\n'
++ '------------------------\r\n'
++ 'IBM Lotus Notes Plug-In\r\n'
++ '------------------------\r\n'
++ 'The Lotus Notes plug-in is included in the release of Google \r\n'
++ 'Desktop for Enterprise. The IBM Lotus Notes Plug-in for Google \r\n'
++ 'Desktop indexes mail, calendar, task, contact and journal \r\n'
++ 'documents from Notes. Discussion documents including those from \r\n'
++ 'the discussion and team room templates can also be indexed by \r\n'
++ 'selecting an option from the preferences. Once indexed, this data\r\n'
++ 'will be returned in Google Desktop searches. The corresponding\r\n'
++ 'document can be opened in Lotus Notes from the Google Desktop \r\n'
++ 'results page.\r\n'
++ '\r\n'
++ 'Install: The plug-in will install automatically during the Google \r\n'
++ 'Desktop setup process if Lotus Notes is already installed. Lotus \r\n'
++ 'Notes must not be running in order for the install to occur. \r\n'
++ '\r\n'
++ 'Preferences: Preferences and selection of databases to index are\r\n'
++ 'set in the \'Google Desktop for Notes\' dialog reached through the \r\n'
++ '\'Actions\' menu.\r\n'
++ '\r\n'
++ 'Reindexing: Selecting \'Reindex all databases\' will index all the \r\n'
++ 'documents in each database again.\r\n'
++ '\r\n'
++ '\r\n'
++ 'Notes Plug-in Known Issues\r\n'
++ '---------------------------\r\n'
++ '\r\n'
++ 'If the \'Google Desktop for Notes\' item is not available from the \r\n'
++ 'Lotus Notes Actions menu, then installation was not successful. \r\n'
++ 'Installation consists of writing one file, notesgdsplugin.dll, to \r\n'
++ 'the Notes application directory and a setting to the notes.ini \r\n'
++ 'configuration file. The most likely cause of an unsuccessful \r\n'
++ 'installation is that the installer was not able to locate the \r\n'
++ 'notes.ini file. Installation will complete if the user closes Notes\r\n'
++ 'and manually adds the following setting to this file on a new line:\r\n'
++ 'AddinMenus=notegdsplugin.dll\r\n'
++ '\r\n'
++ 'If the notesgdsplugin.dll file is not in the application directory\r\n'
++ r'(e.g., C:\Program Files\Lotus\Notes) after Google Desktop \r\n'
++ 'installation, it is likely that Notes was not installed correctly. \r\n'
++ '\r\n'
++ 'Only local databases can be indexed. If they can be determined, \r\n'
++ 'the user\'s local mail file and address book will be included in the\r\n'
++ 'list automatically. Mail archives and other databases must be \r\n'
++ 'added with the \'Add\' button.\r\n'
++ '\r\n'
++ 'Some users may experience performance issues during the initial \r\n'
++ 'indexing of a database. The \'Perform the initial index of a \r\n'
++ 'database only when I\'m idle\' option will limit the indexing process\r\n'
++ 'to times when the user is not using the machine. If this does not \r\n'
++ 'alleviate the problem or the user would like to continually index \r\n'
++ 'but just do so more slowly or quickly, the GoogleWaitTime notes.ini\r\n'
++ 'value can be set. Increasing the GoogleWaitTime value will slow \r\n'
++ 'down the indexing process, and lowering the value will speed it up.\r\n'
++ 'A value of zero causes the fastest possible indexing. Removing the\r\n'
++ 'ini parameter altogether returns it to the default (20).\r\n'
++ '\r\n'
++ 'Crashes have been known to occur with certain types of history \r\n'
++ 'bookmarks. If the Notes client seems to crash randomly, try \r\n'
++ 'disabling the \'Index note history\' option. If it crashes before,\r\n'
++ 'you can get to the preferences, add the following line to your \r\n'
++ 'notes.ini file:\r\n'
++ 'GDSNoIndexHistory=1\r\n')
++ self.assertEqual(id, '7660964495923572726')
++
++ def testPlaceholderNameChecking(self):
++ try:
++ ph = tclib.Placeholder('BINGO BONGO', 'bla', 'bla')
++ raise Exception("We shouldn't get here")
++ except exception.InvalidPlaceholderName:
++ pass # Expect exception to be thrown because presentation contained space
++
++ def testTagsWithCommonSubstring(self):
++ word = 'ABCDEFGHIJ'
++ text = ' '.join([word[:i] for i in range(1, 11)])
++ phs = [tclib.Placeholder(word[:i], str(i), str(i)) for i in range(1, 11)]
++ try:
++ msg = tclib.Message(text=text, placeholders=phs)
++ self.failUnless(msg.GetRealContent() == '1 2 3 4 5 6 7 8 9 10')
++ except:
++ self.fail('tclib.Message() should handle placeholders that are '
++ 'substrings of each other')
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/test_suite_all.py b/tools/grit/grit/test_suite_all.py
+new file mode 100644
+index 0000000000..3bfe2a79d5
+--- /dev/null
++++ b/tools/grit/grit/test_suite_all.py
+@@ -0,0 +1,34 @@
++#!/usr/bin/env python3
++# Copyright (c) 2011 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit test suite that collects all test cases for GRIT.'''
++
++from __future__ import print_function
+
-+#endif // MISSING_CTOR_H_
-diff --git a/tools/clang/plugins/tests/missing_ctor.txt b/tools/clang/plugins/tests/missing_ctor.txt
++import os
++import sys
++
++
++CUR_DIR = os.path.dirname(os.path.realpath(__file__))
++SRC_DIR = os.path.dirname(os.path.dirname(os.path.dirname(CUR_DIR)))
++TYP_DIR = os.path.join(
++ SRC_DIR, 'third_party', 'catapult', 'third_party', 'typ')
++
++if TYP_DIR not in sys.path:
++ sys.path.insert(0, TYP_DIR)
++
++
++import typ # pylint: disable=import-error,unused-import
++
++
++def main(args):
++ return typ.main(
++ top_level_dirs=[os.path.join(CUR_DIR, '..')],
++ skip=['grit.format.gen_predetermined_ids_unittest.*',
++ 'grit.pseudo_unittest.*']
++ )
++
++if __name__ == '__main__':
++ sys.exit(main(sys.argv[1:]))
+diff --git a/tools/grit/grit/testdata/GoogleDesktop.adm b/tools/grit/grit/testdata/GoogleDesktop.adm
+new file mode 100644
+index 0000000000..082f56bb1a
+--- /dev/null
++++ b/tools/grit/grit/testdata/GoogleDesktop.adm
+@@ -0,0 +1,945 @@
++CLASS MACHINE
++ CATEGORY !!Cat_Google
++ CATEGORY !!Cat_GoogleDesktopSearch
++ KEYNAME "Software\Policies\Google\Google Desktop"
++
++ CATEGORY !!Cat_Preferences
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences"
++
++ CATEGORY !!Cat_IndexAndCaptureControl
++ POLICY !!Blacklist_Email
++ EXPLAIN !!Explain_Blacklist_Email
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
++ VALUENAME "1"
++ END POLICY
++
++ POLICY !!Blacklist_Gmail
++ EXPLAIN !!Explain_Blacklist_Gmail
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-pop"
++ VALUENAME "gmail"
++ END POLICY
++
++ POLICY !!Blacklist_WebHistory
++ EXPLAIN !!Explain_Blacklist_WebHistory
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
++ VALUENAME "2"
++ END POLICY
++
++ POLICY !!Blacklist_Chat
++ EXPLAIN !!Explain_Blacklist_Chat
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
++ ACTIONLISTON
++ VALUENAME "3" VALUE NUMERIC 1
++ END ACTIONLISTON
++ END POLICY
++
++ POLICY !!Blacklist_Text
++ EXPLAIN !!Explain_Blacklist_Text
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
++ ACTIONLISTON
++ VALUENAME "4" VALUE NUMERIC 1
++ END ACTIONLISTON
++ END POLICY
++
++ POLICY !!Blacklist_Media
++ EXPLAIN !!Explain_Blacklist_Media
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
++ ACTIONLISTON
++ VALUENAME "5" VALUE NUMERIC 1
++ END ACTIONLISTON
++ END POLICY
++
++ POLICY !!Blacklist_Contact
++ EXPLAIN !!Explain_Blacklist_Contact
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
++ ACTIONLISTON
++ VALUENAME "9" VALUE NUMERIC 1
++ END ACTIONLISTON
++ END POLICY
++
++ POLICY !!Blacklist_Calendar
++ EXPLAIN !!Explain_Blacklist_Calendar
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
++ ACTIONLISTON
++ VALUENAME "10" VALUE NUMERIC 1
++ END ACTIONLISTON
++ END POLICY
++
++ POLICY !!Blacklist_Task
++ EXPLAIN !!Explain_Blacklist_Task
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
++ ACTIONLISTON
++ VALUENAME "11" VALUE NUMERIC 1
++ END ACTIONLISTON
++ END POLICY
++
++ POLICY !!Blacklist_Note
++ EXPLAIN !!Explain_Blacklist_Note
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
++ ACTIONLISTON
++ VALUENAME "12" VALUE NUMERIC 1
++ END ACTIONLISTON
++ END POLICY
++
++ POLICY !!Blacklist_Journal
++ EXPLAIN !!Explain_Blacklist_Journal
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
++ ACTIONLISTON
++ VALUENAME "13" VALUE NUMERIC 1
++ END ACTIONLISTON
++ END POLICY
++
++ POLICY !!Blacklist_Word
++ EXPLAIN !!Explain_Blacklist_Word
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-2"
++ VALUENAME "DOC"
++ END POLICY
++
++ POLICY !!Blacklist_Excel
++ EXPLAIN !!Explain_Blacklist_Excel
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-2"
++ VALUENAME "XLS"
++ END POLICY
++
++ POLICY !!Blacklist_Powerpoint
++ EXPLAIN !!Explain_Blacklist_Powerpoint
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-2"
++ VALUENAME "PPT"
++ END POLICY
++
++ POLICY !!Blacklist_PDF
++ EXPLAIN !!Explain_Blacklist_PDF
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-2"
++ VALUENAME "PDF"
++ END POLICY
++
++ POLICY !!Blacklist_ZIP
++ EXPLAIN !!Explain_Blacklist_ZIP
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-2"
++ VALUENAME "ZIP"
++ END POLICY
++
++ POLICY !!Blacklist_HTTPS
++ EXPLAIN !!Explain_Blacklist_HTTPS
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-3"
++ VALUENAME "HTTPS"
++ END POLICY
++
++ POLICY !!Blacklist_PasswordProtectedOffice
++ EXPLAIN !!Explain_Blacklist_PasswordProtectedOffice
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-13"
++ VALUENAME "SECUREOFFICE"
++ END POLICY
++
++ POLICY !!Blacklist_URI_Contains
++ EXPLAIN !!Explain_Blacklist_URI_Contains
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-6"
++ PART !!Blacklist_URI_Contains LISTBOX
++ END PART
++ END POLICY
++
++ POLICY !!Blacklist_Extensions
++ EXPLAIN !!Explain_Blacklist_Extensions
++ PART !!Blacklist_Extensions EDITTEXT
++ VALUENAME "file_extensions_to_skip"
++ END PART
++ END POLICY
++
++ POLICY !!Pol_Disallow_UserSearchLocations
++ EXPLAIN !!Explain_Disallow_UserSearchLocations
++ VALUENAME user_search_locations
++ VALUEON NUMERIC 1
++ END POLICY
++
++ POLICY !!Pol_Search_Location_Whitelist
++ EXPLAIN !!Explain_Search_Location_Whitelist
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\policy_search_location_whitelist"
++ PART !!Search_Locations_Whitelist LISTBOX
++ END PART
++ END POLICY
++
++ POLICY !!Email_Retention
++ EXPLAIN !!Explain_Email_Retention
++ PART !!Email_Retention_Edit NUMERIC
++ VALUENAME "email_days_to_retain"
++ MIN 1 MAX 65535 DEFAULT 30 SPIN 1
++ END PART
++ END POLICY
++
++ POLICY !!Webpage_Retention
++ EXPLAIN !!Explain_Webpage_Retention
++ PART !!Webpage_Retention_Edit NUMERIC
++ VALUENAME "webpage_days_to_retain"
++ MIN 1 MAX 65535 DEFAULT 30 SPIN 1
++ END PART
++ END POLICY
++
++ POLICY !!File_Retention
++ EXPLAIN !!Explain_File_Retention
++ PART !!File_Retention_Edit NUMERIC
++ VALUENAME "file_days_to_retain"
++ MIN 1 MAX 65535 DEFAULT 30 SPIN 1
++ END PART
++ END POLICY
++
++ POLICY !!IM_Retention
++ EXPLAIN !!Explain_IM_Retention
++ PART !!IM_Retention_Edit NUMERIC
++ VALUENAME "im_days_to_retain"
++ MIN 1 MAX 65535 DEFAULT 30 SPIN 1
++ END PART
++ END POLICY
++
++ POLICY !!Pol_Remove_Deleted_Items
++ EXPLAIN !!Explain_Remove_Deleted_Items
++ VALUENAME remove_deleted_items
++ VALUEON NUMERIC 1
++ END POLICY
++
++ POLICY !!Pol_Allow_Simultaneous_Indexing
++ EXPLAIN !!Explain_Allow_Simultaneous_Indexing
++ VALUENAME simultaneous_indexing
++ VALUEON NUMERIC 1
++ END POLICY
++
++ END CATEGORY
++
++ POLICY !!Pol_TurnOffAdvancedFeatures
++ EXPLAIN !!Explain_TurnOffAdvancedFeatures
++ VALUENAME error_report_on
++ VALUEON NUMERIC 0
++ END POLICY
++
++ POLICY !!Pol_TurnOffImproveGd
++ EXPLAIN !!Explain_TurnOffImproveGd
++ VALUENAME improve_gd
++ VALUEON NUMERIC 0
++ VALUEOFF NUMERIC 1
++ END POLICY
++
++ POLICY !!Pol_NoPersonalizationInfo
++ EXPLAIN !!Explain_NoPersonalizationInfo
++ VALUENAME send_personalization_info
++ VALUEON NUMERIC 0
++ VALUEOFF NUMERIC 1
++ END POLICY
++
++ POLICY !!Pol_OneBoxMode
++ EXPLAIN !!Explain_OneBoxMode
++ VALUENAME onebox_mode
++ VALUEON NUMERIC 0
++ END POLICY
++
++ POLICY !!Pol_EncryptIndex
++ EXPLAIN !!Explain_EncryptIndex
++ VALUENAME encrypt_index
++ VALUEON NUMERIC 1
++ END POLICY
++
++ POLICY !!Pol_Hyper
++ EXPLAIN !!Explain_Hyper
++ VALUENAME hyper_off
++ VALUEON NUMERIC 1
++ END POLICY
++
++ POLICY !!Pol_Display_Mode
++ EXPLAIN !!Explain_Display_Mode
++ PART !!Pol_Display_Mode DROPDOWNLIST
++ VALUENAME display_mode
++ ITEMLIST
++ NAME !!Sidebar VALUE NUMERIC 1
++ NAME !!Deskbar VALUE NUMERIC 8
++ NAME !!FloatingDeskbar VALUE NUMERIC 4
++ NAME !!None VALUE NUMERIC 0
++ END ITEMLIST
++ END PART
++ END POLICY
++
++ END CATEGORY ; Preferences
++
++ CATEGORY !!Cat_Enterprise
++ KEYNAME "Software\Policies\Google\Google Desktop\Enterprise"
++
++ POLICY !!Pol_Autoupdate
++ EXPLAIN !!Explain_Autoupdate
++ VALUENAME autoupdate_host
++ VALUEON ""
++ END POLICY
++
++ POLICY !!Pol_AutoupdateAsSystem
++ EXPLAIN !!Explain_AutoupdateAsSystem
++ VALUENAME autoupdate_impersonate_user
++ VALUEON NUMERIC 0
++ VALUEOFF NUMERIC 1
++ END POLICY
++
++ POLICY !!Pol_EnterpriseTab
++ EXPLAIN !!Explain_EnterpriseTab
++ PART !!EnterpriseTabText EDITTEXT
++ VALUENAME enterprise_tab_text
++ END PART
++ PART !!EnterpriseTabHomepage EDITTEXT
++ VALUENAME enterprise_tab_homepage
++ END PART
++ PART !!EnterpriseTabHomepageQuery CHECKBOX
++ VALUENAME enterprise_tab_homepage_query
++ END PART
++ PART !!EnterpriseTabResults EDITTEXT
++ VALUENAME enterprise_tab_results
++ END PART
++ PART !!EnterpriseTabResultsQuery CHECKBOX
++ VALUENAME enterprise_tab_results_query
++ END PART
++ END POLICY
++
++ POLICY !!Pol_GSAHosts
++ EXPLAIN !!Explain_GSAHosts
++ KEYNAME "Software\Policies\Google\Google Desktop\Enterprise\GSAHosts"
++ PART !!Pol_GSAHosts LISTBOX
++ END PART
++ END POLICY
++
++ POLICY !!Pol_PolicyUnawareClientProhibitedFlag
++ EXPLAIN !!Explain_PolicyUnawareClientProhibitedFlag
++ KEYNAME "Software\Policies\Google\Google Desktop"
++ VALUENAME PolicyUnawareClientProhibitedFlag
++ END POLICY
++
++ POLICY !!Pol_MinimumAllowedVersion
++ EXPLAIN !!Explain_MinimumAllowedVersion
++ PART !!Pol_MinimumAllowedVersion EDITTEXT
++ VALUENAME minimum_allowed_version
++ END PART
++ END POLICY
++
++ POLICY !!Pol_MaximumAllowedVersion
++ EXPLAIN !!Explain_MaximumAllowedVersion
++ PART !!Pol_MaximumAllowedVersion EDITTEXT
++ VALUENAME maximum_allowed_version
++ END PART
++ END POLICY
++
++ POLICY !!Pol_Disallow_Gadgets
++ EXPLAIN !!Explain_Disallow_Gadgets
++ VALUENAME disallow_gadgets
++ VALUEON NUMERIC 1
++ PART !!Disallow_Only_Non_Builtin_Gadgets CHECKBOX DEFCHECKED
++ VALUENAME disallow_only_non_builtin_gadgets
++ VALUEON NUMERIC 1
++ VALUEOFF NUMERIC 0
++ END PART
++ END POLICY
++
++ POLICY !!Pol_Gadget_Whitelist
++ EXPLAIN !!Explain_Gadget_Whitelist
++ KEYNAME "Software\Policies\Google\Google Desktop\Enterprise\gadget_whitelist"
++ PART !!Pol_Gadget_Whitelist LISTBOX
++ END PART
++ END POLICY
++
++ POLICY !!Pol_Gadget_Install_Confirmation_Whitelist
++ EXPLAIN !!Explain_Gadget_Install_Confirmation_Whitelist
++ KEYNAME "Software\Policies\Google\Google Desktop\Enterprise\install_confirmation_whitelist"
++ PART !!Pol_Gadget_Install_Confirmation_Whitelist LISTBOX
++ END PART
++ END POLICY
++
++ POLICY !!Pol_Alternate_User_Data_Dir
++ EXPLAIN !!Explain_Alternate_User_Data_Dir
++ PART !!Pol_Alternate_User_Data_Dir EDITTEXT
++ VALUENAME alternate_user_data_dir
++ END PART
++ END POLICY
++
++ POLICY !!Pol_MaxAllowedOutlookConnections
++ EXPLAIN !!Explain_MaxAllowedOutlookConnections
++ PART !!Pol_MaxAllowedOutlookConnections NUMERIC
++ VALUENAME max_allowed_outlook_connections
++ MIN 1 MAX 65535 DEFAULT 400 SPIN 1
++ END PART
++ END POLICY
++
++ POLICY !!Pol_DisallowSsdService
++ EXPLAIN !!Explain_DisallowSsdService
++ VALUENAME disallow_ssd_service
++ VALUEON NUMERIC 1
++ END POLICY
++
++ POLICY !!Pol_DisallowSsdOutbound
++ EXPLAIN !!Explain_DisallowSsdOutbound
++ VALUENAME disallow_ssd_outbound
++ VALUEON NUMERIC 1
++ END POLICY
++
++ POLICY !!Pol_Disallow_Store_Gadget_Service
++ EXPLAIN !!Explain_Disallow_Store_Gadget_Service
++ VALUENAME disallow_store_gadget_service
++ VALUEON NUMERIC 1
++ END POLICY
++
++ POLICY !!Pol_MaxExchangeIndexingRate
++ EXPLAIN !!Explain_MaxExchangeIndexingRate
++ PART !!Pol_MaxExchangeIndexingRate NUMERIC
++ VALUENAME max_exchange_indexing_rate
++ MIN 1 MAX 1000 DEFAULT 60 SPIN 1
++ END PART
++ END POLICY
++
++ POLICY !!Pol_EnableSafeweb
++ EXPLAIN !!Explain_Safeweb
++ VALUENAME safe_browsing
++ VALUEON NUMERIC 1
++ VALUEOFF NUMERIC 0
++ END POLICY
++
++ END CATEGORY ; Enterprise
++
++ END CATEGORY ; GoogleDesktopSearch
++ END CATEGORY ; Google
++
++
++CLASS USER
++ CATEGORY !!Cat_Google
++ CATEGORY !!Cat_GoogleDesktopSearch
++ KEYNAME "Software\Policies\Google\Google Desktop"
++
++ CATEGORY !!Cat_Preferences
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences"
++
++ CATEGORY !!Cat_IndexAndCaptureControl
++ POLICY !!Blacklist_Email
++ EXPLAIN !!Explain_Blacklist_Email
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
++ VALUENAME "1"
++ END POLICY
++
++ POLICY !!Blacklist_Gmail
++ EXPLAIN !!Explain_Blacklist_Gmail
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-pop"
++ VALUENAME "gmail"
++ END POLICY
++
++ POLICY !!Blacklist_WebHistory
++ EXPLAIN !!Explain_Blacklist_WebHistory
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
++ VALUENAME "2"
++ END POLICY
++
++ POLICY !!Blacklist_Chat
++ EXPLAIN !!Explain_Blacklist_Chat
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
++ ACTIONLISTON
++ VALUENAME "3" VALUE NUMERIC 1
++ END ACTIONLISTON
++ END POLICY
++
++ POLICY !!Blacklist_Text
++ EXPLAIN !!Explain_Blacklist_Text
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
++ ACTIONLISTON
++ VALUENAME "4" VALUE NUMERIC 1
++ END ACTIONLISTON
++ END POLICY
++
++ POLICY !!Blacklist_Media
++ EXPLAIN !!Explain_Blacklist_Media
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
++ ACTIONLISTON
++ VALUENAME "5" VALUE NUMERIC 1
++ END ACTIONLISTON
++ END POLICY
++
++ POLICY !!Blacklist_Contact
++ EXPLAIN !!Explain_Blacklist_Contact
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
++ ACTIONLISTON
++ VALUENAME "9" VALUE NUMERIC 1
++ END ACTIONLISTON
++ END POLICY
++
++ POLICY !!Blacklist_Calendar
++ EXPLAIN !!Explain_Blacklist_Calendar
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
++ ACTIONLISTON
++ VALUENAME "10" VALUE NUMERIC 1
++ END ACTIONLISTON
++ END POLICY
++
++ POLICY !!Blacklist_Task
++ EXPLAIN !!Explain_Blacklist_Task
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
++ ACTIONLISTON
++ VALUENAME "11" VALUE NUMERIC 1
++ END ACTIONLISTON
++ END POLICY
++
++ POLICY !!Blacklist_Note
++ EXPLAIN !!Explain_Blacklist_Note
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
++ ACTIONLISTON
++ VALUENAME "12" VALUE NUMERIC 1
++ END ACTIONLISTON
++ END POLICY
++
++ POLICY !!Blacklist_Journal
++ EXPLAIN !!Explain_Blacklist_Journal
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
++ ACTIONLISTON
++ VALUENAME "13" VALUE NUMERIC 1
++ END ACTIONLISTON
++ END POLICY
++
++ POLICY !!Blacklist_Word
++ EXPLAIN !!Explain_Blacklist_Word
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-2"
++ VALUENAME "DOC"
++ END POLICY
++
++ POLICY !!Blacklist_Excel
++ EXPLAIN !!Explain_Blacklist_Excel
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-2"
++ VALUENAME "XLS"
++ END POLICY
++
++ POLICY !!Blacklist_Powerpoint
++ EXPLAIN !!Explain_Blacklist_Powerpoint
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-2"
++ VALUENAME "PPT"
++ END POLICY
++
++ POLICY !!Blacklist_PDF
++ EXPLAIN !!Explain_Blacklist_PDF
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-2"
++ VALUENAME "PDF"
++ END POLICY
++
++ POLICY !!Blacklist_ZIP
++ EXPLAIN !!Explain_Blacklist_ZIP
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-2"
++ VALUENAME "ZIP"
++ END POLICY
++
++ POLICY !!Blacklist_HTTPS
++ EXPLAIN !!Explain_Blacklist_HTTPS
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-3"
++ VALUENAME "HTTPS"
++ END POLICY
++
++ POLICY !!Blacklist_PasswordProtectedOffice
++ EXPLAIN !!Explain_Blacklist_PasswordProtectedOffice
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-13"
++ VALUENAME "SECUREOFFICE"
++ END POLICY
++
++ POLICY !!Blacklist_URI_Contains
++ EXPLAIN !!Explain_Blacklist_URI_Contains
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-6"
++ PART !!Blacklist_URI_Contains LISTBOX
++ END PART
++ END POLICY
++
++ POLICY !!Blacklist_Extensions
++ EXPLAIN !!Explain_Blacklist_Extensions
++ PART !!Blacklist_Extensions EDITTEXT
++ VALUENAME "file_extensions_to_skip"
++ END PART
++ END POLICY
++
++ POLICY !!Pol_Disallow_UserSearchLocations
++ EXPLAIN !!Explain_Disallow_UserSearchLocations
++ VALUENAME user_search_locations
++ VALUEON NUMERIC 1
++ END POLICY
++
++ POLICY !!Pol_Search_Location_Whitelist
++ EXPLAIN !!Explain_Search_Location_Whitelist
++ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\policy_search_location_whitelist"
++ PART !!Search_Locations_Whitelist LISTBOX
++ END PART
++ END POLICY
++
++ POLICY !!Email_Retention
++ EXPLAIN !!Explain_Email_Retention
++ PART !!Email_Retention_Edit NUMERIC
++ VALUENAME "email_days_to_retain"
++ MIN 1 MAX 65535 DEFAULT 30 SPIN 1
++ END PART
++ END POLICY
++
++ POLICY !!Webpage_Retention
++ EXPLAIN !!Explain_Webpage_Retention
++ PART !!Webpage_Retention_Edit NUMERIC
++ VALUENAME "webpage_days_to_retain"
++ MIN 1 MAX 65535 DEFAULT 30 SPIN 1
++ END PART
++ END POLICY
++
++ POLICY !!File_Retention
++ EXPLAIN !!Explain_File_Retention
++ PART !!File_Retention_Edit NUMERIC
++ VALUENAME "file_days_to_retain"
++ MIN 1 MAX 65535 DEFAULT 30 SPIN 1
++ END PART
++ END POLICY
++
++ POLICY !!IM_Retention
++ EXPLAIN !!Explain_IM_Retention
++ PART !!IM_Retention_Edit NUMERIC
++ VALUENAME "im_days_to_retain"
++ MIN 1 MAX 65535 DEFAULT 30 SPIN 1
++ END PART
++ END POLICY
++
++ POLICY !!Pol_Remove_Deleted_Items
++ EXPLAIN !!Explain_Remove_Deleted_Items
++ VALUENAME remove_deleted_items
++ VALUEON NUMERIC 1
++ END POLICY
++
++ POLICY !!Pol_Allow_Simultaneous_Indexing
++ EXPLAIN !!Explain_Allow_Simultaneous_Indexing
++ VALUENAME simultaneous_indexing
++ VALUEON NUMERIC 1
++ END POLICY
++
++ END CATEGORY
++
++ POLICY !!Pol_TurnOffAdvancedFeatures
++ EXPLAIN !!Explain_TurnOffAdvancedFeatures
++ VALUENAME error_report_on
++ VALUEON NUMERIC 0
++ END POLICY
++
++ POLICY !!Pol_TurnOffImproveGd
++ EXPLAIN !!Explain_TurnOffImproveGd
++ VALUENAME improve_gd
++ VALUEON NUMERIC 0
++ VALUEOFF NUMERIC 1
++ END POLICY
++
++ POLICY !!Pol_NoPersonalizationInfo
++ EXPLAIN !!Explain_NoPersonalizationInfo
++ VALUENAME send_personalization_info
++ VALUEON NUMERIC 0
++ VALUEOFF NUMERIC 1
++ END POLICY
++
++ POLICY !!Pol_OneBoxMode
++ EXPLAIN !!Explain_OneBoxMode
++ VALUENAME onebox_mode
++ VALUEON NUMERIC 0
++ END POLICY
++
++ POLICY !!Pol_EncryptIndex
++ EXPLAIN !!Explain_EncryptIndex
++ VALUENAME encrypt_index
++ VALUEON NUMERIC 1
++ END POLICY
++
++ POLICY !!Pol_Hyper
++ EXPLAIN !!Explain_Hyper
++ VALUENAME hyper_off
++ VALUEON NUMERIC 1
++ END POLICY
++
++ POLICY !!Pol_Display_Mode
++ EXPLAIN !!Explain_Display_Mode
++ PART !!Pol_Display_Mode DROPDOWNLIST
++ VALUENAME display_mode
++ ITEMLIST
++ NAME !!Sidebar VALUE NUMERIC 1
++ NAME !!Deskbar VALUE NUMERIC 8
++ NAME !!FloatingDeskbar VALUE NUMERIC 4
++ NAME !!None VALUE NUMERIC 0
++ END ITEMLIST
++ END PART
++ END POLICY
++
++ END CATEGORY ; Preferences
++
++ CATEGORY !!Cat_Enterprise
++ KEYNAME "Software\Policies\Google\Google Desktop\Enterprise"
++
++ POLICY !!Pol_Autoupdate
++ EXPLAIN !!Explain_Autoupdate
++ VALUENAME autoupdate_host
++ VALUEON ""
++ END POLICY
++
++ POLICY !!Pol_AutoupdateAsSystem
++ EXPLAIN !!Explain_AutoupdateAsSystem
++ VALUENAME autoupdate_impersonate_user
++ VALUEON NUMERIC 0
++ VALUEOFF NUMERIC 1
++ END POLICY
++
++ POLICY !!Pol_EnterpriseTab
++ EXPLAIN !!Explain_EnterpriseTab
++ PART !!EnterpriseTabText EDITTEXT
++ VALUENAME enterprise_tab_text
++ END PART
++ PART !!EnterpriseTabHomepage EDITTEXT
++ VALUENAME enterprise_tab_homepage
++ END PART
++ PART !!EnterpriseTabHomepageQuery CHECKBOX
++ VALUENAME enterprise_tab_homepage_query
++ END PART
++ PART !!EnterpriseTabResults EDITTEXT
++ VALUENAME enterprise_tab_results
++ END PART
++ PART !!EnterpriseTabResultsQuery CHECKBOX
++ VALUENAME enterprise_tab_results_query
++ END PART
++ END POLICY
++
++ POLICY !!Pol_GSAHosts
++ EXPLAIN !!Explain_GSAHosts
++ KEYNAME "Software\Policies\Google\Google Desktop\Enterprise\GSAHosts"
++ PART !!Pol_GSAHosts LISTBOX
++ END PART
++ END POLICY
++
++ POLICY !!Pol_Disallow_Gadgets
++ EXPLAIN !!Explain_Disallow_Gadgets
++ VALUENAME disallow_gadgets
++ VALUEON NUMERIC 1
++ PART !!Disallow_Only_Non_Builtin_Gadgets CHECKBOX DEFCHECKED
++ VALUENAME disallow_only_non_builtin_gadgets
++ VALUEON NUMERIC 1
++ VALUEOFF NUMERIC 0
++ END PART
++ END POLICY
++
++ POLICY !!Pol_Gadget_Whitelist
++ EXPLAIN !!Explain_Gadget_Whitelist
++ KEYNAME "Software\Policies\Google\Google Desktop\Enterprise\gadget_whitelist"
++ PART !!Pol_Gadget_Whitelist LISTBOX
++ END PART
++ END POLICY
++
++ POLICY !!Pol_Gadget_Install_Confirmation_Whitelist
++ EXPLAIN !!Explain_Gadget_Install_Confirmation_Whitelist
++ KEYNAME "Software\Policies\Google\Google Desktop\Enterprise\install_confirmation_whitelist"
++ PART !!Pol_Gadget_Install_Confirmation_Whitelist LISTBOX
++ END PART
++ END POLICY
++
++ POLICY !!Pol_Alternate_User_Data_Dir
++ EXPLAIN !!Explain_Alternate_User_Data_Dir
++ PART !!Pol_Alternate_User_Data_Dir EDITTEXT
++ VALUENAME alternate_user_data_dir
++ END PART
++ END POLICY
++
++ POLICY !!Pol_MaxAllowedOutlookConnections
++ EXPLAIN !!Explain_MaxAllowedOutlookConnections
++ PART !!Pol_MaxAllowedOutlookConnections NUMERIC
++ VALUENAME max_allowed_outlook_connections
++ MIN 1 MAX 65535 DEFAULT 400 SPIN 1
++ END PART
++ END POLICY
++
++ POLICY !!Pol_DisallowSsdService
++ EXPLAIN !!Explain_DisallowSsdService
++ VALUENAME disallow_ssd_service
++ VALUEON NUMERIC 1
++ END POLICY
++
++ POLICY !!Pol_DisallowSsdOutbound
++ EXPLAIN !!Explain_DisallowSsdOutbound
++ VALUENAME disallow_ssd_outbound
++ VALUEON NUMERIC 1
++ END POLICY
++
++ POLICY !!Pol_Disallow_Store_Gadget_Service
++ EXPLAIN !!Explain_Disallow_Store_Gadget_Service
++ VALUENAME disallow_store_gadget_service
++ VALUEON NUMERIC 1
++ END POLICY
++
++ POLICY !!Pol_MaxExchangeIndexingRate
++ EXPLAIN !!Explain_MaxExchangeIndexingRate
++ PART !!Pol_MaxExchangeIndexingRate NUMERIC
++ VALUENAME max_exchange_indexing_rate
++ MIN 1 MAX 1000 DEFAULT 60 SPIN 1
++ END PART
++ END POLICY
++
++ POLICY !!Pol_EnableSafeweb
++ EXPLAIN !!Explain_Safeweb
++ VALUENAME safe_browsing
++ VALUEON NUMERIC 1
++ VALUEOFF NUMERIC 0
++ END POLICY
++
++ END CATEGORY ; Enterprise
++
++ END CATEGORY ; GoogleDesktopSearch
++ END CATEGORY ; Google
++
++;------------------------------------------------------------------------------
++
++[strings]
++Cat_Google="Google"
++Cat_GoogleDesktopSearch="Google Desktop"
++
++;------------------------------------------------------------------------------
++; Preferences
++;------------------------------------------------------------------------------
++Cat_Preferences="Preferences"
++Explain_Preferences="Controls Google Desktop preferences"
++
++Cat_IndexAndCaptureControl="Indexing and Capture Control"
++Explain_IndexAndCaptureControl="Controls what files, web pages, and other content will be indexed by Google Desktop."
++
++Blacklist_Email="Prevent indexing of email"
++Explain_Blacklist_Email="Enabling this policy will prevent Google Desktop from indexing emails.\n\nIf this policy is not configured, the user can choose whether or not to index emails."
++Blacklist_Gmail="Prevent indexing of Gmail"
++Explain_Blacklist_Gmail="Enabling this policy prevents Google Desktop from indexing Gmail messages.\n\nThis policy is in effect only when the policy "Prevent indexing of email" is disabled. When that policy is enabled, all email indexing is disabled, including Gmail indexing.\n\nIf both this policy and "Prevent indexing of email" are disabled or not configured, a user can choose whether or not to index Gmail messages."
++Blacklist_WebHistory="Prevent indexing of web pages"
++Explain_Blacklist_WebHistory="Enabling this policy will prevent Google Desktop from indexing web pages.\n\nIf this policy is disabled or not configured, the user can choose whether or not to index web pages."
++Blacklist_Text="Prevent indexing of text files"
++Explain_Blacklist_Text="Enabling this policy will prevent Google Desktop from indexing text files.\n\nIf this policy is disabled or not configured, the user can choose whether or not to index text files."
++Blacklist_Media="Prevent indexing of media files"
++Explain_Blacklist_Media="Enabling this policy will prevent Google Desktop from indexing media files.\n\nIf this policy is disabled or not configured, the user can choose whether or not to index media files."
++Blacklist_Contact="Prevent indexing of contacts"
++Explain_Blacklist_Contact="Enabling this policy will prevent Google Desktop from indexing contacts.\n\nIf this policy is disabled or not configured, the user can choose whether or not to index contacts."
++Blacklist_Calendar="Prevent indexing of calendar entries"
++Explain_Blacklist_Calendar="Enabling this policy will prevent Google Desktop from indexing calendar entries.\n\nIf this policy is disabled or not configured, the user can choose whether or not to index calendar entries."
++Blacklist_Task="Prevent indexing of tasks"
++Explain_Blacklist_Task="Enabling this policy will prevent Google Desktop from indexing tasks.\n\nIf this policy is disabled or not configured, the user can choose whether or not to index tasks."
++Blacklist_Note="Prevent indexing of notes"
++Explain_Blacklist_Note="Enabling this policy will prevent Google Desktop from indexing notes.\n\nIf this policy is disabled or not configured, the user can choose whether or not to index notes."
++Blacklist_Journal="Prevent indexing of journal entries"
++Explain_Blacklist_Journal="Enabling this policy will prevent Google Desktop from indexing journal entries.\n\nIf this policy is disabled or not configured, the user can choose whether or not to index journal entries."
++Blacklist_Word="Prevent indexing of Word documents"
++Explain_Blacklist_Word="Enabling this policy will prevent Google Desktop from indexing Word documents.\n\nIf this policy is disabled or not configured, the user can choose whether or not to index Word documents."
++Blacklist_Excel="Prevent indexing of Excel documents"
++Explain_Blacklist_Excel="Enabling this policy will prevent Google Desktop from indexing Excel documents.\n\nIf this policy is disabled or not configured, the user can choose whether or not to index Excel documents."
++Blacklist_Powerpoint="Prevent indexing of PowerPoint documents"
++Explain_Blacklist_Powerpoint="Enabling this policy will prevent Google Desktop from indexing PowerPoint documents.\n\nIf this policy is disabled or not configured, the user can choose whether or not to index PowerPoint documents."
++Blacklist_PDF="Prevent indexing of PDF documents"
++Explain_Blacklist_PDF="Enabling this policy will prevent Google Desktop from indexing PDF documents.\n\nIf this policy is disabled or not configured, the user can choose whether or not to index PDF documents."
++Blacklist_ZIP="Prevent indexing of ZIP files"
++Explain_Blacklist_ZIP="Enabling this policy will prevent Google Desktop from indexing ZIP files.\n\nIf this policy is disabled or not configured, the user can choose whether or not to index ZIP files."
++Blacklist_HTTPS="Prevent indexing of secure web pages"
++Explain_Blacklist_HTTPS="Enabling this policy will prevent Google Desktop from indexing secure web pages (pages with HTTPS in the URL).\n\nIf this policy is disabled or not configured, the user can choose whether or not to index secure web pages."
++Blacklist_URI_Contains="Prevent indexing of specific web sites and folders"
++Explain_Blacklist_URI_Contains="This policy allows you to prevent Google Desktop from indexing specific websites or folders. If an item's URL or path name contains any of these specified strings, it will not be indexed. These restrictions will be applied in addition to any websites or folders that the user has specified.\n\nThis policy has no effect when disabled or not configured."
++Blacklist_Chat="Prevent indexing of IM chats"
++Explain_Blacklist_Chat="Enabling this policy will prevent Google Desktop from indexing IM chat conversations.\n\nIf this policy is disabled or not configured, the user can choose whether or not to index IM chat conversations."
++Blacklist_PasswordProtectedOffice="Prevent indexing of password-protected Office documents (Word, Excel)"
++Explain_Blacklist_PasswordProtectedOffice="Enabling this policy will prevent Google Desktop from indexing password-protected office documents.\n\nIf this policy is disabled or not configured, the user can choose whether or not to index password-protected office documents."
++Blacklist_Extensions="Prevent indexing of specific file extensions"
++Explain_Blacklist_Extensions="This policy allows you to prevent Google Desktop from indexing files with specific extensions. Enter a list of file extensions, separated by commas, that you wish to exclude from indexing.\n\nThis policy has no effect when disabled or not configured."
++Pol_Disallow_UserSearchLocations="Disallow adding search locations for indexing"
++Explain_Disallow_UserSearchLocations="Enabling this policy will prevent the user from specifying additional drives or networked folders to be indexed by Google Desktop.\n\nIf this policy is disabled or not configured, users may specify additional drives and networked folders to be indexed."
++Pol_Search_Location_Whitelist="Allow indexing of specific folders"
++Explain_Search_Location_Whitelist="This policy allows you to add additional drives and networked folders to index."
++Search_Locations_Whitelist="Search these locations"
++Email_Retention="Only retain emails that are less than x days old"
++Explain_Email_Retention="This policy allows you to configure Google Desktop to only retain emails that are less than the specified number of days old in the index. Enter the number of days to retain emails for\n\nThis policy has no effect when disabled or not configured."
++Email_Retention_Edit="Number of days to retain emails"
++Webpage_Retention="Only retain webpages that are less than x days old"
++Explain_Webpage_Retention="This policy allows you to configure Google Desktop to only retain webpages that are less than the specified number of days old in the index. Enter the number of days to retain webpages for\n\nThis policy has no effect when disabled or not configured."
++Webpage_Retention_Edit="Number of days to retain webpages"
++File_Retention="Only retain files that are less than x days old"
++Explain_File_Retention="This policy allows you to configure Google Desktop to only retain files that are less than the specified number of days old in the index. Enter the number of days to retain files for\n\nThis policy has no effect when disabled or not configured."
++File_Retention_Edit="Number of days to retain files"
++IM_Retention="Only retain IM that are less than x days old"
++Explain_IM_Retention="This policy allows you to configure Google Desktop to only retain IM that are less than the specified number of days old in the index. Enter the number of days to retain IM for\n\nThis policy has no effect when disabled or not configured."
++IM_Retention_Edit="Number of days to retain IM"
++
++Pol_Remove_Deleted_Items="Remove deleted items from the index."
++Explain_Remove_Deleted_Items="Enabling this policy will remove all deleted items from the index and cache. Any items that are deleted will no longer be searchable."
++
++Pol_Allow_Simultaneous_Indexing="Allow historical indexing for multiple users simultaneously."
++Explain_Allow_Simultaneous_Indexing="Enabling this policy will allow a computer to generate first-time indexes for multiple users simultaneously. \n\nIf this policy is disabled or not configured, historical indexing will happen only for the logged-in user that was connected last; historical indexing for any other logged-in user will happen the next time that other user connects."
++
++Pol_TurnOffAdvancedFeatures="Turn off Advanced Features options"
++Explain_TurnOffAdvancedFeatures="Enabling this policy will prevent Google Desktop from sending Advanced Features data to Google (for either improvements or personalization), and users won't be able to change these options. Enabling this policy also prevents older versions of Google Desktop from sending data.\n\nIf this policy is disabled or not configured and the user has a pre-5.5 version of Google Desktop, the user can choose whether or not to enable sending data to Google. If the user has version 5.5 or later, the 'Turn off Improve Google Desktop option' and 'Do not send personalization info' policies will be used instead."
++
++Pol_TurnOffImproveGd="Turn off Improve Google Desktop option"
++Explain_TurnOffImproveGd="Enabling this policy will prevent Google Desktop from sending improvement data, including crash reports and anonymous usage data, to Google.\n\nIf this policy is disabled, improvement data will be sent to Google and the user won't be able to change the option.\n\nIf this policy is not configured, the user can choose whether or not to enable the Improve Google Desktop option.\n\nNote that this policy applies only to version 5.5 or later and doesn't affect previous versions of Google Desktop.\n\nAlso note that this policy can be overridden by the 'Turn off Advanced Features options' policy."
++
++Pol_NoPersonalizationInfo="Do not send personalization info"
++Explain_NoPersonalizationInfo="Enabling this policy will prevent Google Desktop from displaying personalized content, such as news that reflects the user's past interest in articles. Personalized content is derived from anonymous usage data sent to Google.\n\nIf this policy is disabled, personalized content will be displayed for all users, and users won't be able to disable this feature.\n\nIf this policy is not configured, users can choose whether or not to enable personalization in each gadget that supports this feature.\n\nNote that this policy applies only to version 5.5 or later and doesn't affect previous versions of Google Desktop.\n\nAlso note that this policy can be overridden by the 'Turn off Advanced Features options' policy."
++
++Pol_OneBoxMode="Turn off Google Web Search Integration"
++Explain_OneBoxMode="Enabling this policy will prevent Google Desktop from displaying Desktop Search results in queries to google.com.\n\nIf this policy is disabled or not configured, the user can choose whether or not to include Desktop Search results in queries to google.com."
++
++Pol_EncryptIndex="Encrypt index data"
++Explain_EncryptIndex="Enabling this policy will cause Google Desktop to turn on Windows file encryption for the folder containing the Google Desktop index and related user data the next time it is run.\n\nNote that Windows EFS is only available on NTFS volumes. If the user's data is stored on a FAT volume, this policy will have no effect.\n\nThis policy has no effect when disabled or not configured."
++
++Pol_Hyper="Turn off Quick Find"
++Explain_Hyper="Enabling this policy will cause Google Desktop to turn off Quick Find feature. Quick Find allows you to see results as you type.\n\nIf this policy is disabled or not configured, the user can choose whether or not to enable it."
++
++Pol_Display_Mode="Choose display option"
++Explain_Display_Mode="This policy sets the Google Desktop display option: Sidebar, Deskbar, Floating Deskbar or none.\n\nNote that on 64-bit systems, a setting of Deskbar will be interpreted as Floating Deskbar.\n\nIf this policy is disabled or not configured, the user can choose a display option."
++Sidebar="Sidebar"
++Deskbar="Deskbar"
++FloatingDeskbar="Floating Deskbar"
++None="None"
++
++;------------------------------------------------------------------------------
++; Enterprise
++;------------------------------------------------------------------------------
++Cat_Enterprise="Enterprise Integration"
++Explain_Enterprise="Controls features specific to Enterprise installations of Google Desktop"
++
++Pol_Autoupdate="Block Auto-update"
++Explain_Autoupdate="Enabling this policy prevents Google Desktop from automatically checking for and installing updates from google.com.\n\nIf you enable this policy, you must distribute updates to Google Desktop using Group Policy, SMS, or a similar enterprise software distribution mechanism. You should check http://desktop.google.com/enterprise/ for updates.\n\nIf this policy is disabled or not configured, Google Desktop will periodically check for updates from desktop.google.com."
++
++Pol_AutoupdateAsSystem="Use system proxy settings when auto-updating"
++Explain_AutoupdateAsSystem="Enabling this policy makes Google Desktop use the machine-wide proxy settings (as specified using e.g. proxycfg.exe) when performing autoupdates (if enabled).\n\nIf this policy is disabled or not configured, Google Desktop will use the logged-on user's Internet Explorer proxy settings when checking for auto-updates (if enabled)."
++
++Pol_EnterpriseTab="Enterprise search tab"
++Explain_EnterpriseTab="This policy allows you to add a search tab for your Google Search Appliance to Google Desktop and google.com web pages.\n\nYou must provide the name of the tab, such as "Intranet", as well as URLs for the search homepage and for retrieving search results. Use [DISP_QUERY] in place of the query term for the search results URL.\n\nSee the administrator's guide for more details."
++EnterpriseTabText="Tab name"
++EnterpriseTabHomepage="Search homepage URL"
++EnterpriseTabHomepageQuery="Check if search homepage supports '&&q=<query>'"
++EnterpriseTabResults="Search results URL"
++EnterpriseTabResultsQuery="Check if search results page supports '&&q=<query>'"
++
++Pol_GSAHosts="Google Search Appliances"
++Explain_GSAHosts="This policy allows you to list any Google Search Appliances in your intranet. When properly configured, Google Desktop will insert Google Desktop results into the results of queries on the Google Search Appliance"
++
++Pol_PolicyUnawareClientProhibitedFlag="Prohibit Policy-Unaware versions"
++Explain_PolicyUnawareClientProhibitedFlag="Prohibits installation and execution of versions of Google Desktop that are unaware of group policy.\n\nEnabling this policy will prevent users from installing or running version 1.0 of Google Desktop.\n\nThis policy has no effect when disabled or not configured."
++
++Pol_MinimumAllowedVersion="Minimum allowed version"
++Explain_MinimumAllowedVersion="This policy allows you to prevent installation and/or execution of older versions of Google Desktop by specifying the minimum version you wish to allow. When enabling this policy, you should also enable the "Prohibit Policy-Unaware versions" policy to block versions of Google Desktop that did not support group policy.\n\nThis policy has no effect when disabled or not configured."
++
++Pol_MaximumAllowedVersion="Maximum allowed version"
++Explain_MaximumAllowedVersion="This policy allows you to prevent installation and/or execution of newer versions of Google Desktop by specifying the maximum version you wish to allow.\n\nThis policy has no effect when disabled or not configured."
++
++Pol_Disallow_Gadgets="Disallow gadgets and indexing plug-ins"
++Explain_Disallow_Gadgets="This policy prevents the use of all Google Desktop gadgets and indexing plug-ins. The policy applies to gadgets that are included in the Google Desktop installation package (built-in gadgets), built-in indexing plug-ins (currently only the Lotus Notes plug-in), and to gadgets or indexing plug-ins that a user might want to add later (non-built-in gadgets and indexing plug-ins).\n\nYou can prohibit use of all non-built-in gadgets and indexing plug-ins, but allow use of built-in gadgets and indexing plug-ins. To do so, enable this policy and then select the option "Disallow only non-built-in gadgets and indexing plug-ins.\n\nYou can supersede this policy to allow specified built-in and non-built-in gadgets and indexing plug-ins. To do so, enable this policy and then specify the gadgets and/or indexing plug-ins you want to allow under "Gadget and Plug-in Whitelist.""
++Disallow_Only_Non_Builtin_Gadgets="Disallow only non-built-in gadgets and indexing plug-ins"
++
++Pol_Gadget_Whitelist="Gadget and plug-in whitelist"
++Explain_Gadget_Whitelist="This policy specifies a list of Google Desktop gadgets and indexing plug-ins that you want to allow, as exceptions to the "Disallow gadgets and indexing plug-ins" policy. This policy is valid only when the "Disallow gadgets and indexing plug-ins" policy is enabled.\n\nFor each gadget or indexing plug-in you wish to allow, add the CLSID or PROGID of the gadget or indexing plug-in (see the administrator's guide for more details).\n\nThis policy has no effect when disabled or not configured."
++
++Pol_Gadget_Install_Confirmation_Whitelist="Allow silent installation of gadgets"
++Explain_Gadget_Install_Confirmation_Whitelist="Enabling this policy lets you specify a list of Google Desktop gadgets or indexing plug-ins that can be installed without confirmation from the user.\n\nAdd a gadget or indexing plug-in by placing its class ID (CLSID) or program identifier (PROGID) in the list, surrounded with curly braces ({ }).\n\nThis policy has no effect when disabled or not configured."
++
++Pol_Alternate_User_Data_Dir="Alternate user data directory"
++Explain_Alternate_User_Data_Dir="This policy allows you to specify a directory to be used to store user data for Google Desktop (such as index data and cached documents).\n\nYou may use [USER_NAME] or [DOMAIN_NAME] in the path to specify the current user's name or domain. If [USER_NAME] is not specified, the user name will be appended at the end of the path.\n\nThis policy has no effect when disabled or not configured."
++
++Pol_MaxAllowedOutlookConnections="Maximum allowed Outlook connections"
++Explain_MaxAllowedOutlookConnections="This policy specifies the maximum number of open connections that Google Desktop maintains with the Exchange server. Google Desktop opens a connection for each email folder that it indexes. If insufficient connections are allowed, Google Desktop cannot index all the user email folders.\n\nThe default value is 400. Because users rarely have as many as 400 email folders, Google Desktop rarely reaches the limit.\n\nIf you set this policy's value above 400, you must also configure the number of open connections between Outlook and the Exchange server. By default, approximately 400 connections are allowed. If Google Desktop uses too many of these connections, Outlook might be unable to access email.\n\nThis policy has no effect when disabled or not configured."
++
++Pol_DisallowSsdService="Disallow sharing and receiving of web history and documents across computers"
++Explain_DisallowSsdService="Enabling this policy will prevent Google Desktop from sharing the user's web history and document contents across the user's different Google Desktop installations, and will also prevent it from receiving such shared items from the user's other machines. To allow reception but disallow sharing, use DisallowSsdOutbound.\nThis policy has no effect when disabled or not configured."
++
++Pol_DisallowSsdOutbound="Disallow sharing of web history and documents to user's other computers."
++Explain_DisallowSsdOutbound="Enabling this policy will prevent Google Desktop from sending the user's web history and document contents from this machine to the user's other machines. It does not prevent reception of items from the user's other machines; to disallow both, use DisallowSsdService.\nThis policy has no effect when disabled or not configured."
++
++Pol_Disallow_Store_Gadget_Service="Disallow storage of gadget content and settings."
++Explain_Disallow_Store_Gadget_Service="Enabling this policy will prevent users from storing their gadget content and settings with Google. Users will be unable to access their gadget content and settings from other computers and all content and settings will be lost if Google Desktop is uninstalled."
++
++Pol_MaxExchangeIndexingRate="Maximum allowed Exchange indexing rate"
++Explain_MaxExchangeIndexingRate="This policy allows you to specify the maximum number of emails that are indexed per minute. \n\nThis policy has no effect when disabled or not configured."
++
++Pol_EnableSafeweb="Enable or disable safe browsing"
++Explain_Safeweb="Google Desktop safe browsing informs the user whenever they visit any site which is a suspected forgery site or may harm their computer. Enabling this policy turns on safe browsing; disabling the policy turns it off. \n\nIf this policy is not configured, the user can select whether to turn on safe browsing."
+\ No newline at end of file
+diff --git a/tools/grit/grit/testdata/README.txt b/tools/grit/grit/testdata/README.txt
+new file mode 100644
+index 0000000000..a683b3b9e3
+--- /dev/null
++++ b/tools/grit/grit/testdata/README.txt
+@@ -0,0 +1,87 @@
++Google Desktop for Enterprise
++Copyright (C) 2007 Google Inc.
++All Rights Reserved
++
++---------
++Contents
++---------
++This distribution contains the following files:
++
++GoogleDesktopSetup.msi - Installation and setup program
++GoogleDesktop.adm - Group Policy administrative template file
++AdminGuide.pdf - Google Desktop for Enterprise administrative guide
++
++
++--------------
++Documentation
++--------------
++Full documentation and installation instructions are in the
++administrative guide, and also online at
++http://desktop.google.com/enterprise/adminguide.html.
++
++
++------------------------
++IBM Lotus Notes Plug-In
++------------------------
++The Lotus Notes plug-in is included in the release of Google
++Desktop for Enterprise. The IBM Lotus Notes Plug-in for Google
++Desktop indexes mail, calendar, task, contact and journal
++documents from Notes. Discussion documents including those from
++the discussion and team room templates can also be indexed by
++selecting an option from the preferences. Once indexed, this data
++will be returned in Google Desktop searches. The corresponding
++document can be opened in Lotus Notes from the Google Desktop
++results page.
++
++Install: The plug-in will install automatically during the Google
++Desktop setup process if Lotus Notes is already installed. Lotus
++Notes must not be running in order for the install to occur. The
++Class ID for this plug-in is {8F42BDFB-33E8-427B-AFDC-A04E046D3F07}.
++
++Preferences: Preferences and selection of databases to index are
++set in the 'Google Desktop for Notes' dialog reached through the
++'Actions' menu.
++
++Reindexing: Selecting 'Reindex all databases' will index all the
++documents in each database again.
++
++
++Notes Plug-in Known Issues
++---------------------------
++
++If the 'Google Desktop for Notes' item is not available from the
++Lotus Notes Actions menu, then installation was not successful.
++Installation consists of writing one file, notesgdsplugin.dll, to
++the Notes application directory and a setting to the notes.ini
++configuration file. The most likely cause of an unsuccessful
++installation is that the installer was not able to locate the
++notes.ini file. Installation will complete if the user closes Notes
++and manually adds the following setting to this file on a new line:
++AddinMenus=notesgdsplugin.dll
++
++If the notesgdsplugin.dll file is not in the application directory
++(e.g., C:\Program Files\Lotus\Notes) after Google Desktop
++installation, it is likely that Notes was not installed correctly.
++
++Only local databases can be indexed. If they can be determined,
++the user's local mail file and address book will be included in the
++list automatically. Mail archives and other databases must be
++added with the 'Add' button.
++
++Some users may experience performance issues during the initial
++indexing of a database. The 'Perform the initial index of a
++database only when I'm idle' option will limit the indexing process
++to times when the user is not using the machine. If this does not
++alleviate the problem or the user would like to continually index
++but just do so more slowly or quickly, the GoogleWaitTime notes.ini
++value can be set. Increasing the GoogleWaitTime value will slow
++down the indexing process, and lowering the value will speed it up.
++A value of zero causes the fastest possible indexing. Removing the
++ini parameter altogether returns it to the default (20).
++
++Crashes have been known to occur with certain types of history
++bookmarks. If the Notes client seems to crash randomly, try
++disabling the 'Index note history' option. If it crashes before,
++you can get to the preferences, add the following line to your
++notes.ini file:
++GDSNoIndexHistory=1
+diff --git a/tools/grit/grit/testdata/about.html b/tools/grit/grit/testdata/about.html
+new file mode 100644
+index 0000000000..8e5fad7b2b
+--- /dev/null
++++ b/tools/grit/grit/testdata/about.html
+@@ -0,0 +1,45 @@
++[HEADER]
++<table cellspacing=0 cellPadding=0 width="100%" border=0><tr bgcolor=#3399cc><td align=middle height=1><img height=1 width=1></td></tr></table>
++<table cellspacing=0 cellPadding=1 width="100%" bgcolor=#e8f4f7 border=0><tr><td height=20><font size=+1 color=#000000>&nbsp;<b>[TITLE]</b></font></td></tr></table>
++<br><center><span style="line-height:16pt"><font color=#335cec><B>Google Desktop Search: Search your own computer.</B></font></span></center><br>
++
++<table cellspacing=1 cellpadding=0 width=300 align=center border=0>
++<tr><td nowrap><font size=-1><img style="vertical-align:middle" height=16 src="outlook.gif" width=16>&nbsp;&nbsp;Outlook Email</font></td>
++<td nowrap>&nbsp;</td>
++<td nowrap><font size=-1><img style="vertical-align:middle" height=16 src="netscape.gif" width=16>&nbsp;&nbsp;Netscape Mail / Thunderbird</font></td></tr>
++
++<tr><td nowrap><font size=-1><img style="vertical-align:middle" height=16 src="oe.gif" width=16>&nbsp;&nbsp;Outlook Express</font></td>
++<td nowrap>&nbsp;</td>
++<td nowrap><font size=-1><img style="vertical-align:middle" height=16 src="ff.gif" width=16>&nbsp;&nbsp;Netscape / Firefox / Mozilla</font></td></tr>
++
++<tr><td nowrap><font size=-1><img style="vertical-align:middle" height=16 src="doc.gif" width=16>&nbsp;&nbsp;Word</font></td>
++<td nowrap>&nbsp;</td>
++<td nowrap><font size=-1><img style="vertical-align:middle" height=16 src="pdf.gif" width=16>&nbsp;&nbsp;PDF</font></td></tr>
++
++<tr><td nowrap><font size=-1><img style="vertical-align:middle" height=16 src="xls.gif" width=16>&nbsp;&nbsp;Excel</font></td>
++<td nowrap>&nbsp;</td>
++<td nowrap><font size=-1><img style="vertical-align:middle" height=16 src="mus.gif" width=16>&nbsp;&nbsp;Music</font></td></tr>
++
++<tr><td nowrap><font size=-1><img style="vertical-align:middle" height=16 src="ppt.gif" width=16>&nbsp;&nbsp;PowerPoint</font></td>
++<td nowrap>&nbsp;</td>
++<td nowrap><font size=-1><img style="vertical-align:middle" height=16 src="jpg.gif" width=16>&nbsp;&nbsp;Images</font></td></tr>
++
++<tr><td nowrap><font size=-1><img style="vertical-align:middle" height=16 src="ie.gif" width=16>&nbsp;&nbsp;Internet Explorer</font></td>
++<td nowrap>&nbsp;</td>
++<td nowrap><font size=-1><img style="vertical-align:middle" height=16 src="mov.gif" width=16>&nbsp;&nbsp;Video</font></td></tr>
++
++<tr><td nowrap><font size=-1><img style="vertical-align:middle" height=16 src="aim.gif" width=16>&nbsp;&nbsp;AOL Instant Messenger</font></td>
++<td nowrap>&nbsp;</td>
++<td nowrap><font size=-1><img style="vertical-align:middle" height=16 src="other.gif" width=16>&nbsp;&nbsp;Even more with <a href="http://desktop.google.com/plugins.html">these plug-ins</A></font></td></tr>
++
++<tr><td nowrap><font size=-1><img style="vertical-align:middle" height=16 src="txt.gif" width=16>&nbsp;&nbsp;Text and others</font></td></tr>
++</table>
++<center>
++<p><table cellpadding=1>
++<tr><td><a href="http://desktop.google.com/gettingstarted.html?hl=[LANG_CODE]"><B>Getting Started</B></A> - Learn more about using Google Desktop Search</td></tr>
++<tr><td><a href="http://desktop.google.com/help.html?hl=[LANG_CODE]"><B>Online Help</B></A> - Up-to-date answers to your questions</td></tr>
++<tr><td><a href="[$~PRIVACY~$]"><B>Privacy</B></A> - A few words about privacy and Google Desktop Search</td></tr>
++<tr><td><a href="http://desktop.google.com/uninstall.html?hl=[LANG_CODE]"><B>Uninstall</B></A> - How to uninstall Google Desktop Search</td></tr>
++<tr><td><a href="http://desktop.google.com/feedback.html?hl=[LANG_CODE]"><B>Submit Feedback</B></A> - Send us your comments and ideas</td></tr>
++</table><br><font size=-2>Google Desktop Search [$~BUILDNUMBER~$]</font><br><br>
++[FOOTER]
+\ No newline at end of file
+diff --git a/tools/grit/grit/testdata/android.xml b/tools/grit/grit/testdata/android.xml
+new file mode 100644
+index 0000000000..cc3b141f70
+--- /dev/null
++++ b/tools/grit/grit/testdata/android.xml
+@@ -0,0 +1,24 @@
++<!--
++ Copyright (c) 2012 The Chromium Authors. All rights reserved.
++ Use of this source code is governed by a BSD-style license that can be
++ found in the LICENSE file.
++-->
++
++<resources>
++ <!-- A string with placeholder. -->
++ <string xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" name="placeholders">
++ Open <xliff:g id="FILENAME" example="internet.html">%s</xliff:g>?
++ </string>
++
++ <!-- A simple string. -->
++ <string name="simple">A simple string.</string>
++
++ <!-- A string with a comment. -->
++ <string name="comment">Contains a <!-- ignore this --> comment. </string>
++
++ <!-- A second simple string. -->
++ <string name="simple2"> Another simple string. </string>
++
++ <!-- A non-translatable string. -->
++ <string name="constant" translatable="false">Do not translate me.</string>
++</resources>
+diff --git a/tools/grit/grit/testdata/bad_browser.html b/tools/grit/grit/testdata/bad_browser.html
+new file mode 100644
+index 0000000000..e8cf34664d
+--- /dev/null
++++ b/tools/grit/grit/testdata/bad_browser.html
+@@ -0,0 +1,16 @@
++<p><b>We're sorry, but we don't seem to be compatible.</b></p>
++<p><font size="-1">Our software suggests that you're using a browser incompatible with Google Desktop Search.
++ Google Desktop Search currently supports the following:</font></p>
++<ul><font size="-1">
++ <li>Microsoft IE 5 and newer (<a href="http://www.microsoft.com/windows/ie/downloads/default.asp">Download</a>)</li>
++ <li>Mozilla (<a href="http://www.mozilla.org/products/mozilla1.x/">Download</a>)</li>
++ <li>Mozilla Firefox (<a href="http://www.mozilla.org/products/firefox/">Download</a>)</li>
++ <li>Netscape 7 and newer (<a href="http://channels.netscape.com/ns/browsers/download.jsp">Download</a>)</li>
++</font></ul>
++
++<p><font size="-1">You may <a href="[REDIR]">click here</a> to use your
++ unsupported browser, though you likely will encounter some areas that don't
++ work as expected. You need to have Javascript enabled, regardless of the
++ browser you use.</font>
++<p><font size="-1">We hope to expand this list in the near future and announce new
++ browsers as they become available.
+diff --git a/tools/grit/grit/testdata/browser.html b/tools/grit/grit/testdata/browser.html
+new file mode 100644
+index 0000000000..45d364d56f
+--- /dev/null
++++ b/tools/grit/grit/testdata/browser.html
+@@ -0,0 +1,42 @@
++<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
++<html><head><title>[$~TITLE~$]</title>
++<style>
++BODY { MARGIN-LEFT: 1em; MARGIN-RIGHT: 1em }
++BODY, TD, DIV, A { FONT-FAMILY: arial,sans-serif}
++DIV, TD { COLOR: #000}
++A:link { COLOR: #00c}
++A:visited { COLOR: #551a8b}
++A:active { COLOR: #f00 }
++</style>
++</head>
++
++<body bgcolor="#ffffff" text="#000000" link="#0000cc" vlink="#800080" alink="#ff0000" topmargin=2>
++
++<table cellspacing=2 cellpadding=0 width="99%" border=0>
++<tr>
++ <td width="1%" rowspan=2>[$~IMAGE~$]
++ <td>&nbsp;</td>
++ <td rowspan=2>
++ <table cellspacing=0 cellpadding=0 width="100%" border=0>
++ <tr>
++ <td bgcolor=#3399cc><img height=1 width=1></td>
++ </tr>
++ </table>
++ <table cellspacing=0 cellpadding=0 width="100%" border=0 bgcolor=#efefef>
++ <tr>
++ <td nowrap bgcolor=#E8F4F7><font face=arial,sans-serif color=#000000 size=+1><b>&nbsp;[$~CHROME_TITLE~$]</b></font></td>
++ </tr>
++ </table>
++ </td>
++</tr>
++</table>
++
++<table cellpadding=3 width="94%" align="center" cellspacing=0 border=0>
++<tr valign="middle">
++ <td valign="top">
++ [$~BODY~$]
++ </td>
++ </tr>
++</table>
++[$~FOOTER~$]
++</body></html>
+\ No newline at end of file
+diff --git a/tools/grit/grit/testdata/buildinfo.grd b/tools/grit/grit/testdata/buildinfo.grd
+new file mode 100644
+index 0000000000..80458a8265
+--- /dev/null
++++ b/tools/grit/grit/testdata/buildinfo.grd
+@@ -0,0 +1,46 @@
++<?xml version="1.0" encoding="UTF-8"?> <!-- -*- XML -*- -->
++<grit
++ base_dir="."
++ source_lang_id="en"
++ tc_project="GoogleDesktopWindowsClient"
++ latest_public_release="0"
++ current_release="1"
++ enc_check="möl">
++ <outputs>
++ <output filename="resource.h" type="rc_header" />
++ <output filename="en_generated_resources.rc" type="rc_all" lang="en" />
++ <output filename="sv_generated_resources.rc" type="rc_all" lang="sv" />
++ </outputs>
++ <translations>
++ <file path="substitute.xmb" lang="sv" />
++ </translations>
++ <release seq="1" allow_pseudo="false">
++ <includes>
++ <include type="BITMAP" name="IDB_PR" file="pr.bmp" />
++ <if expr="lang == 'sv'">
++ <include type="BITMAP" name="IDB_PR2" file="pr2.bmp" />
++ </if>
++ </includes>
++ <structures>
++ <structure name="SIDEBAR_LOADING.HTML" encoding="utf-8" file="sidebar_loading.html" type="tr_html" generateid="false" expand_variables="false"/>
++ <structure name="IDS_PLACEHOLDER" file="transl.rc" type="dialog" >
++ <skeleton expr="lang == 'sv'" file="transl1.rc" variant_of_revision="1"/>
++ </structure>
++ <if expr="lang != 'sv'">
++ <structure name="WELCOME_TOAST.HTML" encoding="utf-8" file="welcome_toast.html" type="tr_html" generateid="false" expand_variables="true"/>
++ </if>
++ </structures>
++ <messages first_id="8192">
++ <message name="IDS_COPYRIGHT_GOOGLE_LONG" sub_variable="true" desc="Gadget copyright notice. Needs to be updated every year.">
++ Copyright 2008 Google Inc. All Rights Reserved.
++ </message>
++ <message name="IDS_NEWS_PANEL_COPYRIGHT">
++ Google Desktop News gadget
++[IDS_COPYRIGHT_GOOGLE_LONG]
++View news that is personalized based on the articles you read.
++
++For example, if you read lots of sports news, you'll see more sports articles. If you read technology news less often, you'll see fewer of those articles.
++ </message>
++ </messages>
++ </release>
++</grit>
+diff --git a/tools/grit/grit/testdata/cache_prefix.html b/tools/grit/grit/testdata/cache_prefix.html
+new file mode 100644
+index 0000000000..b1f91dd82b
+--- /dev/null
++++ b/tools/grit/grit/testdata/cache_prefix.html
+@@ -0,0 +1,24 @@
++<head>
++<meta http-equiv="cache-control" content="no-cache">
++<meta http-equiv="pragma" content="no-cache">
++<meta http-equiv="expires" content="-1">
++</head>
++<body onload="[ONLOAD]">
++<table width="100%" border=1><tr><td>
++<table cellspacing=0 cellpadding=10 width="100%" bgcolor=#ffffff border=1 color="#ffffff">
++<tr><td><font face="arial,sans-serif" color=black size=-1>This is one version of <a href="[$~URL~$]">
++<font color="blue">[URL-DISP]</font></a> from your personal <a href="http://desktop.google.com/webcache.html"><font color=blue>cache</font></a>.<br>
++The page may have changed since that time. Click here for the <a href="[$~URL~$]"><font color="blue">current page</font></a>.<br>
++Since this page is stored on your computer, publicly linking to this page will not work.[$~EXTRA~$]<br><br>
++<font size="-2"><i>Google may not be affiliated with the authors of this page nor responsible for its content. This page may be protected by copyright.</i></font>
++</td>
++</tr></table></td></tr></table>
++<style>
++.hl { color:black; background-color:#ffff88}
++</style>
++<script>
++[$~HIGHLIGHT_SCRIPT~$]
++window.onerror=new Function(';');
++</script>
++<hr id=gg_1>
++</body>
+\ No newline at end of file
+diff --git a/tools/grit/grit/testdata/cache_prefix_file.html b/tools/grit/grit/testdata/cache_prefix_file.html
+new file mode 100644
+index 0000000000..f3eb8e0f11
+--- /dev/null
++++ b/tools/grit/grit/testdata/cache_prefix_file.html
+@@ -0,0 +1,25 @@
++<head>
++<meta http-equiv="cache-control" content="no-cache">
++<meta http-equiv="pragma" content="no-cache">
++<meta http-equiv="expires" content="-1"></head>
++<body onload="[ONLOAD]">
++<table width="100%" border=1>
++<tr><td>
++<table cellspacing=0 cellpadding=10 width="100%" bgcolor=#ffffff border=1 color="#ffffff">
++<tr><td><font face=arial,sans-serif color=black size=-1>This is one version of <a href="[$~URL~$]"><font color=blue>[URL-DISP]</font></a>
++from your personal <a href="http://desktop.google.com/filecache.html"><font color=blue>cache</font></a>.<br>
++The file may have changed since that time. Click here for the <a href="[$~URL~$]"><font color=blue>current file</font></a>.<br>
++Since this file is stored on your computer, publicly linking to it will not work.[$~EXTRA~$]<br><br>
++<font size="-2"><i>Google may not be affiliated with the authors of this page nor responsible for its content. This page may be protected by copyright.</i></font>
++</td></tr>
++</table>
++</td></tr></table>
++<style>
++.hl { color:black; background-color:#ffff88}
++</style>
++<script>
++[$~HIGHLIGHT_SCRIPT~$]
++window.onerror=new Function(';');
++</script>
++<hr id=gg_1>
++</body>
+\ No newline at end of file
+diff --git a/tools/grit/grit/testdata/chat_result.html b/tools/grit/grit/testdata/chat_result.html
+new file mode 100644
+index 0000000000..318078bc3d
+--- /dev/null
++++ b/tools/grit/grit/testdata/chat_result.html
+@@ -0,0 +1,24 @@
++[HEADER]
++[CHROME]
++<table border=0 cellpadding=2 cellspacing=2>
++<tr><td>[$~STARTCHAT~$]</td></tr>
++</table>
++<blockquote id=gg_1>
++<table bgcolor=#f0f8ff width=80% cellpadding=5><tr><td>
++<img style="vertical-align:middle;" height=16 src="16x16_chat.gif" width=16> &nbsp; <b>[$~TITLE~$]</b>
++<font size=-1><br><br>Participants: [USERNAME], [BUDDYNAME]<br>
++Date: [TIME]</font></td></tr></table>
++<br id=contents>
++<label>[CONTENTS]</label>
++</blockquote>
++<table border=0 cellpadding=2 cellspacing=2>
++<tr><td>[$~STARTCHAT~$]</td></tr>
++</table>
++<style>
++.hl { color:black; background-color:#ffff88}
++</style>
++<script>
++[$~HIGHLIGHT_SCRIPT~$]
++[ONLOAD]
++</script>
++[FOOTER]
+diff --git a/tools/grit/grit/testdata/chrome/app/generated_resources.grd b/tools/grit/grit/testdata/chrome/app/generated_resources.grd
+new file mode 100644
+index 0000000000..c2efb77fd8
+--- /dev/null
++++ b/tools/grit/grit/testdata/chrome/app/generated_resources.grd
+@@ -0,0 +1,199 @@
++<?xml version="1.0" encoding="UTF-8"?>
++
++<!--
++This file contains definitions of resources that will be translated for each
++locale. The variables is_win, is_macosx, is_linux, and is_posix are available
++for making strings OS specific. Other platform defines such as use_titlecase
++are declared in build/common.gypi.
++-->
++
++<grit base_dir="." latest_public_release="0" current_release="1"
++ source_lang_id="en" enc_check="möl">
++ <outputs>
++ <output filename="grit/generated_resources.h" type="rc_header">
++ <emit emit_type='prepend'></emit>
++ </output>
++ <output filename="generated_resources_am.pak" type="data_package" lang="am" />
++ <output filename="generated_resources_ar.pak" type="data_package" lang="ar" />
++ <if expr="pp_ifdef('use_third_party_translations')">
++ <output filename="generated_resources_ast.pak" type="data_package" lang="ast" />
++ </if>
++ <output filename="generated_resources_bg.pak" type="data_package" lang="bg" />
++ <output filename="generated_resources_bn.pak" type="data_package" lang="bn" />
++ <if expr="pp_ifdef('use_third_party_translations')">
++ <output filename="generated_resources_bs.pak" type="data_package" lang="bs" />
++ </if>
++ <output filename="generated_resources_ca.pak" type="data_package" lang="ca" />
++ <if expr="pp_ifdef('use_third_party_translations')">
++ <output filename="generated_resources_ca@valencia.pak" type="data_package" lang="ca@valencia" />
++ </if>
++ <output filename="generated_resources_cs.pak" type="data_package" lang="cs" />
++ <output filename="generated_resources_da.pak" type="data_package" lang="da" />
++ <output filename="generated_resources_de.pak" type="data_package" lang="de" />
++ <output filename="generated_resources_el.pak" type="data_package" lang="el" />
++ <if expr="pp_ifdef('use_third_party_translations')">
++ <output filename="generated_resources_en-AU.pak" type="data_package" lang="en-AU" />
++ </if>
++ <output filename="generated_resources_en-GB.pak" type="data_package" lang="en-GB" />
++ <output filename="generated_resources_en-US.pak" type="data_package" lang="en" />
++ <if expr="pp_ifdef('use_third_party_translations')">
++ <output filename="generated_resources_eo.pak" type="data_package" lang="eo" />
++ </if>
++ <output filename="generated_resources_es.pak" type="data_package" lang="es" />
++ <output filename="generated_resources_es-419.pak" type="data_package" lang="es-419" />
++ <output filename="generated_resources_et.pak" type="data_package" lang="et" />
++ <if expr="pp_ifdef('use_third_party_translations')">
++ <output filename="generated_resources_eu.pak" type="data_package" lang="eu" />
++ </if>
++ <output filename="generated_resources_fa.pak" type="data_package" lang="fa" />
++ <output filename="generated_resources_fake-bidi.pak" type="data_package" lang="fake-bidi" />
++ <output filename="generated_resources_fi.pak" type="data_package" lang="fi" />
++ <output filename="generated_resources_fil.pak" type="data_package" lang="fil" />
++ <output filename="generated_resources_fr.pak" type="data_package" lang="fr" />
++ <if expr="pp_ifdef('use_third_party_translations')">
++ <output filename="generated_resources_gl.pak" type="data_package" lang="gl" />
++ </if>
++ <output filename="generated_resources_gu.pak" type="data_package" lang="gu" />
++ <output filename="generated_resources_he.pak" type="data_package" lang="he" />
++ <output filename="generated_resources_hi.pak" type="data_package" lang="hi" />
++ <output filename="generated_resources_hr.pak" type="data_package" lang="hr" />
++ <output filename="generated_resources_hu.pak" type="data_package" lang="hu" />
++ <if expr="pp_ifdef('use_third_party_translations')">
++ <output filename="generated_resources_hy.pak" type="data_package" lang="hy" />
++ <output filename="generated_resources_ia.pak" type="data_package" lang="ia" />
++ </if>
++ <output filename="generated_resources_id.pak" type="data_package" lang="id" />
++ <output filename="generated_resources_it.pak" type="data_package" lang="it" />
++ <output filename="generated_resources_ja.pak" type="data_package" lang="ja" />
++ <if expr="pp_ifdef('use_third_party_translations')">
++ <output filename="generated_resources_ka.pak" type="data_package" lang="ka" />
++ </if>
++ <output filename="generated_resources_kn.pak" type="data_package" lang="kn" />
++ <output filename="generated_resources_ko.pak" type="data_package" lang="ko" />
++ <if expr="pp_ifdef('use_third_party_translations')">
++ <output filename="generated_resources_ku.pak" type="data_package" lang="ku" />
++ <output filename="generated_resources_kw.pak" type="data_package" lang="kw" />
++ </if>
++ <output filename="generated_resources_lt.pak" type="data_package" lang="lt" />
++ <output filename="generated_resources_lv.pak" type="data_package" lang="lv" />
++ <output filename="generated_resources_ml.pak" type="data_package" lang="ml" />
++ <output filename="generated_resources_mr.pak" type="data_package" lang="mr" />
++ <if expr="pp_ifdef('use_third_party_translations')">
++ <output filename="generated_resources_ms.pak" type="data_package" lang="ms" />
++ </if>
++ <output filename="generated_resources_nl.pak" type="data_package" lang="nl" />
++ <!-- The translation console uses 'no' for Norwegian Bokmål. It should
++ be 'nb'. -->
++ <output filename="generated_resources_nb.pak" type="data_package" lang="no" />
++ <output filename="generated_resources_pl.pak" type="data_package" lang="pl" />
++ <output filename="generated_resources_pt-BR.pak" type="data_package" lang="pt-BR" />
++ <output filename="generated_resources_pt-PT.pak" type="data_package" lang="pt-PT" />
++ <output filename="generated_resources_ro.pak" type="data_package" lang="ro" />
++ <output filename="generated_resources_ru.pak" type="data_package" lang="ru" />
++ <output filename="generated_resources_sk.pak" type="data_package" lang="sk" />
++ <output filename="generated_resources_sl.pak" type="data_package" lang="sl" />
++ <output filename="generated_resources_sr.pak" type="data_package" lang="sr" />
++ <output filename="generated_resources_sv.pak" type="data_package" lang="sv" />
++ <output filename="generated_resources_sw.pak" type="data_package" lang="sw" />
++ <output filename="generated_resources_ta.pak" type="data_package" lang="ta" />
++ <output filename="generated_resources_te.pak" type="data_package" lang="te" />
++ <output filename="generated_resources_th.pak" type="data_package" lang="th" />
++ <output filename="generated_resources_tr.pak" type="data_package" lang="tr" />
++ <if expr="pp_ifdef('use_third_party_translations')">
++ <output filename="generated_resources_ug.pak" type="data_package" lang="ug" />
++ </if>
++ <output filename="generated_resources_uk.pak" type="data_package" lang="uk" />
++ <output filename="generated_resources_vi.pak" type="data_package" lang="vi" />
++ <output filename="generated_resources_zh-CN.pak" type="data_package" lang="zh-CN" />
++ <output filename="generated_resources_zh-TW.pak" type="data_package" lang="zh-TW" />
++ </outputs>
++ <translations>
++ <file path="resources/generated_resources_am.xtb" lang="am" />
++ <file path="resources/generated_resources_ar.xtb" lang="ar" />
++ <file path="../../third_party/launchpad_translations/generated_resources_ast.xtb" lang="ast" />
++ <file path="resources/generated_resources_bg.xtb" lang="bg" />
++ <file path="resources/generated_resources_bn.xtb" lang="bn" />
++ <file path="../../third_party/launchpad_translations/generated_resources_bs.xtb" lang="bs" />
++ <file path="resources/generated_resources_ca.xtb" lang="ca" />
++ <file path="../../third_party/launchpad_translations/generated_resources_ca-valencia.xtb" lang="ca@valencia" />
++ <file path="resources/generated_resources_cs.xtb" lang="cs" />
++ <file path="resources/generated_resources_da.xtb" lang="da" />
++ <file path="resources/generated_resources_de.xtb" lang="de" />
++ <file path="resources/generated_resources_el.xtb" lang="el" />
++ <file path="../../third_party/launchpad_translations/generated_resources_en-AU.xtb" lang="en-AU" />
++ <file path="resources/generated_resources_en-GB.xtb" lang="en-GB" />
++ <file path="../../third_party/launchpad_translations/generated_resources_eo.xtb" lang="eo" />
++ <file path="resources/generated_resources_es.xtb" lang="es" />
++ <file path="resources/generated_resources_es-419.xtb" lang="es-419" />
++ <file path="resources/generated_resources_et.xtb" lang="et" />
++ <file path="../../third_party/launchpad_translations/generated_resources_eu.xtb" lang="eu" />
++ <file path="resources/generated_resources_fa.xtb" lang="fa" />
++ <file path="resources/generated_resources_fi.xtb" lang="fi" />
++ <file path="resources/generated_resources_fil.xtb" lang="fil" />
++ <file path="resources/generated_resources_fr.xtb" lang="fr" />
++ <file path="../../third_party/launchpad_translations/generated_resources_gl.xtb" lang="gl" />
++ <file path="resources/generated_resources_gu.xtb" lang="gu" />
++ <file path="resources/generated_resources_hi.xtb" lang="hi" />
++ <file path="resources/generated_resources_hr.xtb" lang="hr" />
++ <file path="resources/generated_resources_hu.xtb" lang="hu" />
++ <file path="../../third_party/launchpad_translations/generated_resources_hy.xtb" lang="hy" />
++ <file path="../../third_party/launchpad_translations/generated_resources_ia.xtb" lang="ia" />
++ <file path="resources/generated_resources_id.xtb" lang="id" />
++ <file path="resources/generated_resources_it.xtb" lang="it" />
++ <!-- The translation console uses 'iw' for Hebrew, but we use 'he'. -->
++ <file path="resources/generated_resources_iw.xtb" lang="he" />
++ <file path="resources/generated_resources_ja.xtb" lang="ja" />
++ <file path="../../third_party/launchpad_translations/generated_resources_ka.xtb" lang="ka" />
++ <file path="resources/generated_resources_kn.xtb" lang="kn" />
++ <file path="resources/generated_resources_ko.xtb" lang="ko" />
++ <file path="../../third_party/launchpad_translations/generated_resources_ku.xtb" lang="ku" />
++ <file path="../../third_party/launchpad_translations/generated_resources_kw.xtb" lang="kw" />
++ <file path="resources/generated_resources_lt.xtb" lang="lt" />
++ <file path="resources/generated_resources_lv.xtb" lang="lv" />
++ <file path="resources/generated_resources_ml.xtb" lang="ml" />
++ <file path="resources/generated_resources_mr.xtb" lang="mr" />
++ <file path="../../third_party/launchpad_translations/generated_resources_ms.xtb" lang="ms" />
++ <file path="resources/generated_resources_nl.xtb" lang="nl" />
++ <file path="resources/generated_resources_no.xtb" lang="no" />
++ <file path="resources/generated_resources_pl.xtb" lang="pl" />
++ <file path="resources/generated_resources_pt-BR.xtb" lang="pt-BR" />
++ <file path="resources/generated_resources_pt-PT.xtb" lang="pt-PT" />
++ <file path="resources/generated_resources_ro.xtb" lang="ro" />
++ <file path="resources/generated_resources_ru.xtb" lang="ru" />
++ <file path="resources/generated_resources_sk.xtb" lang="sk" />
++ <file path="resources/generated_resources_sl.xtb" lang="sl" />
++ <file path="resources/generated_resources_sr.xtb" lang="sr" />
++ <file path="resources/generated_resources_sv.xtb" lang="sv" />
++ <file path="resources/generated_resources_sw.xtb" lang="sw" />
++ <file path="resources/generated_resources_ta.xtb" lang="ta" />
++ <file path="resources/generated_resources_te.xtb" lang="te" />
++ <file path="resources/generated_resources_th.xtb" lang="th" />
++ <file path="resources/generated_resources_tr.xtb" lang="tr" />
++ <file path="../../third_party/launchpad_translations/generated_resources_ug.xtb" lang="ug" />
++ <file path="resources/generated_resources_uk.xtb" lang="uk" />
++ <file path="resources/generated_resources_vi.xtb" lang="vi" />
++ <file path="resources/generated_resources_zh-CN.xtb" lang="zh-CN" />
++ <file path="resources/generated_resources_zh-TW.xtb" lang="zh-TW" />
++ </translations>
++ <release seq="1" allow_pseudo="false">
++ <messages fallback_to_english="true">
++ <!-- TODO add all of your "string table" messages here. Remember to
++ change nontranslateable parts of the messages into placeholders (using the
++ <ph> element). You can also use the 'grit add' tool to help you identify
++ nontranslateable parts and create placeholders for them. -->
++ <message name="IDS_BACKGROUND_APP_INSTALLED_BALLOON_TITLE" desc="The title of the balloon that is displayed when a background app is installed">
++ New background app installed
++ </message>
++ <message name="IDS_BACKGROUND_APP_INSTALLED_BALLOON_BODY" desc="The contents of the balloon that is displayed when a background app is installed">
++ <ph name="APP_NAME">$1<ex>Background App</ex></ph> will launch at system startup and continue to run in the background even once you've closed all other <ph name="PRODUCT_NAME">$2<ex>Google Chrome</ex></ph> windows.
++ </message>
++ </messages>
++ <structures fallback_to_english="true">
++ <!-- Make sure these stay in sync with the structures in generated_resources.grd. -->
++ <structure name="IDD_CHROME_FRAME_FIND_DIALOG" file="cf_resources.rc" type="dialog" >
++ </structure>
++ <structure name="IDD_CHROME_FRAME_READY_PROMPT" file="cf_resources.rc" type="dialog" >
++ </structure>
++ </structures>
++ </release>
++</grit>
+diff --git a/tools/grit/grit/testdata/chrome_html.html b/tools/grit/grit/testdata/chrome_html.html
new file mode 100644
-index 0000000000..301449c4ac
+index 0000000000..7f7633c5cf
--- /dev/null
-+++ b/tools/clang/plugins/tests/missing_ctor.txt
++++ b/tools/grit/grit/testdata/chrome_html.html
@@ -0,0 +1,6 @@
-+In file included from missing_ctor.cpp:5:
-+./missing_ctor.h:11:1: warning: [chromium-style] Complex class/struct needs an explicit out-of-line constructor.
-+class MissingCtorsArentOKInHeader {
-+^
-+./missing_ctor.h:11:1: warning: [chromium-style] Complex class/struct needs an explicit out-of-line destructor.
-+2 warnings generated.
-diff --git a/tools/clang/plugins/tests/nested_class_inline_ctor.cpp b/tools/clang/plugins/tests/nested_class_inline_ctor.cpp
++<include src="included_sample.html">
++<style type="text/css">
++#image {
++ content: url('chrome://theme/IDR_SOME_FILE');
++}
++</style>
+diff --git a/tools/grit/grit/testdata/default_100_percent/a.png b/tools/grit/grit/testdata/default_100_percent/a.png
+new file mode 100644
+index 0000000000000000000000000000000000000000..5d5089038ca71172e95db9e7aae1e1fa5cebd505
+GIT binary patch
+literal 159
+zcmeAS@N?(olHy`uVBq!ia0vp^oFL4>0wld=oSO}#(mY)pLnNjq|2Y3)zGGzYPN&L+
+zMSC}CcCfp=Dtxv4%6W%G#Q=|R|L;6pCCLUWO)Z<5eoL%TkDTw=s4X!^d(Qa<2khAN
+zZPy!XToBAic1Ss}vcWiD27B3&`Zj^H6CO>7R1{ToQ;=ggdEYbV=IISvfHpFCy85}S
+Ib4q9e0O9jEh5!Hn
+
+literal 0
+HcmV?d00001
+
+diff --git a/tools/grit/grit/testdata/default_100_percent/b.png b/tools/grit/grit/testdata/default_100_percent/b.png
+new file mode 100644
+index 0000000000..6178079822
+--- /dev/null
++++ b/tools/grit/grit/testdata/default_100_percent/b.png
+@@ -0,0 +1 @@
++b
+diff --git a/tools/grit/grit/testdata/del_footer.html b/tools/grit/grit/testdata/del_footer.html
+new file mode 100644
+index 0000000000..4e19950bfc
+--- /dev/null
++++ b/tools/grit/grit/testdata/del_footer.html
+@@ -0,0 +1,8 @@
++<table cellspacing=0 cellpadding=2 width="100%" border=0>
++<tr bgcolor=#EFEFEF><td><font size=-1>&nbsp;<b>Remove</b> checked results and <b>return to search</b>.</font></td>
++<td align=right><font size=-1><a onClick='checkall(1)' href="#">Check all</a> - <a onClick='checkall(0)' href="#">Uncheck all</a>&nbsp;&nbsp;</font></td>
++<td align=right width=1% nowrap><font size=-1>
++<input onclick=deleting() type=submit value="Remove checked results" name=submit2>
++</font></td></tr></table>
++<center><br><font size=-1>[$~BOTTOMLINE~$] - &copy;2005 Google </font></center>
++</body></html>
+diff --git a/tools/grit/grit/testdata/del_header.html b/tools/grit/grit/testdata/del_header.html
+new file mode 100644
+index 0000000000..72bc6756eb
+--- /dev/null
++++ b/tools/grit/grit/testdata/del_header.html
+@@ -0,0 +1,60 @@
++<body bgcolor="#ffffff" topmargin="2" marginheight="2">
++<table cellSpacing="2" cellPadding="0" width="100%" border="0">
++<form action='[$~DELETE~$]' method="post" name="delform">
++<input name="redir" type="hidden" value="[REDIR]">
++<script>
++<!--
++function deleting() {
++f=document.getElementsByName("del");
++var num = 0;
++if (f.length)
++ for(i=0;i<f.length; i++)
++ if(f[i].checked) num++;
++ if (num == 1) alert("One checked result has been removed");
++ else if (num > 1) alert(num + " checked results have been removed");
++ else alert("No results were checked, so no results have been removed");
++}
++function checkall(v) {
++ f=document.getElementsByName("del");
++ if (f.length)
++ for(i=0;i<f.length; i++)
++ f[i].checked=v;
++}
++//-->
++</script>
++<tr>
++<td vAlign="top" width="1%"><A href='[$~HOMEPAGE~$]'> <img alt="Go to Google Desktop Search" width="150" height="55" src="/logo3.gif" border="0" vspace="12"></A></td>
++<td>&nbsp;</td>
++<td noWrap>
++ <table cellSpacing="0" cellPadding="0" width="100%" border="0">
++ <tr>
++ <td bgColor="#DD0000"><img height="1" alt="" width="1"></td>
++ </tr>
++ </table>
++ <table cellSpacing="0" cellPadding="0" width="100%" border="0">
++ <tr>
++ <td noWrap bgColor="#efefef"><font size="+1"><b>&nbsp;Remove Specific Items</b></font></td>
++ <td noWrap align="right" bgColor="#efefef"><font size="-1"><a href="http://desktop.google.com/remove.html">Help</a>&nbsp;&nbsp;</font></td>
++ </tr>
++ </table>
++</td>
++</tr>
++</table>
++<table cellSpacing="0" cellPadding="2" width="100%" border="0">
++<tr bgColor="#EFEFEF">
++<td><font size="-1">&nbsp;<B>Remove</B> checked results and <B>return to search</B>.</font></td>
++<td align="right"><font size="-1"><a onClick='checkall(1)' href="#">Check all</a> - <a onClick='checkall(0)' href="#">
++Uncheck all</a>&nbsp;&nbsp;</font></td>
++<td align="right" width="1%" nowrap><font size="-1"><input onclick="deleting()" type="submit" value="Remove checked results" name="submit2"></font></td>
++</tr>
++</table>
++<br>
++<table cellspacing="0" cellpadding="2" width="100%" border="0">
++<tr>
++<td colSpan="3" bgcolor="#FFFFFF" style="border:solid; border-width:1px; border-color:#DD0000"><font size="-1">&nbsp;<b>Remove
++checked items from Google Desktop Search. Other copies of the same items will not be
++affected.<br>
++&nbsp;If you view the item again, it will be added back to Google Desktop Search.</b></font></td>
++</tr>
++</table>
++<br>
+\ No newline at end of file
+diff --git a/tools/grit/grit/testdata/deleted.html b/tools/grit/grit/testdata/deleted.html
+new file mode 100644
+index 0000000000..5ae5f355fa
+--- /dev/null
++++ b/tools/grit/grit/testdata/deleted.html
+@@ -0,0 +1,21 @@
++<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
++<html><head><title>Database Deleted</title>
++<meta http-equiv="content-type" content="text/html; charset=utf-8">
++<meta http-equiv="cache-control" content="no-cache">
++<meta http-equiv="pragma" content="no-cache">
++<meta http-equiv="expires" content="-1">
++<style>
++BODY,TD,A,P {FONT-FAMILY: arial,sans-serif}
++.q {COLOR: #0000cc}
++</style>
++</head>
++<BODY text=#000000 vLink=#551a8b aLink=#ff0000 link=#0000cc bgColor=#ffffff onload=sf()>
++<center>
++<TABLE cellSpacing=0 cellPadding=0 border=0>
++<tr><td><a href="[$~HOMEPAGE~$]"><IMG border=0 height=110 alt="Google Desktop Search" src="hp_logo.gif" width=276></a>
++</td></tr></table><BR>
++<center>The database has been deleted. Click <a href="[$~HOMEPAGE~$]">here</a> to continue.</center>
++</td></tr>
++</table>
++<br><FONT size=-1>[$~BOTTOMLINE~$]</font></p>
++<p><FONT size=-2>&copy;2005 Google</font></p></center></body></html>
+\ No newline at end of file
+diff --git a/tools/grit/grit/testdata/depfile.grd b/tools/grit/grit/testdata/depfile.grd
+new file mode 100644
+index 0000000000..e2f7191218
+--- /dev/null
++++ b/tools/grit/grit/testdata/depfile.grd
+@@ -0,0 +1,18 @@
++<?xml version="1.0" encoding="UTF-8"?> <!-- -*- XML -*- -->
++<grit
++ base_dir="."
++ latest_public_release="0"
++ current_release="1">
++ <outputs>
++ <output filename="default_100_percent.pak" lang="en" type="data_package" context="default_100_percent" />
++ <output filename="special_100_percent.pak" lang="en" type="data_package" context="special_100_percent" />
++ </outputs>
++ <release seq="1">
++ <structures fallback_to_low_resolution="true">
++ <if expr="False">
++ <part file="grit_part.grdp" />
++ </if>
++ <structure type="chrome_scaled_image" name="IDR_A" file="a.png" />
++ </structures>
++ </release>
++</grit>
+diff --git a/tools/grit/grit/testdata/details.html b/tools/grit/grit/testdata/details.html
+new file mode 100644
+index 0000000000..0ab0e2a90c
+--- /dev/null
++++ b/tools/grit/grit/testdata/details.html
+@@ -0,0 +1,10 @@
++[!]
++title Improve Google Desktop Search by Sending Non-Personal Information
++template
++bottomline
++hp_image
++
++<p><strong>This documentation is not yet available</strong></p>
++<center><br>
++<font size=-1>[$~BOTTOMLINE~$] - &copy;2005 Google </font>
++</center>
+diff --git a/tools/grit/grit/testdata/duplicate-name-input.xml b/tools/grit/grit/testdata/duplicate-name-input.xml
+new file mode 100644
+index 0000000000..cc4d1d65c5
+--- /dev/null
++++ b/tools/grit/grit/testdata/duplicate-name-input.xml
+@@ -0,0 +1,26 @@
++<?xml version="1.0" encoding="UTF-8"?>
++<grit base_dir="." latest_public_release="2" current_release="3" source_lang_id="en-US">
++ <release seq="3">
++ <messages>
++ <message name="IDS_GREETING" desc="Printed to greet the currently logged in user">
++ Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, how are you doing today?
++ </message>
++ </messages>
++ <structures>
++ <!-- Duplicate name here -->
++ <structure type="version" name="IDS_GREETING" file="rc_files/bla.rc" />
++ </structures>
++ </release>
++ <translations>
++ <file path="figs_nl_translations.xml" />
++ <file path="cjk_translations.xml" />
++ </translations>
++ <outputs>
++ <output filename="resource.h" type="rc_header" />
++ <output filename="resource_en.rc" type="rc_all" lang="en-US" />
++ <output filename="resource_fr.rc" type="rc_all" lang="fr-FR" />
++ <output filename="resource_it.rc" type="rc_translateable" lang="it-IT" />
++ <output filename="resource_zh_cn.rc" type="rc_translateable" lang="zh-CN" />
++ <output filename="nontranslateable.rc" type="rc_nontranslateable" />
++ </outputs>
++</grit>
+diff --git a/tools/grit/grit/testdata/email_result.html b/tools/grit/grit/testdata/email_result.html
+new file mode 100644
+index 0000000000..8bb04b988c
+--- /dev/null
++++ b/tools/grit/grit/testdata/email_result.html
+@@ -0,0 +1,34 @@
++[HEADER]
++[CHROME]
++<table border=0 cellpadding=2 cellspacing=2 width='100%'>
++<tr><td><font size=-1>[CONV]
++<a href='[$~REPLY_URL~$]'>Reply</a> | <a href='[$~REPLYALL_URL~$]'>Reply&nbsp;to&nbsp;All</a>[$~FORWARD_URL~$] | <a href='mailto:'>Compose</a>[$~OUTLOOKVIEW~$]
++</font></td></tr>
++</table>
++<blockquote id=gg_1>
++<table bgcolor=#f0f8ff width=80% cellpadding=5><tr><td>
++<img style="vertical-align:middle;" height=16 src='/email.gif' width=16> &nbsp; <b>[SUBJECT]</b>
++<p><font size=-1>[FROM-DISP]
++[TO-DISP]
++[CC-DISP]
++[BCC-DISP]
++[REPLYTO-DISP]
++[DATE-DISP]
++[VIEW-DISP]
++[$~ATTACH~$]
++</font></td></tr></table>
++<p class=g><span style="width:500;"><font size=-1><label>[MESSAGE]</label></span></p>
++</font>
++</blockquote>
++<table border=0 cellpadding=2 cellspacing=2 width='100%'>
++<tr><td><font size=-1>[CONV]
++<a href='[$~REPLY_URL~$]'>Reply</a> | <a href='[$~REPLYALL_URL~$]'>Reply&nbsp;to&nbsp;All</a>[$~FORWARD_URL~$] | <a href='mailto:'>Compose</a>[$~OUTLOOKVIEW~$]
++</font></td></tr></table>
++<style>
++.hl { color:black; background-color:#ffff88}
++</style>
++<script>
++[$~HIGHLIGHT_SCRIPT~$]
++[ONLOAD]
++</script>
++[FOOTER]
+\ No newline at end of file
+diff --git a/tools/grit/grit/testdata/email_thread.html b/tools/grit/grit/testdata/email_thread.html
+new file mode 100644
+index 0000000000..3c7279b841
+--- /dev/null
++++ b/tools/grit/grit/testdata/email_thread.html
+@@ -0,0 +1,10 @@
++[HEADER]
++[CHROME]
++<blockquote [MAXWIDTH]>
++<b><img src=email.gif style="vertical-align:middle;" width=16 height=16> &nbsp; [SUBJECT]</b><br><br>
++<TABLE cellSpacing=0 cellPadding=3 border=0>
++[CONTENTS]
++</table>
++</blockquote>
++[NEXT_PREV]
++[FOOTER]
+\ No newline at end of file
+diff --git a/tools/grit/grit/testdata/error.html b/tools/grit/grit/testdata/error.html
+new file mode 100644
+index 0000000000..66875a234c
+--- /dev/null
++++ b/tools/grit/grit/testdata/error.html
+@@ -0,0 +1,8 @@
++[HEADER]
++[CHROME]
++<br>
++<blockquote>
++[ERROR]<br><br>
++If you think this is an error, please <a href="http://desktop.google.com/feedback.html?version=[VERSION]">contact us</a>.
++</blockquote>
++[FOOTER]
+\ No newline at end of file
+diff --git a/tools/grit/grit/testdata/explicit_web.html b/tools/grit/grit/testdata/explicit_web.html
+new file mode 100644
+index 0000000000..1424adc617
+--- /dev/null
++++ b/tools/grit/grit/testdata/explicit_web.html
+@@ -0,0 +1,11 @@
++[HEADER]
++<style>
++.image {BORDER: #0000cc 1px solid;}
++.imageh {BORDER: #0000cc 1px solid;}
++</style>
++[WEB_TOP_CHROME]
++[$~STATUS~$]
++[$~MESSAGE~$]
++[WEB_FILES]
++<br>[NEXT_PREV]
++[FOOTER]
+\ No newline at end of file
+diff --git a/tools/grit/grit/testdata/footer.html b/tools/grit/grit/testdata/footer.html
+new file mode 100644
+index 0000000000..3372d6afac
+--- /dev/null
++++ b/tools/grit/grit/testdata/footer.html
+@@ -0,0 +1,14 @@
++<center><br clear=all><br>
++<table cellspacing=0 cellpadding=0 width="100%" border=0><tr bgcolor=#3399CC><td align=middle height=1><img height=1 width=1></td></tr></table>
++<table cellspacing=0 cellpadding=0 width="100%" bgcolor=#e8f4f7 border=0>
++<tr bgcolor=#e8f4f7>
++<td><br>
++<table cellpadding=1 align=center border=0 cellspacing=0 bgcolor=#e8f4f7>
++<form action='[$~SEARCHURL~$]' method=get>
++<tr><td noWrap>[$~BOTTOM~$]</td></tr></form>
++</table><br>
++</td></tr></table>
++<table cellspacing=0 cellpadding=0 width="100%" border=0><tr bgcolor=#3399CC><td align=middle height=1><img height=1 width=1></td></tr></table><br>
++<font size=-1>[$~BOTTOMLINE~$] - &copy;2005 Google </font></center>
++[SCRIPT]
++</body></html>
+diff --git a/tools/grit/grit/testdata/generated_resources_fr.xtb b/tools/grit/grit/testdata/generated_resources_fr.xtb
+new file mode 100644
+index 0000000000..373c40feea
+--- /dev/null
++++ b/tools/grit/grit/testdata/generated_resources_fr.xtb
+@@ -0,0 +1,3079 @@
++<?xml version="1.0" ?>
++<!DOCTYPE translationbundle>
++<translationbundle lang="fr">
++<translation id="1525924600121678168">Salut!</translation>
++<translation id="5335090254790956485">Salut <ph name="USERNAME"/></translation>
++<translation id="6779164083355903755">Supprime&amp;r</translation>
++<translation id="6879617193011158416">Activer la barre de favoris</translation>
++<translation id="8130276680150879341">Déconnexion du réseau privé</translation>
++<translation id="1058418043520174283"><ph name="INDEX"/> sur <ph name="COUNT"/></translation>
++<translation id="4480627574828695486">Déconnecter ce compte...</translation>
++<translation id="7040807039050164757">&amp;Vérifier l'orthographe dans ce champ</translation>
++<translation id="778579833039460630">Aucune donnée reçue.</translation>
++<translation id="1852799913675865625">Une erreur s'est produite lors de la tentative de lecture du fichier : <ph name="ERROR_TEXT"/>.</translation>
++<translation id="3828924085048779000">Le mot de passe multiterme est obligatoire.</translation>
++<translation id="8265562484034134517">Importer les données d'un autre navigateur...</translation>
++<translation id="2709516037105925701">Saisie automatique</translation>
++<translation id="4857138207355690859">API P2P</translation>
++<translation id="250599269244456932">Exécuter automatiquement (recommandé)</translation>
++<translation id="3581034179710640788">Le certificat de sécurité du site a expiré !</translation>
++<translation id="2825758591930162672">Clé publique de l'objet</translation>
++<translation id="8275038454117074363">Importer</translation>
++<translation id="8418445294933751433">Afficher dan&amp;s un onglet</translation>
++<translation id="6985276906761169321">ID :</translation>
++<translation id="859285277496340001">Le certificat n'indique aucun mécanisme permettant de vérifier s'il a été révoqué.</translation>
++<translation id="2010799328026760191">Touches de modification...</translation>
++<translation id="3300394989536077382">Signé par :</translation>
++<translation id="654233263479157500">Utiliser un service Web pour résoudre les erreurs de navigation</translation>
++<translation id="4940047036413029306">Guillemet</translation>
++<translation id="1526811905352917883">Une nouvelle tentative de connexion avec SSL 3.0 a dû être effectuée. Cette opération indique généralement que le serveur utilise un logiciel très ancien et qu'il est susceptible de présenter d'autres problèmes de sécurité.</translation>
++<translation id="1497897566809397301">Autoriser le stockage des données locales (recommandé)</translation>
++<translation id="3275778913554317645">Ouvrir dans une fenêtre</translation>
++<translation id="4553117311324416101">Google pense qu'un logiciel malveillant pourrait être installé sur votre ordinateur si vous continuez. Nous vous conseillons de ne pas continuer, même si vous avez déjà consulté ce site auparavant ou si vous avez confiance en celui-ci. Il se peut qu'il ait été piraté récemment. Réessayez demain ou utilisez un autre site.</translation>
++<translation id="509988127256758334">&amp;Rechercher :</translation>
++<translation id="1420684932347524586">Échec de génération de clé privée RSA aléatoire</translation>
++<translation id="2501173422421700905">Certificat en attente</translation>
++<translation id="2313634973119803790">Technologie réseau :</translation>
++<translation id="2382901536325590843">Le certificat du serveur ne figure pas dans le DNS.</translation>
++<translation id="2833791489321462313">Demander le mot de passe au retour de veille</translation>
++<translation id="3850258314292525915">Désactiver la synchronisation</translation>
++<translation id="2721561274224027017">Base de données indexée</translation>
++<translation id="8208216423136871611">Ne pas enregistrer</translation>
++<translation id="684587995079587263"><ph name="PRODUCT_NAME"/> synchronise vos données avec votre compte Google en toute sécurité. Synchronisez toutes vos données ou personnalisez les types de données synchronisées et les options de chiffrement.</translation>
++<translation id="4405141258442788789">Le délai imparti à l'opération est dépassé.</translation>
++<translation id="5048179823246820836">Nordique</translation>
++<translation id="1763046204212875858">Créer des raccourcis vers des applications</translation>
++<translation id="2105006017282194539">Pas encore chargé</translation>
++<translation id="524759338601046922">Confirmer le nouveau code PIN :</translation>
++<translation id="688547603556380205">L2TP/IPSec + Certificat utilisateur</translation>
++<translation id="777702478322588152">Préfecture</translation>
++<translation id="6562437808764959486">Extraction de l'image de récupération...</translation>
++<translation id="561349411957324076">Terminé</translation>
++<translation id="4764776831041365478">Il se peut que la page Web à l'adresse <ph name="URL"/> soit temporairement inaccessible ou qu'elle ait été déplacée de façon permanente à une autre adresse Web.</translation>
++<translation id="6156863943908443225">Cache des scripts</translation>
++<translation id="4610656722473172270">Barre d'outils Google</translation>
++<translation id="151501797353681931">Importés depuis Safari</translation>
++<translation id="6706684875496318067">Le plug-in <ph name="PLUGIN_NAME"/> n'est pas autorisé.</translation>
++<translation id="586567932979200359">Vous exécutez <ph name="PRODUCT_NAME"/> à partir de son image disque. Si vous l'installez sur votre ordinateur, vous pourrez l'utiliser sans image disque et bénéficierez de mises à jour automatiques.</translation>
++<translation id="3775432569830822555">Certificat du serveur SSL</translation>
++<translation id="1829192082282182671">Z&amp;oom arrière</translation>
++<translation id="6102827823267795198">Indique si la suggestion du moteur de recherche doit être entrée immédiatement via la saisie semi-automatique lorsque la fonctionnalité Recherche instantanée est activée.</translation>
++<translation id="1467071896935429871">Mise à jour du système : <ph name="PERCENT"/> % téléchargés</translation>
++<translation id="7881267037441701396">Les informations d'identification associées au partage de vos imprimantes via <ph name="CLOUD_PRINT_NAME"/> sont arrivées à expiration. Cliquez ici pour saisir à nouveau votre nom d'utilisateur et votre mot de passe.</translation>
++<translation id="816055135686411707">Erreur de définition du paramètre de confiance du certificat</translation>
++<translation id="4714531393479055912"><ph name="PRODUCT_NAME"/> peut maintenant synchroniser vos mots de passe.</translation>
++<translation id="5704565838965461712">Sélectionnez le certificat à présenter pour l'identification :</translation>
++<translation id="2025632980034333559"><ph name="APP_NAME"/> a planté. Cliquez sur cette info-bulle pour actualiser l'extension.</translation>
++<translation id="4059593000330943833">Compatibilité expérimentale avec des méthodes Wi-Fi Extensible Authentication Protocol supplémentaires, telles que EAP-TLS et LEAP.</translation>
++<translation id="6322279351188361895">Échec de lecture de la clé privée</translation>
++<translation id="3781072658385678636">Les plug-ins suivants ont été bloqués sur cette page :</translation>
++<translation id="4428782877951507641">Configuration de la synchronisation</translation>
++<translation id="3648460724479383440">Case d'option cochée</translation>
++<translation id="4654488276758583406">Très petite</translation>
++<translation id="6647228709620733774">URL de révocation de l'autorité de certification Netscape</translation>
++<translation id="546411240573627095">Style de pavé numérique</translation>
++<translation id="7663002797281767775">Active les feuilles de style CSS 3D et la composition graphique haute performance des pages Web via le processeur graphique.</translation>
++<translation id="2972581237482394796">&amp;Rétablir</translation>
++<translation id="5895138241574237353">Redémarrer</translation>
++<translation id="1858072074757584559">La connexion n'est pas compressée.</translation>
++<translation id="528468243742722775">Fin</translation>
++<translation id="1723824996674794290">&amp;Nouvelle fenêtre</translation>
++<translation id="1313405956111467313">Configuration automatique du proxy</translation>
++<translation id="1589055389569595240">Afficher l'orthographe et la grammaire</translation>
++<translation id="4364779374839574930">Aucune imprimante n'a été trouvée. Veuillez en installer une.</translation>
++<translation id="7017587484910029005">Saisissez les caractères visibles dans l'image ci-dessous.</translation>
++<translation id="9013589315497579992">Certificat d'authentification de client SSL incorrect</translation>
++<translation id="8595062045771121608">Le certificat du serveur ou un certificat AC intermédiaire présenté au navigateur a été signé avec un algorithme de signature faible tel que RSA-MD2. D'après des études récentes menées par des informaticiens, les algorithmes de signature seraient plus faibles qu'on ne le pensait jusqu'alors. Aujourd'hui, ils sont très rarement utilisés par les sites Web jugés dignes de confiance. Ce certificat a peut-être été contrefait. Nous vous déconseillons vivement de continuer.</translation>
++<translation id="8666632926482119393">Rechercher le précédent</translation>
++<translation id="7567293639574541773">I&amp;nspecter l'élément</translation>
++<translation id="8392896330146417149">État d'itinérance :</translation>
++<translation id="6813971406343552491">&amp;Non</translation>
++<translation id="36224234498066874">Effacer les données de navigation...</translation>
++<translation id="3384773155383850738">Nombre maximal de suggestions</translation>
++<translation id="8331498498435985864">L'accessibilité est désactivée.</translation>
++<translation id="8530339740589765688">Sélectionner par domaine</translation>
++<translation id="8677212948402625567">Tout réduire...</translation>
++<translation id="7600965453749440009">Ne jamais traduire les pages rédigées en <ph name="LANGUAGE"/> </translation>
++<translation id="3208703785962634733">Non confirmé</translation>
++<translation id="6523841952727744497">Avant de vous connecter, démarrez une session en tant qu'invité afin d'activer le réseau <ph name="NETWORK_ID"/>.</translation>
++<translation id="7450044767321666434">La gravure de l'image est terminée.</translation>
++<translation id="2653266418988778031">Si vous supprimez le certificat d'une autorité de certification, votre navigateur ne fera plus confiance aux certificats émis par cette autorité de certification.</translation>
++<translation id="298068999958468740">Synchronisez toutes les données de cet ordinateur ou sélectionnez celles que vous souhaitez synchroniser.</translation>
++<translation id="5341849548509163798"><ph name="NUMBER_MANY"/> hours ago</translation>
++<translation id="4422428420715047158">Domaine :</translation>
++<translation id="3602290021589620013">Aperçu</translation>
++<translation id="7516602544578411747">Associe chaque fenêtre du navigateur à un profil et ajoute une option de sélection des profils en haut à droite. Chaque profil possède ses propres favoris, extensions, applications, etc.</translation>
++<translation id="7082055294850503883">Ignorer le verrouillage des majuscules et saisir des minuscules par défaut</translation>
++<translation id="1800124151523561876">Aucune parole détectée</translation>
++<translation id="7814266509351532385">Changer de moteur de recherche par défaut</translation>
++<translation id="5376169624176189338">Cliquer pour revenir en arrière, maintenir pour voir l'historique</translation>
++<translation id="6310545596129886942"><ph name="NUMBER_FEW"/> secondes restantes</translation>
++<translation id="9181716872983600413">Unicode</translation>
++<translation id="1383861834909034572">Ouverture à la fin du téléchargement</translation>
++<translation id="5727728807527375859">Les extensions, les applications et les thèmes peuvent endommager votre ordinateur. Voulez-vous vraiment continuer ?</translation>
++<translation id="3857272004253733895">Schéma du pinyin double</translation>
++<translation id="1636842079139032947">Déconnecter ce compte...</translation>
++<translation id="6721972322305477112">&amp;Fichier</translation>
++<translation id="1076818208934827215">Microsoft Internet Explorer</translation>
++<translation id="9056810968620647706">Aucune correspondance trouvée</translation>
++<translation id="1901494098092085382">État de votre commentaire</translation>
++<translation id="2861301611394761800">Mise à jour terminée. Veuillez redémarrer le système.</translation>
++<translation id="2231238007119540260">Lorsque vous supprimez un certificat de serveur, vous rétablissez les contrôles de sécurité habituels du serveur et un certificat valide lui est demandé.</translation>
++<translation id="5463582782056205887">Essayez d'ajouter
++ <ph name="PRODUCT_NAME"/>
++ aux programmes autorisés dans les paramètres de votre pare-feu ou de votre antivirus. S'il
++ est déjà autorisé, tentez de le supprimer de la liste et de l'ajouter à nouveau à
++ la liste des programmes autorisés.</translation>
++<translation id="7624154074265342755">Réseaux sans fil</translation>
++<translation id="3315158641124845231">Masquer <ph name="PRODUCT_NAME"/></translation>
++<translation id="3496213124478423963">Zoom arrière</translation>
++<translation id="2296019197782308739">Méthode EAP :</translation>
++<translation id="42981349822642051">Développer</translation>
++<translation id="4013794286379809233">Veuillez vous connecter</translation>
++<translation id="7693221960936265065">de n'importe quand</translation>
++<translation id="1763138995382273070">Désactiver la validation des formulaires interactifs HTML5</translation>
++<translation id="4920887663447894854">Le suivi de votre position géographique sur cette page a été bloqué pour les sites suivants :</translation>
++<translation id="7690346658388844119">La gravure de l'image a été interrompue.</translation>
++<translation id="8133676275609324831">&amp;Afficher dans le dossier</translation>
++<translation id="645705751491738698">Continuer à bloquer JavaScript</translation>
++<translation id="4780321648949301421">Enregistrer la page sous...</translation>
++<translation id="9154072353677278078">Le serveur <ph name="DOMAIN"/> à l'adresse <ph name="REALM"/> requiert un nom d'utilisateur et un mot de passe.</translation>
++<translation id="2551191967044410069">Exceptions de géolocalisation</translation>
++<translation id="4092066334306401966">13px</translation>
++<translation id="8178665534778830238">Contenu :</translation>
++<translation id="153384433402665971">Le plug-in <ph name="PLUGIN_NAME"/> a été bloqué, car il n'est plus à jour.</translation>
++<translation id="2610260699262139870">Taille ré&amp;elle</translation>
++<translation id="4535734014498033861">Échec de la connexion au serveur proxy.</translation>
++<translation id="558170650521898289">Vérification de pilote matériel Microsoft Windows</translation>
++<translation id="98515147261107953">Paysage</translation>
++<translation id="8974161578568356045">Détecter automatiquement</translation>
++<translation id="1818606096021558659">Page</translation>
++<translation id="5388588172257446328">Nom d'utilisateur :</translation>
++<translation id="1657406563541664238">Nous aider à améliorer <ph name="PRODUCT_NAME"/> en envoyant automatiquement les statistiques d'utilisation et les rapports d'erreur à Google</translation>
++<translation id="7982789257301363584">Réseau</translation>
++<translation id="8528962588711550376">Connexion en cours</translation>
++<translation id="2336228925368920074">Ajouter tous les onglets aux favoris...</translation>
++<translation id="4985312428111449076">Onglets ou fenêtres</translation>
++<translation id="7481475534986701730">Sites récemment consultés</translation>
++<translation id="4260722247480053581">Ouvrir dans une fenêtre de navigation privée</translation>
++<translation id="8503758797520866434">Préférences de saisie automatique...</translation>
++<translation id="2757031529886297178">Compteur d'images par seconde</translation>
++<translation id="6657585470893396449">Mot de passe</translation>
++<translation id="7881483672146086348">Afficher le compte</translation>
++<translation id="1776883657531386793"><ph name="OID"/> : <ph name="INFO"/></translation>
++<translation id="1510030919967934016">Le suivi de votre position géographique a été bloqué pour cette page.</translation>
++<translation id="4640525840053037973">Connexion à l'aide de votre compte Google</translation>
++<translation id="5255315797444241226">Le mot de passe multiterme entré est incorrect.</translation>
++<translation id="6242054993434749861">télécopie : #<ph name="FAX"/></translation>
++<translation id="762917759028004464">Le navigateur par défaut est actuellement <ph name="BROWSER_NAME"/>.</translation>
++<translation id="9213479837033539041"><ph name="NUMBER_MANY"/> secondes restantes</translation>
++<translation id="300544934591011246">Mot de passe précédent</translation>
++<translation id="5078796286268621944">Code PIN incorrect</translation>
++<translation id="989988560359834682">Modifier l'adresse</translation>
++<translation id="8487678622945914333">Zoom avant</translation>
++<translation id="2972557485845626008">Micrologiciel</translation>
++<translation id="735327918767574393">Une erreur s'est produite lors de l'affichage de cette page Web. Pour continuer, actualisez cette page ou ouvrez-en une autre.</translation>
++<translation id="8028060951694135607">Récupération de clé Microsoft</translation>
++<translation id="9187657844611842955">recto verso</translation>
++<translation id="6391832066170725637">Fichier ou répertoire introuvable</translation>
++<translation id="4694445829210540512">Aucun forfait de données <ph name="NETWORK"/> actif</translation>
++<translation id="5494920125229734069">Tout sélectionner</translation>
++<translation id="2857834222104759979">Le fichier manifeste est incorrect.</translation>
++<translation id="7931071620596053769">Les pages suivantes ne répondent plus. Vous pouvez attendre qu'elles soient de nouveau accessibles ou les supprimer.</translation>
++<translation id="1209866192426315618"><ph name="NUMBER_DEFAULT"/> minutes restantes</translation>
++<translation id="7938958445268990899">Le certificat du serveur n'est pas encore valide.</translation>
++<translation id="4569998400745857585">Menu contenant des extensions masquées</translation>
++<translation id="4081383687659939437">Enregistrer les infos</translation>
++<translation id="5786805320574273267">Configuration de l'accès à distance à cet ordinateur.</translation>
++<translation id="1801827354178857021">Point</translation>
++<translation id="2179052183774520942">Ajouter un moteur de recherche</translation>
++<translation id="5498951625591520696">Impossible d'atteindre le serveur.</translation>
++<translation id="2956948609882871496">Importer mes favoris...</translation>
++<translation id="1621207256975573490">Enregistrer le &amp;cadre sous...</translation>
++<translation id="4681260323810445443">Vous n'êtes pas autorisé à accéder à la page Web <ph name="URL"/>. Votre connexion peut être requise.</translation>
++<translation id="2176444992480806665">Envoyer la capture d'écran du dernier onglet actif</translation>
++<translation id="1165039591588034296">Erreur</translation>
++<translation id="2064942105849061141">Utiliser le thème GTK+</translation>
++<translation id="2278562042389100163">Ouvrir une fenêtre du navigateur</translation>
++<translation id="5246282308050205996"><ph name="APP_NAME"/> a planté. Cliquez sur cette info-bulle pour redémarrer l'application.</translation>
++<translation id="9218430445555521422">Définir comme navigateur par défaut</translation>
++<translation id="5027550639139316293">Certificat de courrier électronique</translation>
++<translation id="938582441709398163">Clavier en superposition</translation>
++<translation id="427208986916971462">La connexion est compressée avec <ph name="COMPRESSION"/>.</translation>
++<translation id="4589279373639964403">Exporter mes favoris...</translation>
++<translation id="8876215549894133151">Format :</translation>
++<translation id="5234764350956374838">Ignorer</translation>
++<translation id="40027638859996362">Déplacer un mot</translation>
++<translation id="5463275305984126951">Index de <ph name="LOCATION"/></translation>
++<translation id="5154917547274118687">Mémoire</translation>
++<translation id="1493492096534259649">Impossible d'utiliser cette langue pour corriger l'orthographe.</translation>
++<translation id="6628463337424475685">Recherche <ph name="ENGINE"/></translation>
++<translation id="2502105862509471425">Ajouter une autre carte de paiement...</translation>
++<translation id="4037618776454394829">Envoyer la dernière capture d'écran enregistrée</translation>
++<translation id="8987670145726065238">Ce fichier contient du code malveillant. Voulez-vous vraiment continuer ?</translation>
++<translation id="182729337634291014">Erreur de synchronisation...</translation>
++<translation id="4465830120256509958">Clavier brésilien</translation>
++<translation id="2459861677908225199">Utiliser TLS 1.0</translation>
++<translation id="4792711294155034829">&amp;Signaler un problème...</translation>
++<translation id="5819484510464120153">Créer des raccourci&amp;s vers des applications...</translation>
++<translation id="6845180713465955339">Le certificat &quot;<ph name="CERTIFICATE_NAME"/>&quot; a été émis par :</translation>
++<translation id="7531238562312180404"><ph name="PRODUCT_NAME"/> ne contrôlant pas la façon dont les extensions gèrent vos données personnelles, toutes les extensions sont désactivées dans les fenêtres de navigation privée. Vous pouvez les réactiver individuellement dans le <ph name="BEGIN_LINK"/>gestionnaire des extensions<ph name="END_LINK"/>.</translation>
++<translation id="5667293444945855280">Logiciels malveillants</translation>
++<translation id="3435845180011337502">Mise en page ou mise en forme de la page</translation>
++<translation id="3838186299160040975">Acheter davantage...</translation>
++<translation id="6831043979455480757">Traduire</translation>
++<translation id="3587482841069643663">Tout</translation>
++<translation id="6698381487523150993">Créé :</translation>
++<translation id="4684748086689879921">Annuler l'importation</translation>
++<translation id="9130015405878219958">Le mode indiqué est incorrect.</translation>
++<translation id="6615807189585243369"><ph name="BURNT_AMOUNT"/> copié(s) sur <ph name="TOTAL_SIZE"/></translation>
++<translation id="8518425453349204360">L'accès à distance à cet ordinateur est activé pour <ph name="USER_EMAIL_ADDRESS"/>.</translation>
++<translation id="4950138595962845479">Options...</translation>
++<translation id="4653235815000740718">Un problème est survenu lors de la création du support de récupération du système d'exploitation. Le périphérique de stockage utilisé est introuvable.</translation>
++<translation id="5516565854418269276">Toujours &amp;afficher la barre de favoris</translation>
++<translation id="6426222199977479699">Erreur SSL</translation>
++<translation id="7104784605502674932">Confirmer les préférences de synchronisation</translation>
++<translation id="1788636309517085411">Utiliser les valeurs par défaut</translation>
++<translation id="1661867754829461514">Code secret manquant</translation>
++<translation id="7406714851119047430">L'accès à distance à cet ordinateur est désactivé.</translation>
++<translation id="8589311641140863898">API des extensions expérimentales</translation>
++<translation id="2804922931795102237">Inclure les informations système</translation>
++<translation id="869891660844655955">Date d'expiration</translation>
++<translation id="2178614541317717477">Autorité de certification compromise</translation>
++<translation id="4449935293120761385">À propos de la saisie automatique</translation>
++<translation id="4194570336751258953">Activer la fonction &quot;Taper pour cliquer&quot;</translation>
++<translation id="6066742401428748382">Accès à la page Web refusé</translation>
++<translation id="5111692334209731439">&amp;Gestionnaire de favoris</translation>
++<translation id="8295070100601117548">Erreur serveur</translation>
++<translation id="5661272705528507004">Cette carte SIM est désactivée et ne peut être utilisée. Veuillez demander à votre fournisseur de services de la remplacer.</translation>
++<translation id="443008484043213881">Outils</translation>
++<translation id="2529657954821696995">Clavier néerlandais</translation>
++<translation id="1128128132059598906">EAP-TTLS</translation>
++<translation id="6585234750898046415">Choisissez une image à associer à votre compte. Celle-ci s'affichera sur l'écran de connexion.</translation>
++<translation id="7957054228628133943">Configurer le blocage des fenêtres pop-up...</translation>
++<translation id="179767530217573436">des 4 dernières semaines</translation>
++<translation id="2279770628980885996">Une situation inattendue s'est produite tandis que le serveur tentait de traiter la demande.</translation>
++<translation id="8079135502601738761">Impossible d'afficher certaines parties de ce document PDF. Souhaitez-vous l'ouvrir dans Adobe Reader ?</translation>
++<translation id="9123413579398459698">Proxy FTP</translation>
++<translation id="3887875461425980041">Si vous utilisez la version PPAPI de Flash, exécutez-la dans chaque processus de moteur du rendu plutôt que dans un processus de plug-in dédié.</translation>
++<translation id="8534801226027872331">Cela signifie que le certificat présenté à votre navigateur contient des erreurs et qu'il ne peut pas être compris. Il est possible que les informations sur l'identité du certificat ou que d'autres informations du certificat relatives à la sécurité de la connexion soient incompréhensibles. Ne poursuivez pas.</translation>
++<translation id="3608527593787258723">Activer l'onglet 1</translation>
++<translation id="4497369307931735818">Communication à distance</translation>
++<translation id="3855676282923585394">Importer les favoris et les paramètres...</translation>
++<translation id="1116694919640316211">À propos</translation>
++<translation id="4422347585044846479">Modifier le favori de cette page</translation>
++<translation id="1684638090259711957">Ajouter un format d'exception</translation>
++<translation id="4925481733100738363">Configurer l'accès à distance...</translation>
++<translation id="1880905663253319515">Supprimer le certificat &quot;<ph name="CERTIFICATE_NAME"/>&quot; ?</translation>
++<translation id="8546306075665861288">Cache des images</translation>
++<translation id="5904093760909470684">Configuration du proxy</translation>
++<translation id="2874027208508018603">En l'absence de connexion Wi-Fi, Google Chrome utilise les données 3G.</translation>
++<translation id="4558734465070698159">Appuyez sur <ph name="HOTKEY_NAME"/> pour sélectionner le mode de saisie précédent.</translation>
++<translation id="3348643303702027858">La création du support de récupération du système d'exploitation a été annulée.</translation>
++<translation id="3391060940042023865">Le plug-in suivant est bloqué : <ph name="PLUGIN_NAME"/></translation>
++<translation id="4237016987259239829">Erreur de connexion réseau</translation>
++<translation id="9050666287014529139">Mot de passe multiterme</translation>
++<translation id="5197255632782567636">Internet</translation>
++<translation id="4755860829306298968">Configurer les paramètres de blocage des plug-ins...</translation>
++<translation id="8879284080359814990">Afficher dan&amp;s un onglet</translation>
++<translation id="2786847742169026523">Synchroniser vos mots de passe</translation>
++<translation id="41293960377217290">Le serveur proxy agit comme un intermédiaire entre votre ordinateur et les autres serveurs. Votre configuration système utilise actuellement un proxy, mais
++ <ph name="PRODUCT_NAME"/>
++ ne parvient pas à s'y connecter.</translation>
++<translation id="4520722934040288962">Sélectionner par type d'application</translation>
++<translation id="3873139305050062481">Procéder à l'i&amp;nspection de l'élément</translation>
++<translation id="7445762425076701745">Impossible de valider entièrement l'identité du serveur auquel vous êtes connecté. Le nom utilisé pour cette connexion n'est valide que sur votre réseau et aucune autorité de certification externe ne peut en vérifier la propriété. Certaines autorités de certification délivrent tout de même des certificats pour ces types de nom, par conséquent nous ne sommes pas en mesure de vérifier que vous êtes connecté au site voulu et non à un site malveillant.</translation>
++<translation id="1556537182262721003">Impossible de déplacer le répertoire d'extensions dans le profil.</translation>
++<translation id="5866557323934807206">Supprimer ces paramètres pour les prochaines visites</translation>
++<translation id="6506104645588011859">L'accessibilité est activée.</translation>
++<translation id="5355351445385646029">Appuyer sur la touche Espace pour sélectionner la suggestion</translation>
++<translation id="6978622699095559061">Vos favoris</translation>
++<translation id="6370820475163108109"><ph name="ORGANIZATION_NAME"/> (<ph name="DOMAIN_NAME"/>)</translation>
++<translation id="5453029940327926427">Fermer les onglets</translation>
++<translation id="406070391919917862">Applications en arrière-plan</translation>
++<translation id="8820817407110198400">Favoris</translation>
++<translation id="3214837514330816581">Supprimer les données synchronisées de Google Dashboard</translation>
++<translation id="2580170710466019930">Veuillez patienter pendant que <ph name="PRODUCT_NAME"/> installe les dernières mises à jour système.</translation>
++<translation id="7428061718435085649">Utilisez les touches Maj gauche et droite pour sélectionner les 2e et 3e propositions</translation>
++<translation id="1070066693520972135">WEP</translation>
++<translation id="206683469794463668">Mode Zhuyin complet. La sélection automatique de la suggestion et les options associées sont désactivées ou ignorées.</translation>
++<translation id="5191625995327478163">&amp;Paramètres linguistiques...</translation>
++<translation id="8833054222610756741">CRX-less Web Apps</translation>
++<translation id="4031729365043810780">Connexion au réseau</translation>
++<translation id="3332115613788466465">Reliure bord long</translation>
++<translation id="1985136186573666099"><ph name="PRODUCT_NAME"/> utilise les paramètres proxy du système pour se connecter au réseau.</translation>
++<translation id="6508261954199872201">Application : <ph name="APP_NAME"/></translation>
++<translation id="5585645215698205895">&amp;Descendre</translation>
++<translation id="8366757838691703947">? Toutes les données présentes sur le périphérique seront supprimées.</translation>
++<translation id="6596816719288285829">Adresse IP</translation>
++<translation id="7747704580171477003">Active le nouveau design de la page &quot;Nouvel onglet&quot; (en cours de développement).</translation>
++<translation id="4508265954913339219">Échec de l'activation</translation>
++<translation id="8656768832129462377">Ne pas vérifier</translation>
++<translation id="715487527529576698">Le chinois simplifié est le mode de saisie initial</translation>
++<translation id="1674989413181946727">Paramètres SSL sur tout l'ordinateur :</translation>
++<translation id="8703575177326907206">Votre connexion à <ph name="DOMAIN"/> n'est pas chiffrée.</translation>
++<translation id="8472623782143987204">matériel requis</translation>
++<translation id="4865571580044923428">Gérer les exceptions...</translation>
++<translation id="2526619973349913024">Rechercher des mises à jour</translation>
++<translation id="3874070094967379652">Utiliser un mot de passe multiterme pour chiffrer mes données de synchronisation</translation>
++<translation id="4864369630010738180">Connexion en cours...</translation>
++<translation id="6500116422101723010">Le serveur ne parvient pas à traiter la demande pour le moment. Ce code indique une situation temporaire. Le serveur sera de nouveau opérationnel ultérieurement.</translation>
++<translation id="1644574205037202324">Historique</translation>
++<translation id="1297175357211070620">Destination</translation>
++<translation id="6219983382864672018">Web audio</translation>
++<translation id="479280082949089240">Cookies placés par cette page</translation>
++<translation id="4198861010405014042">Accès partagé</translation>
++<translation id="6204930791202015665">Afficher...</translation>
++<translation id="5941343993301164315">Veuillez vous connecter à <ph name="TOKEN_NAME"/>.</translation>
++<translation id="4417229845571722044">Ajouter un nouvel e-mail</translation>
++<translation id="8049151370369915255">Personnaliser les polices...</translation>
++<translation id="2886862922374605295">Matériel :</translation>
++<translation id="4497097279402334319">Erreur de connexion au réseau.</translation>
++<translation id="5303618139271450299">Cette page Web est introuvable.</translation>
++<translation id="4256316378292851214">En&amp;registrer la vidéo sous...</translation>
++<translation id="3528171143076753409">Le certificat du serveur n'est pas approuvé.</translation>
++<translation id="6518014396551869914">Cop&amp;ier l'image</translation>
++<translation id="3236997602556743698">Sebeol-sik 390</translation>
++<translation id="542155483965056918"><ph name="NUMBER_ZERO"/> mins ago</translation>
++<translation id="289426338439836048">Autre réseau mobile...</translation>
++<translation id="3986287159189541211">Erreur HTTP <ph name="ERROR_NUMBER"/> (<ph name="ERROR_NAME"/>) : <ph name="ERROR_TEXT"/></translation>
++<translation id="3225319735946384299">Signature du code</translation>
++<translation id="3118319026408854581">Aide <ph name="PRODUCT_NAME"/></translation>
++<translation id="2422426094670600218">&lt;sans nom&gt;</translation>
++<translation id="2012766523151663935">Version du micrologiciel :</translation>
++<translation id="4120898696391891645">La page ne se charge pas</translation>
++<translation id="6060685159320643512">Attention, ces fonctionnalités expérimentales peuvent mordre.</translation>
++<translation id="5829990587040054282">Verrouiller l'écran ou éteindre</translation>
++<translation id="7800304661137206267">La connexion est chiffrée au moyen de <ph name="CIPHER"/>, avec <ph name="MAC"/> pour l'authentification des messages et <ph name="KX"/> pour la méthode d'échange de clés.</translation>
++<translation id="7706319470528945664">Clavier portugais</translation>
++<translation id="5584537427775243893">Importation</translation>
++<translation id="9128870381267983090">Connexion au réseau</translation>
++<translation id="4181841719683918333">Langues</translation>
++<translation id="6535131196824081346">Cette erreur peut se produire lors de la connexion à un serveur sécurisé (HTTPS).
++ Elle indique que le serveur tente d'établir une connexion sécurisée, mais
++ que celle-ci ne sera pas du tout sécurisée en raison d'une grave erreur de configuration.
++ <ph name="LINE_BREAK"/> Dans ce cas, une intervention
++ est requise sur le serveur.
++ <ph name="PRODUCT_NAME"/>
++ n'utilise pas de connexion non sécurisée pour protéger la confidentialité
++ de vos données.</translation>
++<translation id="5235889404533735074">La synchronisation de <ph name="PRODUCT_NAME"/> vous permet de partager vos données (favoris, préférences) sur vos ordinateurs en toute simplicité. Pour ce faire, <ph name="PRODUCT_NAME"/> enregistre vos données en ligne via Google lorsque vous vous connectez à votre compte.</translation>
++<translation id="6533668113756472185">Format ou mise en forme de la page</translation>
++<translation id="5640179856859982418">Clavier suisse</translation>
++<translation id="5910363049092958439">En&amp;registrer l'image sous...</translation>
++<translation id="1363055550067308502">Basculer en mode pleine chasse ou demi-chasse</translation>
++<translation id="3108967419958202225">Sélectionner...</translation>
++<translation id="6451650035642342749">Effacer les paramètres d'ouverture automatique</translation>
++<translation id="5948544841277865110">Ajouter un réseau privé</translation>
++<translation id="7121570032414343252"><ph name="NUMBER_TWO"/> secondes</translation>
++<translation id="1378451347523657898">Ne pas envoyer de capture d'écran</translation>
++<translation id="5098629044894065541">Hébreu</translation>
++<translation id="7751559664766943798">Toujours afficher la barre de favoris</translation>
++<translation id="5098647635849512368">Impossible de trouver le chemin d'accès absolu du répertoire à empaqueter.</translation>
++<translation id="780617032715125782">Créer un profil</translation>
++<translation id="933712198907837967">Diners Club</translation>
++<translation id="6380224340023442078">Paramètres de contenu...</translation>
++<translation id="950108145290971791">Activer la recherche instantanée pour accélérer la recherche et la navigation ?</translation>
++<translation id="144136026008224475">Plus d'extensions &gt;&gt;</translation>
++<translation id="5486326529110362464">La valeur d'entrée de la clé privée est obligatoire.</translation>
++<translation id="9039663905644212491">PEAP</translation>
++<translation id="62780591024586043">Fonctionnalités de localisation expérimentales</translation>
++<translation id="8584280235376696778">Ou&amp;vrir la vidéo dans un nouvel onglet</translation>
++<translation id="2845382757467349449">Toujours afficher la barre de favoris</translation>
++<translation id="2516384155283419848">Reliure</translation>
++<translation id="3053013834507634016">Utilisation de la clé du certificat</translation>
++<translation id="4487088045714738411">Clavier belge</translation>
++<translation id="7511635910912978956"><ph name="NUMBER_FEW"/> heures restantes</translation>
++<translation id="2152580633399033274">Afficher toutes les images (recommandé)</translation>
++<translation id="7894567402659809897">Cliquez sur
++ <ph name="BEGIN_BOLD"/>Démarrer<ph name="END_BOLD"/>,
++ puis sur
++ <ph name="BEGIN_BOLD"/>Exécuter<ph name="END_BOLD"/>.
++ Saisissez
++ et cliquez sur
++ <ph name="BEGIN_BOLD"/>OK<ph name="END_BOLD"/>.</translation>
++<translation id="2934952234745269935">Nom de volume</translation>
++<translation id="7960533875494434480">Reliure bord court</translation>
++<translation id="6431347207794742960"><ph name="PRODUCT_NAME"/> va configurer les mises à jour automatiques pour tous les utilisateurs de cet ordinateur.</translation>
++<translation id="4973698491777102067">Effacer les éléments datant :</translation>
++<translation id="6074963268421707432">Interdire à tous les sites d'afficher des notifications sur le Bureau</translation>
++<translation id="8508050303181238566">Appuyez sur <ph name="HOTKEY_NAME"/> pour passer d'un mode de saisie à l'autre.</translation>
++<translation id="6273404661268779365">Ajouter un nouveau fax</translation>
++<translation id="1995173078718234136">Recherche de contenu en cours...</translation>
++<translation id="4735819417216076266">Style d'entrée avec Espace</translation>
++<translation id="2977095037388048586">Vous avez tenté d'accéder à <ph name="DOMAIN"/>, mais, au lieu de cela, vous communiquez actuellement avec un serveur identifié comme <ph name="DOMAIN2"/>. Cela est peut-être dû à un défaut de configuration du serveur ou à quelque chose de plus grave. Un pirate informatique sur votre réseau cherche peut-être à vous faire visiter une version falsifiée de <ph name="DOMAIN3"/>, donc potentiellement préjudiciable. Nous vous déconseillons vivement de continuer.</translation>
++<translation id="220138918934036434">Masquer le bouton</translation>
++<translation id="5374359983950678924">Modifier l'image</translation>
++<translation id="5158548125608505876">Ne pas synchroniser mes mots de passe</translation>
++<translation id="2167276631610992935">JavaScript</translation>
++<translation id="6974306300279582256">Activer les notifications de <ph name="SITE"/></translation>
++<translation id="492914099844938733">Afficher les incompatibilités</translation>
++<translation id="5233638681132016545">Nouvel onglet</translation>
++<translation id="6567688344210276845">Impossible de charger l'icône &quot;<ph name="ICON"/>&quot; d'action de page.</translation>
++<translation id="5210365745912300556">Fermer l'onglet</translation>
++<translation id="8628085465172583869">Nom d'hôte du serveur :</translation>
++<translation id="498765271601821113">Ajouter une carte de paiement</translation>
++<translation id="7694379099184430148"><ph name="FILENAME"/> : type de fichier inconnu</translation>
++<translation id="1992397118740194946">Non défini</translation>
++<translation id="7966826846893205925">Gérer les paramètres de saisie automatique...</translation>
++<translation id="8556732995053816225">Respecter la &amp;casse</translation>
++<translation id="3314070176311241517">Autoriser tous les sites à exécuter JavaScript (recommandé)</translation>
++<translation id="2406911946387278693">Gérer vos périphériques depuis le cloud</translation>
++<translation id="7419631653042041064">Clavier catalan</translation>
++<translation id="5710740561465385694">Me demander lorsqu'un site essaie de stocker des données</translation>
++<translation id="3897092660631435901">Menu</translation>
++<translation id="7024867552176634416">Sélectionnez le périphérique de stockage amovible à utiliser.</translation>
++<translation id="8553075262323480129">La traduction a échoué, car nous n'avons pas pu déterminer la langue de la page.</translation>
++<translation id="5910680277043747137">Vous pouvez créer des profils supplémentaires pour autoriser plusieurs personnes à utiliser et personnaliser Google Chrome.</translation>
++<translation id="4381849418013903196">Deux-points</translation>
++<translation id="1103523840287552314">Toujours traduire en <ph name="LANGUAGE"/></translation>
++<translation id="2263497240924215535">(désactivée)</translation>
++<translation id="2159087636560291862">Cela signifie que le certificat n'a pas été vérifié par un tiers reconnu par votre ordinateur. N'importe qui peut émettre un certificat en se faisant passer pour un autre site Web. Ce certificat doit donc être vérifié par un tiers approuvé. Sans cette vérification, les informations sur l'identité du certificat sont sans intérêt. Par conséquent, il nous est impossible de vérifier que vous communiquez bien avec <ph name="DOMAIN"/> et non avec un pirate informatique ayant émis son propre certificat en prétendant être <ph name="DOMAIN2"/>. Nous vous déconseillons vivement de continuer.</translation>
++<translation id="58625595078799656"><ph name="PRODUCT_NAME"/> requiert que vous cryptiez vos données à l'aide de votre mot de passe Google ou de votre propre mot de passe multiterme.</translation>
++<translation id="8017335670460187064"><ph name="LABEL"/></translation>
++<translation id="6840184929775541289">N'est pas une autorité de certification</translation>
++<translation id="6099520380851856040">Date et heure : <ph name="CRASH_TIME"/></translation>
++<translation id="144518587530125858">Impossible de charger &quot;<ph name="IMAGE_PATH"/>&quot; pour le thème.</translation>
++<translation id="5355097969896547230">Rechercher à nouveau</translation>
++<translation id="7925285046818567682">En attente de <ph name="HOST_NAME"/>...</translation>
++<translation id="2553440850688409052">Masquer ce plug-in</translation>
++<translation id="3280237271814976245">Enregistrer &amp;sous...</translation>
++<translation id="8301162128839682420">Ajouter une langue :</translation>
++<translation id="7658239707568436148">Annuler</translation>
++<translation id="8695825812785969222">Ouvrir une &amp;adresse...</translation>
++<translation id="4538417792467843292">Supprimer le mot</translation>
++<translation id="8412392972487953978">Vous devez saisir deux fois le même mot de passe multiterme.</translation>
++<translation id="9121814364785106365">Ouvrir dans un onglet épinglé</translation>
++<translation id="6996264303975215450">Page Web, tout type de contenu</translation>
++<translation id="3435896845095436175">Activer</translation>
++<translation id="1891668193654680795">Considérer ce certificat comme fiable pour identifier les développeurs de logiciels.</translation>
++<translation id="5078638979202084724">Ajouter tous les onglets aux favoris</translation>
++<translation id="5585118885427931890">Impossible de créer le dossier de favoris.</translation>
++<translation id="2154710561487035718">Copier l'URL</translation>
++<translation id="8163672774605900272">Si vous pensez ne pas avoir à utiliser de serveur proxy, procédez comme suit :
++ <ph name="PLATFORM_TEXT"/></translation>
++<translation id="5510687173983454382">Définir les utilisateurs autorisés à se connecter à un périphérique et autoriser les sessions de navigation en tant qu'invité</translation>
++<translation id="3241680850019875542">Sélectionnez le répertoire racine de l'extension à empaqueter. Pour mettre à jour une extension, sélectionnez également le fichier de clé privée à réutiliser.</translation>
++<translation id="7500424997253660722">Pool restreint :</translation>
++<translation id="657402800789773160">&amp;Rafraîchir cette page</translation>
++<translation id="6163363155248589649">&amp;Normal</translation>
++<translation id="7972714317346275248">PKCS #1 SHA-384 avec chiffrement RSA</translation>
++<translation id="3020990233660977256">Numéro de série : <ph name="SERIAL_NUMBER"/></translation>
++<translation id="8426519927982004547">HTTPS/SSL</translation>
++<translation id="8216781342946147825">Toutes les données de votre ordinateur et des sites Web que vous visitez</translation>
++<translation id="5548207786079516019">Ceci est une installation secondaire de <ph name="PRODUCT_NAME"/> et ce dernier ne peut pas être défini comme navigateur par défaut.</translation>
++<translation id="3984413272403535372">Erreur lors de la signature de l'extension</translation>
++<translation id="8807083958935897582"><ph name="PRODUCT_NAME"/> permet d'effectuer des recherches sur Internet à l'aide du champ polyvalent. Sélectionnez le moteur de recherche à utiliser :</translation>
++<translation id="6629104427484407292">Sécurité : <ph name="SECURITY"/></translation>
++<translation id="9208886416788010685">Adobe Reader n'est pas à jour</translation>
++<translation id="3373604799988099680">Extensions ou applications</translation>
++<translation id="318408932946428277">Effacer les cookies et autres données de site et de plug-in lorsque je ferme le navigateur</translation>
++<translation id="314141447227043789">Téléchargement de l'image terminé.</translation>
++<translation id="8725178340343806893">Favoris</translation>
++<translation id="5177526793333269655">Afficher les vignettes</translation>
++<translation id="8926389886865778422">Ne plus afficher ce message</translation>
++<translation id="6985235333261347343">Agent de récupération de clé Microsoft</translation>
++<translation id="3605499851022050619">Page de diagnostic de navigation sécurisée</translation>
++<translation id="4417271111203525803">Adresse ligne 2</translation>
++<translation id="7617095560120859490">Décrivez-nous le problème recontré. (Champ obligatoire)</translation>
++<translation id="5618333180342767515">Cela peut prendre quelques minutes.</translation>
++<translation id="4307992518367153382">Options de base</translation>
++<translation id="8480417584335382321">Niveau de zoom par défaut :</translation>
++<translation id="3872166400289564527">Stockage externe</translation>
++<translation id="5912378097832178659">Modifi&amp;er les moteurs de recherche...</translation>
++<translation id="8272426682713568063">Cartes de paiement</translation>
++<translation id="3749289110408117711">Nom du fichier</translation>
++<translation id="3173397526570909331">Arrêter la synchronisation</translation>
++<translation id="5538092967727216836">Actualiser le cadre</translation>
++<translation id="4813345808229079766">Connexion</translation>
++<translation id="411666854932687641">Mémoire privée</translation>
++<translation id="119944043368869598">Tout effacer</translation>
++<translation id="3467848195100883852">Activer la correction orthographique automatique</translation>
++<translation id="1336254985736398701">Afficher les &amp;infos sur la page</translation>
++<translation id="7550830279652415241">favoris_<ph name="DATESTAMP"/>.html</translation>
++<translation id="6828153365543658583">Autoriser uniquement les utilisateurs suivants à se connecter :</translation>
++<translation id="1652965563555864525">&amp;Muet</translation>
++<translation id="4200983522494130825">Nouvel ongle&amp;t</translation>
++<translation id="7979036127916589816">Erreur de synchronisation</translation>
++<translation id="1029317248976101138">Zoom</translation>
++<translation id="5455790498993699893"><ph name="ACTIVE_MATCH"/> sur <ph name="TOTAL_MATCHCOUNT"/></translation>
++<translation id="8890069497175260255">Type de clavier</translation>
++<translation id="1202290638211552064">Délai d'expiration atteint au niveau de la passerelle ou du serveur proxy en attente d'une réponse d'un serveur en amont.</translation>
++<translation id="7765158879357617694">Déplacer</translation>
++<translation id="5731751937436428514">Mode de saisie du vietnamien (VIQR)</translation>
++<translation id="8412144371993786373">Ajouter la page actuelle aux favoris</translation>
++<translation id="7615851733760445951">&lt;aucun cookie sélectionné&gt;</translation>
++<translation id="469553822757430352">Le mot de passe de l'application est incorrect.</translation>
++<translation id="2493021387995458222">Sélectionner &quot;un mot à la fois&quot;</translation>
++<translation id="5279600392753459966">Tout bloquer</translation>
++<translation id="6846298663435243399">Chargement en cours…</translation>
++<translation id="7392915005464253525">&amp;Rouvrir la fenêtre fermée</translation>
++<translation id="1144684570366564048">Gérer les exceptions...</translation>
++<translation id="7400418766976504921">URL</translation>
++<translation id="1541725072327856736">Katakana demi-chasse</translation>
++<translation id="7456847797759667638">Ouvrir une adresse</translation>
++<translation id="1388866984373351434">Données de navigation</translation>
++<translation id="3754634516926225076">Code PIN incorrect. Veuillez réessayer.</translation>
++<translation id="7378627244592794276">Non</translation>
++<translation id="2800537048826676660">Utiliser cette langue pour corriger l'orthographe</translation>
++<translation id="68541483639528434">Fermer les autres onglets</translation>
++<translation id="941543339607623937">Clé privée non valide.</translation>
++<translation id="6499058468232888609">Une erreur réseau s'est produite pendant la communication avec le service de gestion des périphériques.</translation>
++<translation id="4433862206975946675">Importer les données d'un autre navigateur...</translation>
++<translation id="4022426551683927403">&amp;Ajouter au dictionnaire</translation>
++<translation id="2897878306272793870">Voulez-vous vraiment ouvrir <ph name="TAB_COUNT"/> onglets ?</translation>
++<translation id="312759608736432009">Fabricant du périphérique :</translation>
++<translation id="362276910939193118">Afficher l'historique complet</translation>
++<translation id="6079696972035130497">Illimité</translation>
++<translation id="4365411729367255048">Clavier Neo2 allemand</translation>
++<translation id="6348657800373377022">Liste déroulante</translation>
++<translation id="8064671687106936412">Clé :</translation>
++<translation id="2218515861914035131">Coller en texte brut</translation>
++<translation id="1725149567830788547">Afficher les &amp;commandes</translation>
++<translation id="3528033729920178817">Cette page suit votre position géographique.</translation>
++<translation id="5518584115117143805">Certificat de chiffrement de courrier électronique</translation>
++<translation id="9203398526606335860">&amp;Profilage activé</translation>
++<translation id="4307281933914537745">En savoir plus sur la récupération du système</translation>
++<translation id="2849936225196189499">Essentielle</translation>
++<translation id="9001035236599590379">Type MIME</translation>
++<translation id="5612754943696799373">Autoriser le téléchargement ?</translation>
++<translation id="6353618411602605519">Clavier croate</translation>
++<translation id="5515810278159179124">Interdire à tous les sites de suivre ma position géographique</translation>
++<translation id="5999606216064768721">Utiliser la barre de titre du système et les bordures de la fenêtre</translation>
++<translation id="904752364881701675">En bas à gauche</translation>
++<translation id="3398951731874728419">Informations sur l'erreur :</translation>
++<translation id="1464570622807304272">Essayez : saisissez &quot;orchidées&quot;, puis appuyez sur Entrée.</translation>
++<translation id="8026684114486203427">Pour utiliser Chrome Web Store, vous devez être connecté à un compte Google.</translation>
++<translation id="8417276187983054885">Configurer <ph name="CLOUD_PRINT_NAME"/></translation>
++<translation id="3056462238804545033">Petit problème... Nous n'avons pas réussi à vous authentifier. Veuillez vérifier vos identifiants de connexion puis réessayer.</translation>
++<translation id="5298420986276701358">Pour gérer à distance la configuration de ce périphérique <ph name="PRODUCT_NAME"/> depuis le cloud, connectez-vous avec votre compte Google Apps.</translation>
++<translation id="2678063897982469759">Réactiver</translation>
++<translation id="1779766957982586368">Fermer la fenêtre</translation>
++<translation id="4850886885716139402">Présentation</translation>
++<translation id="89217462949994770">Vous avez saisi un trop grand nombre de codes PIN incorrects. Veuillez contacter <ph name="CARRIER_ID"/> pour obtenir une nouvelle clé de déverrouillage du code PIN à 8 chiffres.</translation>
++<translation id="5920618722884262402">Bloquer le contenu inapproprié</translation>
++<translation id="5120247199412907247">Configuration avancée</translation>
++<translation id="5922220455727404691">Utiliser SSL 3.0</translation>
++<translation id="1368352873613152012">Règles de confidentialité liées à la navigation sécurisée</translation>
++<translation id="5105859138906591953">Vous devez être connecté à votre compte Google pour importer les favoris de la barre d'outils Google dans Google Chrome. Connectez-vous et relancez l'importation.</translation>
++<translation id="8899851313684471736">Ouvrir le lien dans une nouvelle &amp;fenêtre</translation>
++<translation id="4110342520124362335">Les cookies de <ph name="DOMAIN"/> ont été bloqués.</translation>
++<translation id="3303818374450886607">Copies</translation>
++<translation id="2019718679933488176">&amp;Ouvrir le fichier audio dans un nouvel onglet</translation>
++<translation id="4138267921960073861">Afficher les noms d'utilisateurs et leur photo sur la page de connexion</translation>
++<translation id="7465778193084373987">URL de révocation de certificat Netscape</translation>
++<translation id="5976690834266782200">Ajoute des options de regroupement des onglets dans le menu contextuel des onglets.</translation>
++<translation id="4755240240651974342">Clavier finnois</translation>
++<translation id="7049893973755373474">Vérifiez votre connexion Internet. Redémarrez votre routeur, votre modem
++ ou tout autre périphérique réseau que vous utilisez.</translation>
++<translation id="7421925624202799674">&amp;Afficher le code source de la page</translation>
++<translation id="3940082421246752453">Le serveur ne prend pas en charge la version HTTP utilisée dans la demande.</translation>
++<translation id="661719348160586794">Vos mots de passe enregistrés s'afficheront ici.</translation>
++<translation id="6686490380836145850">Fermer les onglets sur la droite</translation>
++<translation id="5608669887400696928"><ph name="NUMBER_DEFAULT"/> heures</translation>
++<translation id="8844709414456935411"><ph name="PRODUCT_NAME"/>
++ indique qu'un produit ESET intercepte les connexions sécurisées.
++ En général, cela ne constitue pas un problème de sécurité car le
++ logiciel ESET s'exécute souvent sur le même ordinateur. Toutefois, en raison
++ de certaines incompatibilités avec les connexions sécurisées
++ <ph name="PRODUCT_NAME"/>,
++ vous devez configurer les produits ESET de manière à éviter ces
++ interceptions. Cliquez sur le lien En savoir plus pour obtenir des instructions.</translation>
++<translation id="3936877246852975078">Les requêtes adressées au serveur ont été temporairement limitées.</translation>
++<translation id="2600306978737826651">Impossible de télécharger l'image. Gravure annulée.</translation>
++<translation id="609978099044725181">Activer/désactiver le mode Hanja</translation>
++<translation id="1829483195200467833">Effacer les paramètres d'ouverture automatique</translation>
++<translation id="2738771556149464852">Pas après le</translation>
++<translation id="5774515636230743468">Manifeste :</translation>
++<translation id="719464814642662924">Visa</translation>
++<translation id="7474889694310679759">Clavier anglais canadien</translation>
++<translation id="1817871734039893258">Récupération de fichier Microsoft</translation>
++<translation id="2423578206845792524">En&amp;registrer l'image sous...</translation>
++<translation id="6839929833149231406">Zone</translation>
++<translation id="9068931793451030927">Chemin :</translation>
++<translation id="283278805979278081">Prendre la photo</translation>
++<translation id="7320906967354320621">Inactif</translation>
++<translation id="1407050882688520094">Certains de vos certificats enregistrés identifient ces autorités de certification :</translation>
++<translation id="4287689875748136217">Impossible d'afficher la page Web, car le serveur n'a envoyé aucune donnée.</translation>
++<translation id="1634788685286903402">Considérer ce certificat comme fiable pour identifier les utilisateurs de messageries.</translation>
++<translation id="7052402604161570346">Ce type de fichier peut endommager votre ordinateur. Voulez-vous vraiment télécharger <ph name="FILE_NAME"/> ?</translation>
++<translation id="8642489171979176277">Importés depuis la barre d'outils Google</translation>
++<translation id="4142744419835627535">Recherche instantanée et saisie semi-automatique</translation>
++<translation id="4684427112815847243">Tout synchroniser</translation>
++<translation id="1125520545229165057">Dvorak (Hsu)</translation>
++<translation id="8940229512486821554">Exécuter la commande <ph name="EXTENSION_NAME"/> : <ph name="SEARCH_TERMS"/></translation>
++<translation id="2232876851878324699">Le fichier contenait un certificat, qui n'a pas été importé :</translation>
++<translation id="7787129790495067395">Vous utilisez actuellement un mot de passe multiterme. Si vous l'oubliez, vous pouvez réinitialiser la synchronisation afin de supprimer vos données des serveurs Google à l'aide de Google Dashboard.</translation>
++<translation id="2686759344028411998">Impossible de détecter les modules chargés.</translation>
++<translation id="572525680133754531">Cette fonctionnalité affiche une bordure autour des couches de rendu afin de déboguer et d'étudier leur composition.</translation>
++<translation id="2011110593081822050">Processus de traitement Web : <ph name="WORKER_NAME"/></translation>
++<translation id="3294437725009624529">Invité</translation>
++<translation id="350069200438440499">Nom du fichier :</translation>
++<translation id="9058204152876341570">Un élément est manquant.</translation>
++<translation id="8494979374722910010">Échec de la tentative de connexion au serveur.</translation>
++<translation id="7810202088502699111">Des fenêtres pop-up ont été bloquées sur cette page.</translation>
++<translation id="8190698733819146287">Personnaliser les langues et la saisie...</translation>
++<translation id="646727171725540434">Proxy HTTP</translation>
++<translation id="1006316751839332762">Mot de passe multiterme de chiffrement</translation>
++<translation id="8795916974678578410">Nouvelle fenêtre</translation>
++<translation id="2733275712367076659">Certains certificats provenant de ces organisations vous identifient :</translation>
++<translation id="4801512016965057443">Autoriser l'itinérance des données mobiles</translation>
++<translation id="2515586267016047495">Alt</translation>
++<translation id="2046040965693081040">Utiliser les pages actuelles</translation>
++<translation id="3798449238516105146">Version</translation>
++<translation id="5764483294734785780">En&amp;registrer le fichier audio sous...</translation>
++<translation id="5252456968953390977">Itinérance</translation>
++<translation id="8744641000906923997">Romaji</translation>
++<translation id="8507996248087185956"><ph name="NUMBER_DEFAULT"/> minutes</translation>
++<translation id="4845656988780854088">Synchroniser uniquement les paramètres et\ndonnées qui ont changé depuis la dernière connexion\n(requiert votre mot de passe précédent)</translation>
++<translation id="348620396154188443">Autoriser tous les sites à afficher des notifications sur le Bureau</translation>
++<translation id="8214489666383623925">Ouvrir le fichier...</translation>
++<translation id="5376120287135475614">Changer de fenêtre</translation>
++<translation id="5230160809118287008">Chèvres téléportées</translation>
++<translation id="1701567960725324452">Si vous arrêtez la synchronisation, les données stockées sur cet ordinateur et dans votre compte Google demeureront à ces deux emplacements. Toutefois, les nouvelles données ou les modifications apportées au contenu existant ne seront pas synchronisées.</translation>
++<translation id="7761701407923456692">Le certificat du serveur ne correspond pas à l'URL.</translation>
++<translation id="3885155851504623709">Commune</translation>
++<translation id="4910171858422458941">Impossible d'activer les plug-ins désactivés par une stratégie d'entreprise.</translation>
++<translation id="4495419450179050807">Ne pas afficher sur cette page</translation>
++<translation id="4745800796303246012">Méthodes EAP en Wi-Fi expérimentales</translation>
++<translation id="1225544122210684390">Disque dur</translation>
++<translation id="939736085109172342">Nouveau dossier</translation>
++<translation id="4933484234309072027">intégration sur <ph name="URL"/></translation>
++<translation id="5554720593229208774">Autorité de certification de messagerie</translation>
++<translation id="862750493060684461">Cache CSS</translation>
++<translation id="2832519330402637498">En haut à gauche</translation>
++<translation id="6749695674681934117">Saisissez le nom du nouveau dossier.</translation>
++<translation id="6204994989617056362">L'extension de renégociation SSL était introuvable lors de la négociation sécurisée. Avec certains sites, connus pour leur prise en charge de l'extension de renégociation, Google Chrome exige une négociation mieux sécurisée afin de prévenir certaines attaques. L'absence de cette extension suggère que votre connexion a été interceptée et manipulée au cours du transfert.</translation>
++<translation id="6679492495854441399">Petit problème... Une erreur de communication avec le réseau est survenue lors de la tentative d'inscription de ce périphérique. Veuillez vérifier votre connexion réseau et réessayer.</translation>
++<translation id="7789962463072032349">pause</translation>
++<translation id="121827551500866099">Afficher tous les téléchargements...</translation>
++<translation id="1562633988311880769">Connexion à <ph name="CLOUD_PRINT_NAME"/></translation>
++<translation id="5949910269212525572">Impossible de résoudre l'adresse DNS du serveur.</translation>
++<translation id="3115147772012638511">En attente de l'affichage du cache</translation>
++<translation id="257088987046510401">Thèmes</translation>
++<translation id="6771079623344431310">Impossible de se connecter au serveur proxy.</translation>
++<translation id="2200129049109201305">Ignorer la synchronisation des données chiffrées ?</translation>
++<translation id="1426410128494586442">Oui</translation>
++<translation id="6725970970008349185">Nombre de suggestions par page</translation>
++<translation id="6198252989419008588">Modifier le code PIN</translation>
++<translation id="5749483996735055937">Un problème est survenu lors de la copie de l'image de récupération sur le périphérique.</translation>
++<translation id="3520476450377425184"><ph name="NUMBER_MANY"/> jours restants</translation>
++<translation id="7643817847124207232">La connexion Internet a été interrompue.</translation>
++<translation id="932327136139879170">Début</translation>
++<translation id="4764675709794295630">« Précédent</translation>
++<translation id="2560794850818211873">C&amp;opier l'URL de la vidéo</translation>
++<translation id="6042708169578999844">Vos données sur <ph name="WEBSITE_1"/> et <ph name="WEBSITE_2"/></translation>
++<translation id="5302048478445481009">Langue</translation>
++<translation id="5553089923092577885">Mappages des stratégies de certificat</translation>
++<translation id="5600907569873192868"><ph name="NUMBER_MANY"/> minutes restantes</translation>
++<translation id="1519704592140256923">Sélectionner la position</translation>
++<translation id="1275018677838892971">Le site Web à l'adresse <ph name="HOST_NAME"/> contient des éléments provenant de sites signalés comme étant des sites de phishing. Ces derniers incitent les internautes à divulguer leurs informations personnelles en se faisant passer pour des institutions de confiance, telles que des banques.</translation>
++<translation id="702455272205692181"><ph name="EXTENSION_NAME"/></translation>
++<translation id="7170041865419449892">Hors de portée</translation>
++<translation id="908263542783690259">Effacer l'historique de navigation</translation>
++<translation id="7518003948725431193">Aucune page Web trouvée à l'adresse :<ph name="URL"/></translation>
++<translation id="745602119385594863">Nouveau moteur de recherche :</translation>
++<translation id="7484645889979462775">Jamais pour ce site</translation>
++<translation id="8666066831007952346"><ph name="NUMBER_TWO"/> jours restants</translation>
++<translation id="9086455579313502267">Impossible d'accéder au réseau.</translation>
++<translation id="5595485650161345191">Modifier l'adresse</translation>
++<translation id="2374144379568843525">&amp;Masquer le panneau de la vérification orthographique</translation>
++<translation id="2694026874607847549">1 cookie</translation>
++<translation id="4393664266930911253">Activer ces fonctionnalités...</translation>
++<translation id="6390842777729054533"><ph name="NUMBER_ZERO"/> secondes restantes</translation>
++<translation id="3909791450649380159">Cou&amp;per</translation>
++<translation id="2955913368246107853">Fermer la barre de recherche</translation>
++<translation id="5642508497713047">Signataire de la liste de révocation de certificats</translation>
++<translation id="813082847718468539">Afficher des informations à propos du site</translation>
++<translation id="3122464029669770682">UC</translation>
++<translation id="1684861821302948641">Fermer les pages</translation>
++<translation id="6092270396854197260">MSPY</translation>
++<translation id="6802031077390104172"><ph name="USAGE"/> (<ph name="OID"/>)</translation>
++<translation id="4052120076834320548">Très petite</translation>
++<translation id="6623138136890659562">Afficher les réseaux privés dans le menu Réseau pour activer la connexion à un VPN</translation>
++<translation id="8969837897925075737">Vérification de la mise à jour du système...</translation>
++<translation id="3393716657345709557">L'entrée demandée est introuvable dans le cache.</translation>
++<translation id="7241389281993241388">Connectez-vous à <ph name="TOKEN_NAME"/> pour importer le certificat client.</translation>
++<translation id="40334469106837974">Modifier la mise en page</translation>
++<translation id="4804818685124855865">Se déconnecter</translation>
++<translation id="2617919205928008385">Espace insuffisant.</translation>
++<translation id="210445503571712769">Préférences synchronisées</translation>
++<translation id="1608306110678187802">Imp&amp;rimer le cadre...</translation>
++<translation id="7427315641433634153">MSCHAP</translation>
++<translation id="6622980291894852883">Continuer à bloquer les images</translation>
++<translation id="5937837224523037661">Lorsqu'un site utilise des plug-ins :</translation>
++<translation id="4988792151665380515">Échec d'exportation de la clé publique</translation>
++<translation id="6333049849394141510">Choisir les éléments à synchroniser</translation>
++<translation id="446322110108864323">Paramètres de saisie du Pinyin</translation>
++<translation id="4948468046837535074">Ouvrir les pages suivantes :</translation>
++<translation id="5222676887888702881">Déconnexion</translation>
++<translation id="6978121630131642226">Moteurs de recherche</translation>
++<translation id="6839225236531462745">Erreur de suppression de certificat</translation>
++<translation id="6745994589677103306">Ne rien faire</translation>
++<translation id="855081842937141170">Épingler l'onglet</translation>
++<translation id="6263541650532042179">réinitialiser la synchronisation</translation>
++<translation id="6055392876709372977">PKCS #1 SHA-256 avec chiffrement RSA</translation>
++<translation id="7903984238293908205">Katakana</translation>
++<translation id="3781488789734864345">Choisir un réseau mobile</translation>
++<translation id="268053382412112343">&amp;Historique</translation>
++<translation id="2723893843198727027">Mode développeur :</translation>
++<translation id="1722567105086139392">Lien</translation>
++<translation id="2620436844016719705">Système</translation>
++<translation id="5362741141255528695">Sélectionnez le fichier de clé privée.</translation>
++<translation id="5292890015345653304">Insérez une carte SD ou une carte mémoire USB.</translation>
++<translation id="5583370583559395927">Temps restant : <ph name="TIME_REMAINING"/></translation>
++<translation id="8065982201906486420">Cliquez ici pour exécuter le plug-in <ph name="PLUGIN_NAME"/>.</translation>
++<translation id="6219717821796422795">Hanyu</translation>
++<translation id="3725367690636977613">pages</translation>
++<translation id="2688477613306174402">Configuration en cours</translation>
++<translation id="1195447618553298278">Erreur inconnue</translation>
++<translation id="3353284378027041011"><ph name="NUMBER_FEW"/> days ago</translation>
++<translation id="8811462119186190367">La langue utilisée pour Google Chrome est passée de &quot;<ph name="FROM_LOCALE"/>&quot; à &quot;<ph name="TO_LOCALE"/>&quot; après la synchronisation de vos paramètres.</translation>
++<translation id="1087119889335281750">&amp;Aucune suggestion orthographique</translation>
++<translation id="5228309736894624122">Erreur de protocole SSL</translation>
++<translation id="8216170236829567922">Mode de saisie du thaï (clavier Pattachote)</translation>
++<translation id="8464132254133862871">Ce compte utilisateur n'est pas compatible avec ce service.</translation>
++<translation id="6812349420832218321"><ph name="PRODUCT_NAME"/> ne peut pas être exécuté en tant que root.</translation>
++<translation id="5076340679995252485">C&amp;oller</translation>
++<translation id="2904348843321044456">Paramètres de contenu...</translation>
++<translation id="1055216403268280980">Dimensions de l'image</translation>
++<translation id="1784284518684746740">Sélectionner le fichier à enregistrer sous</translation>
++<translation id="7032947513385578725">Disque Flash</translation>
++<translation id="5518442882456325299">Moteur de recherche actuel :</translation>
++<translation id="14171126816530869">L'identité de <ph name="ORGANIZATION"/> situé à <ph name="LOCALITY"/> a été vérifiée par <ph name="ISSUER"/>.</translation>
++<translation id="6263082573641595914">Version de l'autorité de certification Microsoft</translation>
++<translation id="3105917916468784889">Enregistrer une capture d'écran</translation>
++<translation id="1741763547273950878">Page sur <ph name="SITE"/></translation>
++<translation id="1587275751631642843">Console &amp;JavaScript</translation>
++<translation id="8460696843433742627">Réponse reçue incorrecte lors de la tentative de chargement de <ph name="URL"/>.
++ Cela peut être dû à une opération de maintenance ou à une configuration incorrecte sur le serveur.</translation>
++<translation id="297870353673992530">Serveur DNS :</translation>
++<translation id="3222066309010235055">Pré-rendu : <ph name="PRERENDER_CONTENTS_NAME"/></translation>
++<translation id="6410063390789552572">Impossible d'accéder à la bibliothèque réseau.</translation>
++<translation id="6880587130513028875">Des images ont été bloquées sur cette page.</translation>
++<translation id="851263357009351303">Toujours autoriser <ph name="HOST"/> à afficher les images</translation>
++<translation id="3511307672085573050">Copier l'adr&amp;esse du lien</translation>
++<translation id="1134009406053225289">Ouvrir dans une fenêtre de navigation privée</translation>
++<translation id="6655190889273724601">Mode développeur</translation>
++<translation id="1071917609930274619">Chiffrement des données</translation>
++<translation id="3473105180351527598">Activer la protection contre le phishing et les logiciels malveillants</translation>
++<translation id="6151323131516309312">Appuyez sur <ph name="SEARCH_KEY"/> pour rechercher sur <ph name="SITE_NAME"/></translation>
++<translation id="3753317529742723206">Voulez-vous utiliser <ph name="HANDLER_TITLE"/> (<ph name="HANDLER_HOSTNAME"/>) au lieu de <ph name="REPLACED_HANDLER_TITLE"/> pour gérer les liens <ph name="PROTOCOL"/>:// à partir de maintenant ?</translation>
++<translation id="6216679966696797604">Démarrer une session en tant qu'invité</translation>
++<translation id="5456397824015721611">Nombre maximal de caractères chinois dans la mémoire tampon de pré-édition, notamment les entrées de symboles Zhuyin</translation>
++<translation id="2055443983279698110">Barre de menus GNOME expérimentale disponible</translation>
++<translation id="2342959293776168129">Effacer l'historique des téléchargements</translation>
++<translation id="2503522102815150840">Navigateur bloqué...</translation>
++<translation id="7201354769043018523">Parenthèse drte</translation>
++<translation id="425878420164891689">Calcul du temps de chargement</translation>
++<translation id="508794495705880051">Ajouter une carte de paiement...</translation>
++<translation id="1425975335069981043">Itinérance :</translation>
++<translation id="1272079795634619415">Arrêter</translation>
++<translation id="5442787703230926158">Erreur de synchronisation...</translation>
++<translation id="2462724976360937186">ID de clé de l'autorité de certification</translation>
++<translation id="6786747875388722282">Extensions</translation>
++<translation id="3944384147860595744">Imprimez où que vous soyez.</translation>
++<translation id="2570648609346224037">Un problème est survenu lors du téléchargement de l'image de récupération.</translation>
++<translation id="4306718255138772973">Cloud Print Proxy</translation>
++<translation id="9053965862400494292">Une erreur s'est produite lors de la configuration de la synchronisation.</translation>
++<translation id="8596540852772265699">Fichiers personnalisés</translation>
++<translation id="7017354871202642555">Impossible de définir le mode une fois la fenêtre créée.</translation>
++<translation id="3101709781009526431">Date et heure</translation>
++<translation id="69375245706918574">Personnaliser les préférences de synchronisation</translation>
++<translation id="833853299050699606">Aucune information disponible sur le forfait</translation>
++<translation id="1737968601308870607">Signaler un problème</translation>
++<translation id="4571852245489094179">Importer mes favoris et paramètres</translation>
++<translation id="99648783926443049">Sélectionnez le <ph name="BEGIN_BOLD"/>menu clé à molette &gt; Paramètres &gt; Options avancées &gt; Modifier les paramètres du proxy<ph name="END_BOLD"/> et vérifiez que vos paramètres sont définis sur &quot;sans proxy&quot; ou &quot;direct&quot;.</translation>
++<translation id="4421917670248123270">Fermer et annuler les téléchargements</translation>
++<translation id="5605623530403479164">Autres moteurs de recherche</translation>
++<translation id="8887243200615092733"><ph name="PRODUCT_NAME"/> peut maintenant synchroniser vos mots de passe. Pour protéger vos données, vous devez confirmer les informations relatives à votre compte.</translation>
++<translation id="4740663705480958372">Cette fonctionnalité active les API P2P Pepper et P2P JavaScript. L'API est en cours de développement et n'est pas encore opérationnelle.</translation>
++<translation id="5710435578057952990">L'identité de ce site Web n'a pas été vérifiée.</translation>
++<translation id="1421046588786494306">Sessions à l'étranger</translation>
++<translation id="1661245713600520330">Cette page répertorie tous les modules chargés dans le processus principal et les modules enregistrés de manière à être chargés ultérieurement.</translation>
++<translation id="5451646087589576080">Afficher les &amp;infos sur le cadre</translation>
++<translation id="3368922792935385530">Connecté</translation>
++<translation id="3498309188699715599">Paramètres d'entrée en Chewing</translation>
++<translation id="8486154204771389705">Conserver sur cette page</translation>
++<translation id="3866443872548686097">Votre support de récupération est prêt. Vous pouvez le retirer du système.</translation>
++<translation id="6824564591481349393">Copi&amp;er l'adresse e-mail</translation>
++<translation id="907148966137935206">Interdire à tous les sites d'afficher des fenêtres pop-up (recommandé)</translation>
++<translation id="5184063094292164363">Console &amp;JavaScript</translation>
++<translation id="333371639341676808">Empêcher cette page de générer des boîtes de dialogue supplémentaires</translation>
++<translation id="7632380866023782514">En haut à droite</translation>
++<translation id="4925520021222027859">Entrez le mot de passe associé à votre application :</translation>
++<translation id="3494768541638400973">Mode de saisie Google du japonais (pour clavier japonais)</translation>
++<translation id="5844183150118566785"><ph name="PRODUCT_NAME"/> est à jour (<ph name="VERSION"/>)</translation>
++<translation id="3118046075435288765">Le serveur a mis fin à la connexion de manière inattendue.</translation>
++<translation id="8041140688818013446">Il est possible que le serveur hébergeant la page Web soit surchargé ou ait rencontré une erreur. Pour éviter de générer
++ trop de trafic et d'aggraver la situation,
++ <ph name="PRODUCT_NAME"/> a temporairement
++ bloqué l'acceptation des requêtes adressées au serveur.
++ <ph name="LINE_BREAK"/>
++ Si vous pensez que ce comportement n'est pas souhaitable, (par exemple, dans le cas où vous déboguez votre propre site Web), vous pouvez
++ consulter la page <ph name="NET_INTERNALS_PAGE"/>,
++ sur laquelle vous pourrez trouver plus d'informations ou désactiver cette fonctionnalité.</translation>
++<translation id="1725068750138367834">Gestionnaire de &amp;fichiers</translation>
++<translation id="4254921211241441775">Arrêter la synchronisation du compte</translation>
++<translation id="7791543448312431591">Ajouter</translation>
++<translation id="8569764466147087991">Sélectionnez le fichier à ouvrir</translation>
++<translation id="5449451542704866098">Aucun forfait de données</translation>
++<translation id="307505906468538196">Créer un compte Google</translation>
++<translation id="2053553514270667976">Code postal</translation>
++<translation id="48838266408104654">&amp;Gestionnaire de tâches</translation>
++<translation id="4378154925671717803">Téléphone</translation>
++<translation id="3694027410380121301">Sélectionner l'onglet précédent</translation>
++<translation id="6178664161104547336">Sélectionner un certificat</translation>
++<translation id="1375321115329958930">Mots de passe enregistrés</translation>
++<translation id="3341703758641437857">Autoriser l'accès aux URL de fichier</translation>
++<translation id="5702898740348134351">Modifi&amp;er les moteurs de recherche...</translation>
++<translation id="734303607351427494">Gérer les moteurs de recherche...</translation>
++<translation id="8326478304147373412">PKCS #7, chaîne de certificats</translation>
++<translation id="3242765319725186192">Clé pré-partagée :</translation>
++<translation id="8089798106823170468">Contrôlez et partagez l'accès à vos imprimantes depuis n'importe quel compte Google.</translation>
++<translation id="5984992849064510607">Ajoute l'option &quot;Utiliser les onglets latéraux&quot; au menu contextuel de la barre d'onglets. Utilisez cette option pour déplacer les onglets du haut de l'écran (affichage par défaut) vers le côté. Particulièrement utile sur les grands écrans.</translation>
++<translation id="839736845446313156">S'inscrire</translation>
++<translation id="4668929960204016307">,</translation>
++<translation id="2409527877874991071">Saisissez un nouveau nom.</translation>
++<translation id="4240069395079660403"><ph name="PRODUCT_NAME"/> ne peut pas être affiché dans cette langue.</translation>
++<translation id="747114903913869239">Erreur : impossible de décoder l'extension.</translation>
++<translation id="5412637665001827670">Clavier bulgare</translation>
++<translation id="2113921862428609753">Accès aux informations de l'autorité</translation>
++<translation id="5227536357203429560">Ajouter un réseau privé...</translation>
++<translation id="732677191631732447">C&amp;opier l'URL du fichier audio</translation>
++<translation id="7224023051066864079">Masque de sous-réseau :</translation>
++<translation id="2401813394437822086">Impossible d'accéder à votre compte ?</translation>
++<translation id="2344262275956902282">Utiliser les touches - et = pour paginer une liste d'entrées</translation>
++<translation id="3609138628363401169">Le serveur ne prend pas en charge l'extension de renégociation TLS.</translation>
++<translation id="3369624026883419694">Résolution de l'hôte...</translation>
++<translation id="8870413625673593573">Récemment fermés</translation>
++<translation id="9145357542626308749">Le certificat de sécurité du site a été signé avec un algorithme de signature faible.</translation>
++<translation id="8502803898357295528">Votre mot de passe a été modifié</translation>
++<translation id="4064488613268730704">Gérer les paramètres de saisie automatique...</translation>
++<translation id="6830600606572693159">La page Web <ph name="URL"/> n'est pas disponible pour le moment. Cela peut être dû à une surcharge ou à une opération de maintenance.</translation>
++<translation id="4145797339181155891">Éjecter</translation>
++<translation id="7886793013438592140">Impossible de lancer le processus de service.</translation>
++<translation id="8417944620073548444"><ph name="MEGABYTES"/> Mo restants</translation>
++<translation id="7339898014177206373">Nouvelle fenêtre</translation>
++<translation id="3026202950002788510">Sélectionnez
++ <ph name="BEGIN_BOLD"/>
++ Applications &gt; Préférences système &gt; Réseau &gt; Avancé &gt; Proxys
++ <ph name="END_BOLD"/>
++ et désélectionnez les serveurs proxy sélectionnés.</translation>
++<translation id="7033648024564583278">Gravure en cours d'initialisation...</translation>
++<translation id="2246340272688122454">Téléchargement de l'image de récupération...</translation>
++<translation id="7770995925463083016">il y a <ph name="NUMBER_TWO"/> minutes</translation>
++<translation id="2816269189405906839">Mode de saisie du chinois (cangjie)</translation>
++<translation id="7087282848513945231">Comté</translation>
++<translation id="2149951639139208969">Ouvrir l'adresse dans un nouvel onglet</translation>
++<translation id="175196451752279553">&amp;Rouvrir l'onglet fermé</translation>
++<translation id="5992618901488170220">Impossible d'afficher la page Web, car votre ordinateur est passé en mode
++ veille ou hibernation. Dans ce cas, les connexions réseau sont
++ coupées et les requêtes réseau échouent. L'actualisation de la page
++ devrait permettre de résoudre ce problème.</translation>
++<translation id="2655386581175833247">Certificat utilisateur :</translation>
++<translation id="5039804452771397117">Autoriser</translation>
++<translation id="5435964418642993308">Appuyer sur Entrée pour revenir en arrière et sur la touche de menu contextuel pour afficher l'historique</translation>
++<translation id="81686154743329117">ZRM</translation>
++<translation id="7564146504836211400">Cookies et autres données</translation>
++<translation id="2266011376676382776">Page(s) ne répondant pas</translation>
++<translation id="2714313179822741882">Paramètres d'entrée hangûl</translation>
++<translation id="8658163650946386262">Configurer la synchronisation...</translation>
++<translation id="3100609564180505575">Modules (<ph name="TOTAL_COUNT"/>). Conflits connus : <ph name="BAD_COUNT"/>, conflits probables : <ph name="SUSPICIOUS_COUNT"/></translation>
++<translation id="3627671146180677314">Date de renouvellement du certificat Netscape</translation>
++<translation id="1319824869167805246">Ouvrir tous les favoris dans une nouvelle fenêtre</translation>
++<translation id="8652487083013326477">bouton radio concernant l'étendue de pages</translation>
++<translation id="5204967432542742771">Saisissez votre mot de passe</translation>
++<translation id="4388712255200933062"><ph name="CLOUD_PRINT_NAME"/> est conçu pour rendre l'impression plus intuitive, accessible et utile. <ph name="CLOUD_PRINT_NAME"/> vous permet de rendre vos imprimantes accessibles depuis n'importe quelle application Web ou mobile associée à <ph name="CLOUD_PRINT_NAME"/>.</translation>
++<translation id="2932611376188126394">Dictionnaire de kanji unique</translation>
++<translation id="5485754497697573575">Rétablir tous les onglets</translation>
++<translation id="3371861036502301517">Échec de l'installation de l'extension</translation>
++<translation id="644038709730536388">En savoir plus sur la manière de se protéger des logiciels malveillants en ligne</translation>
++<translation id="2155931291251286316">Toujours afficher les fenêtres pop-up de <ph name="HOST"/></translation>
++<translation id="3445830502289589282">Authentification phase 2 :</translation>
++<translation id="5650551054760837876">Aucun résultat de recherche trouvé</translation>
++<translation id="5494362494988149300">Ouvrir une fois le téléchargement &amp;terminé</translation>
++<translation id="2956763290572484660"><ph name="COOKIES"/> cookies</translation>
++<translation id="6989836856146457314">Mode de saisie du japonais (pour clavier américain)</translation>
++<translation id="9187787570099877815">Continuer à bloquer les plug-ins</translation>
++<translation id="8425492902634685834">Épingler sur la barre des tâches</translation>
++<translation id="825608351287166772">Les certificats ont une période de validité, comme tous les documents relatifs à votre identité (tel qu'un passeport). Le certificat présenté à votre navigateur n'est pas encore valide ! Lorsqu'un certificat est en dehors de sa période de validité, il n'est pas nécessaire d'assurer la maintenance de certaines informations relatives à son état (s'il a été révoqué ou s'il n'est plus approuvé). Par conséquent, il est impossible de vérifier que le certificat est fiable. Ne poursuivez pas.</translation>
++<translation id="741630086309232721">Fermer la session d'invité</translation>
++<translation id="7309459761865060639">Contrôlez vos tâches d'impression et l'état de connexion de vos imprimantes en ligne.</translation>
++<translation id="4803909571878637176">Désinstallation</translation>
++<translation id="5209518306177824490">Empreinte SHA-1</translation>
++<translation id="3300768886937313568">Modifier le code PIN de la carte SIM</translation>
++<translation id="7447657194129453603">État du réseau :</translation>
++<translation id="1553538517812678578">sans limite</translation>
++<translation id="7947315300197525319">(Choisir une autre capture d'écran)</translation>
++<translation id="3612070600336666959">Désactivation</translation>
++<translation id="3759461132968374835">Aucune erreur n'a été signalée récemment. Les erreurs n'apparaissent ici que lorsque l'envoi de rapports d'erreur est activé.</translation>
++<translation id="1516602185768225813">Rouvrir les dernières pages ouvertes</translation>
++<translation id="189210018541388520">Ouvrir en mode plein écran</translation>
++<translation id="8795668016723474529">Ajouter une carte de paiement</translation>
++<translation id="5860033963881614850">Désactivé</translation>
++<translation id="3956882961292411849">Chargement des informations sur votre forfait Internet mobile, veuillez patienter...</translation>
++<translation id="689050928053557380">Acheter un forfait de données...</translation>
++<translation id="4235618124995926194">Inclure cet e-mail :</translation>
++<translation id="4874539263382920044">Le titre doit comporter au moins un caractère.</translation>
++<translation id="798525203920325731">Espaces de noms réseau</translation>
++<translation id="263325223718984101"><ph name="PRODUCT_NAME"/> n'a pas pu terminer l'installation, mais va poursuivre son exécution à partir de son image disque.</translation>
++<translation id="7025190659207909717">Gestion des services Internet mobiles</translation>
++<translation id="8265096285667890932">Utiliser les onglets latéraux</translation>
++<translation id="4250680216510889253">Non</translation>
++<translation id="3949593566929137881">Saisir le code PIN de la carte SIM</translation>
++<translation id="6291953229176937411">&amp;Afficher dans le Finder</translation>
++<translation id="2476990193835943955">Maintenez la touche Ctrl, Alt ou Maj enfoncée&lt;br&gt;pour afficher le raccourci clavier qui lui est associé.</translation>
++<translation id="9187827965378254003">Vraiment désolé, aucun prototype n'est disponible pour le moment.</translation>
++<translation id="8933960630081805351">&amp;Afficher dans le Finder</translation>
++<translation id="3041612393474885105">Informations relatives au certificat</translation>
++<translation id="7378810950367401542">/</translation>
++<translation id="4611079913162790275">La synchronisation des mots de passe requiert votre attention.</translation>
++<translation id="6562758426028728553">Veuillez saisir l'ancien et le nouveau code PIN.</translation>
++<translation id="614161640521680948">Langue :</translation>
++<translation id="3665650519256633768">Résultats de recherche</translation>
++<translation id="3733127536501031542">Serveur SSL avec fonction d'optimisation</translation>
++<translation id="3614837889828516995">Enregistrer en PDF</translation>
++<translation id="5745056705311424885">Mémoire USB détectée</translation>
++<translation id="5895875028328858187">M'avertir lorsque le flux de données est faible ou presque inexistant</translation>
++<translation id="939598580284253335">Saisir le mot de passe multiterme</translation>
++<translation id="7917972308273378936">Clavier lituanien</translation>
++<translation id="8371806639176876412">Les éléments saisis dans le champ polyvalent peuvent être enregistrés.</translation>
++<translation id="4216499942524365685">Les informations de connexion à votre compte sont obsolètes. Cliquez ici pour saisir à nouveau votre mot de passe.</translation>
++<translation id="8899388739470541164">Vietnamien</translation>
++<translation id="4091434297613116013">feuilles de papier</translation>
++<translation id="7475671414023905704">URL de mot de passe perdu Netscape</translation>
++<translation id="3335947283844343239">Rouvrir l'onglet fermé</translation>
++<translation id="4089663545127310568">Effacer les mots de passe enregistrés</translation>
++<translation id="6500444002471948304">Créer un nouveau dossier...</translation>
++<translation id="2480626392695177423">Basculer en mode ponctuation pleine chasse ou demi-chasse</translation>
++<translation id="5830410401012830739">Gérer les paramètres de localisation...</translation>
++<translation id="8977410484919641907">Synchronisé...</translation>
++<translation id="2794293857160098038">Options de recherche par défaut</translation>
++<translation id="3947376313153737208">Aucune sélection</translation>
++<translation id="1346104802985271895">Mode de saisie du vietnamien (TELEX)</translation>
++<translation id="5935630983280450497"><ph name="NUMBER_ONE"/> minute restante</translation>
++<translation id="5889282057229379085">Le nombre maximal d'autorités de certification intermédiaires a été dépassé : <ph name="NUM_INTERMEDIATE_CA"/></translation>
++<translation id="3180365125572747493">Saisissez un mot de passe pour chiffrer ce fichier de certificat.</translation>
++<translation id="5496587651328244253">Organiser</translation>
++<translation id="4821086771593057290">Votre mot de passe a changé. Veuillez réessayer avec votre nouveau mot de passe.</translation>
++<translation id="7075513071073410194">PKCS #1 MD5 avec chiffrement RSA</translation>
++<translation id="4378727699507047138">Utiliser le thème classique</translation>
++<translation id="7124398136655728606">Échap efface toute la mémoire tampon de pré-édition</translation>
++<translation id="8293206222192510085">Ajouter aux favoris</translation>
++<translation id="2592884116796016067">Un incident est survenu sur une partie de cette page (HTML WebWorker). Elle risque de ne pas fonctionner correctement.</translation>
++<translation id="2529133382850673012">Clavier américain</translation>
++<translation id="4411578466613447185">Signataire de code</translation>
++<translation id="1354868058853714482">Adobe Reader n'est pas à jour et risque de ne plus être sécurisé.</translation>
++<translation id="6252594924928912846">Personnaliser les paramètres de synchronisation...</translation>
++<translation id="8425755597197517046">Co&amp;ller et rechercher</translation>
++<translation id="1093148655619282731">Détails du certificat sélectionné :</translation>
++<translation id="5568069709869097550">Impossible de se connecter</translation>
++<translation id="2743322561779022895">Activation :</translation>
++<translation id="4181898366589410653">Système de révocation introuvable dans le certificat du serveur</translation>
++<translation id="8705331520020532516">Numéro de série</translation>
++<translation id="1665770420914915777">Afficher la page &quot;Nouvel onglet&quot;</translation>
++<translation id="2629089419211541119">il y a <ph name="NUMBER_ONE"/> heure</translation>
++<translation id="1691063574428301566">Votre ordinateur redémarrera une fois la mise à jour effectuée.</translation>
++<translation id="131364520783682672">Verr. maj.</translation>
++<translation id="6259308910735500867">L'accès au répertoire de l'hôte de communication à distance a été refusé. Essayez avec un autre compte.</translation>
++<translation id="3415261598051655619">Accessible aux scripts :</translation>
++<translation id="2335122562899522968">Cette page place des cookies.</translation>
++<translation id="8461914792118322307">Proxy</translation>
++<translation id="4089521618207933045">Avec sous-menu</translation>
++<translation id="1936157145127842922">Afficher dans le dossier</translation>
++<translation id="6982279413068714821">il y a <ph name="NUMBER_DEFAULT"/> minutes</translation>
++<translation id="7977590112176369853">&lt;saisir une requête&gt;</translation>
++<translation id="3449839693241009168">Appuyez sur <ph name="SEARCH_KEY"/> pour envoyer des commandes à <ph name="EXTENSION_NAME"/>.</translation>
++<translation id="7443484992065838938">Prévisualiser le rapport</translation>
++<translation id="5714678912774000384">Activer le dernier onglet</translation>
++<translation id="3799598397265899467">Lorsque je quitte le navigateur</translation>
++<translation id="2125314715136825419">Continuer sans mettre à jour Adobe Reader (non recommandé)</translation>
++<translation id="1120026268649657149">Le champ de mot clé doit être vide ou comporter un mot unique</translation>
++<translation id="542318722822983047">Déplacer le curseur automatiquement au caractère suivant</translation>
++<translation id="5317780077021120954">Enregistrer</translation>
++<translation id="9027459031423301635">Ouvrir le lien dans un nouvel ongle&amp;t</translation>
++<translation id="2251809247798634662">Nouvelle fenêtre de navigation privée</translation>
++<translation id="358344266898797651">Celtique</translation>
++<translation id="3625870480639975468">Réinitialiser le zoom</translation>
++<translation id="5199729219167945352">Prototypes</translation>
++<translation id="5055518462594137986">Mémoriser mes choix pour tous les liens de ce type</translation>
++<translation id="246059062092993255">Les plug-ins de cette page ont été bloqués.</translation>
++<translation id="2870560284913253234">Site</translation>
++<translation id="6945221475159498467">Sélectionner</translation>
++<translation id="7724603315864178912">Couper</translation>
++<translation id="4164507027399414915">Restaurer toutes les miniatures supprimées</translation>
++<translation id="917051065831856788">Utiliser les onglets latéraux</translation>
++<translation id="1976150099241323601">Se connecter au dispositif de sécurité</translation>
++<translation id="6620110761915583480">Enregistrer le fichier</translation>
++<translation id="4988526792673242964">Pages</translation>
++<translation id="7543025879977230179">Options de <ph name="PRODUCT_NAME"/></translation>
++<translation id="2175607476662778685">Barre de lancement rapide</translation>
++<translation id="6434309073475700221">Annuler</translation>
++<translation id="1367951781824006909">Choisir un fichier</translation>
++<translation id="1425127764082410430">&amp;Rechercher <ph name="SEARCH_TERMS"/> avec <ph name="SEARCH_ENGINE"/></translation>
++<translation id="684265517037058883">(pas encore valide)</translation>
++<translation id="2027538664690697700">Mettre à jour le plug-in...</translation>
++<translation id="8205333955675906842">Police Sans-Serif</translation>
++<translation id="39964277676607559">Impossible de charger le JavaScript &quot;<ph name="RELATIVE_PATH"/>&quot; du script de contenu.</translation>
++<translation id="4378551569595875038">Connexion...</translation>
++<translation id="7029809446516969842">Mots de passe</translation>
++<translation id="8053278772142718589">Fichiers PKCS #12</translation>
++<translation id="3129020372442395066">Options de saisie automatique de <ph name="PRODUCT_NAME_SHORT"/></translation>
++<translation id="4114360727879906392">Fenêtre précédente</translation>
++<translation id="8238649969398088015">Astuce</translation>
++<translation id="5958418293370246440"><ph name="SAVED_FILES"/> / <ph name="TOTAL_FILES"/> fichiers</translation>
++<translation id="2350172092385603347">Localisation utilisée, mais les paramètres régionaux par défaut (default_locale) n'ont pas été indiqués dans le manifeste. </translation>
++<translation id="8221729492052686226">Si vous n'êtes pas à l'origine de cette requête, il s'agit probablement d'une attaque contre votre système. Si vous n'avez pas lancé cette requête de manière intentionnelle, cliquez sur Ne rien faire.</translation>
++<translation id="5894314466642127212">Votre commentaire a bien été envoyé.</translation>
++<translation id="894360074127026135">Fonction d'optimisation internationale Netscape </translation>
++<translation id="6025294537656405544">Taille de police minimale</translation>
++<translation id="1201402288615127009">Suivant</translation>
++<translation id="1335588927966684346">Utilitaire :</translation>
++<translation id="7857823885309308051">Cette opération peut prendre une minute...</translation>
++<translation id="662870454757950142">Le format du mot de passe est incorrect.</translation>
++<translation id="370665806235115550">Chargement...</translation>
++<translation id="1808792122276977615">Ajouter la page...</translation>
++<translation id="2076269580855484719">Masquer ce plug-in</translation>
++<translation id="254416073296957292">&amp;Paramètres linguistiques...</translation>
++<translation id="6652975592920847366">Créer un support de récupération du système d'exploitation</translation>
++<translation id="52912272896845572">Le fichier de clé privée est incorrect.</translation>
++<translation id="3232318083971127729">Valeur :</translation>
++<translation id="8807632654848257479">Stable</translation>
++<translation id="4209092469652827314">Grande</translation>
++<translation id="4222982218026733335">Certificat serveur invalide</translation>
++<translation id="152234381334907219">Jamais enregistrés</translation>
++<translation id="5600599436595580114">Cette page a été préchargée.</translation>
++<translation id="8926468725336609312">Google Chrome ne peut pas afficher l'aperçu avant impression lorsque la visionneuse de documents PDF intégrée est désactivée. Pour l'afficher, veuillez accéder à <ph name="BEGIN_LINK"/>chrome://plugins<ph name="END_LINK"/>, activer &quot;Chrome PDF Viewer&quot; et réessayer.</translation>
++<translation id="8494214181322051417">Nouveau !</translation>
++<translation id="7762841930144642410"><ph name="BEGIN_BOLD"/>Vous êtes passé en navigation privée<ph name="END_BOLD"/>. Les pages que vous consultez dans cette fenêtre n'apparaîtront ni dans l'historique de votre navigateur, ni dans l'historique des recherches, et ne laisseront aucune trace (comme les cookies) sur votre ordinateur une fois que vous aurez fermé la fenêtre de navigation privée. Tous les fichiers téléchargés et les favoris créés seront toutefois conservés. <ph name="LINE_BREAK"/> <ph name="BEGIN_BOLD"/>Passer en navigation privée n'a aucun effet sur les autres utilisateurs, serveurs ou logiciels. Méfiez-vous :<ph name="END_BOLD"/> <ph name="BEGIN_LIST"/> <ph name="BEGIN_LIST_ITEM"/>Des sites Web qui collectent ou partagent des informations vous concernant<ph name="END_LIST_ITEM"/> <ph name="BEGIN_LIST_ITEM"/>Des fournisseurs d'accès Internet ou des employeurs qui conservent une trace des pages que vous visitez<ph name="END_LIST_ITEM"/> <ph name="BEGIN_LIST_ITEM"/>Des programmes indésirables qui enregistrent vos frappes en échange d'émoticônes gratuites<ph name="END_LIST_ITEM"/> <ph name="BEGIN_LIST_ITEM"/>Des personnes qui pourraient surveiller vos activités<ph name="END_LIST_ITEM"/> <ph name="BEGIN_LIST_ITEM"/>Des personnes qui se tiennent derrière vous<ph name="END_LIST_ITEM"/> <ph name="END_LIST"/> <ph name="BEGIN_LINK"/>En savoir plus sur la navigation privée<ph name="END_LINK"/></translation>
++<translation id="2386255080630008482">Le certificat du serveur a été révoqué.</translation>
++<translation id="2135787500304447609">&amp;Reprendre</translation>
++<translation id="8309505303672555187">Sélectionnez un réseau :</translation>
++<translation id="6143635259298204954">Impossible d'extraire les fichiers de l'extension. Pour effectuer cette opération en toute sécurité, vous devez disposer d'un chemin d'accès à votre répertoire de profils ne contenant pas de lien symbolique. Aucun chemin de ce type n'existe pour votre profil.</translation>
++<translation id="1813414402673211292">Effacer les données de navigation</translation>
++<translation id="4062903950301992112">Si vous êtes conscient que la visite de ce site peut être préjudiciable à votre ordinateur, vous pouvez <ph name="PROCEED_LINK"/>.</translation>
++<translation id="32330993344203779">Votre périphérique est inscrit pour bénéficier de la gestion d'entreprise.</translation>
++<translation id="2356762928523809690">Serveur de mise à jour non disponible (erreur : <ph name="ERROR_NUMBER"/>)</translation>
++<translation id="219008588003277019">Module client natif : <ph name="NEXE_NAME"/></translation>
++<translation id="5436510242972373446">Rechercher sur <ph name="SITE_NAME"/> :</translation>
++<translation id="3800764353337460026">Style de symboles</translation>
++<translation id="6719684875142564568"><ph name="NUMBER_ZERO"/> hours</translation>
++<translation id="2096368010154057602">Département</translation>
++<translation id="1036561994998035917">Continuer à utiliser <ph name="ENGINE_NAME"/></translation>
++<translation id="8730621377337864115">OK</translation>
++<translation id="665757950158579497">Essayez de désactiver les prédictions d'actions du réseau en procédant comme suit :
++ Sélectionnez le
++ <ph name="BEGIN_BOLD"/>
++ menu clé à molette &gt;
++ <ph name="SETTINGS_TITLE"/>
++ &gt;
++ <ph name="ADVANCED_TITLE"/>
++ <ph name="END_BOLD"/>
++ et désélectionnez &quot;<ph name="NO_PREFETCH_DESCRIPTION"/>&quot;.
++ Si le problème n'est pas résolu, nous vous conseillons de sélectionner de nouveau
++ cette option pour améliorer les performances.</translation>
++<translation id="4932733599132424254">Date</translation>
++<translation id="6267166720438879315">Sélectionnez un certificat pour vous authentifier sur <ph name="HOST_NAME"/>.</translation>
++<translation id="2422927186524098759">Barre latérale</translation>
++<translation id="7839809549045544450">La clé publique éphémère Diffie-Hellman associée au serveur est peu sûre.</translation>
++<translation id="5515806255487262353">Rechercher dans Dictionnaire</translation>
++<translation id="350048665517711141">Sélectionnez un moteur de recherche</translation>
++<translation id="2790805296069989825">Clavier russe</translation>
++<translation id="5708171344853220004">Nom Microsoft principal</translation>
++<translation id="5464696796438641524">Clavier polonais</translation>
++<translation id="2080010875307505892">Clavier serbe</translation>
++<translation id="2953767478223974804"><ph name="NUMBER_ONE"/> minute</translation>
++<translation id="201192063813189384">Erreur lors de la lecture des données du cache.</translation>
++<translation id="7851768487828137624">Canary</translation>
++<translation id="6129938384427316298">Commentaire du certificat Netscape</translation>
++<translation id="8210608804940886430">Page suivante</translation>
++<translation id="9065596142905430007"><ph name="PRODUCT_NAME"/> est à jour.</translation>
++<translation id="1035650339541835006">Paramètres de saisie automatique...</translation>
++<translation id="6315493146179903667">Tout ramener au premier plan</translation>
++<translation id="1000498691615767391">Sélectionner le dossier à ouvrir</translation>
++<translation id="3593152357631900254">Activer le mode Pinyin fuzzy</translation>
++<translation id="5015344424288992913">Résolution du proxy...</translation>
++<translation id="8506299468868975633">Le téléchargement de l'image a été interrompu.</translation>
++<translation id="4724168406730866204">Eten 26</translation>
++<translation id="308268297242056490">URI</translation>
++<translation id="4479812471636796472">Clavier Dvorak américain</translation>
++<translation id="8673026256276578048">Rechercher sur le Web...</translation>
++<translation id="1437307674059038925">Si vous utilisez un serveur proxy, vérifiez les paramètres associés ou demandez à votre administrateur réseau
++ si ce serveur fonctionne.</translation>
++<translation id="149347756975725155">Impossible de charger l'icône de l'extension &quot;<ph name="ICON"/>&quot;.</translation>
++<translation id="3675321783533846350">Définir un proxy pour se connecter au réseau</translation>
++<translation id="5451285724299252438">zone de texte concernant l'étendue de pages</translation>
++<translation id="5669267381087807207">Activation</translation>
++<translation id="7434823369735508263">Clavier Dvorak britannique</translation>
++<translation id="1572103024875503863"><ph name="NUMBER_MANY"/> jours</translation>
++<translation id="2084978867795361905">MS-IME</translation>
++<translation id="7227669995306390694">Aucun forfait de données <ph name="NETWORK"/></translation>
++<translation id="3481915276125965083">Les fenêtres pop-up suivantes ont été bloquées sur cette page :</translation>
++<translation id="7163503212501929773"><ph name="NUMBER_MANY"/> heures restantes</translation>
++<translation id="7705276765467986571">Impossible de charger le modèle du favori.</translation>
++<translation id="1196338895211115272">Échec d'exportation de la clé privée</translation>
++<translation id="5586329397967040209">Utiliser comme page d'accueil</translation>
++<translation id="629730747756840877">Compte</translation>
++<translation id="8525306231823319788">Plein écran</translation>
++<translation id="9054208318010838">Autoriser tous les sites à suivre ma position géographique</translation>
++<translation id="3058212636943679650">Si la restauration du système d'exploitation de votre ordinateur s'avère nécessaire, une carte SD ou une clé USB de récupération vous sera demandée.</translation>
++<translation id="2815382244540487333">Les cookies suivants ont été bloqués :</translation>
++<translation id="8882395288517865445">Inclure les adresses de ma fiche de Carnet d’adresses</translation>
++<translation id="374530189620960299">Le certificat de sécurité du site n'est pas approuvé !</translation>
++<translation id="8852407435047342287">Votre liste d'applications, d'extensions et de thèmes installés</translation>
++<translation id="5647283451836752568">Exécuter tous les plug-ins de cette page</translation>
++<translation id="8642947597466641025">Augmente la taille du texte</translation>
++<translation id="5188181431048702787">Accepter et continuer »</translation>
++<translation id="1293556467332435079">Fichiers
++</translation>
++<translation id="2490270303663597841">Appliquer uniquement à cette session de navigation privée</translation>
++<translation id="1757915090001272240">Latin large</translation>
++<translation id="8496717697661868878">Exécuter ce plug-in</translation>
++<translation id="3450660100078934250">MasterCard</translation>
++<translation id="2916073183900451334">Sur le Web, Tab permet de sélectionner les liens, ainsi que les champs de formulaire.</translation>
++<translation id="7772127298218883077">À propos de <ph name="PRODUCT_NAME"/></translation>
++<translation id="2090876986345970080">Paramètres de sécurité du système</translation>
++<translation id="9219103736887031265">Images</translation>
++<translation id="5453632173748266363">Cyrillique</translation>
++<translation id="1008557486741366299">Pas maintenant</translation>
++<translation id="8415351664471761088">Attendre la fin du téléchargement</translation>
++<translation id="1545775234664667895">Thème &quot;<ph name="THEME_NAME"/>&quot; installé</translation>
++<translation id="5329858601952122676">&amp;Supprimer</translation>
++<translation id="6100736666660498114">Menu Démarrer</translation>
++<translation id="3994878504415702912">&amp;Zoom</translation>
++<translation id="9009369504041480176">Transfert en cours (<ph name="PROGRESS_PERCENT"/> %)...</translation>
++<translation id="8995603266996330174">Géré par <ph name="DOMAIN"/></translation>
++<translation id="5602600725402519729">&amp;Rafraîchir</translation>
++<translation id="172612876728038702">Configuration du module de plate-forme sécurisée (TPM) en cours. Veuillez patienter, cela peut prendre quelques minutes.</translation>
++<translation id="1362165759943288856">Vous avez acheté une quantité illimitée de données le <ph name="DATE"/>.</translation>
++<translation id="2078019350989722914">Confirmer avant de quitter (<ph name="KEY_EQUIVALENT"/>)</translation>
++<translation id="7965010376480416255">Mémoire partagée</translation>
++<translation id="6248988683584659830">Rech. dans les paramètres</translation>
++<translation id="8323232699731382745">mot de passe d'accès au réseau</translation>
++<translation id="6588399906604251380">Activer la vérification orthographique</translation>
++<translation id="7167621057293532233">Types de données</translation>
++<translation id="7053983685419859001">Bloquer</translation>
++<translation id="2485056306054380289">Certificat de l'autorité de certification du serveur :</translation>
++<translation id="6462109140674788769">Clavier grec</translation>
++<translation id="2727712005121231835">Taille réelle</translation>
++<translation id="8887733174653581061">Toujours en haut</translation>
++<translation id="5581211282705227543">Aucun plug-in installé.</translation>
++<translation id="610886263749567451">Alerte JavaScript</translation>
++<translation id="5488468185303821006">Autoriser en mode navigation privée</translation>
++<translation id="6556866813142980365">Rétablir</translation>
++<translation id="2107287771748948380"><ph name="OBFUSCATED_CC_NUMBER"/>, expire le : <ph name="CC_EXPIRATION_DATE"/></translation>
++<translation id="6584811624537923135">Confirmer la désinstallation</translation>
++<translation id="7429235532957570505">Impossible de désactiver les plug-ins ayant été activés par une stratégie d'entreprise.</translation>
++<translation id="7866522434127619318">Cette fonctionnalité active l'option &quot;Lire en un clic&quot; dans les paramètres de contenu du plug-in.</translation>
++<translation id="8860923508273563464">Attendre la fin des téléchargements</translation>
++<translation id="6406506848690869874">Synchronisation</translation>
++<translation id="5288678174502918605">&amp;Rouvrir l'onglet fermé</translation>
++<translation id="7238461040709361198">Votre mot de passe de compte Google a changé depuis votre dernière connexion à partir de cet ordinateur.</translation>
++<translation id="1956050014111002555">Le fichier contenait plusieurs certificats, aucun d'eux n'a été importé :</translation>
++<translation id="302620147503052030">Afficher le bouton</translation>
++<translation id="5512074755152723588">La saisie dans le champ polyvalent d'une URL déjà ouverte dans un autre onglet entraîne l'affichage de l'onglet en question, et non l'affichage de l'URL dans l'onglet actuel.</translation>
++<translation id="9157595877708044936">Configuration en cours...</translation>
++<translation id="4475552974751346499">Rechercher dans les téléchargements</translation>
++<translation id="3021256392995617989">Me demander lorsqu'un site tente de suivre ma position géographique (recommandé)</translation>
++<translation id="5185386675596372454">La nouvelle version de &quot;<ph name="EXTENSION_NAME"/>&quot; a été désactivée, car elle nécessite davantage d'autorisations.</translation>
++<translation id="4285669636069255873">Clavier phonétique russe</translation>
++<translation id="4148925816941278100">American Express</translation>
++<translation id="2320435940785160168">Ce serveur exige un certificat d'authentification et n'a pas accepté celui envoyé par le navigateur.
++Votre certificat a peut-être expiré ou le serveur n'a pas approuvé l'émetteur.
++Réessayez avec un autre certificat si vous en avez un.
++Sinon, vous devrez en obtenir un nouveau d'un autre émetteur.</translation>
++<translation id="6295228342562451544">Lorsque vous vous connectez à un site Web sécurisé, le serveur hébergeant ce site présente à votre navigateur un &quot;certificat&quot; afin de vérifier l'identité du site. Ce certificat contient des informations d'identité, telles que l'adresse du site Web, laquelle est vérifiée par un tiers approuvé par votre ordinateur. En vérifiant que l'adresse du certificat correspond à l'adresse du site Web, il est possible de s'assurer que vous êtes connecté de façon sécurisée avec le site Web souhaité et non pas avec un tiers (tel qu'un pirate informatique sur votre réseau).</translation>
++<translation id="6342069812937806050">À l'instant</translation>
++<translation id="5605716740717446121">Votre carte SIM sera définitivement désactivée si vous ne saisissez pas correctement la clé de déverrouillage du code PIN. Nombre de tentatives restantes : <ph name="TRIES_COUNT"/></translation>
++<translation id="8836712291807476944"><ph name="SAVED_BYTES"/> / <ph name="TOTAL_BYTES"/> octets, Interrompu</translation>
++<translation id="5502500733115278303">Importés depuis Firefox</translation>
++<translation id="569109051430110155">Détection automatique</translation>
++<translation id="4408599188496843485">&amp;Aide</translation>
++<translation id="5399158067281117682">Les codes PIN sont différents !</translation>
++<translation id="8494234776635784157">Contenu Web</translation>
++<translation id="2681441671465314329">Vider le cache</translation>
++<translation id="3646789916214779970">Rétablir le thème par défaut</translation>
++<translation id="1592960452683145077">Le service de communication à distance a démarré correctement. Vous devriez maintenant pouvoir vous connecter à distance à cet ordinateur.</translation>
++<translation id="1679068421605151609">Outils de développement</translation>
++<translation id="6648524591329069940">Police Serif</translation>
++<translation id="6896758677409633944">Copier</translation>
++<translation id="5260508466980570042">Adresse e-mail ou mot de passe incorrect. Veuillez réessayer.</translation>
++<translation id="7887998671651498201">Le plug-in suivant ne répond pas : souhaitez-vous interrompre <ph name="PLUGIN_NAME"/> ?</translation>
++<translation id="173188813625889224">Sens</translation>
++<translation id="8088823334188264070"><ph name="NUMBER_MANY"/> secondes</translation>
++<translation id="1337036551624197047">Clavier tchèque</translation>
++<translation id="4212108296677106246">Voulez-vous que &quot;<ph name="CERTIFICATE_NAME"/>&quot; soit considérée comme une autorité de certification fiable ?</translation>
++<translation id="2861941300086904918">Gestionnaire de sécurité natif du client</translation>
++<translation id="6991443949605114807">&lt;p&gt;Lorsque vous exécutez <ph name="PRODUCT_NAME"/> dans un environnement de bureau pris en charge, les paramètres proxy du système sont utilisés. Toutefois, soit votre système n'est pas pris en charge, soit un problème est survenu lors du lancement de votre configuration système.&lt;/p&gt;
++
++ &lt;p&gt;Vous avez toujours la possibilité d'effectuer la configuration via la ligne de commande. Pour plus d'informations sur les indicateurs et les variables d'environnement, veuillez vous reporter à &lt;code&gt;man <ph name="PRODUCT_BINARY_NAME"/>&lt;/code&gt;.&lt;/p&gt;</translation>
++<translation id="9071590393348537582">La page Web à l'adresse <ph name="URL"/> a déclenché trop de redirections. Pour résoudre le problème, effacez les cookies de ce site ou autorisez les cookies tiers. Si le problème persiste, il peut être dû à une mauvaise configuration du serveur et n'être aucunement lié à votre ordinateur.</translation>
++<translation id="7205869271332034173">SSID :</translation>
++<translation id="7084579131203911145">Nom du forfait :</translation>
++<translation id="5815645614496570556">Adresse X.400</translation>
++<translation id="3551320343578183772">Fermer l'onglet</translation>
++<translation id="3345886924813989455">Impossible de trouver un navigateur pris en charge.</translation>
++<translation id="74354239584446316">Le compte associé à la boutique en ligne est le suivant : <ph name="EMAIL_ADDRESS"/>. L'utilisation d'un autre compte pour la synchronisation provoque des erreurs.</translation>
++<translation id="3712897371525859903">Enregistrer la p&amp;age sous...</translation>
++<translation id="7926251226597967072"><ph name="PRODUCT_NAME"/> importe actuellement les éléments suivants à partir de <ph name="IMPORT_BROWSER_NAME"/> :</translation>
++<translation id="2767649238005085901">Appuyez sur Entrée pour avancer et sur la touche de menu contextuel pour afficher l'historique</translation>
++<translation id="8580634710208701824">Actualiser le cadre</translation>
++<translation id="1018656279737460067">Annulé</translation>
++<translation id="7606992457248886637">Autorités</translation>
++<translation id="707392107419594760">Sélectionnez votre clavier :</translation>
++<translation id="2007404777272201486">Signaler un problème...</translation>
++<translation id="2390045462562521613">Ignorer ce réseau</translation>
++<translation id="3348038390189153836">Nouveau matériel détecté</translation>
++<translation id="1666788816626221136">Vous disposez de certificats qui n'appartiennent à aucune autre catégorie :</translation>
++<translation id="4821935166599369261">&amp;Profilage activé</translation>
++<translation id="1603914832182249871">(Navigation privée)</translation>
++<translation id="7910768399700579500">&amp;Nouveau dossier</translation>
++<translation id="7472639616520044048">Types MIME :</translation>
++<translation id="2307164895203900614">Afficher les pages en arrière-plan (<ph name="NUM_BACKGROUND_APPS"/>)</translation>
++<translation id="3192947282887913208">Fichiers audio</translation>
++<translation id="6295535972717341389">Plug-ins</translation>
++<translation id="8116190140324504026">Plus d'informations...</translation>
++<translation id="7469894403370665791">Se connecter automatiquement à ce réseau</translation>
++<translation id="4807098396393229769">Titulaire de la carte</translation>
++<translation id="4094130554533891764">Elle peut désormais accéder à :</translation>
++<translation id="4131410914670010031">Noir et blanc</translation>
++<translation id="3800503346337426623">Ignorer la connexion et naviguer en tant qu'invité</translation>
++<translation id="2615413226240911668">Toutefois, cette page inclut d'autres ressources qui ne sont pas sécurisées. Ces ressources peuvent être consultées par des tiers pendant leur transfert, et modifiées par un pirate informatique dans le but de changer l'aspect et le comportement de cette page.</translation>
++<translation id="5880867612172997051">Accès réseau interrompu</translation>
++<translation id="7842346819602959665">La dernière version de l'extension &quot;<ph name="EXTENSION_NAME"/>&quot; requiert d'autres permissions. Elle a donc été désactivée.</translation>
++<translation id="3776667127601582921">Dans ce cas, le certificat du serveur ou un certificat d'autorité intermédiaire présenté à votre navigateur n'est pas valide. Cela peut signifier que le certificat est incorrect, qu'il contient des champs non valides ou qu'il n'est pas compatible.</translation>
++<translation id="2412835451908901523">Veuillez saisir la clé de déverrouillage du code PIN à 8 chiffres fournie par <ph name="CARRIER_ID"/>.</translation>
++<translation id="6979448128170032817">Exceptions...</translation>
++<translation id="7584802760054545466">Connexion à <ph name="NETWORK_ID"/></translation>
++<translation id="208047771235602537">Voulez-vous vraiment quitter <ph name="PRODUCT_NAME"/> alors qu'un téléchargement est en cours ?</translation>
++<translation id="4060383410180771901">Le site Web ne parvient pas à gérer la demande associée à <ph name="URL"/>.</translation>
++<translation id="6710213216561001401">Précédent</translation>
++<translation id="1108600514891325577">&amp;Arrêter</translation>
++<translation id="6035087343161522833">Lorsque l'option permettant de bloquer l'enregistrement des cookies tiers est activée, la lecture de ces cookies est également bloquée.</translation>
++<translation id="8619892228487928601"><ph name="CERTIFICATE_NAME"/> : <ph name="ERROR"/></translation>
++<translation id="1567993339577891801">Console JavaScript</translation>
++<translation id="1548132948283577726">Les sites pour lesquels vos mots de passe ne seront jamais enregistrés s'afficheront ici.</translation>
++<translation id="583281660410589416">Inconnu</translation>
++<translation id="3774278775728862009">Mode de saisie du thaï (clavier TIS-820.2538)</translation>
++<translation id="9115675100829699941">&amp;Favoris</translation>
++<translation id="2485422356828889247">Désinstaller</translation>
++<translation id="2621889926470140926">Voulez-vous vraiment quitter <ph name="PRODUCT_NAME"/> alors que <ph name="DOWNLOAD_COUNT"/> téléchargements sont en cours ?</translation>
++<translation id="7279701417129455881">Configurer le blocage des cookies...</translation>
++<translation id="1166359541137214543">ABC</translation>
++<translation id="5412713837047574330">L'application hébergée par <ph name="HOST_NAME"/> est inaccessible, car vous êtes déconnecté du réseau. Cette page s'affichera dès que la connexion réseau sera rétablie. &lt;br&gt;</translation>
++<translation id="5528368756083817449">Gestionnaire de favoris</translation>
++<translation id="7275974018215686543"><ph name="NUMBER_MANY"/> secs ago</translation>
++<translation id="215753907730220065">Quitter le mode plein écran</translation>
++<translation id="7849264908733290972">Ouvrir l'&amp;image dans un nouvel onglet</translation>
++<translation id="1560991001553749272">Favori ajouté !</translation>
++<translation id="3966072572894326936">Choisir un autre dossier...</translation>
++<translation id="8766796754185931010">Kotoeri</translation>
++<translation id="7781829728241885113">Hier</translation>
++<translation id="2762402405578816341">Synchroniser automatiquement les éléments suivants :</translation>
++<translation id="1623661092385839831">Votre ordinateur intègre un périphérique de sécurité TPM (module de plate-forme sécurisée) qui permet de mettre en œuvre plusieurs fonctionnalités de sécurité critiques dans Google Chrome OS.</translation>
++<translation id="3359256513598016054">Contraintes des stratégies de certificat</translation>
++<translation id="4433914671537236274">Créer un support de récupération</translation>
++<translation id="4509345063551561634">Emplacement :</translation>
++<translation id="7596288230018319236">Toutes les pages que vous consultez apparaîtront ici à moins que vous ne les ouvriez dans une fenêtre en navigation privée. Vous pouvez utiliser le bouton Rechercher de cette page pour rechercher dans toutes les pages de votre historique.</translation>
++<translation id="7434509671034404296">Options pour les développeurs</translation>
++<translation id="6447842834002726250">Cookies</translation>
++<translation id="2609371827041010694">Toujours exécuter pour ce site</translation>
++<translation id="5170568018924773124">Afficher le dossier</translation>
++<translation id="883848425547221593">Autres favoris</translation>
++<translation id="6054173164583630569">Clavier français</translation>
++<translation id="4870177177395420201"><ph name="PRODUCT_NAME"/> ne parvient pas à déterminer ou à définir le navigateur par défaut.</translation>
++<translation id="8898786835233784856">Sélectionner l'onglet suivant</translation>
++<translation id="2674170444375937751">Voulez-vous vraiment supprimer ces pages de votre historique ?</translation>
++<translation id="9111102763498581341">Déverrouiller</translation>
++<translation id="289695669188700754">ID de clé : <ph name="KEY_ID"/></translation>
++<translation id="3067198360141518313">Exécuter ce plug-in</translation>
++<translation id="8767072502252310690">Utilisateurs</translation>
++<translation id="683526731807555621">Ajouter un moteur</translation>
++<translation id="6871644448911473373">Répondeur OCSP : <ph name="LOCATION"/></translation>
++<translation id="8281886186245836920">Ignorer</translation>
++<translation id="3867944738977021751">Champs de certificat</translation>
++<translation id="2114224913786726438">Modules (<ph name="TOTAL_COUNT"/>) : aucun conflit détecté.</translation>
++<translation id="7629827748548208700">Onglet : <ph name="TAB_NAME"/></translation>
++<translation id="388442998277590542">Impossible de charger la page d'options &quot;<ph name="OPTIONS_PAGE"/>&quot;.</translation>
++<translation id="8449008133205184768">Coller en adaptant le style</translation>
++<translation id="9114223350847410618">Veuillez ajouter une autre langue avant de supprimer celle-ci.</translation>
++<translation id="4408427661507229495">nom du réseau</translation>
++<translation id="8886960478266132308"><ph name="PRODUCT_NAME"/> synchronise de manière sécurisée vos données avec votre compte Google.</translation>
++<translation id="8028993641010258682">Taille</translation>
++<translation id="5031603669928715570">Activer...</translation>
++<translation id="1383876407941801731">Recherche</translation>
++<translation id="8398877366907290961">Poursuivre quand même</translation>
++<translation id="5063180925553000800">Nouveau code PIN :</translation>
++<translation id="2496540304887968742">La capacité du périphérique doit être d'au moins 4 Go.</translation>
++<translation id="6974053822202609517">De droite à gauche</translation>
++<translation id="2370882663124746154">Activer le mode Pinyin double</translation>
++<translation id="5463856536939868464">Menu contenant des favoris masqués</translation>
++<translation id="8286227656784970313">Utiliser le dictionnaire système</translation>
++<translation id="5431084084184068621">Vous avez choisi de chiffrer les données à l'aide de votre mot de passe Google. Vous pouvez modifier vos paramètres de synchronisation à tout moment, si vous changez d'avis.</translation>
++<translation id="1493263392339817010">Personnaliser les polices...</translation>
++<translation id="5352033265844765294">Enregistrement des informations de date</translation>
++<translation id="6449085810994685586">&amp;Vérifier l'orthographe du texte de ce champ</translation>
++<translation id="3621320549246006887">Ceci est un modèle expérimental qui permet aux enregistrements DNS (utilisant le protocole de sécurité DNSSEC) d'autoriser ou de refuser des certificats HTTPS. Ce message s'affiche lorsque vous activez des fonctionnalités expérimentales via des options de ligne de commande. Vous pouvez supprimer ces options de ligne de commande pour ignorer cette erreur.</translation>
++<translation id="50960180632766478"><ph name="NUMBER_FEW"/> minutes restantes</translation>
++<translation id="3174168572213147020">Île</translation>
++<translation id="748138892655239008">Contraintes de base du certificat</translation>
++<translation id="457386861538956877">Autres...</translation>
++<translation id="8063491445163840780">Activer l'onglet 4</translation>
++<translation id="5966654788342289517">Données personnelles</translation>
++<translation id="9137013805542155359">Afficher l'original</translation>
++<translation id="4792385443586519711">Nom de la société</translation>
++<translation id="6423731501149634044">Définir Adobe Reader comme visionneuse de documents PDF par défaut ?</translation>
++<translation id="8839907368860424444">Pour gérer les extensions installées, cliquez sur Extensions dans le menu Fenêtre.</translation>
++<translation id="2461687051570989462">Accédez à vos imprimantes depuis n'importe quel ordinateur ou smartphone. <ph name="BEGIN_LINK"/>En savoir plus<ph name="END_LINK"/></translation>
++<translation id="7194430665029924274">Me &amp;le rappeler plus tard</translation>
++<translation id="5790085346892983794">Opération réussie !</translation>
++<translation id="1901769927849168791">Carte SD détectée.</translation>
++<translation id="818454486170715660"><ph name="NAME"/> - Propriétaire</translation>
++<translation id="1358032944105037487">Clavier japonais</translation>
++<translation id="8201956630388867069">WPA</translation>
++<translation id="603890000178803545">janv.^févr.^mars^avr.^mai^juin^juil.^août^sept.^oct.^nov.^déc.</translation>
++<translation id="8302838426652833913">Sélectionnez
++ <ph name="BEGIN_BOLD"/>
++ Applications &gt; Préférences système &gt; Réseau &gt; Assistant
++ <ph name="END_BOLD"/>
++ pour tester votre connexion.</translation>
++<translation id="8664389313780386848">&amp;Afficher le code source de la page</translation>
++<translation id="8970407809569722516">Micrologiciel :</translation>
++<translation id="1180549724812639004">Créer un profil</translation>
++<translation id="57646104491463491">Date de modification</translation>
++<translation id="5992752872167177798">Sandbox seccomp</translation>
++<translation id="6362853299801475928">Signale&amp;r un problème...</translation>
++<translation id="3289566588497100676">Entrée de symboles simplifiée</translation>
++<translation id="6507969014813375884">Chinois simplifié</translation>
++<translation id="7314244761674113881">Hôte SOCKS</translation>
++<translation id="5285794783728826432">Considérer ce certificat comme fiable pour identifier les sites Web.</translation>
++<translation id="4224803122026931301">Exceptions de localisation</translation>
++<translation id="749452993132003881">Hiragana</translation>
++<translation id="8226742006292257240">Le mot de passe TPM ci-dessous, généré de façon aléatoire, a été attribué à votre ordinateur :</translation>
++<translation id="8487693399751278191">Importer mes favoris maintenant...</translation>
++<translation id="7985242821674907985"><ph name="PRODUCT_NAME"/></translation>
++<translation id="7484580869648358686">Avertissement : Un problème a été détecté sur cette page.</translation>
++<translation id="2074739700630368799">Avec Google Chrome OS for business, vous pouvez connecter votre périphérique à Google Apps, ce qui vous permet de le rechercher et de le contrôler depuis le panneau de configuration de Google Apps.</translation>
++<translation id="4474155171896946103">Ajouter tous les onglets aux favoris...</translation>
++<translation id="5895187275912066135">Émis le</translation>
++<translation id="1190844492833803334">Lorsque je ferme le navigateur</translation>
++<translation id="5646376287012673985">Localisation</translation>
++<translation id="1110155001042129815">Attendre</translation>
++<translation id="2607101320794533334">Infos sur la clé publique de l'objet</translation>
++<translation id="7071586181848220801">Plug-in inconnu</translation>
++<translation id="3354601307791487577">Connexion en mode invité</translation>
++<translation id="4419409365248380979">Toujours autoriser <ph name="HOST"/> à paramétrer les cookies</translation>
++<translation id="2956070106555335453">Résumé</translation>
++<translation id="917450738466192189">Le certificat du serveur n'est pas valide.</translation>
++<translation id="2649045351178520408">Chaîne de certificats codés Base 64 ASCII</translation>
++<translation id="7424526482660971538">Choisir mon propre mot de passe multiterme</translation>
++<translation id="380271916710942399">Certificat de serveur non répertorié</translation>
++<translation id="6459488832681039634">Rechercher la sélection</translation>
++<translation id="2392369802118427583">Activer</translation>
++<translation id="9040421302519041149">L'accès à ce réseau est protégé.</translation>
++<translation id="5659593005791499971">E-mail</translation>
++<translation id="8235325155053717782">Erreur <ph name="ERROR_NUMBER"/> (<ph name="ERROR_NAME"/>) : <ph name="ERROR_TEXT"/></translation>
++<translation id="6584878029876017575">Signature permanente Microsoft</translation>
++<translation id="562901740552630300">Sélectionnez
++ <ph name="BEGIN_BOLD"/>
++ Démarrer &gt; Panneau de configuration &gt; Réseau et Internet &gt; Centre Réseau et partage &gt; Résolution des problèmes (en bas) &gt; Connexions Internet.
++ <ph name="END_BOLD"/></translation>
++<translation id="8816996941061600321">Gestionnaire de &amp;fichiers</translation>
++<translation id="2773223079752808209">Service client</translation>
++<translation id="4585473702689066695">Impossible de se connecter au réseau &quot;<ph name="NAME"/>&quot;.</translation>
++<translation id="4647175434312795566">J'accepte ces termes</translation>
++<translation id="1084824384139382525">Copier l'adr&amp;esse du lien</translation>
++<translation id="1221462285898798023">Veuillez démarrer <ph name="PRODUCT_NAME"/> en tant qu'utilisateur normal. Pour l'exécuter en tant que root, vous devez indiquer un autre répertoire de données utilisateur pour stocker les informations du profil.</translation>
++<translation id="3220586366024592812">Le processus du connecteur <ph name="CLOUD_PRINT_NAME"/> est bloqué. Voulez-vous le redémarrer ?</translation>
++<translation id="5042992464904238023">Contenu Web</translation>
++<translation id="6254503684448816922">Clé compromise</translation>
++<translation id="1181037720776840403">Supprimer</translation>
++<translation id="4006726980536015530">Si vous fermez <ph name="PRODUCT_NAME"/> maintenant, ces téléchargements seront annulés.</translation>
++<translation id="4194415033234465088">Dachen 26</translation>
++<translation id="1664712100580477121">Voulez-vous vraiment graver l'image sur le périphérique suivant :</translation>
++<translation id="6639554308659482635">Mémoire SQLite</translation>
++<translation id="8141503649579618569"><ph name="DOWNLOAD_RECEIVED"/>/<ph name="DOWNLOAD_TOTAL"/>, <ph name="TIME_LEFT"/></translation>
++<translation id="7650701856438921772"><ph name="PRODUCT_NAME"/> est affiché dans cette langue.</translation>
++<translation id="740624631517654988">Fenêtre pop-up bloquée</translation>
++<translation id="3738924763801731196"><ph name="OID"/> :</translation>
++<translation id="6550769511678490130">Ouvrir tous les favoris</translation>
++<translation id="1847961471583915783">Effacer les cookies et autres données de site et de plug-in lorsque je ferme le navigateur</translation>
++<translation id="8870318296973696995">Page d'accueil</translation>
++<translation id="6659594942844771486">Onglet</translation>
++<translation id="6575134580692778371">Non configuré</translation>
++<translation id="4624768044135598934">Opération réussie !</translation>
++<translation id="6014776969142880350">Relancez <ph name="PRODUCT_NAME"/> pour terminer la mise à jour.</translation>
++<translation id="5582768900447355629">Chiffrer toutes mes données</translation>
++<translation id="6122365914076864562">Veuillez patienter pendant que nous configurons votre réseau pour mobile.</translation>
++<translation id="1974043046396539880">Points de distribution de listes de révocation des certificats</translation>
++<translation id="7049357003967926684">Association</translation>
++<translation id="8641392906089904981">Appuyez sur Maj+Alt pour changer la disposition du clavier.</translation>
++<translation id="3024374909719388945">Utiliser l'horloge au format 24 heures</translation>
++<translation id="1867780286110144690"><ph name="PRODUCT_NAME"/> est prêt à terminer l'installation.</translation>
++<translation id="5316814419223884568">Lancez votre recherche à partir d'ici</translation>
++<translation id="8142732521333266922">OK, synchroniser tout</translation>
++<translation id="965674096648379287">Afin d'être correctement affichée, cette page requiert des données que vous avez précédemment entrées. Vous pouvez de nouveau transmettre ces données, mais, en procédant ainsi, vous devrez répéter chaque action que cette page a effectuée auparavant. Cliquez sur Rafraîchir pour transmettre de nouveau ces données et pour afficher cette page.</translation>
++<translation id="43742617823094120">Cela signifie que le certificat présenté à votre navigateur a été révoqué par son émetteur. L'intégrité de ce certificat a certainement été compromise, et il ne doit donc pas être approuvé. Ne poursuivez pas.</translation>
++<translation id="9019654278847959325">Clavier slovaque</translation>
++<translation id="18139523105317219">Nom de partie EDI</translation>
++<translation id="6657193944556309583">Vous avez déjà chiffré des données avec un mot de passe multiterme. Saisissez-le ci-dessous.</translation>
++<translation id="3328801116991980348">Informations sur le site</translation>
++<translation id="1205605488412590044">Créer un raccourci vers l'application...</translation>
++<translation id="2065985942032347596">Authentification requise</translation>
++<translation id="2553340429761841190"><ph name="PRODUCT_NAME"/> n'est pas parvenu à se connecter à <ph name="NETWORK_ID"/>. Sélectionnez un autre réseau ou réessayez.</translation>
++<translation id="2086712242472027775">Votre compte n'est pas compatible avec <ph name="PRODUCT_NAME"/>. Contactez l'administrateur de votre domaine ou utilisez un compte Google standard pour vous connecter.</translation>
++<translation id="7222232353993864120">Adresse e-mail</translation>
++<translation id="2128531968068887769">Client natif</translation>
++<translation id="7175353351958621980">Chargé depuis :</translation>
++<translation id="4590074117005971373">Active les balises canvas hautes performances dans un contexte 2D, pour effectuer le rendu via le processeur graphique.</translation>
++<translation id="7186367841673660872">Cette page en<ph name="ORIGINAL_LANGUAGE"/>a été traduite en<ph name="LANGUAGE_LANGUAGE"/></translation>
++<translation id="8448695406146523553">Seule une personne en possession de votre mot de passe multiterme peut lire vos données chiffrées. Google ne reçoit ni n'enregistre votre mot de passe multiterme. Si vous oubliez votre mot de passe multiterme, vous devrez</translation>
++<translation id="6052976518993719690">Autorité de certification SSL</translation>
++<translation id="1636959874332483835"><ph name="HOST_NAME"/> contient un logiciel malveillant. Votre ordinateur pourrait être infecté par un virus si vous consultez ce site.</translation>
++<translation id="8050783156231782848">Aucune donnée disponible.</translation>
++<translation id="1175364870820465910">Im&amp;primer...</translation>
++<translation id="3866249974567520381">Description</translation>
++<translation id="2900139581179749587">Voix non reconnue.</translation>
++<translation id="953692523250483872">Aucun fichier sélectionné</translation>
++<translation id="2294358108254308676">Souhaitez-vous installer <ph name="PRODUCT_NAME"/> ?</translation>
++<translation id="6549689063733911810">Activité récente</translation>
++<translation id="1529968269513889022">de la dernière semaine</translation>
++<translation id="5542132724887566711">Profil</translation>
++<translation id="5196117515621749903">Actualiser sans utiliser le cache</translation>
++<translation id="5552632479093547648">Logiciels malveillants et sites de phishing détectés !</translation>
++<translation id="4310537301481716192">Onglet fermé !</translation>
++<translation id="4988273303304146523">il y a <ph name="NUMBER_DEFAULT"/> jours</translation>
++<translation id="8428213095426709021">Paramètres</translation>
++<translation id="1588343679702972132">Ce site exige que vous vous identifiiez avec un certificat :</translation>
++<translation id="7211994749225247711">Supprimer...</translation>
++<translation id="2819994928625218237">&amp;Aucune suggestion orthographique</translation>
++<translation id="1065449928621190041">Clavier franco-canadien</translation>
++<translation id="8327626790128680264">Clavier étendu américain</translation>
++<translation id="2950186680359523359">Le serveur a mis fin à la connexion sans envoyer de données.</translation>
++<translation id="9142623379911037913">Autoriser <ph name="SITE"/> à afficher des notifications sur le Bureau ?</translation>
++<translation id="4196320913210960460">Pour gérer les extensions installées, cliquez sur Extensions dans le menu Outils.</translation>
++<translation id="3449494395612243720">Erreur de synchronisation, veuillez vous connecter à nouveau.</translation>
++<translation id="9118804773997839291">La liste suivante fait état des éléments dangereux détectés sur la page. Cliquez sur le lien &quot;Diagnostic&quot; pour obtenir plus d'informations sur un élément particulier.</translation>
++<translation id="7139724024395191329">Émirat</translation>
++<translation id="1761265592227862828">Synchroniser tous les paramètres et toutes les données\n(peut prendre un certain temps)</translation>
++<translation id="7754704193130578113">Toujours demander où enregistrer les fichiers</translation>
++<translation id="204914487372604757">Créer un raccourci</translation>
++<translation id="2497284189126895209">Tous les fichiers</translation>
++<translation id="696036063053180184">Sebeol-sik No-shift</translation>
++<translation id="452785312504541111">Anglais (pleine chasse)</translation>
++<translation id="945332329539165145">2D avec canvas et accélération matérielle</translation>
++<translation id="5220797120063118010">Cette fonctionnalité autorise l'installation d'applications Google Chrome déployées à partir d'un manifeste situé sur une page Web, plutôt qu'avec un fichier crx contenant le manifeste et les icônes.</translation>
++<translation id="9148126808321036104">Nouvelle connexion</translation>
++<translation id="2282146716419988068">GPU</translation>
++<translation id="428771275901304970">Moins de 1 Mo disponible</translation>
++<translation id="1682548588986054654">Nouvelle fenêtre de navigation privée</translation>
++<translation id="6833901631330113163">Europe du Sud</translation>
++<translation id="8691262314411702087">Sélectionner les éléments à synchroniser</translation>
++<translation id="6065289257230303064">Attributs du répertoire de l'objet du certificat</translation>
++<translation id="2423017480076849397">Accédez à vos imprimantes et partagez-les en ligne via <ph name="CLOUD_PRINT_NAME"/>.</translation>
++<translation id="569520194956422927">&amp;Ajouter...</translation>
++<translation id="4018133169783460046">Afficher <ph name="PRODUCT_NAME"/> dans cette langue</translation>
++<translation id="5110450810124758964">il y a <ph name="NUMBER_ONE"/> jour</translation>
++<translation id="3264544094376351444">Police Sans-Serif</translation>
++<translation id="5586942249556966598">Ne rien faire</translation>
++<translation id="2820806154655529776"><ph name="NUMBER_ONE"/> seconde</translation>
++<translation id="1077946062898560804">Configurer les mises à jour automatiques pour tous les utilisateurs</translation>
++<translation id="3122496702278727796">Échec de la création du répertoire des données</translation>
++<translation id="4517036173149081027">Fermer et annuler le chargement</translation>
++<translation id="7150146631451105528"><ph name="DATE"/></translation>
++<translation id="3166547286524371413">Adresse :</translation>
++<translation id="4522570452068850558">Détails</translation>
++<translation id="59659456909144943">Notification : <ph name="NOTIFICATION_NAME"/></translation>
++<translation id="6731320427842222405">Cette opération peut prendre quelques minutes.</translation>
++<translation id="4806525999832945986">Géré par <ph name="DOMAIN"/> (<ph name="STATUS"/>)</translation>
++<translation id="7503191893372251637">Type de certificat Netscape</translation>
++<translation id="1502960562739459116">Impossible d'afficher certaines parties de ce document PDF. Souhaitez-vous installer Adobe Reader ?</translation>
++<translation id="4135450933899346655">Vos certificats</translation>
++<translation id="4731578803613910821">Vos données personnelles sur <ph name="WEBSITE_1"/>, <ph name="WEBSITE_2"/> et <ph name="WEBSITE_3"/></translation>
++<translation id="7716781361494605745">URL de stratégie de l'autorité de certification Netscape</translation>
++<translation id="2881966438216424900">Dernier accès :</translation>
++<translation id="7552203043556919163">Synchroniser les mots de passe</translation>
++<translation id="630065524203833229">&amp;Quitter</translation>
++<translation id="4647090755847581616">&amp;Fermer l'onglet</translation>
++<translation id="2649204054376361687"><ph name="CITY"/>, <ph name="COUNTRY"/></translation>
++<translation id="7886758531743562066">Le site Web à l'adresse <ph name="HOST_NAME"/> contient des éléments provenant de sites qui semblent héberger des logiciels malveillants. Ces derniers peuvent nuire à votre ordinateur ou agir à votre insu. Le simple fait de visiter un site hébergeant ce type de logiciels peut infecter votre ordinateur.</translation>
++<translation id="2064746092913005102">Total : <ph name="NUMBER_OF_PAGES"/> <ph name="PAGE_OR_PAGES_LABEL"/> <ph name="TWO_SIDED"/> <ph name="TIMES"/> <ph name="NUMBER_OF_COPIES"/> <ph name="COPIES_LABEL"/> <ph name="EQUAL_SIGN"/> <ph name="NUMBER_OF_SHEETS"/> <ph name="SHEETS_LABEL"/></translation>
++<translation id="7538227655922918841">Les cookies de plusieurs sites ont été autorisés pour la session uniquement.</translation>
++<translation id="2385700042425247848">Nom du service :</translation>
++<translation id="7751005832163144684">Imprimer une page de test</translation>
++<translation id="3638865692466101147">Aperçu avant impression - <ph name="PREVIEW_TAB_TITLE"/></translation>
++<translation id="1471300011765310414"><ph name="PRODUCT_NAME"/>
++ ne peut pas à afficher la page Web, car votre ordinateur n'est pas connecté à Internet.</translation>
++<translation id="5464632865477611176">Exécuter cette fois</translation>
++<translation id="4268025649754414643">Chiffrement de la clé</translation>
++<translation id="7925247922861151263">Échec de la vérification AAA</translation>
++<translation id="1168020859489941584">Ouverture dans <ph name="TIME_REMAINING"/>...</translation>
++<translation id="7814458197256864873">&amp;Copier</translation>
++<translation id="8186706823560132848">Logiciel</translation>
++<translation id="4692623383562244444">Moteurs de recherche</translation>
++<translation id="567760371929988174">&amp;Méthodes d'entrée</translation>
++<translation id="10614374240317010">Jamais enregistrés</translation>
++<translation id="5116300307302421503">Impossible d'analyser le fichier.</translation>
++<translation id="2745080116229976798">Subordination qualifiée Microsoft</translation>
++<translation id="2526590354069164005">Bureau</translation>
++<translation id="7983301409776629893">Toujours traduire en <ph name="TARGET_LANGUAGE"/> les pages en <ph name="ORIGINAL_LANGUAGE"/></translation>
++<translation id="4890284164788142455">Thaï</translation>
++<translation id="4312207540304900419">Activer l'onglet suivant</translation>
++<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> de chargement</translation>
++<translation id="7648048654005891115">Style de mappage du clavier</translation>
++<translation id="539295039523818097">Un problème lié à votre microphone s'est produit.</translation>
++<translation id="4033319557821527966"><ph name="CLOUD_PRINT_NAME"/> vous permet d'accéder aux imprimantes de cet ordinateur, où que vous soyez. Connectez-vous pour l'activer.</translation>
++<translation id="6970216967273061347">District</translation>
++<translation id="4479639480957787382">Ethernet</translation>
++<translation id="6312403991423642364">Erreur de réseau inconnue.</translation>
++<translation id="751377616343077236">Nom du certificat</translation>
++<translation id="7154108546743862496">Plus d'informations</translation>
++<translation id="8637688295594795546">Mise à jour du système disponible. Préparation du téléchargement…</translation>
++<translation id="5167270755190684957">Galerie des thèmes Google Chrome</translation>
++<translation id="8382913212082956454">Copi&amp;er l'adresse e-mail</translation>
++<translation id="7447930227192971403">Activer l'onglet 3</translation>
++<translation id="2903493209154104877">Adresses</translation>
++<translation id="2056143100006548702">Plug-in : <ph name="PLUGIN_NAME"/> (<ph name="PLUGIN_VERSION"/>)</translation>
++<translation id="3479552764303398839">Pas maintenant</translation>
++<translation id="6445051938772793705">Pays</translation>
++<translation id="3251759466064201842">&lt;Ne fait pas partie du certificat&gt;</translation>
++<translation id="4229495110203539533">il y a <ph name="NUMBER_ONE"/> seconde</translation>
++<translation id="6410257289063177456">Fichiers image</translation>
++<translation id="6419902127459849040">Europe centrale</translation>
++<translation id="6707389671160270963">Certificat client SSL</translation>
++<translation id="6083557600037991373">Pour accélérer l'affichage des pages Web,
++ <ph name="PRODUCT_NAME"/>
++ enregistre temporairement les fichiers téléchargés sur le disque. Si
++ <ph name="PRODUCT_NAME"/>
++ ne s'arrête pas correctement, ces fichiers peuvent être endommagés, ce qui
++ génère cette erreur. L'actualisation de la page devrait permettre de résoudre
++ ce problème ; celui-ci ne se reproduira vraisemblablement plus si l'arrêt s'effectue
++ correctement.
++ <ph name="LINE_BREAK"/>
++ Si le problème persiste, essayez de supprimer le contenu du cache. Cette
++ erreur peut aussi indiquer que le matériel est sur le point de tomber
++ en panne.</translation>
++<translation id="5298219193514155779">Thème créé par</translation>
++<translation id="7366909168761621528">Données de navigation</translation>
++<translation id="1047726139967079566">Ajouter cette page aux favoris</translation>
++<translation id="9020142588544155172">Le serveur a refusé la connexion.</translation>
++<translation id="6113225828180044308">Module (<ph name="MODULUS_NUM_BITS"/> bits) :\n<ph name="MODULUS_HEX_DUMP"/>\n\nExposant public (<ph name="PUBLIC_EXPONENT_NUM_BITS"/> bits) :\n<ph name="EXPONENT_HEX_DUMP"/></translation>
++<translation id="2544782972264605588"><ph name="NUMBER_DEFAULT"/> secondes restantes</translation>
++<translation id="8871696467337989339">Vous utilisez un indicateur de ligne de commande non pris en charge : <ph name="BAD_FLAG"/>. La stabilité et la sécurité en seront affectées.</translation>
++<translation id="4767443964295394154">Emplacement de téléchargement</translation>
++<translation id="5031870354684148875">À propos de Google Traduction</translation>
++<translation id="720658115504386855">Les lettres ne sont pas sensibles à la casse.</translation>
++<translation id="2454247629720664989">Mot clé</translation>
++<translation id="3950820424414687140">Connexion</translation>
++<translation id="4626106357471783850">Redémarrez <ph name="PRODUCT_NAME"/> pour appliquer la mise à jour.</translation>
++<translation id="1697068104427956555">Sélectionner un carré dans l'image</translation>
++<translation id="2840798130349147766">Bases de données Web</translation>
++<translation id="1628736721748648976">Codage</translation>
++<translation id="1198271701881992799">Mise en route</translation>
++<translation id="782590969421016895">Utiliser les pages actuelles</translation>
++<translation id="6521850982405273806">Signaler une erreur</translation>
++<translation id="736515969993332243">Recherche de réseaux en cours</translation>
++<translation id="8026334261755873520">Effacer les données de navigation</translation>
++<translation id="1769104665586091481">Ouvrir le lien dans une nouvelle &amp;fenêtre</translation>
++<translation id="8503813439785031346">Nom d'utilisateur</translation>
++<translation id="5319782540886810524">Clavier letton</translation>
++<translation id="8651585100578802546">Forcer l'actualisation de cette page</translation>
++<translation id="685714579710025096">Disposition du clavier :</translation>
++<translation id="1361655923249334273">Non utilisé</translation>
++<translation id="290555789621781773"><ph name="NUMBER_TWO"/> minutes</translation>
++<translation id="5434065355175441495">Chiffrement RSA PKCS #1</translation>
++<translation id="7073704676847768330">Ce n'est probablement pas le site que vous recherchez !</translation>
++<translation id="8477384620836102176">&amp;Général</translation>
++<translation id="1074663319790387896">Configurer la synchronisation</translation>
++<translation id="4302315780171881488">État de connexion :</translation>
++<translation id="3391392691301057522">Ancien code PIN :</translation>
++<translation id="1344519653668879001">Désactiver le contrôle des liens hypertexte</translation>
++<translation id="6463795194797719782">&amp;Modifier</translation>
++<translation id="4262113024799883061">Chinois</translation>
++<translation id="4775879719735953715">Navigateur par défaut</translation>
++<translation id="5575473780076478375">Extension en mode navigation privée :<ph name="EXTENSION_NAME"/></translation>
++<translation id="4188026131102273494">Mot clé :</translation>
++<translation id="2930644991850369934">Un problème est survenu lors du téléchargement de l'image de récupération. La connexion réseau a été perdue.</translation>
++<translation id="3461610253915486539">Votre administrateur a désactivé certaines préférences.</translation>
++<translation id="5750053751252005701">Forfait de données <ph name="NETWORK"/> épuisé</translation>
++<translation id="8858939932848080433">Veuillez indiquer à quel niveau vous rencontrez des problèmes avant d'envoyer vos commentaires.</translation>
++<translation id="1720318856472900922">Authentification du serveur WWW TLS</translation>
++<translation id="8550022383519221471">Le service de synchronisation n'est pas disponible pour votre domaine.</translation>
++<translation id="3355823806454867987">Modifier les paramètres du proxy...</translation>
++<translation id="4780374166989101364">Cette fonctionnalité active les API des extensions expérimentales. Notez que vous ne pouvez pas mettre en ligne des extensions qui font appel aux API expérimentales dans la galerie d'extensions.</translation>
++<translation id="7227780179130368205">Un logiciel malveillant a été détecté !</translation>
++<translation id="435243347905038008">Forfait de données <ph name="NETWORK"/> presque épuisé</translation>
++<translation id="2489428929217601177">des dernières 24 heures</translation>
++<translation id="7418490403869327287">Une fois activée, la recherche instantanée charge la plupart des pages Web dès que vous saisissez l'URL dans le champ polyvalent, avant même que vous n'appuyiez sur Entrée. Si votre moteur de recherche par défaut est compatible, toute lettre saisie dans ce champ offre de nouveaux résultats et les prédictions intégrées vous guident dans vos recherches.\n\nChaque touche utilisée fait l'objet d'une requête, par conséquent il se peut que les éléments saisies dans le champ polyvalent soient enregistrés par votre moteur de recherche par défaut.\n</translation>
++<translation id="5149131957118398098"><ph name="NUMBER_ZERO"/> hours left</translation>
++<translation id="2541913031883863396">poursuivre quand même</translation>
++<translation id="4278390842282768270">Autorisé</translation>
++<translation id="2074527029802029717">Retirer l'onglet</translation>
++<translation id="1533897085022183721">Moins de <ph name="MINUTES"/></translation>
++<translation id="7503821294401948377">Impossible de charger l'icône &quot;<ph name="ICON"/>&quot; d'action du navigateur.</translation>
++<translation id="5539694491979265537">Consulter Google Dashboard</translation>
++<translation id="3942946088478181888">Plus d'informations</translation>
++<translation id="3722396466546931176">Ajoutez des langues puis faites-les glisser pour les classer dans l'ordre souhaité.</translation>
++<translation id="7396845648024431313"><ph name="APP_NAME"/> sera lancé au démarrage du système et continuera de s'exécuter en arrière-plan, même toutes les fenêtres de <ph name="PRODUCT_NAME"/> sont fermées.</translation>
++<translation id="8539727552378197395">Non (HttpOnly)</translation>
++<translation id="4519351128520996510">Saisir votre mot de passe multiterme pour la synchronisation</translation>
++<translation id="2391419135980381625">Police standard</translation>
++<translation id="7893393459573308604"><ph name="ENGINE_NAME"/> (par défaut)</translation>
++<translation id="5392544185395226057">Cette fonctionnalité active la prise en charge du client natif.</translation>
++<translation id="5400640815024374115">La puce du module de plate-forme sécurisée (TPM) est désactivée ou inexistante.</translation>
++<translation id="2151576029659734873">L'index de l'onglet indiqué est incorrect.</translation>
++<translation id="5150254825601720210">Nom du serveur SSL du certificat Netscape</translation>
++<translation id="6771503742377376720">Est une autorité de certification</translation>
++<translation id="8814190375133053267">Wi-Fi</translation>
++<translation id="2040078585890208937">Connexion à <ph name="NAME"/></translation>
++<translation id="8410619858754994443">Confirmer le mot de passe :</translation>
++<translation id="2210840298541351314">Aperçu avant impression</translation>
++<translation id="3858678421048828670">Clavier italien</translation>
++<translation id="4938277090904056629">Impossible d'établir une connexion sécurisée à cause de l'antivirus ESET.</translation>
++<translation id="4521805507184738876">(expiré)</translation>
++<translation id="111844081046043029">Voulez-vous vraiment quitter cette page ?</translation>
++<translation id="1951615167417147110">Faire défiler d'une page vers le haut</translation>
++<translation id="4154664944169082762">Empreintes</translation>
++<translation id="3202578601642193415">Le plus récent</translation>
++<translation id="8112886015144590373"><ph name="NUMBER_FEW"/> heures</translation>
++<translation id="1398853756734560583">Agrandir</translation>
++<translation id="8988255471271407508">La page Web est introuvable dans le cache. Certaines ressources ne sont restituées fidèlement que si elles sont extraites du cache, notamment les pages générées à partir de données que vous avez envoyées. <ph name="LINE_BREAK"/> Cette erreur peut également être due à un cache endommagé lors d'une fermeture incorrecte. <ph name="LINE_BREAK"/> Si le problème persiste, essayez d'effacer le cache.</translation>
++<translation id="1195977189444203128">Le plug-in <ph name="PLUGIN_NAME"/> n'est plus à jour.</translation>
++<translation id="3878562341724547165">Vous avez changé de position. Souhaitez-vous utiliser <ph name="NEW_GOOGLE_URL"/> ?</translation>
++<translation id="1758018619400202187">EAP-TLS</translation>
++<translation id="6690744523875189208"><ph name="NUMBER_TWO"/> heures</translation>
++<translation id="8053390638574070785">Rafraîchir cette page</translation>
++<translation id="5507756662695126555">Non-répudiation</translation>
++<translation id="3678156199662914018">Extension : <ph name="EXTENSION_NAME"/></translation>
++<translation id="9194519262242876737">Active l'API Web audio.</translation>
++<translation id="3531250013160506608">Zone de saisie de mot de passe</translation>
++<translation id="8314066201485587418">Effacer les cookies et autres données de site lorsque je quitte le navigateur</translation>
++<translation id="4094105377635924481">Ajouter l'option de regroupement au menu contextuel des onglets</translation>
++<translation id="8655295600908251630">Version</translation>
++<translation id="8250690786522693009">Latin</translation>
++<translation id="2119721408814495896">Le connecteur <ph name="CLOUD_PRINT_NAME"/> requiert l'installation du pack Microsoft XML Paper Specification Essentials.</translation>
++<translation id="7624267205732106503">Effacer les cookies et autres données de site lorsque je ferme le navigateur</translation>
++<translation id="8401363965527883709">Case décochée</translation>
++<translation id="7771452384635174008">Mise en page</translation>
++<translation id="6188939051578398125">Saisir un nom ou une adresse</translation>
++<translation id="8443621894987748190">Choix de l'image du compte</translation>
++<translation id="10122177803156699">Me montrer</translation>
++<translation id="5260878308685146029"><ph name="NUMBER_TWO"/> minutes restantes</translation>
++<translation id="2192505247865591433">De :</translation>
++<translation id="238391805422906964">Ouvrir un rapport de phishing</translation>
++<translation id="5921544176073914576">Page de phishing</translation>
++<translation id="3727187387656390258">Inspecter le pop-up</translation>
++<translation id="569068482611873351">Importer...</translation>
++<translation id="6571070086367343653">Modifier la carte de paiement</translation>
++<translation id="1204242529756846967">Cette langue est utilisée pour corriger l'orthographe.</translation>
++<translation id="3981760180856053153">Le type d'enregistrement indiqué est incorrect.</translation>
++<translation id="8464591670878858520">Forfait de données <ph name="NETWORK"/> arrivé à expiration</translation>
++<translation id="4568660204877256194">Exporter mes favoris...</translation>
++<translation id="3116361045094675131">Clavier britannique</translation>
++<translation id="4577070033074325641">Importer des favoris...</translation>
++<translation id="1641504961675316934"><ph name="CLOUD_PRINT_NAME"/></translation>
++<translation id="1715941336038158809">Nom d'utilisateur ou mot de passe incorrect</translation>
++<translation id="1901303067676059328">&amp;Tout sélectionner</translation>
++<translation id="674375294223700098">Erreur inconnue liée au certificat du serveur.</translation>
++<translation id="7780428956635859355">Envoyer une capture d'écran enregistrée</translation>
++<translation id="2850961597638370327">Émis pour : <ph name="NAME"/></translation>
++<translation id="2168039046890040389">Page précédente</translation>
++<translation id="1767519210550978135">Hsu</translation>
++<translation id="2498539833203011245">Réduire</translation>
++<translation id="2893168226686371498">Navigateur par défaut</translation>
++<translation id="2435457462613246316">Afficher le mot de passe</translation>
++<translation id="7988355189918024273">Activer les fonctionnalités d'accessibilité</translation>
++<translation id="5438653034651341183">Inclure la capture d'écran actuelle :</translation>
++<translation id="1899708097738826574"><ph name="OPTIONS_TITLE"/> - <ph name="SUBPAGE_TITLE"/></translation>
++<translation id="1765313842989969521">(cette extension est gérée et ne peut être désinstallée ni désactivée)</translation>
++<translation id="6983783921975806247">OID enregistré</translation>
++<translation id="394984172568887996">Importés depuis IE</translation>
++<translation id="5311260548612583999">Fichier de clé privée (facultatif) :</translation>
++<translation id="2430043402233747791">Autoriser pour la session uniquement</translation>
++<translation id="7363290921156020669"><ph name="NUMBER_ZERO"/> mins</translation>
++<translation id="7568790562536448087">Mise à jour en cours</translation>
++<translation id="4856408283021169561">Aucun microphone trouvé.</translation>
++<translation id="8190193592390505034">Connexion à <ph name="PROVIDER_NAME"/></translation>
++<translation id="6144890426075165477"><ph name="PRODUCT_NAME"/> n'est pas votre navigateur par défaut.</translation>
++<translation id="823241703361685511">Forfait</translation>
++<translation id="4068506536726151626">Cette page contient des éléments des sites ci-dessous qui suivent votre position géographique :</translation>
++<translation id="4721475475128190282">Plusieurs profils</translation>
++<translation id="4220128509585149162">Plantages</translation>
++<translation id="8798099450830957504">Par défaut</translation>
++<translation id="9107059250669762581"><ph name="NUMBER_DEFAULT"/> jours</translation>
++<translation id="1640283014264083726">PKCS #1 MD4 avec chiffrement RSA</translation>
++<translation id="872451400847464257">Modifier le moteur de recherche</translation>
++<translation id="6463061331681402734"><ph name="NUMBER_MANY"/> minutes</translation>
++<translation id="2466804342846034717">Indiquez le mot de passe approprié ci-dessus, puis saisissez les caractères figurant dans l'image ci-dessous.</translation>
++<translation id="3881435075661337013">Expiration de <ph name="NETWORK"/> imminente</translation>
++<translation id="5681833099441553262">Activer l'onglet précédent</translation>
++<translation id="4792057643643237295">Désactiver l'accès à distance</translation>
++<translation id="1681614449735360921">Afficher les incompatibilités</translation>
++<translation id="19094784437781028">Carte de débit Solo</translation>
++<translation id="2657327428424666237"><ph name="BEGIN_LINK"/>Actualisez<ph name="END_LINK"/> cette page Web ultérieurement.</translation>
++<translation id="7347751611463936647">Pour utiliser cette extension, saisissez &quot;<ph name="EXTENSION_KEYWORD"/>&quot;, TAB, puis votre commande ou votre recherche.</translation>
++<translation id="659432221160402784"><ph name="PRODUCT_NAME"/> synchronisera les applications installées, afin que vous puissiez y accéder en vous connectant depuis tout navigateur <ph name="PRODUCT_NAME"/>.</translation>
++<translation id="892464165639979917">Langues et paramètres du correcteur orthographique...</translation>
++<translation id="5645845270586517071">Erreur de sécurité</translation>
++<translation id="2805756323405976993">Applications</translation>
++<translation id="3651020361689274926">La ressource demandée n'existe plus et aucune adresse de transfert n'est disponible. Il semble que cet état de fait soit permanent.</translation>
++<translation id="2989786307324390836">Certificat unique binaire codé DER</translation>
++<translation id="3827774300009121996">&amp;Plein écran</translation>
++<translation id="3771294271822695279">Fichiers vidéo</translation>
++<translation id="6704875430222476107"><ph name="PRODUCT_NAME"/> indique que
++ NetNanny intercepte les connexions sécurisées. En général, cela
++ ne constitue pas un problème de sécurité, car le logiciel NetNanny s'exécute souvent
++ sur le même ordinateur. Toutefois, en raison de certaines incompatibilités avec
++ les connexions sécurisées Google Chrome, vous devez configurer NetNanny
++ de manière à éviter ces interceptions. Cliquez sur le lien En savoir plus pour obtenir des instructions.</translation>
++<translation id="3388026114049080752">Vos onglets et activités de navigation</translation>
++<translation id="7525067979554623046">Créer</translation>
++<translation id="4711094779914110278">Turc</translation>
++<translation id="1031460590482534116">Une erreur s'est produite lors de la tentative d'enregistrement du certificat client. Erreur <ph name="ERROR_NUMBER"/> (<ph name="ERROR_NAME"/>)</translation>
++<translation id="7136984461011502314">Bienvenue dans <ph name="PRODUCT_NAME"/></translation>
++<translation id="1594030484168838125">Sélectionner</translation>
++<translation id="204497730941176055">Nom du modèle de certificat Microsoft</translation>
++<translation id="6705264787989366486">Configuration de l'adresse IP pour <ph name="NAME"/></translation>
++<translation id="8970721300630048025">Immortalisez votre plus beau sourire et utilisez la photo comme image de compte.</translation>
++<translation id="4087089424473531098">Extension créée :
++
++<ph name="EXTENSION_FILE"/></translation>
++<translation id="16620462294541761">Mot de passe incorrect. Veuillez réessayer.</translation>
++<translation id="5017508259293544172">LEAP</translation>
++<translation id="1394630846966197578">Échec de la connexion aux serveurs de reconnaissance vocale.</translation>
++<translation id="2498765460639677199">Très grande</translation>
++<translation id="2378982052244864789">Sélectionner le répertoire de l'extension</translation>
++<translation id="7861215335140947162">&amp;Téléchargements</translation>
++<translation id="4778630024246633221">Gestionnaire des certificats</translation>
++<translation id="6705050455568279082"><ph name="URL"/> souhaite suivre votre position géographique</translation>
++<translation id="4708849949179781599">Quitter <ph name="PRODUCT_NAME"/></translation>
++<translation id="2505402373176859469"><ph name="RECEIVED_AMOUNT"/> sur <ph name="TOTAL_SIZE"/></translation>
++<translation id="6644512095122093795">Proposer d'enregistrer les mots de passe</translation>
++<translation id="4724450788351008910">Modification de l'affiliation</translation>
++<translation id="2249605167705922988">par exemple : 1-5, 8, 11-13</translation>
++<translation id="8691686986795184760">(Activé par une stratégie d'entreprise)</translation>
++<translation id="1911483096198679472">Qu'est-ce que c'est ?</translation>
++<translation id="1976323404609382849">Les cookies de plusieurs sites ont été bloqués.</translation>
++<translation id="2662952950313424742">Serveur DNS spécifié par l'utilisateur et utilisé par Google Chrome, à la place du paramètre système par défaut, pour les résolutions DNS.</translation>
++<translation id="4176463684765177261">Désactivé</translation>
++<translation id="2079545284768500474">Annuler</translation>
++<translation id="114140604515785785">Répertoire racine de l'extension :</translation>
++<translation id="4788968718241181184">Mode de saisie du vietnamien (TCVN6064)</translation>
++<translation id="1512064327686280138">Échec de l'activation</translation>
++<translation id="3254409185687681395">Ajouter cette page aux favoris</translation>
++<translation id="1384616079544830839">L'identité de ce site Web a été vérifiée par <ph name="ISSUER"/>.</translation>
++<translation id="8710160868773349942">Adresse e-mail : <ph name="EMAIL_ADDRESSES"/></translation>
++<translation id="4057991113334098539">Activation...</translation>
++<translation id="9073281213608662541">PAP</translation>
++<translation id="1800035677272595847">Sites de phishing</translation>
++<translation id="8448317557906454022"><ph name="NUMBER_ZERO"/> secs ago</translation>
++<translation id="402759845255257575">Interdire à tous les sites d'exécuter JavaScript</translation>
++<translation id="4610637590575890427">Vouliez-vous accéder à <ph name="SITE"/> ?</translation>
++<translation id="7723779034587221017">La connexion avec le service de configuration a été perdue. Veuillez réinitialiser votre périphérique ou contacter votre représentant de l'assistance technique.</translation>
++<translation id="3046388203776734202">Paramètres des fenêtres pop-up :</translation>
++<translation id="3437994698969764647">Tout exporter...</translation>
++<translation id="8349305172487531364">Barre de favoris</translation>
++<translation id="1898064240243672867">Stocké dans : <ph name="CERT_LOCATION"/></translation>
++<translation id="444134486829715816">Développer...</translation>
++<translation id="1401874662068168819">Gin Yieh</translation>
++<translation id="7208899522964477531">Rechercher <ph name="SEARCH_TERMS"/> sur <ph name="SITE_NAME"/></translation>
++<translation id="6255097610484507482">Modifier la carte de paiement</translation>
++<translation id="5584091888252706332">Au démarrage</translation>
++<translation id="8960795431111723921">Nous examinons actuellement le problème.</translation>
++<translation id="2482878487686419369">Notifications</translation>
++<translation id="8004582292198964060">Navigateur</translation>
++<translation id="695755122858488207">Case d'option décochée</translation>
++<translation id="6357135709975569075"><ph name="NUMBER_ZERO"/> days</translation>
++<translation id="8666678546361132282">Anglais</translation>
++<translation id="2224551243087462610">Modifier le nom du dossier</translation>
++<translation id="1358741672408003399">Grammaire et orthographe</translation>
++<translation id="4910673011243110136">Réseaux privés</translation>
++<translation id="2527167509808613699">Toutes sortes de connexions</translation>
++<translation id="9095710730982563314">Exceptions liées aux notifications</translation>
++<translation id="8072988827236813198">Épingler les onglets</translation>
++<translation id="1234466194727942574">Barre d'onglets</translation>
++<translation id="7974087985088771286">Activer l'onglet 6</translation>
++<translation id="4035758313003622889">Gestionnaire de &amp;tâches</translation>
++<translation id="6356936121715252359">Paramètres de stockage d'Adobe Flash Player...</translation>
++<translation id="5885996401168273077">Connexion au réseau</translation>
++<translation id="7313804056609272439">Mode de saisie du vietnamien (VNI)</translation>
++<translation id="1768211415369530011">L'application suivante va être lancée si vous acceptez cette requête :\n\n<ph name="APPLICATION"/></translation>
++<translation id="8793043992023823866">Importation...</translation>
++<translation id="8106211421800660735">N° de carte</translation>
++<translation id="2550839177807794974">Gérer les moteurs de recherche...</translation>
++<translation id="7031711645186424727">Utiliser un moniteur externe</translation>
++<translation id="6316768948917110108">Gravure de l'image en cours...</translation>
++<translation id="5089810972385038852">État</translation>
++<translation id="2872961005593481000">Éteindre</translation>
++<translation id="8986267729801483565">Enregistrer les fichiers dans le dossier :</translation>
++<translation id="4322394346347055525">Fermer les autres onglets</translation>
++<translation id="4411770745820968260">Répertoire de fichiers</translation>
++<translation id="881799181680267069">Masquer les autres</translation>
++<translation id="1812631533912615985">Annuler l'épinglage des onglets</translation>
++<translation id="6042308850641462728">Plus</translation>
++<translation id="8318945219881683434">Échec de la vérification de la révocation</translation>
++<translation id="1650709179466243265">Ajouter www. et .com, puis ouvrir la page</translation>
++<translation id="3524079319150349823">Pour inspecter un pop-up, cliquez avec le bouton droit sur la page ou sur l'icône d'action du navigateur, puis sélectionnez Inspecter le pop-up.</translation>
++<translation id="994289308992179865">&amp;Répéter</translation>
++<translation id="7793343764764530903"><ph name="CLOUD_PRINT_NAME"/> est à présent activé. <ph name="PRODUCT_NAME"/> a enregistré les imprimantes installées sur cette machine en les associant à &lt;b&gt;<ph name="EMAIL_ADDRESSES"/>&lt;/b&gt;. Vous pouvez désormais utiliser vos imprimantes depuis n'importe quelle application Web ou mobile associée à <ph name="CLOUD_PRINT_NAME"/>.</translation>
++<translation id="1703490097606704369">Le serveur de <ph name="HOST_NAME"/>
++ est introuvable, car la résolution DNS a échoué. DNS est le service Web qui
++ traduit les noms de site Web en adresses Internet. Cette erreur est
++ généralement due à l'absence de connexion Internet ou à une configuration
++ incorrecte du réseau. Cela peut également venir d'un serveur DNS qui ne
++ répond pas ou d'un pare-feu interdisant l'accès de
++ <ph name="PRODUCT_NAME"/>
++ au réseau.</translation>
++<translation id="8887090188469175989">ZGPY</translation>
++<translation id="3302709122321372472">Impossible de charger le fichier css &quot;<ph name="RELATIVE_PATH"/>&quot; du script de contenu.</translation>
++<translation id="305803244554250778">Créer des raccourcis vers des applications aux emplacements suivants :</translation>
++<translation id="574392208103952083">Moyenne</translation>
++<translation id="3745810751851099214">Envoyé pour :</translation>
++<translation id="3937609171782005782">Aider Google à détecter les logiciels malveillants en envoyant des données supplémentaires concernant les sites pour lesquels cet avertissement s'affiche. Ces données seront gérées conformément aux règles définies sur la page <ph name="PRIVACY_PAGE_LINK"/>.</translation>
++<translation id="8877448029301136595">[répertoire parent]</translation>
++<translation id="7301360164412453905">Touches de sélection du clavier Hsu</translation>
++<translation id="8631271110654520730">Copie de l'image de récupération...</translation>
++<translation id="1963227389609234879">Tout supprimer</translation>
++<translation id="7779140087128114262">Seule une personne en possession de votre mot de passe multiterme peut lire vos données chiffrées. Google ne reçoit ni n'enregistre votre mot de passe multiterme. Si vous oubliez votre mot de passe multiterme, vous devrez réinitialiser la synchronisation.</translation>
++<translation id="8027581147000338959">Ouvrir dans une nouvelle fenêtre</translation>
++<translation id="8019305344918958688">Dommage... Aucune extension n'est installée. :-(</translation>
++<translation id="7466861475611330213">Style de ponctuation</translation>
++<translation id="2496180316473517155">Historique de navigation</translation>
++<translation id="602251597322198729">Ce site tente de télécharger plusieurs fichiers. Voulez-vous autoriser le chargement ?</translation>
++<translation id="5843685321177053287">Établissement de la liaison avec le service de gestion des périphériques en attente...</translation>
++<translation id="2052389551707911401"><ph name="NUMBER_MANY"/> heures</translation>
++<translation id="5411472733320185105">Ne pas utiliser les paramètres du proxy pour les hôtes et domaines suivants :</translation>
++<translation id="6691936601825168937">&amp;Avancer</translation>
++<translation id="6566142449942033617">Impossible de charger &quot;<ph name="PLUGIN_PATH"/>&quot; pour le plug-in.</translation>
++<translation id="7065534935986314333">À propos du système</translation>
++<translation id="45025857977132537">Utilisation de la clé du certificat : <ph name="USAGES"/></translation>
++<translation id="6454421252317455908">Mode de saisie du chinois (quick)</translation>
++<translation id="368789413795732264">Une erreur s'est produite lors de la tentative d'écriture du fichier : <ph name="ERROR_TEXT"/>.</translation>
++<translation id="1173894706177603556">Renommer</translation>
++<translation id="5670032673361607750">La synchronisation requiert votre attention.</translation>
++<translation id="2148716181193084225">Aujourd'hui</translation>
++<translation id="1002064594444093641">Imp&amp;rimer le cadre...</translation>
++<translation id="7234674978021619913">Le site <ph name="HOST_NAME"/> a déjà été informé qu'un logiciel malveillant a été détecté sur ses pages. Pour plus d'informations concernant les problèmes rencontrés sur <ph name="HOST_NAME2"/>, consultez notre <ph name="DIAGNOSTIC_PAGE"/> Google.</translation>
++<translation id="8202390211066742724">Adresse de serveur DNS spécifiée par l'utilisateur.</translation>
++<translation id="4608500690299898628">&amp;Rechercher...</translation>
++<translation id="3574305903863751447"><ph name="CITY"/>, <ph name="STATE"/> <ph name="COUNTRY"/></translation>
++<translation id="8724859055372736596">&amp;Afficher dans le dossier</translation>
++<translation id="4605399136610325267">Non connecté à Internet.</translation>
++<translation id="978407797571588532">Sélectionnez
++ <ph name="BEGIN_BOLD"/>
++ Démarrer &gt; Panneau de configuration &gt; Connexions réseau &gt; Assistant Nouvelle connexion
++ <ph name="END_BOLD"/>
++ pour tester votre connexion.</translation>
++<translation id="5554489410841842733">Cette icône s'affiche lorsque l'extension peut agir sur la page active.</translation>
++<translation id="579702532610384533">Reconnexion</translation>
++<translation id="4862642413395066333">Réponses OCSP de signature</translation>
++<translation id="5266113311903163739">Erreur d'importation de l'autorité de certification</translation>
++<translation id="9563164493805065">Gravure de l'image terminée.</translation>
++<translation id="4756388243121344051">&amp;Historique</translation>
++<translation id="3789841737615482174">Installer</translation>
++<translation id="4320697033624943677">Ajouter des utilisateurs</translation>
++<translation id="9153934054460603056">Enregistrer l'authentification et le mot de passe</translation>
++<translation id="1455548678241328678">Clavier norvégien</translation>
++<translation id="2520481907516975884">Basculer en mode chinois/anglais</translation>
++<translation id="8571890674111243710">Traduction de la page en <ph name="LANGUAGE"/>...</translation>
++<translation id="4789872672210757069">À propos de &amp;<ph name="PRODUCT_NAME"/></translation>
++<translation id="4056561919922437609"><ph name="TAB_COUNT"/> onglets</translation>
++<translation id="4373894838514502496">il y a <ph name="NUMBER_FEW"/> minutes</translation>
++<translation id="6358450015545214790">Qu'est-ce que c'est ?</translation>
++<translation id="6264365405983206840">Tout &amp;sélectionner</translation>
++<translation id="1017280919048282932">&amp;Ajouter au dictionnaire</translation>
++<translation id="8319414634934645341">Utilisation étendue de la clé</translation>
++<translation id="4563210852471260509">Le chinois est la langue de saisie initiale</translation>
++<translation id="6897140037006041989">Agent utilisateur</translation>
++<translation id="3413122095806433232">Émetteurs de l'autorité de certification : <ph name="LOCATION"/></translation>
++<translation id="4115153316875436289"><ph name="NUMBER_TWO"/> jours</translation>
++<translation id="701080569351381435">Code source</translation>
++<translation id="3286538390144397061">Redémarrer maintenant</translation>
++<translation id="163309982320328737">La largeur de caractères initiale est Complète</translation>
++<translation id="5107325588313356747">Pour masquer l'accès à ce programme, vous devez le désinstaller au moyen de \n<ph name="CONTROL_PANEL_APPLET_NAME"/> du Panneau de configuration.\n\nSouhaitez-vous exécuter <ph name="CONTROL_PANEL_APPLET_NAME"/> ?</translation>
++<translation id="4841055638263130507">Paramètres du microphone</translation>
++<translation id="6965648386495488594">Port</translation>
++<translation id="7631887513477658702">&amp;Toujours ouvrir les fichiers de ce type</translation>
++<translation id="8627795981664801467">Uniquement les connexions sécurisées</translation>
++<translation id="8680787084697685621">Les informations de connexion au compte sont obsolètes.</translation>
++<translation id="3228969707346345236">La traduction a échoué, car la page est déjà en <ph name="LANGUAGE"/>.</translation>
++<translation id="1873879463550486830">Sandbox SUID</translation>
++<translation id="2190355936436201913">(vide)</translation>
++<translation id="8515737884867295000">Échec de l'authentification basée sur le certificat</translation>
++<translation id="5868426874618963178">Envoyer le code source de la page actuelle</translation>
++<translation id="1269138312169077280">Votre administrateur a désactivé certains paramètres.</translation>
++<translation id="5818003990515275822">Coréen</translation>
++<translation id="4182252350869425879">Avertissement : Il s'agit peut-être d'un site de phishing !</translation>
++<translation id="5458214261780477893">Dvorak</translation>
++<translation id="5353719617589986386">Étendue de pages incorrecte</translation>
++<translation id="1164369517022005061"><ph name="NUMBER_DEFAULT"/> heures restantes</translation>
++<translation id="5943260032016910017">Exceptions liées aux cookies et aux données de site</translation>
++<translation id="2214283295778284209"><ph name="SITE"/> n'est pas accessible</translation>
++<translation id="8755376271068075440">P&amp;lus grand</translation>
++<translation id="8132793192354020517">Connecté à <ph name="NAME"/></translation>
++<translation id="8187473050234053012">Le certificat de sécurité du site a été révoqué !</translation>
++<translation id="7444983668544353857">Désactiver <ph name="NETWORKDEVICE"/></translation>
++<translation id="6003177993629630467"><ph name="PRODUCT_NAME"/> risque de ne pas rester à jour.</translation>
++<translation id="421577943854572179">intégré sur tout autre site</translation>
++<translation id="580886651983547002"><ph name="PRODUCT_NAME"/>
++ ne parvient pas à atteindre le site Web. Cela vient probablement d'un problème de réseau,
++ mais peut également être dû à un pare-feu ou à un serveur proxy mal configuré.</translation>
++<translation id="5445557969380904478">À propos de la reconnaissance vocale</translation>
++<translation id="3093473105505681231">Langues et paramètres du correcteur orthographique...</translation>
++<translation id="152482086482215392"><ph name="NUMBER_ONE"/> seconde restante</translation>
++<translation id="529172024324796256">Nom d'utilisateur :</translation>
++<translation id="3308116878371095290">Le stockage des cookies n'est pas autorisé pour cette page.</translation>
++<translation id="7521387064766892559">JavaScript</translation>
++<translation id="7219179957768738017">La connexion utilise <ph name="SSL_VERSION"/>.</translation>
++<translation id="7014174261166285193">Échec de l'installation</translation>
++<translation id="1970746430676306437">Afficher les &amp;infos sur la page</translation>
++<translation id="3199127022143353223">Serveurs</translation>
++<translation id="2805646850212350655">Système de fichiers de chiffrement Microsoft </translation>
++<translation id="8053959338015477773">Un plug-in supplémentaire est requis pour afficher certains éléments sur cette page.</translation>
++<translation id="3541661933757219855">Appuyez sur Ctrl+Alt+/ ou sur Échap pour masquer</translation>
++<translation id="8813873272012220470">Cette fonctionnalité effectue des vérifications en arrière-plan et vous avertit en cas d'incompatibilité logicielle (modules tiers bloquant le navigateur, par exemple).</translation>
++<translation id="5020734739305654865">Connexion avec</translation>
++<translation id="2679385451463308372">Imprimer depuis la boîte de dialogue système…</translation>
++<translation id="7414887922320653780"><ph name="NUMBER_ONE"/> heure restante</translation>
++<translation id="121632099317611328">Échec de l'initialisation de l'appareil photo</translation>
++<translation id="399179161741278232">Importés</translation>
++<translation id="3829932584934971895">Type de fournisseur :</translation>
++<translation id="462288279674432182">IP restreinte :</translation>
++<translation id="3927932062596804919">Refuser</translation>
++<translation id="3524915994314972210">Démarrage du téléchargement en cours...</translation>
++<translation id="6484929352454160200">Une nouvelle version de <ph name="PRODUCT_NAME"/> est disponible.</translation>
++<translation id="3187212781151025377">Clavier hébreu</translation>
++<translation id="351152300840026870">Police à largeur fixe</translation>
++<translation id="5827266244928330802">Safari</translation>
++<translation id="778881183694837592">Les champs obligatoires ne doivent pas rester vides.</translation>
++<translation id="2371076942591664043">Ouvrir une fois le téléchargement &amp;terminé</translation>
++<translation id="3920504717067627103">Stratégies de certificat</translation>
++<translation id="155865706765934889">Pavé tactile</translation>
++<translation id="7701040980221191251">Aucun</translation>
++<translation id="5917011688104426363">Activer la barre d'adresse en mode recherche</translation>
++<translation id="6910239454641394402">Exceptions pour JavaScript</translation>
++<translation id="2979639724566107830">Ouvrir dans une nouvelle fenêtre</translation>
++<translation id="3269101346657272573">Veuillez saisir le code PIN.</translation>
++<translation id="9204065299849069896">Options de saisie automatique...</translation>
++<translation id="2822854841007275488">Arabe</translation>
++<translation id="5857090052475505287">Nouveau dossier</translation>
++<translation id="7450732239874446337">E/S réseau interrompue</translation>
++<translation id="5178667623289523808">Rechercher le précédent</translation>
++<translation id="2815448242176260024">Ne jamais enregistrer les mots de passe</translation>
++<translation id="2989805286512600854">Ouvrir dans un nouvel onglet</translation>
++<translation id="8687485617085920635">Fenêtre suivante</translation>
++<translation id="4122118036811378575">&amp;Rechercher le suivant</translation>
++<translation id="6008256403891681546">JCB</translation>
++<translation id="2610780100389066815">Signature de liste d'approbation Microsoft</translation>
++<translation id="8289811203643526145">Gérer les certificats...</translation>
++<translation id="2788575669734834343">Sélectionnez le fichier de certificat.</translation>
++<translation id="8404409224170843728">Fabricant :</translation>
++<translation id="8267453826113867474">Bloquer le contenu inapproprié</translation>
++<translation id="7959074893852789871">Le fichier contenait plusieurs certificats, dont certains n'ont pas été importés :</translation>
++<translation id="1213999834285861200">Exceptions pour les images</translation>
++<translation id="2805707493867224476">Autoriser tous les sites à afficher des fenêtres pop-up</translation>
++<translation id="3561217442734750519">Vous devez indiquer un chemin valide comme valeur de clé privée.</translation>
++<translation id="2444609190341826949">Sans mot de passe multiterme, vos mots de passe et autres données chiffrées ne seront pas synchronisés sur cet ordinateur.</translation>
++<translation id="77221669950527621">Extensions ou applications</translation>
++<translation id="6650142020817594541">Ce site recommande Google Chrome Frame (déjà installé).</translation>
++<translation id="6503077044568424649">Les plus visités</translation>
++<translation id="4625904365165566833">Vous n'êtes pas autorisé à vous connecter. Consultez le propriétaire de cet ordinateur portable.</translation>
++<translation id="7450633916678972976">Remarque : Lorsque vous cliquez sur &quot;Envoyer&quot;, Google Chrome joint à votre
++ envoi un journal indiquant votre version de Google Chrome et celle du système
++ d'exploitation utilisé, ainsi que l'URL associée à votre rapport. Vous pouvez
++ également joindre une capture d'écran. Ces informations nous
++ permettent de diagnostiquer les problèmes et d'améliorer les performances de
++ Google Chrome. Les informations personnelles fournies sciemment dans vos
++ commentaires ou involontairement dans le journal, l'URL ou la capture
++ d'écran sont protégées conformément à nos règles de
++ confidentialité. Si vous ne souhaitez pas indiquer d'URL et/ou de capture
++ d'écran, décochez les cases &quot;Inclure cette URL&quot; et/ou &quot;Inclure cette capture d'écran&quot;. Vous acceptez que Google utilise vos commentaires pour améliorer ses produits ou services.</translation>
++<translation id="465365366590259328">Vos modifications seront prises en compte au prochain démarrage de <ph name="PRODUCT_NAME"/>.</translation>
++<translation id="7168109975831002660">Taille de police minimale</translation>
++<translation id="7070804685954057874">Entrée directe</translation>
++<translation id="3265459715026181080">Fermer la fenêtre</translation>
++<translation id="6074871234879228294">Mode de saisie du japonais (pour clavier japonais)</translation>
++<translation id="7855296476260297092">Inscription réussie</translation>
++<translation id="907841381057066561">Échec de création du fichier zip temporaire lors de la création du pack</translation>
++<translation id="1294298200424241932">Modifier les paramètres de confiance :</translation>
++<translation id="1384617406392001144">Votre historique de navigation</translation>
++<translation id="3831099738707437457">&amp;Masquer le panneau de la vérification orthographique</translation>
++<translation id="1040471547130882189">Plug-in ne répondant pas</translation>
++<translation id="5473075389972733037">IBM</translation>
++<translation id="8307664665247532435">Les paramètres seront effacés lors de la prochaine actualisation.</translation>
++<translation id="790025292736025802"><ph name="URL"/> introuvable</translation>
++<translation id="895347679606913382">Démarrage...</translation>
++<translation id="3319048459796106952">Nouvelle fenêtre de nav&amp;igation privée</translation>
++<translation id="5832669303303483065">Ajouter une adresse postale...</translation>
++<translation id="3127919023693423797">Authentification en cours...</translation>
++<translation id="4195643157523330669">Ouvrir dans un nouvel onglet</translation>
++<translation id="8030169304546394654">Déconnecté</translation>
++<translation id="4010065515774514159">Action du navigateur</translation>
++<translation id="4286563808063000730">Le mot de passe multiterme saisi ne peut pas être utilisé, car vous avez déjà chiffré des données avec un mot de passe multiterme. Entrez ci-dessous le mot de passe multiterme actuellement défini pour la synchronisation.</translation>
++<translation id="1154228249304313899">Ouvrir cette page :</translation>
++<translation id="9074348188580488499">Voulez-vous vraiment supprimer tous les mots de passe ?</translation>
++<translation id="6635491740861629599">Sélectionner par domaine</translation>
++<translation id="3627588569887975815">Ouvrir le lien dans une fenêtre en navi&amp;gation privée</translation>
++<translation id="5851868085455377790">Émetteur</translation>
++<translation id="8223496248037436966">Options de saisie automatique</translation>
++<translation id="1470719357688513792">Les nouveaux paramètres des cookies seront appliqués quand vous aurez actualisé la page.</translation>
++<translation id="5578327870501192725">Votre connexion à <ph name="DOMAIN"/> est sécurisée par un chiffrement <ph name="BIT_COUNT"/> bits.</translation>
++<translation id="869884720829132584">Menu Applications</translation>
++<translation id="7764209408768029281">Outi&amp;ls</translation>
++<translation id="1139892513581762545">Onglets latéraux</translation>
++<translation id="7634357567062076565">Reprendre</translation>
++<translation id="4779083564647765204">Zoom</translation>
++<translation id="3282430104564575032">Inspecteur de DOM</translation>
++<translation id="1526560967942511387">Document sans titre</translation>
++<translation id="1291144580684226670">Police standard</translation>
++<translation id="3979748722126423326">Activer <ph name="NETWORKDEVICE"/></translation>
++<translation id="5538307496474303926">Opération en cours...</translation>
++<translation id="4367133129601245178">C&amp;opier l'URL de l'image</translation>
++<translation id="7542995811387359312">La saisie automatique des numéros de carte de paiement est désactivée, car la connexion utilisée par ce formulaire n'est pas sécurisée.</translation>
++<translation id="3494444535872870968">Enregistrer le &amp;cadre sous...</translation>
++<translation id="987264212798334818">Général</translation>
++<translation id="7005812687360380971">Défaillance</translation>
++<translation id="2356070529366658676">Demander</translation>
++<translation id="5731247495086897348">Coller l'URL et y a&amp;ccéder</translation>
++<translation id="8467548439852845758">Pour plus de sécurité, <ph name="PRODUCT_NAME"/> va chiffrer vos mots de passe.</translation>
++<translation id="2524947000814989347">Si vous avez oublié votre mot de passe multiterme, vous devrez arrêter la synchronisation via Google Dashboard.</translation>
++<translation id="8018154597338652331"><ph name="BURNT_AMOUNT"/> sur <ph name="TOTAL_SIZE"/></translation>
++<translation id="7635741716790924709">Adresse ligne 1</translation>
++<translation id="5135533361271311778">Impossible de créer le favori.</translation>
++<translation id="5271247532544265821">Basculer en mode chinois simplifié/traditionnel</translation>
++<translation id="2052610617971448509">Votre système Sandbox n'est pas correctement configuré.</translation>
++<translation id="7384913436093989340">Sélectionnez le <ph name="BEGIN_BOLD"/>menu clé à molette &gt; Préférences &gt; Options avancées &gt; Modifier les paramètres du proxy<ph name="END_BOLD"/> et vérifiez que vos paramètres sont définis sur &quot;sans proxy&quot; ou &quot;direct&quot;.</translation>
++<translation id="6417515091412812850">Impossible de vérifier si le certificat a été révoqué.</translation>
++<translation id="7282743297697561153">Stockage des données</translation>
++<translation id="3363332416643747536"><ph name="DOWNLOAD_RECEIVED"/>/<ph name="DOWNLOAD_TOTAL"/>, Interrompu</translation>
++<translation id="7347702518873971555">Acheter un forfait</translation>
++<translation id="5285267187067365830">Installer le plug-in...</translation>
++<translation id="5334844597069022743">Afficher le code source</translation>
++<translation id="1166212789817575481">Fermer les onglets sur la droite</translation>
++<translation id="6472893788822429178">Afficher le bouton &quot;Accueil&quot;</translation>
++<translation id="4270393598798225102">Version <ph name="NUMBER"/></translation>
++<translation id="534916491091036097">Parenthèse gche</translation>
++<translation id="4157869833395312646">Microsoft Server Gated Cryptography</translation>
++<translation id="8903921497873541725">Zoom avant</translation>
++<translation id="2195729137168608510">Protection du courrier électronique</translation>
++<translation id="1425734930786274278">Les cookies suivants ont été bloqués (tous les cookies tiers sont bloqués, sans exception) :</translation>
++<translation id="6805647936811177813">Connectez-vous à <ph name="TOKEN_NAME"/> pour importer le certificat client de <ph name="HOST_NAME"/></translation>
++<translation id="3437016096396740659">La batterie est chargée.</translation>
++<translation id="6916146760805488559">Créer un nouveau profil...</translation>
++<translation id="1199232041627643649">Maintenez la touche <ph name="KEY_EQUIVALENT"/> enfoncée pour quitter.</translation>
++<translation id="5428562714029661924">Masquer ce plug-in</translation>
++<translation id="7907591526440419938">Ouvrir le fichier</translation>
++<translation id="2568774940984945469">Conteneur de barres d'infos</translation>
++<translation id="8971063699422889582">Le certificat du serveur a expiré.</translation>
++<translation id="8281596639154340028">Utiliser <ph name="HANDLER_TITLE"/></translation>
++<translation id="7134098520442464001">Réduit la taille du texte</translation>
++<translation id="21133533946938348">Épingler l'onglet</translation>
++<translation id="1325040735987616223">Mise à jour du système</translation>
++<translation id="2864069933652346933"><ph name="NUMBER_ZERO"/> days left</translation>
++<translation id="9090669887503413452">Inclure les informations système</translation>
++<translation id="3084771660770137092">Google Chrome n'avait pas suffisamment de mémoire ou le processus de la page Web a été arrêté pour une autre raison. Pour continuer, actualisez la page ou ouvrez-en une autre.</translation>
++<translation id="1114901192629963971">Impossible de valider votre mot de passe sur le réseau actuel. Sélectionnez un autre réseau.</translation>
++<translation id="5179510805599951267">Cette page n'est pas rédigée en <ph name="ORIGINAL_LANGUAGE"/> ? Signaler l'erreur</translation>
++<translation id="6430814529589430811">Certificat unique codé Base 64 ASCII</translation>
++<translation id="5143712164865402236">Activer le mode plein écran</translation>
++<translation id="8434177709403049435">Codag&amp;e</translation>
++<translation id="4051923669149193910"><ph name="HANDLER_TITLE"/> est déjà utilisé pour gérer les liens <ph name="PROTOCOL"/>://.</translation>
++<translation id="2722201176532936492">Touches de sélection</translation>
++<translation id="385120052649200804">Clavier international américain</translation>
++<translation id="9012607008263791152">Je comprends que la visite de ce site peut être préjudiciable à mon ordinateur.</translation>
++<translation id="6640442327198413730">Ressource cache manquante.</translation>
++<translation id="1441458099223378239">Impossible d'accéder à mon compte</translation>
++<translation id="5793220536715630615">C&amp;opier l'URL de la vidéo</translation>
++<translation id="523397668577733901">Vous préférez <ph name="BEGIN_LINK"/>parcourir la galerie<ph name="END_LINK"/> ?</translation>
++<translation id="2922350208395188000">Impossible de vérifier le certificat du serveur.</translation>
++<translation id="3778740492972734840">Outils de &amp;développement</translation>
++<translation id="8335971947739877923">Exporter...</translation>
++<translation id="5680966941935662618">Paramètres de saisie automatique</translation>
++<translation id="38385141699319881">Téléchargement de l'image en cours...</translation>
++<translation id="6004539838376062211">&amp;Options du vérificateur d'orthographe</translation>
++<translation id="5350198318881239970">Impossible d'ouvrir votre profil correctement.\n\nIl est possible que certaines fonctionnalités ne soient pas disponibles. Vérifiez que ce profil existe et que vous disposez d'une autorisation d'accès à son contenu en lecture et en écriture.</translation>
++<translation id="4058793769387728514">Vérifier le document maintenant</translation>
++<translation id="1810107444790159527">Zone de liste</translation>
++<translation id="3338239663705455570">Clavier slovène</translation>
++<translation id="1859234291848436338">Sens de l'écriture</translation>
++<translation id="4567836003335927027">Vos données sur <ph name="WEBSITE_1"/></translation>
++<translation id="756445078718366910">Ouvrir une fenêtre du navigateur</translation>
++<translation id="4126154898592630571">Conversion de la date et de l'heure</translation>
++<translation id="5088534251099454936">PKCS #1 SHA-512 avec chiffrement RSA</translation>
++<translation id="6392373519963504642">Clavier coréen</translation>
++<translation id="7887334752153342268">Dupliquer</translation>
++<translation id="4980691186726139495">Ne pas conserver sur cette page</translation>
++<translation id="3081523290047420375">Désactiver <ph name="CLOUD_PRINT_NAME"/></translation>
++<translation id="9207194316435230304">ATOK</translation>
++<translation id="9026731007018893674">téléchargement</translation>
++<translation id="7646591409235458998">E-mail :</translation>
++<translation id="703748601351783580">Ouvrir tous les favoris dans une nouvelle &amp;fenêtre</translation>
++<translation id="6199775032047436064">Rafraîchir la page actuelle</translation>
++<translation id="6981982820502123353">Accessibilité</translation>
++<translation id="112343676265501403">Exceptions pour les plug-ins</translation>
++<translation id="770273299705142744">Remplissage automatique des formulaires</translation>
++<translation id="7210998213739223319">Nom d'utilisateur</translation>
++<translation id="4478664379124702289">Enregistrer le lie&amp;n sous...</translation>
++<translation id="8725066075913043281">Réessayer</translation>
++<translation id="8502249598105294518">Personnaliser et configurer <ph name="PRODUCT_NAME"/></translation>
++<translation id="7392089327262158658">Préférences de saisie automatique <ph name="PRODUCT_NAME_SHORT"/></translation>
++<translation id="4163521619127344201">Votre position géographique</translation>
++<translation id="3797008485206955964">Afficher les pages en arrière-plan (<ph name="NUM_BACKGROUND_APPS"/>)</translation>
++<translation id="8590375307970699841">Configurer les mises à jour automatiques</translation>
++<translation id="2797524280730715045">il y a <ph name="NUMBER_DEFAULT"/> heures</translation>
++<translation id="265390580714150011">Valeur du champ</translation>
++<translation id="9073247318500677671">Les dernières versions d'Unity et GNOME (ainsi que la prochaine version d'Ubuntu, Natty Narwhal) affichent une barre de menus de type OSX sur toute la largeur supérieure de l'écran.</translation>
++<translation id="3869917919960562512">Index erroné.</translation>
++<translation id="7031962166228839643">Préparation du module de plate-forme sécurisée (TPM) en cours. Veuillez patienter, l'opération peut prendre quelques minutes.</translation>
++<translation id="4250377793615429299">Nombre de copies incorrect</translation>
++<translation id="7180865173735832675">Personnaliser</translation>
++<translation id="5737306429639033676">Prédire les actions du réseau pour améliorer les performances de chargement des pages</translation>
++<translation id="8123426182923614874">Données restantes :</translation>
++<translation id="3707020109030358290">N'est pas une autorité de certification.</translation>
++<translation id="2115926821277323019">L'URL doit être valide.</translation>
++<translation id="8986494364107987395">Envoyer automatiquement les statistiques d'utilisation et les rapports d'erreur à Google</translation>
++<translation id="7070714457904110559">Cette fonctionnalité active la géolocalisation dans les extensions expérimentales. Cela implique l'utilisation des API de localisation du système d'exploitation (si disponibles) et l'envoi de données sur la configuration réseau locale au service de localisation de Google afin de déterminer une position précise.</translation>
++<translation id="6701535245008341853">Impossible de charger le profil.</translation>
++<translation id="527605982717517565">Toujours exécuter JavaScript sur <ph name="HOST"/></translation>
++<translation id="702373420751953740">Version PRL :</translation>
++<translation id="1307041843857566458">Confirmer la réactivation</translation>
++<translation id="8314308967132194952">Ajouter une adresse postale...</translation>
++<translation id="1221024147024329929">PKCS #1 MD2 avec chiffrement RSA</translation>
++<translation id="853265131227167869">Dim.^Lun.^Mar.^Mer.^Jeu.^Ven.^Sam.</translation>
++<translation id="3323447499041942178">Zone de saisie</translation>
++<translation id="580571955903695899">Trier par nom</translation>
++<translation id="5230516054153933099">Fenêtre</translation>
++<translation id="7554791636758816595">Nouvel onglet</translation>
++<translation id="5503844897713343920">Vous tentez d'accéder au site <ph name="DOMAIN"/>, mais le certificat présenté par le serveur a été révoqué par son émetteur. Cela signifie que les informations d'identification présentées par le serveur ne sont pas approuvées. Vous communiquez peut-être avec un pirate informatique. Nous vous déconseillons vivement de continuer.</translation>
++<translation id="6928853950228839340">Composition hors écran</translation>
++<translation id="1308727876662951186"><ph name="NUMBER_ZERO"/> mins left</translation>
++<translation id="7671576867600624">Technologie :</translation>
++<translation id="1103966635949043187">Accédez à la page d'accueil du site :</translation>
++<translation id="1951332921786364801">Configurer la communication à distance</translation>
++<translation id="1086613338090581534">L'émetteur d'un certificat n'ayant pas expiré est tenu d'assurer la maintenance de ce qui s'appelle &quot;une liste de révocation&quot;. Si un certificat est compromis, l'émetteur peut le révoquer en l'ajoutant à la liste de révocation. Ce certificat n'est alors plus approuvé par votre navigateur. Il n'est pas nécessaire d'assurer la maintenance de l'état &quot;révoqué&quot; des certificats expirés. Donc, bien qu'un certificat ait été qualifié de valide pour le site Web que vous visitez actuellement, il est impossible de déterminer s'il a été, depuis, compromis puis révoqué ou s'il est toujours valide. Par conséquent, il n'est pas possible de s'assurer si vous communiquez avec un site Web légitime ou si le certificat a été compromis et se trouve maintenant en la possession d'un pirate informatique avec lequel vous communiquez. Ne poursuivez pas.</translation>
++<translation id="2645575947416143543">Néanmoins, si vous travaillez dans une entreprise qui génère ses propres certificats, et que vous essayez de vous connecter au site Web interne de l'entreprise avec un certificat de ce type, vous pouvez résoudre ce problème en toute sécurité. Pour ce faire, importez le certificat racine de l'entreprise en tant que &quot;certificat racine&quot;. Par la suite, les certificats émis ou vérifiés par votre entreprise seront approuvés et vous ne verrez plus cette erreur lorsque vous tenterez de vous connecter à nouveau au site Web interne. Contactez le support informatique de votre entreprise pour savoir comment ajouter un nouveau certificat racine sur votre ordinateur.</translation>
++<translation id="376466258076168640">Définir <ph name="PRODUCT_NAME"/> en tant que navigateur par défaut</translation>
++<translation id="1056898198331236512">Avertissement</translation>
++<translation id="8630826211403662855">Préférences de recherche</translation>
++<translation id="8432745813735585631">Clavier Colemak américain</translation>
++<translation id="8151639108075998630">Activer la navigation en tant qu'invité</translation>
++<translation id="2608770217409477136">Utiliser les paramètres par défaut</translation>
++<translation id="3157931365184549694">Rétablir</translation>
++<translation id="7426243339717063209">Désinstaller &quot;<ph name="EXTENSION_NAME"/>&quot; ?</translation>
++<translation id="996250603853062861">Établissement de la connexion sécurisée...</translation>
++<translation id="6059232451013891645">Dossier :</translation>
++<translation id="4274292172790327596">Erreur non reconnue</translation>
++<translation id="760537465793895946">Consultez les conflits connus avec des modules tiers.</translation>
++<translation id="7042418530779813870">Co&amp;ller et rechercher</translation>
++<translation id="9110447413660189038">&amp;Remonter</translation>
++<translation id="375403751935624634">Échec de la traduction en raison d'une erreur de serveur</translation>
++<translation id="2101225219012730419">Version :</translation>
++<translation id="1570242578492689919">Polices et codage</translation>
++<translation id="3082374807674020857"><ph name="PAGE_TITLE"/> - <ph name="PAGE_URL"/></translation>
++<translation id="8050038245906040378">Signature du code commercial Microsoft</translation>
++<translation id="3031557471081358569">Sélectionnez les éléments à importer :</translation>
++<translation id="1368832886055348810">De gauche à droite</translation>
++<translation id="3031433885594348982">Votre connexion à <ph name="DOMAIN"/> est sécurisée par le biais d'un faible chiffrement.</translation>
++<translation id="4047345532928475040">sans objet</translation>
++<translation id="5604324414379907186">Toujours afficher la barre de favoris</translation>
++<translation id="3220630151624181591">Activer l'onglet 2</translation>
++<translation id="8898139864468905752">Aperçu des onglets</translation>
++<translation id="2799223571221894425">Redémarrer</translation>
++<translation id="5771816112378578655">Configuration en cours...</translation>
++<translation id="1197979282329025000">Une erreur s'est produite lors de la récupération des fonctions de l'imprimante <ph name="PRINTER_NAME"/>. Cette imprimante n'a pas pu être enregistrée dans <ph name="CLOUD_PRINT_NAME"/>.</translation>
++<translation id="8820901253980281117">Exceptions pour les fenêtres pop-up</translation>
++<translation id="1143142264369994168">Signataire du certificat </translation>
++<translation id="904949795138183864">La page Web <ph name="URL"/> n'existe plus.</translation>
++<translation id="3228279582454007836">Vous n'avez jamais visité ce site auparavant.</translation>
++<translation id="2159017110205600596">Personnaliser...</translation>
++<translation id="5449716055534515760">Fe&amp;rmer la fenêtre</translation>
++<translation id="2814489978934728345">Arrêter le chargement de cette page</translation>
++<translation id="2354001756790975382">Autres favoris</translation>
++<translation id="8561574028787046517"><ph name="PRODUCT_NAME"/> a été mis à jour.</translation>
++<translation id="5234325087306733083">Mode hors connexion</translation>
++<translation id="1779392088388639487">Erreur d'importation de fichier PKCS #12</translation>
++<translation id="166278006618318542">Algorithme de clé publique de l'objet</translation>
++<translation id="5759272020525228995">Le site Web a rencontré une erreur lors de l'extraction de <ph name="URL"/>.
++ Cela peut être dû à une opération de maintenance ou à une configuration incorrecte.</translation>
++<translation id="641480858134062906">Échec du chargement de la page <ph name="URL"/></translation>
++<translation id="3693415264595406141">Mot de passe :</translation>
++<translation id="74568296546932365">Conserver <ph name="PAGE_TITLE"/> en tant que moteur de recherche par défaut</translation>
++<translation id="8602184400052594090">Fichier manifeste absent ou illisible</translation>
++<translation id="2784949926578158345">La connexion a été réinitialisée.</translation>
++<translation id="6663792236418322902">Le mot de passe choisi vous sera demandé pour restaurer le fichier. Veillez à le conserver en lieu sûr.</translation>
++<translation id="4532822216683966758">La vérification de la provenance du certificat DNS est activée, ce qui peut entraîner l'envoi d'informations privées à Google.</translation>
++<translation id="6321196148033717308">À propos de la reconnaissance vocale</translation>
++<translation id="3412265149091626468">Aller à la sélection</translation>
++<translation id="8167737133281862792">Ajouter un certificat</translation>
++<translation id="2911372483530471524">Espaces de noms PID</translation>
++<translation id="6093374025603915876">Préférences de saisie automatique</translation>
++<translation id="8584134039559266300">Activer l'onglet 8</translation>
++<translation id="5189060859917252173">Le certificat &quot;<ph name="CERTIFICATE_NAME"/>&quot; représente une autorité de certification.</translation>
++<translation id="3785852283863272759">Envoyer par e-mail l'emplacement de la page</translation>
++<translation id="2255317897038918278">Enregistrement des informations de date Microsoft</translation>
++<translation id="3493881266323043047">Validité</translation>
++<translation id="5979421442488174909">&amp;Traduire en <ph name="LANGUAGE"/></translation>
++<translation id="7326526699920221209">Batterie : <ph name="PRECENTAGE"/> %</translation>
++<translation id="952992212772159698">Désactivé</translation>
++<translation id="8299269255470343364">Japonais</translation>
++<translation id="5187826826541650604"><ph name="KEY_NAME"/> (<ph name="DEVICE"/>)</translation>
++<translation id="6429639049555216915">L'application est actuellement inaccessible.</translation>
++<translation id="2144536955299248197">Lecteur du certificat : <ph name="CERTIFICATE_NAME"/></translation>
++<translation id="50030952220075532"><ph name="NUMBER_ONE"/> jour restant</translation>
++<translation id="2885378588091291677">Gestionnaire de tâches</translation>
++<translation id="5792852254658380406">Gérer les extensions...</translation>
++<translation id="2359808026110333948">Continuer</translation>
++<translation id="176759384517330673">Synchronisation avec <ph name="USER_EMAIL_ADDRESS"/> effectuée. Dernière synchronisation : <ph name="LAST_SYNC_TIME"/></translation>
++<translation id="1618661679583408047">Le certificat de sécurité du site n'est pas encore valide !</translation>
++<translation id="7039912931802252762">Ouverture de session par carte à puce Microsoft</translation>
++<translation id="6285074077487067719">Format</translation>
++<translation id="3065140616557457172">Tapez votre requête ou saisissez une URL pour commencer la navigation : c'est à vous de choisir.</translation>
++<translation id="5509693895992845810">Enregistrer &amp;sous...</translation>
++<translation id="5986279928654338866">Le serveur <ph name="DOMAIN"/> requiert un nom d'utilisateur et un mot de passe.</translation>
++<translation id="521467793286158632">Supprimer tous les mots de passe</translation>
++<translation id="2491120439723279231">Le certificat du serveur contient des erreurs.</translation>
++<translation id="4448844063988177157">Recherche de réseaux Wi-Fi...</translation>
++<translation id="5765780083710877561">Description :</translation>
++<translation id="338583716107319301">Séparateur</translation>
++<translation id="2079053412993822885">Si vous supprimez l'un de vos propres certificats, vous ne pouvez plus l'utiliser pour vous identifier.</translation>
++<translation id="7221869452894271364">Rafraîchir cette page</translation>
++<translation id="6791443592650989371">État d'activation :</translation>
++<translation id="4801257000660565496">Créer des raccourcis vers des applications</translation>
++<translation id="6503256918647795660">Clavier franco-suisse</translation>
++<translation id="6175314957787328458">GUID de domaine Microsoft</translation>
++<translation id="8179976553408161302">Entrer</translation>
++<translation id="8261506727792406068">Supprimer</translation>
++<translation id="4404805853119650018">Échec de l'enregistrement de cet ordinateur pour l'accès à distance.</translation>
++<translation id="345693547134384690">Ouvrir l'&amp;image dans un nouvel onglet</translation>
++<translation id="7422192691352527311">Préférences...</translation>
++<translation id="354211537509721945">L'administrateur a désactivé les mises à jour.</translation>
++<translation id="1375198122581997741">À propos de la version</translation>
++<translation id="7915471803647590281">Veuillez nous indiquer ce qu'il se passe avant d'envoyer votre rapport.</translation>
++<translation id="5725124651280963564">Connectez-vous à <ph name="TOKEN_NAME"/> afin de générer une clé pour <ph name="HOST_NAME"/>.</translation>
++<translation id="8418113698656761985">Clavier roumain</translation>
++<translation id="5976160379964388480">Autres</translation>
++<translation id="3665842570601375360">Sécurité :</translation>
++<translation id="1430915738399379752">Imprimer</translation>
++<translation id="7999087758969799248">Mode de saisie standard</translation>
++<translation id="2635276683026132559">Signature</translation>
++<translation id="4835836146030131423">Erreur lors de la connexion</translation>
++<translation id="7715454002193035316">Pour cette session uniquement</translation>
++<translation id="2475982808118771221">Une erreur s'est produite.</translation>
++<translation id="3324684065575061611">(Désactivé par une stratégie d'entreprise)</translation>
++<translation id="7385854874724088939">Erreur lors de la tentative d'impression. Vérifiez votre imprimante et réessayez.</translation>
++<translation id="770015031906360009">Grec</translation>
++<translation id="3834901049798243128">Ignorer les exceptions et bloquer l'enregistrement des cookies tiers</translation>
++<translation id="8116152017593700047">Cet outil vous permet de sélectionner une capture d'écran enregistrée. Aucune capture d'écran n'est disponible pour le moment. Appuyez simultanément sur Ctrl et sur la touche &quot;Mode Présentation&quot; pour enregistrer une capture d'écran. Vos trois dernières captures apparaissent ici.</translation>
++<translation id="3454157711543303649">Activation effectuée</translation>
++<translation id="884923133447025588">Aucun système de révocation trouvé</translation>
++<translation id="556042886152191864">Bouton</translation>
++<translation id="1352060938076340443">Interrompu</translation>
++<translation id="8571226144504132898">Dictionnaire de symboles</translation>
++<translation id="7229570126336867161">Technologie EvDo requise</translation>
++<translation id="7582844466922312471">Internet mobile</translation>
++<translation id="945522503751344254">Envoyer le commentaire</translation>
++<translation id="4539401194496451708">Associé au profil Chrome <ph name="USER_EMAIL_ADDRESS"/>. Dernière synchronisation : <ph name="LAST_SYNC_TIME"/></translation>
++<translation id="7369847606959702983">Carte de crédit (autre)</translation>
++<translation id="6867459744367338172">Langues et saisie</translation>
++<translation id="7671130400130574146">Utiliser la barre de titre et les bordures de fenêtre du système</translation>
++<translation id="9170848237812810038">Ann&amp;uler</translation>
++<translation id="284970761985428403"><ph name="ASCII_NAME"/> (<ph name="UNICODE_NAME"/>)</translation>
++<translation id="3903912596042358459">Le serveur a refusé d'exécuter la demande.</translation>
++<translation id="8135557862853121765"><ph name="NUM_KILOBYTES"/> Ko</translation>
++<translation id="4444364671565852729"><ph name="PRODUCT_NAME"/> a été mis à jour vers la version <ph name="VERSION"/>.</translation>
++<translation id="5819890516935349394">Navigateur de contenu</translation>
++<translation id="2731392572903530958">&amp;Rouvrir la fenêtre fermée</translation>
++<translation id="1254593899333212300">Se connecter directement à Internet</translation>
++<translation id="6107012941649240045">Émis pour</translation>
++<translation id="6483805311199035658">Ouverture de <ph name="FILE"/> en cours</translation>
++<translation id="3576278878016363465">Cibles disponibles pour l'image</translation>
++<translation id="895541991026785598">Signaler un problème</translation>
++<translation id="940425055435005472">Taille de police :</translation>
++<translation id="494286511941020793">Aide pour la configuration de proxy</translation>
++<translation id="2765217105034171413">Petite</translation>
++<translation id="1285266685456062655"><ph name="NUMBER_FEW"/> hours ago</translation>
++<translation id="9154176715500758432">Rester sur cette page</translation>
++<translation id="5875565123733157100">Type de bug :</translation>
++<translation id="6988771638657196063">Inclure cette URL :</translation>
++<translation id="5717920936024713315">Cookies et données de site...</translation>
++<translation id="3842552989725514455">Police Serif</translation>
++<translation id="1949795154112250744"><ph name="BEGIN_BOLD"/>Avertissement :<ph name="END_BOLD"/> <ph name="PRODUCT_NAME"/> ne peut pas empêcher les extensions d'enregistrer votre historique de navigation. Pour désactiver cette extension en mode navigation privée, désélectionnez-la.</translation>
++<translation id="4440967101351338638">ChromiumOs Image Burn</translation>
++<translation id="1813278315230285598">Services</translation>
++<translation id="6860097299815761905">Paramètres du proxy...</translation>
++<translation id="373572798843615002">1 onglet</translation>
++<translation id="4162393307849942816"><ph name="BEGIN_BOLD"/>Vous naviguez en tant qu'invité<ph name="END_BOLD"/>. Les pages que vous consultez dans cette fenêtre n'apparaîtront pas dans l'historique de votre navigateur ni dans votre historique des recherches. Les autres traces telles que les cookies seront supprimées de l'ordinateur à la fin de votre session. En revanche, les fichiers téléchargés et les favoris créés seront conservés.
++ <ph name="LINE_BREAK"/>
++ <ph name="BEGIN_LINK"/>En savoir plus<ph name="END_LINK"/> sur le mode invité</translation>
++<translation id="827924395145979961">Chargement des pages impossible</translation>
++<translation id="3092544800441494315">Inclure cette capture d'écran :</translation>
++<translation id="7714464543167945231">Certificat</translation>
++<translation id="3616741288025931835">&amp;Effacer les données de navigation...</translation>
++<translation id="3313622045786997898">Valeur de signature du certificat</translation>
++<translation id="8535005006684281994">URL de renouvellement du certificat Netscape</translation>
++<translation id="2440604414813129000">Afficher la s&amp;ource</translation>
++<translation id="816095449251911490"><ph name="SPEED"/> - <ph name="RECEIVED_AMOUNT"/>, <ph name="TIME_REMAINING"/></translation>
++<translation id="8200772114523450471">Reprendre</translation>
++<translation id="6358975074282722691"><ph name="NUMBER_TWO"/> secs ago</translation>
++<translation id="5423849171846380976">Activé</translation>
++<translation id="6748105842970712833">Carte SIM désactivée</translation>
++<translation id="7323391064335160098">Compatibilité avec VPN</translation>
++<translation id="3929673387302322681">Développement - Instable</translation>
++<translation id="4251486191409116828">Échec de création du raccourci vers l'application</translation>
++<translation id="5190835502935405962">Barre de favoris</translation>
++<translation id="7828272290962178636">Le serveur est en mesure de répondre à la demande.</translation>
++<translation id="7823073559911777904">Modifier les paramètres du proxy...</translation>
++<translation id="5438430601586617544">(non empaquetée)</translation>
++<translation id="6460601847208524483">Rechercher le suivant</translation>
++<translation id="8433186206711564395">Paramètres réseau</translation>
++<translation id="4856478137399998590">Votre service Internet mobile est activé et prêt à l'emploi.</translation>
++<translation id="1676388805288306495">Modifier la police et la langue par défaut des pages Web</translation>
++<translation id="8969761905474557563">Composition graphique avec accélération matérielle</translation>
++<translation id="3937640725563832867">Autre nom de l'émetteur du certificat</translation>
++<translation id="4701488924964507374"><ph name="SENTENCE1"/> <ph name="SENTENCE2"/></translation>
++<translation id="1163931534039071049">&amp;Afficher le code source du cadre</translation>
++<translation id="8770196827482281187">Mode de saisie du persan (clavier ISIRI 2901)</translation>
++<translation id="6423239382391657905">OpenVPN</translation>
++<translation id="7564847347806291057">Arrêter le processus</translation>
++<translation id="1607220950420093847">Votre compte a peut-être été supprimé ou désactivé. Veuillez vous déconnecter.</translation>
++<translation id="5613695965848159202">Authentification anonyme :</translation>
++<translation id="2233320200890047564">Bases de données indexées</translation>
++<translation id="7063412606254013905">En savoir plus sur les escroqueries par phishing</translation>
++<translation id="307767688111441685">Page à l'apparence anormale</translation>
++<translation id="9076523132036239772">Adresse e-mail ou mot de passe incorrect. Essayez tout d'abord de vous connecter à un réseau.</translation>
++<translation id="6965978654500191972">Périphérique</translation>
++<translation id="1242521815104806351">Informations sur la connexion</translation>
++<translation id="5295309862264981122">Confirmer la navigation</translation>
++<translation id="1492817554256909552">Nom du point d'accès :</translation>
++<translation id="5546865291508181392">Rechercher</translation>
++<translation id="1999115740519098545">Au démarrage</translation>
++<translation id="2983818520079887040">Paramètres...</translation>
++<translation id="1465619815762735808">Lire en un clic</translation>
++<translation id="6941937518557314510">Connectez-vous à <ph name="TOKEN_NAME"/> pour vous authentifier auprès de <ph name="HOST_NAME"/> avec votre certificat.</translation>
++<translation id="2783600004153937501">Votre administrateur informatique a désactivé certaines options.</translation>
++<translation id="2099686503067610784">Supprimer le certificat de serveur &quot;<ph name="CERTIFICATE_NAME"/>&quot;?</translation>
++<translation id="9027603907212475920">Configurer la synchronisation...</translation>
++<translation id="6873213799448839504">Valider automatiquement une chaîne</translation>
++<translation id="7238585580608191973">Empreinte SHA-256</translation>
++<translation id="2501278716633472235">Retour</translation>
++<translation id="131461803491198646">Réseau domestique, sans itinérance</translation>
++<translation id="7377249249140280793"><ph name="RELATIVE_DATE"/> - <ph name="FULL_DATE"/></translation>
++<translation id="5679279978772703611">Gérer les mots de passe enregistrés...</translation>
++<translation id="4551440281920791563">Sélectionnez
++ <ph name="BEGIN_BOLD"/>
++ Menu clé à molette &gt; Options &gt; Options avancées &gt; Modifier les paramètres du proxy &gt; Paramètres réseau
++ <ph name="END_BOLD"/>
++ et désélectionnez l'option &quot;Utiliser un serveur proxy pour votre réseau local&quot;.</translation>
++<translation id="1285320974508926690">Ne jamais traduire ce site</translation>
++<translation id="8954894007019320973">(suite)</translation>
++<translation id="3748412725338508953">Trop de redirections</translation>
++<translation id="5833726373896279253">Ces paramètres ne peuvent être modifiés que par le propriétaire :</translation>
++<translation id="6858960932090176617">Active la protection XSS Auditor de WebKit (protection contre le Cross-site Scripting), une fonctionnalité qui vous protège de certaines attaques de sites malveillants et offre une sécurité accrue, mais qui n'est pas compatible avec tous les sites Web.</translation>
++<translation id="6005282720244019462">Clavier latino-américain</translation>
++<translation id="8831104962952173133">Phishing détecté !</translation>
++<translation id="5141720258550370428">Voulez-vous utiliser <ph name="HANDLER_TITLE"/> (<ph name="HANDLER_HOSTNAME"/>) pour gérer les liens <ph name="PROTOCOL"/>:// à partir de maintenant ?</translation>
++<translation id="6751344591405861699"><ph name="WINDOW_TITLE"/> (Navigation privée)</translation>
++<translation id="6681668084120808868">Prendre une photo</translation>
++<translation id="780301667611848630">Non merci</translation>
++<translation id="2812989263793994277">Ne pas afficher les images</translation>
++<translation id="7190251665563814471">Toujours autoriser ces plug-ins sur <ph name="HOST"/></translation>
++<translation id="6845383723252244143">Sélectionner un dossier</translation>
++<translation id="8925458182817574960">&amp;Paramètres</translation>
++<translation id="6361850914223837199">Informations sur l'erreur :</translation>
++<translation id="8948393169621400698">Toujours autoriser les plug-ins sur <ph name="HOST"/></translation>
++<translation id="3865082058368813534">Effacer les données de saisie automatique enregistrées</translation>
++<translation id="8288345061925649502">Changer de moteur de recherche</translation>
++<translation id="5436492226391861498">En attente du tunnel proxy...</translation>
++<translation id="3803991353670408298">Veuillez ajouter un autre mode de saisie avant de supprimer celui-ci.</translation>
++<translation id="1095623615273566396"><ph name="NUMBER_FEW"/> secondes</translation>
++<translation id="7006788746334555276">Paramètres de contenu</translation>
++<translation id="3369521687965833290">Impossible d'extraire les fichiers de l'extension. Pour effectuer cette opération en toute sécurité, vous devez disposer d'un chemin d'accès à votre répertoire de profils commençant par une lettre de lecteur et ne contenant ni jonction, ni point de montage, ni lien symbolique. Aucun chemin de ce type n'existe pour votre profil.</translation>
++<translation id="337920581046691015"><ph name="PRODUCT_NAME"/> va être installé.</translation>
++<translation id="6282194474023008486">Code postal</translation>
++<translation id="7733107687644253241">En bas à droite</translation>
++<translation id="5139955368427980650">&amp;Ouvrir</translation>
++<translation id="8136149669168180907"><ph name="DOWNLOADED_AMOUNT"/> téléchargé(s) sur <ph name="TOTAL_SIZE"/></translation>
++<translation id="7375268158414503514">Commentaires d'ordre général/Autres</translation>
++<translation id="4643612240819915418">Ou&amp;vrir la vidéo dans un nouvel onglet</translation>
++<translation id="7997479212858899587">Identité :</translation>
++<translation id="8300849813060516376">Échec de l'opération OTASP</translation>
++<translation id="2213819743710253654">Action sur la page</translation>
++<translation id="1317130519471511503">Modifier des éléments...</translation>
++<translation id="6391538222494443604">Le répertoire d'extensions est obligatoire.</translation>
++<translation id="8051695050440594747"><ph name="MEGABYTES"/> Mo disponibles</translation>
++<translation id="7088615885725309056">Ancien</translation>
++<translation id="461656879692943278"><ph name="HOST_NAME"/> fournit du contenu provenant de <ph name="ELEMENTS_HOST_NAME"/>, un site connu pour distribuer des logiciels malveillants. Votre ordinateur pourrait être infecté par un virus si vous consultez ce site.</translation>
++<translation id="1387022316521171484">Ces fonctionnalités expérimentales sont susceptibles d'être modifiées, interrompues ou supprimées à tout moment. Nous ne fournissons aucune garantie quant aux effets de leur activation. Votre navigateur pourrait bien prendre feu. Trêve de plaisanterie, il est possible que votre navigateur supprime toutes vos données ou que votre sécurité et votre vie privée soient compromises de manière inattendue. Nous vous prions d'agir avec précaution.</translation>
++<translation id="2143778271340628265">Configuration manuelle du proxy</translation>
++<translation id="5294529402252479912">Mettre à jour Adobe Reader maintenant</translation>
++<translation id="5263972071113911534"><ph name="NUMBER_MANY"/> days ago</translation>
++<translation id="7461850476009326849">Désactiver les plug-ins individuels...</translation>
++<translation id="4097411759948332224">Envoyer une capture d'écran de la page en cours</translation>
++<translation id="2231990265377706070">Point d'exclamation</translation>
++<translation id="7199540622786492483"><ph name="PRODUCT_NAME"/> n'est plus à jour, car il n'a pas été relancé depuis quelque temps. La mise à jour disponible sera installée dès que vous le relancerez.</translation>
++<translation id="8652722422052983852">Petit problème... Tentons de le résoudre.</translation>
++<translation id="5970231080121144965">Cette fonctionnalité permet d'établir des correspondances entre les sous-chaînes et plusieurs fragments d'URL figurant dans l'historique.</translation>
++<translation id="4665674675433053715">Page &quot;Nouvel onglet&quot; expérimentale</translation>
++<translation id="3726527440140411893">Les cookies suivants étaient autorisés lorsque vous avez consulté cette page :</translation>
++<translation id="3320859581025497771">votre opérateur</translation>
++<translation id="8828781037212165374">Activer ces fonctionnalités...</translation>
++<translation id="8562413501751825163">Quitter Firefox avant l'importation</translation>
++<translation id="3435541101098866721">Ajouter un nouveau téléphone</translation>
++<translation id="2448046586580826824">Proxy HTTP sécurisé</translation>
++<translation id="4032534284272647190">Accès à <ph name="URL"/> refusé.</translation>
++<translation id="4928569512886388887">Finalisation de la mise à jour du système...</translation>
++<translation id="8258002508340330928">Voulez-vous continuer ?</translation>
++<translation id="4309420042698375243"><ph name="NUM_KILOBYTES"/> Ko (<ph name="NUM_KILOBYTES_LIVE"/> Ko effectifs)</translation>
++<translation id="5554573843028719904">Autre réseau Wi-Fi...</translation>
++<translation id="5034259512732355072">Choisir un autre répertoire...</translation>
++<translation id="8885905466771744233">L'extension indiquée est déjà associée à une clé privée. Utilisez cette clé ou supprimez-la.</translation>
++<translation id="7831504847856284956">Ajouter une adresse</translation>
++<translation id="7505152414826719222">Stockage local</translation>
++<translation id="2541423446708352368">Afficher tous les téléchargements</translation>
++<translation id="4381021079159453506">Navigateur de contenu</translation>
++<translation id="8109246889182548008">Magasin de certificats</translation>
++<translation id="5030338702439866405">Émis par</translation>
++<translation id="5280833172404792470">Quitter le mode plein écran (<ph name="ACCELERATOR"/>)</translation>
++<translation id="2728127805433021124">Le certificat du serveur a été signé avec un algorithme de signature faible.</translation>
++<translation id="2137808486242513288">Ajouter un utilisateur</translation>
++<translation id="6193618946302416945">Me proposer de traduire les pages qui sont écrites dans une langue que je ne sais pas lire</translation>
++<translation id="129553762522093515">Récemment fermés</translation>
++<translation id="4287167099933143704">Saisir la clé de déverrouillage du code PIN</translation>
++<translation id="8355915647418390920"><ph name="NUMBER_FEW"/> jours</translation>
++<translation id="3129140854689651517">Rechercher du texte</translation>
++<translation id="7221585318879598658">Sans-Serif</translation>
++<translation id="5558129378926964177">Zoom &amp;avant</translation>
++<translation id="6451458296329894277">Confirmer le nouvel envoi du formulaire</translation>
++<translation id="5116333507878097773"><ph name="NUMBER_ONE"/> heure</translation>
++<translation id="8028152732786498049">Cet élément doit être installé depuis <ph name="CHROME_WEB_STORE"/>.</translation>
++<translation id="9199258761842902152">Mise en veille ou reprise</translation>
++<translation id="1851266746056575977">Mettre à jour maintenant</translation>
++<translation id="7017219178341817193">Ajouter une page</translation>
++<translation id="1038168778161626396">Chiffrer seulement</translation>
++<translation id="2756651186786928409">Ne jamais intervertir les touches de modification</translation>
++<translation id="1217515703261622005">Conversion des numéros spéciaux</translation>
++<translation id="7179921470347911571">Relancer maintenant</translation>
++<translation id="3715099868207290855">Synchronisation avec <ph name="USER_EMAIL_ADDRESS"/> effectuée</translation>
++<translation id="2679312662830811292">il y a <ph name="NUMBER_ONE"/> minute</translation>
++<translation id="9065203028668620118">Édition</translation>
++<translation id="4718464510840275738">Préférences synchronisées</translation>
++<translation id="8788572795284305350"><ph name="NUMBER_ZERO"/> hours ago</translation>
++<translation id="1177863135347784049">Personnalisé</translation>
++<translation id="8236028464988198644">Rechercher à partir de la barre d'adresse</translation>
++<translation id="4881695831933465202">Ouvrir</translation>
++<translation id="5988520580879236902">Inspecter les vues actives :</translation>
++<translation id="3593965109698325041">Contraintes de nom du certificat</translation>
++<translation id="4358697938732213860">Ajouter une adresse</translation>
++<translation id="8396532978067103567">Mot de passe incorrect.</translation>
++<translation id="5981759340456370804">Statistiques avancées</translation>
++<translation id="8160015581537295331">Clavier espagnol</translation>
++<translation id="3505920073976671674">Sélectionnez votre réseau</translation>
++<translation id="6644971472240498405"><ph name="NUMBER_ONE"/> jour</translation>
++<translation id="1782924894173027610">Le serveur de synchronisation est occupé. Veuillez réessayer ultérieurement.</translation>
++<translation id="6512448926095770873">Quitter cette page</translation>
++<translation id="5457599981699367932">Naviguer en tant qu'invité</translation>
++<translation id="3169472444629675720">Discover</translation>
++<translation id="6294193300318171613">&amp;Toujours afficher la barre de favoris</translation>
++<translation id="4088820693488683766">Options de recherche</translation>
++<translation id="3414952576877147120">Taille :</translation>
++<translation id="9098468523912235228">il y a <ph name="NUMBER_DEFAULT"/> secondes</translation>
++<translation id="7009102566764819240">La liste suivante fait état des éléments dangereux détectés sur la page. Cliquez sur le lien &quot;Diagnostic&quot; pour obtenir plus d'informations sur une ressource particulière. Si une ressource a été signalée comme site de phishing alors que vous êtes certain de sa fiabilité, cliquez sur le lien &quot;Signaler une erreur&quot;.</translation>
++<translation id="4923417429809017348">Cette page rédigée dans une langue non identifiée a été traduite en <ph name="LANGUAGE_LANGUAGE"/>.</translation>
++<translation id="3631337165634322335">Les exceptions ci-dessous s'appliquent uniquement à la session de navigation privée actuelle.</translation>
++<translation id="676327646545845024">Ne plus afficher la boîte de dialogue pour les liens de ce type</translation>
++<translation id="494645311413743213"><ph name="NUMBER_TWO"/> secondes restantes</translation>
++<translation id="1485146213770915382">Insérez <ph name="SEARCH_TERMS_LITERAL"/> dans l'URL où les termes de recherche devraient apparaître.</translation>
++<translation id="4839303808932127586">En&amp;registrer la vidéo sous...</translation>
++<translation id="5626134646977739690">Nom :</translation>
++<translation id="5854409662653665676">Si vous rencontrez des problèmes fréquents avec ce module, vous pouvez tenter d'y remédier en suivant la procédure ci-après :</translation>
++<translation id="3681007416295224113">Informations relatives au certificat</translation>
++<translation id="3046084099139788433">Activer l'onglet 7</translation>
++<translation id="721197778055552897"><ph name="BEGIN_LINK"/>En savoir plus<ph name="END_LINK"/> sur ce problème.</translation>
++<translation id="1699395855685456105">Version du matériel :</translation>
++<translation id="212464871579942993">Le site Web à l'adresse <ph name="HOST_NAME"/> contient des éléments provenant de sites qui semblent héberger des logiciels malveillants. Ces derniers peuvent nuire à votre ordinateur ou agir à votre insu. Le simple fait de visiter un site hébergeant ce type de logiciels peut infecter votre ordinateur. Ce site héberge également des informations provenant de sites signalés comme étant des sites de phishing. Ces derniers incitent les internautes à divulguer des informations personnelles en se faisant passer pour des institutions de confiance, telles que des banques.</translation>
++<translation id="8156020606310233796">Afficher la liste</translation>
++<translation id="957120528631539888">Désactivez l'affichage des messages de confirmation et le blocage de l'envoi des formulaires.</translation>
++<translation id="146000042969587795">Ce cadre a été bloqué, car il contient des éléments non sécurisés.</translation>
++<translation id="8112223930265703044">Tout</translation>
++<translation id="3968739731834770921">Kana</translation>
++<translation id="3729920814805072072">Gérer les mots de passe enregistrés...</translation>
++<translation id="7387829944233909572">Boîte de dialogue &quot;Effacer les données de navigation&quot;</translation>
++<translation id="8023801379949507775">Mettre à jour les extensions maintenant</translation>
++<translation id="6549677549082720666">Nouvelle application en arrière-plan installée</translation>
++<translation id="1983108933174595844">Envoyer une capture d'écran de la page actuelle</translation>
++<translation id="3298789223962368867">L'URL indiquée est incorrecte.</translation>
++<translation id="2202898655984161076">Un problème est survenu lors de l'affichage de la liste des imprimantes. Certaines de vos imprimantes ne sont peut-être pas correctement enregistrées dans <ph name="CLOUD_PRINT_NAME"/>.</translation>
++<translation id="6154697846084421647">Actuellement connecté</translation>
++<translation id="8241707690549784388">La page que vous recherchez a utilisé des informations que vous avez envoyées. Si vous revenez sur cette page, chaque action précédemment effectuée sera répétée. Souhaitez-vous continuer ?</translation>
++<translation id="5359419173856026110">Cette fonctionnalité indique la vitesse d'affichage réelle d'une page, en images par seconde, lorsque l'accélération matérielle est active.</translation>
++<translation id="4104163789986725820">E&amp;xporter...</translation>
++<translation id="2113479184312716848">&amp;Ouvrir un fichier...</translation>
++<translation id="8412709057120877195">Configurer le contrôle d'accès pour vos périphériques</translation>
++<translation id="486595306984036763">Ouvrir un rapport de phishing</translation>
++<translation id="3140353188828248647">Activer la barre d'adresse</translation>
++<translation id="4860787810836767172"><ph name="NUMBER_FEW"/> secs ago</translation>
++<translation id="5565871407246142825">Cartes de paiement</translation>
++<translation id="2587203970400270934">Code opérateur :</translation>
++<translation id="3355936511340229503">Erreur de connexion</translation>
++<translation id="1824910108648426227">Vous avez la possibilité de désactiver ces services.</translation>
++<translation id="3092040396860056776">Essayer d'afficher la page malgré tout</translation>
++<translation id="4350711002179453268">Impossible d'établir une connexion sécurisée avec le serveur. Le serveur a peut-être rencontré un problème ou exige un certificat d'authentification du client dont vous ne disposez pas.</translation>
++<translation id="91731790394942114">Ajouter un nouveau nom</translation>
++<translation id="5963026469094486319">Obtenir d'autres thèmes</translation>
++<translation id="2441719842399509963">Rétablir les valeurs par défaut</translation>
++<translation id="1893137424981664888">Aucun Plug-in installé.</translation>
++<translation id="3718288130002896473">Action</translation>
++<translation id="2168725742002792683">Extensions de fichier</translation>
++<translation id="1753905327828125965">Les plus visités</translation>
++<translation id="8116972784401310538">&amp;Gestionnaire de favoris</translation>
++<translation id="1849632043866553433">Caches des applications</translation>
++<translation id="3591607774768458617">Cette langue est actuellement utilisée par <ph name="PRODUCT_NAME"/>.</translation>
++<translation id="621638399744152264"><ph name="VALUE"/> %</translation>
++<translation id="4927301649992043040">Empaqueter l'extension</translation>
++<translation id="8679658258416378906">Activer l'onglet 5</translation>
++<translation id="4763816722366148126">Sélectionner le mode de saisie précédent</translation>
++<translation id="6458308652667395253">Configurer le blocage de JavaScript...</translation>
++<translation id="8435334418765210033">Réseaux mémorisés</translation>
++<translation id="6516193643535292276">Impossible de se connecter à Internet.</translation>
++<translation id="5125751979347152379">URL incorrecte</translation>
++<translation id="2791364193466153585">Informations sur la sécurité</translation>
++<translation id="4673916386520338632">Impossible d'installer l'application, car elle est en conflit avec &quot;<ph name="APP_NAME"/>&quot;, qui est déjà installé.</translation>
++<translation id="2024918351532495204">Votre périphérique n'est pas connecté.</translation>
++<translation id="6040143037577758943">Fermer</translation>
++<translation id="5787146423283493983">Accord de la clé</translation>
++<translation id="1101671447232096497"><ph name="NUMBER_MANY"/> mins ago</translation>
++<translation id="4265682251887479829">Vous ne trouvez pas ce que vous recherchez ?</translation>
++<translation id="1804251416207250805">Désactivez l'envoi des pings de contrôle des liens hypertexte.</translation>
++<translation id="5116628073786783676">En&amp;registrer le fichier audio sous...</translation>
++<translation id="2557899542277210112">Accédez rapidement à vos favoris en les ajoutant à la barre de favoris.</translation>
++<translation id="2749881179542288782">Vérifier la grammaire et l'orthographe</translation>
++<translation id="5105855035535475848">Épingler les onglets</translation>
++<translation id="6892450194319317066">Sélectionner par type d'application</translation>
++<translation id="3549436232897695316">assembler</translation>
++<translation id="5414121716219514204"><ph name="ENGINE_HOST_NAME"/> souhaite devenir votre moteur de recherche.</translation>
++<translation id="2752805177271551234">Utiliser l'historique d'entrée</translation>
++<translation id="7268365133021434339">Fermer les onglets</translation>
++<translation id="4910619056351738551">Voici quelques suggestions :</translation>
++<translation id="9131598836763251128">Sélectionnez un ou plusieurs fichiers</translation>
++<translation id="5489059749897101717">Afficher le panneau de la &amp;vérification orthographique</translation>
++<translation id="3423858849633684918">Veuillez relancer <ph name="PRODUCT_NAME"/>.</translation>
++<translation id="1232569758102978740">Sans titre</translation>
++<translation id="1903219944620007795">Pour saisir du texte, sélectionnez une langue et consultez la liste des modes de saisie disponibles.</translation>
++<translation id="4362187533051781987">Ville</translation>
++<translation id="9149866541089851383">Modifier...</translation>
++<translation id="7608619752233383356">Réinitialiser la synchronisation</translation>
++<translation id="1065245965611933814">Inclure une capture d'écran enregistrée :</translation>
++<translation id="7876243839304621966">Tout supprimer</translation>
++<translation id="5663459693447872156">Passer automatiquement en demi-chasse</translation>
++<translation id="4593021220803146968">&amp;Accéder à <ph name="URL"/></translation>
++<translation id="1128987120443782698">La capacité de ce périphérique de stockage est de <ph name="DEVICE_CAPACITY"/>. Veuillez insérer une carte SD ou une clé USB d'au moins 4 Go.</translation>
++<translation id="869257642790614972">Rouvrir le dernier onglet fermé</translation>
++<translation id="3978267865113951599">(blocage)</translation>
++<translation id="8412145213513410671">Erreurs (<ph name="CRASH_COUNT"/>)</translation>
++<translation id="560602183358579978">Traitement de la sélection...</translation>
++<translation id="7649070708921625228">Aide</translation>
++<translation id="5994107996638824097">Désolé ! La visionneuse de documents PDF intégrée à Google Chrome, nécessaire à l'affichage de l'aperçu avant impression, n'est pas incluse dans Chromium.</translation>
++<translation id="976526967778596630">Impossible d'ouvrir <ph name="HOST_NAME"/>, car vous êtes déconnecté du réseau. Cette page s'affichera dès que la connexion réseau sera rétablie. &lt;br&gt;</translation>
++<translation id="1734072960870006811">Télécopie</translation>
++<translation id="3095995014811312755">version</translation>
++<translation id="7052500709156631672">La passerelle ou le serveur proxy a reçu une réponse incorrecte d'un serveur en amont.</translation>
++<translation id="281133045296806353">Nouvelle fenêtre ouverte dans la session du navigateur</translation>
++<translation id="7144878232160441200">Réessayer</translation>
++<translation id="2860002559146138960"><ph name="PRODUCT_NAME"/> peut maintenant synchroniser vos mots de passe. Vos données seront chiffrées avec le mot de passe de votre compte Google ou le mot de passe multiterme de votre choix.</translation>
++<translation id="3951872452847539732">Les paramètres réseau de votre proxy sont gérés par une extension.</translation>
++<translation id="6442697326824312960">Retirer l'onglet</translation>
++<translation id="6382612843547381371">Valable du <ph name="START_DATE_TIME"/> au <ph name="END_DATE_TIME"/></translation>
++<translation id="6869402422344886127">Case cochée</translation>
++<translation id="5637380810526272785">Mode de saisie</translation>
++<translation id="404928562651467259">AVERTISSEMENT</translation>
++<translation id="7172053773111046550">Clavier estonien</translation>
++<translation id="497490572025913070">Ajout de bordures aux couches de rendu composées</translation>
++<translation id="9002707937526687073">Imp&amp;rimer...</translation>
++<translation id="5953934840931207585">Paramètres de saisie automatique <ph name="PRODUCT_NAME_SHORT"/></translation>
++<translation id="5556459405103347317">Rafraîchir</translation>
++<translation id="8000020256436988724">Barre d'outils</translation>
++<translation id="8326395326942127023">Nom de la base de données :</translation>
++<translation id="7507930499305566459">Certificat du répondeur d'état</translation>
++<translation id="2689915906323125315">Utiliser le mot de passe de mon compte Google</translation>
++<translation id="6440205424473899061">Vos favoris sont maintenant synchronisés avec Google Documents !
++Pour fusionner et synchroniser vos favoris dans <ph name="PRODUCT_NAME"/> sur un autre ordinateur, procédez de la même manière que précédemment sur l'ordinateur voulu.</translation>
++<translation id="7727721885715384408">Renommer...</translation>
++<translation id="2604243255129603442"><ph name="NAME_OF_EXTENSION"/> a été désactivé. Si vous arrêtez la synchronisation des favoris, vous pouvez la réactiver sur la page des extensions, via le menu Outils.</translation>
++<translation id="2024621544377454980">Affichage des pages impossible</translation>
++<translation id="7136694880210472378">Utiliser par défaut</translation>
++<translation id="7681202901521675750">La carte SIM est verrouillée. Veuillez saisir votre code PIN. Nombre de tentatives restantes : <ph name="TRIES_COUNT"/></translation>
++<translation id="1731346223650886555">Point-virgule</translation>
++<translation id="158849752021629804">Réseau domestique requis</translation>
++<translation id="7339763383339757376">PKCS #7, certificat unique</translation>
++<translation id="7587108133605326224">Langues baltes</translation>
++<translation id="3991936620356087075">Vous avez saisi un trop grand nombre de clés de verrouillage du code PIN incorrectes. Votre carte SIM est définitivement désactivée.</translation>
++<translation id="936801553271523408">Données de diagnostic système</translation>
++<translation id="6389701355360299052">Page Web, contenu HTML uniquement</translation>
++<translation id="8067791725177197206">Continuer »</translation>
++<translation id="9009144784540995197">Gérez vos imprimantes.</translation>
++<translation id="1055006259534905434">(Choisir un problème dans la liste ci-dessous)</translation>
++<translation id="3021678814754966447">&amp;Afficher le code source du cadre</translation>
++<translation id="8601206103050338563">Authentification du client WWW TLS</translation>
++<translation id="1692799361700686467">Les cookies de plusieurs sites sont autorisés.</translation>
++<translation id="7074488040076962230">Impossible d'afficher la page de la barre latérale &quot;<ph name="SIDEBAR_PAGE"/>&quot;.</translation>
++<translation id="529232389703829405">Vous avez acheté <ph name="DATA_AMOUNT"/> de données le <ph name="DATE"/>.</translation>
++<translation id="5271549068863921519">Enregistrer le mot de passe</translation>
++<translation id="4345587454538109430">Configurer...</translation>
++<translation id="8148264977957212129">Mode de saisie du pinyin</translation>
++<translation id="5787378733537687553">Intervertir les touches Ctrl et Alt de gauche</translation>
++<translation id="7772032839648071052">Confirmer le mot de passe multiterme</translation>
++<translation id="6857811139397017780">Activer <ph name="NETWORKSERVICE"/></translation>
++<translation id="3251855518428926750">Ajouter...</translation>
++<translation id="4120075327926916474">Voulez-vous que Google Chrome enregistre ces informations de carte de paiement pour le remplissage de formulaires Web ?</translation>
++<translation id="6929555043669117778">Continuer à bloquer les fenêtres pop-up</translation>
++<translation id="5864471791310927901">Échec de la vérification DHCP</translation>
++<translation id="3508920295779105875">Choisir un autre dossier...</translation>
++<translation id="2503458975635466059">Le profil semble être utilisé par le processus <ph name="PROCESS_ID"/> sur l'hôte <ph name="HOST_NAME"/>. Si vous êtes certain qu'aucun autre processus n'utilise ce profil, supprimez le fichier <ph name="LOCK_FILE"/> et relancez <ph name="PRODUCT_NAME"/>.</translation>
++<translation id="2987775926667433828">Chinois traditionnel</translation>
++<translation id="6684737638449364721">Effacer les données de navigation...</translation>
++<translation id="3954582159466790312">Ré&amp;activer le son</translation>
++<translation id="1110772031432362678">Aucun réseau trouvé.</translation>
++<translation id="3936390757709632190">&amp;Ouvrir le fichier audio dans un nouvel onglet</translation>
++<translation id="7297622089831776169">&amp;Méthodes d'entrée</translation>
++<translation id="5731698828607291678">Onglets ou fenêtres</translation>
++<translation id="1152775729948968688">Toutefois, cette page inclut d'autres ressources qui ne sont pas sécurisées. Ces ressources peuvent être consultées par des tiers pendant leur transfert, et modifiées par un pirate informatique dans le but de changer le comportement de cette page.</translation>
++<translation id="604124094241169006">Automatique</translation>
++<translation id="862542460444371744">&amp;Extensions</translation>
++<translation id="8045462269890919536">Roumain</translation>
++<translation id="6320286250305104236">Paramètres du réseau...</translation>
++<translation id="2927657246008729253">Changer...</translation>
++<translation id="7978412674231730200">Clé privée</translation>
++<translation id="464745974361668466">Format :</translation>
++<translation id="5308380583665731573">Se connecter</translation>
++<translation id="9111395131601239814"><ph name="NETWORKDEVICE"/> : <ph name="STATUS"/></translation>
++<translation id="9049981332609050619">Vous avez tenté de contacter <ph name="DOMAIN"/>, mais le certificat présenté par le serveur est incorrect.</translation>
++<translation id="4414232939543644979">Nouvelle fenêtre de nav&amp;igation privée</translation>
++<translation id="1693754753824026215">La page à l'adresse <ph name="SITE"/> indique :</translation>
++<translation id="7148804936871729015">Le serveur associé à <ph name="URL"/> n'a pas répondu à temps. Cela peut être dû à une surcharge.</translation>
++<translation id="5950967683057767490">L2TP/IPSec + Clé pré-partagée</translation>
++<translation id="8108473539339615591">XSS Auditor</translation>
++<translation id="1902576642799138955">Durée de validité</translation>
++<translation id="4910021444507283344">WebGL</translation>
++<translation id="6692173217867674490">Mot de passe multiterme erroné</translation>
++<translation id="5550431144454300634">Corriger automatiquement la saisie</translation>
++<translation id="3308006649705061278">Unité d'organisation</translation>
++<translation id="8912362522468806198">Compte Google</translation>
++<translation id="4443536555189480885">&amp;Aide</translation>
++<translation id="340485819826776184">Utiliser un service de prédiction afin de compléter les recherches et les URL saisies dans la barre d'adresse</translation>
++<translation id="4074900173531346617">Certificat du signataire de courrier électronique</translation>
++<translation id="6165508094623778733">En savoir plus</translation>
++<translation id="9052208328806230490">Vous avez enregistré vos imprimantes sur <ph name="CLOUD_PRINT_NAME"/> via le compte <ph name="EMAIL"/>.</translation>
++<translation id="822618367988303761">il y a <ph name="NUMBER_TWO"/> jours</translation>
++<translation id="7928333295097642153"><ph name="HOUR"/>:<ph name="MINUTE"/> restantes</translation>
++<translation id="7568593326407688803">Cette page est en<ph name="ORIGINAL_LANGUAGE"/>Voulez-vous la traduire ?</translation>
++<translation id="563969276220951735">Saisie automatique des formulaires</translation>
++<translation id="6870130893560916279">Clavier ukrainien</translation>
++<translation id="8629974950076222828">Ouvrir tous les favoris dans une fenêtre de navigation privée</translation>
++<translation id="3126026824346185272">Ctrl</translation>
++<translation id="4745438305783437565"><ph name="NUMBER_FEW"/> minutes</translation>
++<translation id="2649911884196340328">Le certificat de sécurité du serveur contient des erreurs !</translation>
++<translation id="6666647326143344290">avec votre compte Google</translation>
++<translation id="3828029223314399057">Rechercher dans les favoris</translation>
++<translation id="4885705234041587624">MSCHAPv2</translation>
++<translation id="5614190747811328134">Avertissement utilisateur</translation>
++<translation id="8906421963862390172">&amp;Options du vérificateur d'orthographe</translation>
++<translation id="9046895021617826162">Échec de la connexion</translation>
++<translation id="1492188167929010410">Identifiant de l'erreur <ph name="CRASH_ID"/></translation>
++<translation id="1963692530539281474"><ph name="NUMBER_DEFAULT"/> jours restants</translation>
++<translation id="4470270245053809099">Émis par : <ph name="NAME"/></translation>
++<translation id="5365539031341696497">Mode de saisie du thaï (clavier Kesmanee)</translation>
++<translation id="2403091441537561402">Passerelle :</translation>
++<translation id="6337234675334993532">Chiffrement</translation>
++<translation id="668171684555832681">Autre...</translation>
++<translation id="1932098463447129402">Pas avant le</translation>
++<translation id="7845920762538502375"><ph name="PRODUCT_NAME"/> n'a pas pu synchroniser vos données, car la connexion avec le serveur de synchronisation n'a pas pu être établie. Nouvel essai...</translation>
++<translation id="2192664328428693215">Me demander lorsqu'un site souhaite afficher des notifications sur le Bureau (recommandé)</translation>
++<translation id="6708242697268981054">Source :</translation>
++<translation id="4786993863723020412">Erreur de lecture du cache</translation>
++<translation id="6630452975878488444">Raccourci de sélection</translation>
++<translation id="8709969075297564489">Vérifier la révocation du certificat serveur</translation>
++<translation id="8698171900303917290">Vous rencontrez des problèmes lors de l'installation ?</translation>
++<translation id="830868413617744215">Bêta</translation>
++<translation id="5925147183566400388">Pointeur de la déclaration CPS (Certification Practice Statement)</translation>
++<translation id="1497270430858433901">Le <ph name="DATE"/>, vous avez reçu <ph name="DATA_AMOUNT"/> à utiliser librement.</translation>
++<translation id="8150167929304790980">Nom complet</translation>
++<translation id="636850387210749493">Inscription d'entreprise</translation>
++<translation id="1947424002851288782">Clavier allemand</translation>
++<translation id="932508678520956232">Impossible de lancer l'impression.</translation>
++<translation id="4861833787540810454">&amp;Lire</translation>
++<translation id="2552545117464357659">Récent</translation>
++<translation id="7269802741830436641">Cette page Web présente une boucle de redirection.</translation>
++<translation id="4180788401304023883">Supprimer le certificat &quot;<ph name="CERTIFICATE_NAME"/>&quot; émis par l'autorité de certification ?</translation>
++<translation id="5869522115854928033">Mots de passe enregistrés</translation>
++<translation id="2089090684895656482">Moins</translation>
++<translation id="1709220265083931213">Options avancées</translation>
++<translation id="5748266869826978907">Vérifiez vos paramètres DNS. Contactez votre administrateur réseau si vous n'êtes pas sûr de vous.</translation>
++<translation id="4771973620359291008">Une erreur inconnue s'est produite.</translation>
++<translation id="5509914365760201064">Émetteur : <ph name="CERTIFICATE_AUTHORITY"/></translation>
++<translation id="7073385929680664879">Passer d'un mode de saisie à l'autre</translation>
++<translation id="6898699227549475383">Organisation (O)</translation>
++<translation id="4333854382783149454">PKCS #1 SHA-1 avec chiffrement RSA</translation>
++<translation id="762904068808419792">Entrez la requête de recherche ici.</translation>
++<translation id="8615618338313291042">Application en mode navigation privée : <ph name="APP_NAME"/></translation>
++<translation id="978146274692397928">La largeur de ponctuation initiale est Complète</translation>
++<translation id="8959027566438633317">Installer <ph name="EXTENSION_NAME"/> ?</translation>
++<translation id="8155798677707647270">Installation d'une nouvelle version...</translation>
++<translation id="6886871292305414135">Ouvrir le lien dans un nouvel ongle&amp;t</translation>
++<translation id="1639192739400715787">Pour accéder aux paramètres de sécurité, saisissez le code PIN de la carte SIM.</translation>
++<translation id="7961015016161918242">Jamais</translation>
++<translation id="3950924596163729246">Impossible d'accéder au réseau.</translation>
++<translation id="2835170189407361413">Effacer le formulaire</translation>
++<translation id="4631110328717267096">Échec de la mise à jour du système</translation>
++<translation id="3695919544155087829">Saisissez le mot de passe utilisé pour chiffrer ce fichier de certificat.</translation>
++<translation id="2230051135190148440">CHAP</translation>
++<translation id="6308937455967653460">Enregistrer le lie&amp;n sous...</translation>
++<translation id="5421136146218899937">Effacer les données de navigation...</translation>
++<translation id="5783059781478674569">Options de reconnaissance vocale</translation>
++<translation id="5441100684135434593">Réseau câblé</translation>
++<translation id="3285322247471302225">Nouvel ongle&amp;t</translation>
++<translation id="3943582379552582368">R&amp;etour</translation>
++<translation id="7607002721634913082">Téléchargement suspendu</translation>
++<translation id="480990236307250886">Ouvrir la page d'accueil</translation>
++<translation id="8286036467436129157">Connexion</translation>
++<translation id="5999940714422617743">L'installation de <ph name="EXTENSION_NAME"/> est terminée.</translation>
++<translation id="1122198203221319518">&amp;Outils</translation>
++<translation id="5757539081890243754">Page d'accueil</translation>
++<translation id="2760009672169282879">Clavier phonétique bulgare</translation>
++<translation id="6608140561353073361">Cookies et données de site...</translation>
++<translation id="8007030362289124303">Batterie faible</translation>
++<translation id="4513946894732546136">Commentaires</translation>
++<translation id="1135328998467923690">Package incorrect : &quot;<ph name="ERROR_CODE"/>&quot;.</translation>
++<translation id="5906719743126878045"><ph name="NUMBER_TWO"/> heures restantes</translation>
++<translation id="1753682364559456262">Configurer les paramètres de blocage des images...</translation>
++<translation id="6550675742724504774">Options</translation>
++<translation id="8959208747503200525"><ph name="NUMBER_TWO"/> hours ago</translation>
++<translation id="431076611119798497">&amp;Détails</translation>
++<translation id="737801893573836157">Masquer la barre de titre du système et utiliser les bordures</translation>
++<translation id="5352235189388345738">Elle peut accéder aux éléments suivants :</translation>
++<translation id="5040262127954254034">Confidentialité</translation>
++<translation id="7666868073052500132">Objets : <ph name="USAGES"/></translation>
++<translation id="6985345720668445131">Paramètres d'entrée du japonais</translation>
++<translation id="3258281577757096226">Sebeol-sik Final</translation>
++<translation id="6906268095242253962">Veuillez vous connecter à Internet pour continuer.</translation>
++<translation id="1908748899139377733">Afficher les &amp;infos sur le cadre</translation>
++<translation id="803771048473350947">Fichier</translation>
++<translation id="6206311232642889873">Cop&amp;ier l'image</translation>
++<translation id="5158983316805876233">Utiliser le même proxy pour tous les protocoles</translation>
++<translation id="7108338896283013870">Masquer</translation>
++<translation id="3366404380928138336">Requête de protocole externe</translation>
++<translation id="5300589172476337783">Afficher</translation>
++<translation id="3160041952246459240">Certains de vos certificats enregistrés identifient ces serveurs :</translation>
++<translation id="566920818739465183">Vous avez visité ce site pour la première fois le <ph name="VISIT_DATE"/>.</translation>
++<translation id="2961695502793809356">Cliquer pour avancer, maintenir pour voir l'historique</translation>
++<translation id="4092878864607680421">La dernière version de l'application &quot;<ph name="APP_NAME"/>&quot; requiert d'autres autorisations. Elle a donc été désactivée.</translation>
++<translation id="8421864404045570940"><ph name="NUMBER_DEFAULT"/> secondes</translation>
++<translation id="5828228029189342317">Vous avez choisi d'ouvrir automatiquement certains types de fichiers après leur téléchargement.</translation>
++<translation id="1416836038590872660">EAP-MD5</translation>
++<translation id="176587472219019965">&amp;Nouvelle fenêtre</translation>
++<translation id="2788135150614412178">+</translation>
++<translation id="4055738107007928968">Vous avez essayé d'accéder au site <ph name="DOMAIN"/>, mais le serveur a présenté un certificat signé avec un algorithme de signature faible. Il se peut que les informations d'identification fournies par le serveur aient été falsifiées. Le serveur n'est peut-être pas celui auquel vous souhaitez accéder (il peut s'agir d'une tentative de piratage). Nous nous déconseillons vivement de continuer.</translation>
++<translation id="5308689395849655368">L'envoi de rapports d'erreur est désactivé.</translation>
++<translation id="8372369524088641025">Clé WEP incorrecte</translation>
++<translation id="8689341121182997459">Date d'expiration :</translation>
++<translation id="899403249577094719">URL de base du certificat Netscape</translation>
++<translation id="2737363922397526254">Réduire...</translation>
++<translation id="4880827082731008257">Rechercher dans l'historique</translation>
++<translation id="8661290697478713397">Ouvrir le lien dans la fenêtre de navi&amp;gation privée</translation>
++<translation id="4197700912384709145"><ph name="NUMBER_ZERO"/> secondes</translation>
++<translation id="7454780465968211330">Historique avancé pour le champ polyvalent</translation>
++<translation id="2158448795143567596">Active l'utilisation de graphismes 3D dans les éléments canvas via l'API WebGL.</translation>
++<translation id="1702534956030472451">Occident</translation>
++<translation id="6636709850131805001">État non reconnu</translation>
++<translation id="6095984072944024315">−</translation>
++<translation id="9141716082071217089">Impossible de vérifier si le certificat du serveur a été révoqué.</translation>
++<translation id="4304224509867189079">Se connecter</translation>
++<translation id="5332624210073556029">Fuseau horaire :</translation>
++<translation id="4799797264838369263">Cette option est soumise à une stratégie d'entreprise. Contactez votre administrateur pour plus d'informations.</translation>
++<translation id="4492190037599258964">Résultats de recherche pour &quot;<ph name="SEARCH_STRING"/>&quot;</translation>
++<translation id="3573179567135747900">Revenir à &quot;<ph name="FROM_LOCALE"/>&quot; (redémarrage requis)</translation>
++<translation id="2238123906478057869"><ph name="PRODUCT_NAME"/> va exécuter les tâches suivantes :</translation>
++<translation id="4042471398575101546">Ajouter la page</translation>
++<translation id="8848709220963126773">Changement de mode via la touche Maj</translation>
++<translation id="4871865824885782245">Options de date et d'heure...</translation>
++<translation id="8828933418460119530">Nom DNS</translation>
++<translation id="988159990683914416">Build de développement</translation>
++<translation id="8026354464835030469"><ph name="BURNT_AMOUNT"/> sur ...</translation>
++<translation id="4114470632216071239">Verrouiller la carte SIM (code PIN obligatoire pour utiliser les données mobiles)</translation>
++<translation id="2183426022964444701">Sélectionnez le répertoire racine de l'extension.</translation>
++<translation id="2517143724531502372">Les cookies de <ph name="DOMAIN"/> sont autorisés uniquement pour cette session.</translation>
++<translation id="9018524897810991865">Confirmer les préférences de synchronisation</translation>
++<translation id="4719905780348837473">RSN</translation>
++<translation id="5212108862377457573">Ajuster la conversion en fonction de l'entrée précédente</translation>
++<translation id="5398353896536222911">Afficher le panneau de la &amp;vérification orthographique</translation>
++<translation id="5811533512835101223">(Revenir à la capture d'écran d'origine)</translation>
++<translation id="5131817835990480221">Mettre à jour &amp;<ph name="PRODUCT_NAME"/></translation>
++<translation id="939519157834106403">SSID</translation>
++<translation id="3705722231355495246">-</translation>
++<translation id="2635102990349508383">Les informations de connexion au compte n'ont pas encore été saisies.</translation>
++<translation id="6902055721023340732">URL de configuration automatique</translation>
++<translation id="4268574628540273656">URL :</translation>
++<translation id="7481312909269577407">Avancer</translation>
++<translation id="3759876923365568382"><ph name="NUMBER_FEW"/> jours restants</translation>
++<translation id="295228163843771014">Vous avez choisi de ne pas synchroniser les mots de passe. Vous pouvez à tout moment modifier vos paramètres de synchronisation, si vous changez d'avis.</translation>
++<translation id="5972826969634861500">Lancer <ph name="PRODUCT_NAME"/></translation>
++<translation id="7828702903116529889"><ph name="PRODUCT_NAME"/>
++ ne parvient pas à accéder au réseau.
++ <ph name="LINE_BREAK"/>
++ Il est possible que votre pare-feu ou votre antivirus considère
++ <ph name="PRODUCT_NAME"/>
++ comme un intrus dans votre ordinateur et qu'il bloque ses tentatives de connexion à Internet.</translation>
++<translation id="878069093594050299">Ce certificat a été vérifié pour les utilisations suivantes :</translation>
++<translation id="5852112051279473187">Petit problème ! Une erreur est survenue lors de l'inscription de ce périphérique. Veuillez réessayer ou contacter votre représentant de l'assistance technique.</translation>
++<translation id="1664314758578115406">Ajouter aux favoris</translation>
++<translation id="7088418943933034707">Gérer les certificats...</translation>
++<translation id="8482183012530311851">Analyse du périphérique...</translation>
++<translation id="3127589841327267804">PYJJ</translation>
++<translation id="8808478386290700967">Web Store</translation>
++<translation id="1732215134274276513">Annuler l'épinglage des onglets</translation>
++<translation id="4084682180776658562">Favori</translation>
++<translation id="8859057652521303089">Sélectionnez votre langue :</translation>
++<translation id="3030138564564344289">Réessayer le téléchargement</translation>
++<translation id="8525552230188318924">Configurer la synchronisation des mots de passe</translation>
++<translation id="4381091992796011497">Nom d'utilisateur :</translation>
++<translation id="5830720307094128296">Enregistrer la p&amp;age sous...</translation>
++<translation id="8114439576766120195">Vos données sur tous les sites Web</translation>
++<translation id="4668954208278016290">Un problème est survenu lors de l'extraction de l'image sur l'ordinateur.</translation>
++<translation id="5822838715583768518">Lancer l'application</translation>
++<translation id="3942974664341190312">Dubeol-sik</translation>
++<translation id="8477241577829954800">Remplacé</translation>
++<translation id="6735304988756581115">Afficher les cookies et autres données de site...</translation>
++<translation id="3048564749795856202">Si vous pensez avoir cerné les risques, vous pouvez <ph name="PROCEED_LINK"/>.</translation>
++<translation id="2433507940547922241">Apparence</translation>
++<translation id="839072384475670817">Créer des raccourci&amp;s vers des applications...</translation>
++<translation id="1478632774608054702">Exécuter le flash PPAPI dans le processus du moteur de rendu</translation>
++<translation id="6756161853376828318">Définir <ph name="PRODUCT_NAME"/> en tant que navigateur par défaut</translation>
++<translation id="9112614144067920641">Veuillez choisir un nouveau code PIN.</translation>
++<translation id="2061855250933714566"><ph name="ENCODING_CATEGORY"/> (<ph name="ENCODING_NAME"/>)</translation>
++<translation id="7138678301420049075">Autre</translation>
++<translation id="9147392381910171771">&amp;Options</translation>
++<translation id="1803557475693955505">Impossible de charger la page d'arrière-plan &quot;<ph name="BACKGROUND_PAGE"/>&quot;.</translation>
++<translation id="5818334088068591797">À quel niveau rencontrez-vous des problèmes ? (Champ obligatoire)</translation>
++<translation id="6264485186158353794">Retour à la sécurité</translation>
++<translation id="5130080518784460891">Eten</translation>
++<translation id="5847724078457510387">Ce site répertorie tous ses certificats valides dans le système DNS. Un certificat non répertorié a cependant été utilisé par le serveur.</translation>
++<translation id="1394853081832053657">Options de reconnaissance vocale</translation>
++<translation id="5037676449506322593">Tout sélectionner</translation>
++<translation id="2785530881066938471">Impossible de charger le fichier &quot;<ph name="RELATIVE_PATH"/>&quot; pour le script de contenu, car ce fichier n'est pas codé en UTF-8.</translation>
++<translation id="3807747707162121253">&amp;Annuler</translation>
++<translation id="3306897190788753224">Désactiver temporairement la personnalisation des conversions, les suggestions basées sur l'historique et le dictionnaire utilisateur</translation>
++<translation id="2574102660421949343">Les cookies de <ph name="DOMAIN"/> sont autorisés.</translation>
++<translation id="77999321721642562">Au fil du temps, la zone ci-dessous affichera les huit sites que vous avez le plus visités.</translation>
++<translation id="1503894213707460512">Le plug-in <ph name="PLUGIN_NAME"/> a besoin de votre autorisation pour s'exécuter.</translation>
++<translation id="471800408830181311">Échec de création de clé privée</translation>
++<translation id="1273291576878293349">Ouvrir tous les favoris dans une fenêtre de navigation privée</translation>
++<translation id="1639058970766796751">Placer dans la file d'attente</translation>
++<translation id="1177437665183591855">Erreur de certificat serveur inconnue</translation>
++<translation id="8467473010914675605">Mode de saisie du coréen</translation>
++<translation id="3819800052061700452">&amp;Plein écran</translation>
++<translation id="5419540894229653647"><ph name="ERROR_DESCRIPTION_TEXT"/>
++ <ph name="LINE_BREAK"/>
++ Vous pouvez essayer de diagnostiquer le problème en procédant comme suit :
++ <ph name="LINE_BREAK"/>
++ <ph name="PLATFORM_TEXT"/></translation>
++<translation id="3533943170037501541">Bienvenue sur votre page d'accueil !</translation>
++<translation id="2333340435262918287">Vos modifications seront prises en compte au prochain démarrage de <ph name="PRODUCT_NAME"/>.</translation>
++<translation id="5906065664303289925">Adresse du matériel :</translation>
++<translation id="3178000186192127858">Lecture seule</translation>
++<translation id="2187895286714876935">Erreur d'importation du certificat serveur</translation>
++<translation id="5460896875189097758">Données stockées localement</translation>
++<translation id="4618990963915449444">Tous les fichiers de <ph name="DEVICE_NAME"/> vont être effacés.</translation>
++<translation id="614998064310228828">Modèle du périphérique :</translation>
++<translation id="1581962803218266616">Afficher dans le Finder</translation>
++<translation id="6096326118418049043">Nom X.500</translation>
++<translation id="6086259540486894113">Vous devez sélectionner au moins un type de données à synchroniser.</translation>
++<translation id="923467487918828349">Tout afficher</translation>
++<translation id="5101042277149003567">Ouvrir tous les favoris</translation>
++<translation id="4298972503445160211">Clavier danois</translation>
++<translation id="6621440228032089700">Cette fonctionnalité permet de réaliser un rendu hors écran de la texture, au lieu d'un affichage direct.</translation>
++<translation id="3488065109653206955">Partiellement activé</translation>
++<translation id="1481244281142949601">Votre système Sandbox est correctement configuré.</translation>
++<translation id="4849517651082200438">Ne pas installer</translation>
++<translation id="8602882075393902833">Activer la recherche instantanée pour accélérer la recherche et la navigation</translation>
++<translation id="6349678711452810642">Utiliser par défaut</translation>
++<translation id="6263284346895336537">Non essentielle</translation>
++<translation id="6409731863280057959">Fenêtres pop-up</translation>
++<translation id="3459774175445953971">Dernière modification :</translation>
++<translation id="73289266812733869">Désélectionné</translation>
++<translation id="3435738964857648380">Sécurité</translation>
++<translation id="9112987648460918699">Rechercher...</translation>
++<translation id="2231233239095101917">Le script de la page utilisait trop de mémoire. Rafraîchissez la page pour réactiver le script.</translation>
++<translation id="870805141700401153">Signature du code individuel Microsoft</translation>
++<translation id="5119173345047096771">Mozilla Firefox</translation>
++<translation id="9020278534503090146">Page Web inaccessible</translation>
++<translation id="4768698601728450387">Recadrer l'image</translation>
++<translation id="6245028464673554252">Si vous fermez <ph name="PRODUCT_NAME"/> maintenant, le téléchargement sera annulé.</translation>
++<translation id="3943857333388298514">Coller</translation>
++<translation id="385051799172605136">Retour</translation>
++<translation id="1742300158964248589">Impossible de graver l'image.</translation>
++<translation id="2670965183549957348">Mode de saisie du Chewing</translation>
++<translation id="5095208057601539847">Province</translation>
++<translation id="4085298594534903246">JavaScript a été bloqué sur cette page.</translation>
++<translation id="5630492933376732170">Remarque : Lorsque vous cliquez sur &quot;Envoyer&quot;, Google Chrome OS
++ joint à votre envoi un journal des événements système de
++ votre périphérique. Ces informations nous permettent de diagnostiquer les
++ problèmes, de comprendre comment vous interagissez avec votre
++ périphérique et d'améliorer les performances de ce dernier. Les
++ informations personnelles fournies sciemment dans vos commentaires ou
++ involontairement dans les journaux système et la capture d'écran sont
++ protégées conformément à nos <ph name="BEGIN_LINK"/>Règles de confidentialité<ph name="END_LINK"/>.
++ Si vous ne souhaitez pas envoyer de journaux système, décochez la case
++ &quot;Inclure les informations système&quot;.</translation>
++<translation id="4341977339441987045">Interdire à tous les sites de stocker des données</translation>
++<translation id="806812017500012252">Trier par nom</translation>
++<translation id="3781751432212184938">Afficher un aperçu des onglets...</translation>
++<translation id="2960316970329790041">Annuler l'importation</translation>
++<translation id="3835522725882634757">Ce serveur envoie des données que <ph name="PRODUCT_NAME"/> ne comprend pas. Veuillez <ph name="BEGIN_LINK"/>signaler un bug<ph name="END_LINK"/> et inclure la <ph name="BEGIN2_LINK"/>liste des raw<ph name="END2_LINK"/>.</translation>
++<translation id="5361734574074701223">Calcul de la durée restante</translation>
++<translation id="6937152069980083337">Mode de saisie Google du japonais (pour clavier américain)</translation>
++<translation id="1731911755844941020">Envoi de la requête...</translation>
++<translation id="8371695176452482769">Parlez maintenant</translation>
++<translation id="2988488679308982380">Impossible d'installer le package : &quot;<ph name="ERROR_CODE"/>&quot;</translation>
++<translation id="2904079386864173492">Modèle :</translation>
++<translation id="3447644283769633681">Bloquer tous les cookies tiers</translation>
++<translation id="8917047707340793412">Remplacer par <ph name="ENGINE_NAME"/></translation>
++<translation id="6129953537138746214">Espace</translation>
++<translation id="3704331259350077894">Arrêt du fonctionnement</translation>
++<translation id="5801568494490449797">Préférences</translation>
++<translation id="1038842779957582377">Nom inconnu</translation>
++<translation id="5327248766486351172">Nom</translation>
++<translation id="5553784454066145694">Choisir un nouveau code PIN</translation>
++<translation id="8989148748219918422"><ph name="ORGANIZATION"/> [<ph name="COUNTRY"/>]</translation>
++<translation id="4664482161435122549">Erreur d'exportation de fichier PKCS #12</translation>
++<translation id="2445081178310039857">Le répertoire racine de l'extension doit être indiqué.</translation>
++<translation id="8251578425305135684">Miniature supprimée</translation>
++<translation id="6163522313638838258">Tout développer...</translation>
++<translation id="3037605927509011580">Aie aie aie</translation>
++<translation id="5803531701633845775">Choisir les expressions en arrière-plan, sans déplacer le pointeur</translation>
++<translation id="1918141783557917887">Plu&amp;s petit</translation>
++<translation id="6996550240668667907">Afficher le clavier en superposition</translation>
++<translation id="4065006016613364460">C&amp;opier l'URL de l'image</translation>
++<translation id="6965382102122355670">OK</translation>
++<translation id="8000066093800657092">Aucun réseau détecté</translation>
++<translation id="4481249487722541506">Charger l'extension non empaquetée...</translation>
++<translation id="8180239481735238521">page</translation>
++<translation id="8321738493186308836">Active l'interface utilisateur et le code de support pour le processus du service de communication à distance, de même que le plug-in client. Avertissement : ce service n'est actuellement disponible que pour les tests de développeurs. Si vous ne faites pas partie de l'équipe de développement et ne figurez pas sur la liste blanche, aucun élément de l'interface utilisateur activée ne fonctionnera.</translation>
++<translation id="2963783323012015985">Clavier turc</translation>
++<translation id="2149973817440762519">Modifier le favori</translation>
++<translation id="5431318178759467895">Couleur</translation>
++<translation id="7064842770504520784">Personnaliser les paramètres de synchronisation...</translation>
++<translation id="2784407158394623927">Activation de votre service Internet mobile</translation>
++<translation id="3679848754951088761"><ph name="SOURCE_ORIGIN"/></translation>
++<translation id="6920989436227028121">Ouvrir dans un onglet standard</translation>
++<translation id="4057041477816018958"><ph name="SPEED"/> - <ph name="RECEIVED_AMOUNT"/></translation>
++<translation id="2050339315714019657">Portrait</translation>
++<translation id="6978839998405419496"><ph name="NUMBER_ZERO"/> days ago</translation>
++<translation id="6139139147415955203">Active un service en arrière-plan qui connecte le service <ph name="CLOUD_PRINT_NAME"/> aux éventuelles imprimantes installées sur cet ordinateur. Une fois ce labo activé, vous pouvez lancer <ph name="CLOUD_PRINT_NAME"/> en vous connectant à votre compte Google via Options/Préférences dans la section Options avancées.</translation>
++<translation id="5112577000029535889">Outils de &amp;développement</translation>
++<translation id="2301382460326681002">Le répertoire racine de l'extension est incorrect.</translation>
++<translation id="7839192898639727867">ID de clé de l'objet du certificat</translation>
++<translation id="4759238208242260848">Téléchargements</translation>
++<translation id="2879560882721503072">Le stockage du certificat client généré par <ph name="ISSUER"/> a réussi.</translation>
++<translation id="1275718070701477396">Sélectionnée</translation>
++<translation id="1178581264944972037">Suspendre</translation>
++<translation id="6492313032770352219">Taille sur le disque :</translation>
++<translation id="5233231016133573565">ID du processus</translation>
++<translation id="5941711191222866238">Réduire</translation>
++<translation id="4121428309786185360">Expire le</translation>
++<translation id="2049137146490122801">Votre administrateur a désactivé l'accès aux fichiers locaux sur votre ordinateur.</translation>
++<translation id="1146498888431277930">Erreur de connexion SSL</translation>
++<translation id="8041089156583427627">Envoyer</translation>
++<translation id="6394627529324717982">Virgule</translation>
++<translation id="253434972992662860">&amp;Pause</translation>
++<translation id="335985608243443814">Parcourir...</translation>
++<translation id="7802488492289385605">Mode de saisie Google du japonais (pour clavier Dvorak américain)</translation>
++<translation id="7452120598248906474">Police à largeur fixe</translation>
++<translation id="3129687551880844787">Stockage de session</translation>
++<translation id="7427348830195639090">Page en arrière-plan : <ph name="BACKGROUND_PAGE_URL"/></translation>
++<translation id="5898154795085152510">Le serveur a renvoyé un certificat client incorrect. Erreur <ph name="ERROR_NUMBER"/> (<ph name="ERROR_NAME"/>)</translation>
++<translation id="2704184184447774363">Signature de document Microsoft</translation>
++<translation id="5677928146339483299">Bloqué</translation>
++<translation id="1474842329983231719">Gérer les paramètres d'impression...</translation>
++<translation id="2455981314101692989">Cette page Web a désactivé la saisie automatique dans ce formulaire.</translation>
++<translation id="1646136617204068573">Clavier hongrois</translation>
++<translation id="5988840637546770870">Les versions en développement permettent de tester de nouvelles idées, mais elles peuvent s'avérer très instables. Nous vous prions d'agir avec précaution.</translation>
++<translation id="3569713929051927529">Ajouter un dossier...</translation>
++<translation id="4032664149172368180">Mode de saisie du japonais (pour clavier Dvorak américain)</translation>
++<translation id="3748706263662799310">Signaler un bug</translation>
++<translation id="7167486101654761064">&amp;Toujours ouvrir les fichiers de ce type</translation>
++<translation id="4283623729247862189">Disque optique</translation>
++<translation id="5826507051599432481">Nom commun</translation>
++<translation id="8914326144705007149">Très grande</translation>
++<translation id="4215444178533108414">Modification terminée</translation>
++<translation id="5154702632169343078">Objet</translation>
++<translation id="2273562597641264981">Opérateur :</translation>
++<translation id="122082903575839559">Algorithme de signature du certificat</translation>
++<translation id="2181257377760181418">Cette fonctionnalité permet d'afficher un onglet d'aperçu avant de lancer une impression.</translation>
++<translation id="7240120331469437312">Autre nom de l'objet du certificat</translation>
++<translation id="6900113680982781280">Activer la saisie automatique pour remplir les formulaires Web d'un simple clic</translation>
++<translation id="1131850611586448366">Le site Web à l'adresse <ph name="HOST_NAME"/> a été signalé comme étant un site de phishing. Ces sites tentent d'amener les internautes à divulguer leurs informations personnelles en se faisant passer pour des institutions de confiance, telles que des banques.</translation>
++<translation id="5413218268059792983">Rechercher directement sur <ph name="SEARCH_ENGINE"/></translation>
++<translation id="1161575384898972166">Connectez-vous à <ph name="TOKEN_NAME"/> pour exporter le certificat client.</translation>
++<translation id="1718559768876751602">Créer un compte Google maintenant</translation>
++<translation id="1884319566525838835">État Sandbox</translation>
++<translation id="2770465223704140727">Retirer de la liste</translation>
++<translation id="3590587280253938212">rapide</translation>
++<translation id="6053401458108962351">&amp;Effacer les données de navigation…</translation>
++<translation id="2339641773402824483">Vérification des mises à jour...</translation>
++<translation id="9111742992492686570">Télécharger les mises à jour de sécurité essentielles</translation>
++<translation id="8636666366616799973">Package incorrect. Détails : &quot;<ph name="ERROR_MESSAGE"/>&quot;.</translation>
++<translation id="2045969484888636535">Continuer à bloquer les cookies</translation>
++<translation id="7353601530677266744">Ligne de commande</translation>
++<translation id="2766006623206032690">Coller l'URL et y a&amp;ccéder</translation>
++<translation id="4394049700291259645">Désactiver</translation>
++<translation id="969892804517981540">Build officiel</translation>
++<translation id="445923051607553918">Se connecter à un réseau Wi-Fi</translation>
++<translation id="100242374795662595">Périphérique inconnu</translation>
++<translation id="9087725134750123268">Supprimer les cookies et autres données de site</translation>
++<translation id="5050255233730056751">URL saisies</translation>
++<translation id="3349155901412833452">Utiliser les touches , et . pour paginer une liste d'entrées</translation>
++<translation id="6872947427305732831">Vider la mémoire</translation>
++<translation id="2742870351467570537">Supprimer les éléments sélectionnés</translation>
++<translation id="7561196759112975576">Toujours</translation>
++<translation id="2116673936380190819">de moins d'une heure</translation>
++<translation id="5765491088802881382">Aucun réseau n'est disponible.</translation>
++<translation id="1971538228422220140">Supprimer les cookies et autres données de site et de plug-in</translation>
++<translation id="883487340845134897">Intervertir les touches Rechercher et Ctrl gauche</translation>
++<translation id="5692957461404855190">Faites glisser trois doigts sur la surface de votre trackpad pour afficher un aperçu de tous vos onglets. Cliquez sur une vignette pour la sélectionner. Idéal en mode plein écran.</translation>
++<translation id="1375215959205954975">Nouveau ! Configurer la synchronisation des mots de passe</translation>
++<translation id="5183088099396036950">Échec de la tentative de connexion au serveur</translation>
++<translation id="4469842253116033348">Désactiver les notifications de <ph name="SITE"/></translation>
++<translation id="7999229196265990314">Les fichiers suivants ont été créés :
++
++Extension : <ph name="EXTENSION_FILE"/>
++Fichier de clé : <ph name="KEY_FILE"/>
++
++Conservez votre fichier de clé en lieu sûr. Vous en aurez besoin lors de la création de nouvelles versions de l'extension.</translation>
++<translation id="1846078536247420691">&amp;Oui</translation>
++<translation id="3036649622769666520">Ouvrir les fichiers</translation>
++<translation id="2966459079597787514">Clavier suédois</translation>
++<translation id="7685049629764448582">Mémoire JavaScript </translation>
++<translation id="6398765197997659313">Quitter le mode plein écran</translation>
++<translation id="6059652578941944813">Hiérarchie des certificats</translation>
++<translation id="4886690096315032939">Afficher l'onglet existant si l'URL associée est demandée dans un autre</translation>
++<translation id="5729712731028706266">&amp;Afficher</translation>
++<translation id="774576312655125744">Vos données personnelles sur <ph name="WEBSITE_1"/>, <ph name="WEBSITE_2"/> et sur <ph name="NUMBER_OF_OTHER_WEBSITES"/> autres sites Web</translation>
++<translation id="6359806961507272919">SMS de <ph name="PHONE_NUMBER"/></translation>
++<translation id="4508765956121923607">Afficher la s&amp;ource</translation>
++<translation id="5975083100439434680">Zoom arrière</translation>
++<translation id="8080048886850452639">C&amp;opier l'URL du fichier audio</translation>
++<translation id="2817109084437064140">Importer et associer au périphérique...</translation>
++<translation id="3331321258768829690">(<ph name="UTCOFFSET"/>) <ph name="LONGTZNAME"/> (<ph name="EXEMPLARCITY"/>)</translation>
++<translation id="619398760000422129">Plug-ins (par ex. Adobe Flash Player, QuickTime, etc.)</translation>
++<translation id="5849869942539715694">Empaqueter l'extension...</translation>
++<translation id="7339785458027436441">Vérifier l'orthographe lors de la frappe</translation>
++<translation id="8308427013383895095">Échec de la traduction en raison d'un problème de connexion réseau</translation>
++<translation id="1801298019027379214">Code PIN incorrect. Veuillez réessayer. Nombre de tentatives restantes : <ph name="TRIES_COUNT"/></translation>
++<translation id="1384721974622518101">Vous pouvez effectuer une recherche directement à partir du champ ci-dessus.</translation>
++<translation id="992543612453727859">Ajouter les expressions au premier plan</translation>
++<translation id="3857773447683694438">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</translation>
++<translation id="1244147615850840081">Opérateur</translation>
++<translation id="8203365863660628138">Confirmer l'installation</translation>
++<translation id="406259880812417922">(Mot clé : <ph name="KEYWORD"/>)</translation>
++<translation id="309628958563171656">Sensibilité :</translation>
++</translationbundle>
+diff --git a/tools/grit/grit/testdata/generated_resources_iw.xtb b/tools/grit/grit/testdata/generated_resources_iw.xtb
+new file mode 100644
+index 0000000000..86b55334c0
+--- /dev/null
++++ b/tools/grit/grit/testdata/generated_resources_iw.xtb
+@@ -0,0 +1,4 @@
++<?xml version="1.0" ?>
++<!DOCTYPE translationbundle>
++<translationbundle lang="iw">
++</translationbundle>
+diff --git a/tools/grit/grit/testdata/generated_resources_no.xtb b/tools/grit/grit/testdata/generated_resources_no.xtb
new file mode 100644
-index 0000000000..aa90a95eb3
+index 0000000000..913638ba4e
--- /dev/null
-+++ b/tools/clang/plugins/tests/nested_class_inline_ctor.cpp
++++ b/tools/grit/grit/testdata/generated_resources_no.xtb
+@@ -0,0 +1,4 @@
++<?xml version="1.0" ?>
++<!DOCTYPE translationbundle>
++<translationbundle lang="no">
++</translationbundle>
+diff --git a/tools/grit/grit/testdata/grit_part.grdp b/tools/grit/grit/testdata/grit_part.grdp
+new file mode 100644
+index 0000000000..c8e9d92692
+--- /dev/null
++++ b/tools/grit/grit/testdata/grit_part.grdp
@@ -0,0 +1,5 @@
-+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+// Use of this source code is governed by a BSD-style license that can be
-+// found in the LICENSE file.
++<?xml version="1.0" encoding="UTF-8"?>
++<grit-part>
++ <!-- Important for test purposes that this file not exist. -->
++ <structure type="chrome_scaled_image" name="IDR_DOES_NOT_EXIST" file="does-not-exist.png" />
++</grit-part>
+diff --git a/tools/grit/grit/testdata/header.html b/tools/grit/grit/testdata/header.html
+new file mode 100644
+index 0000000000..8e9d10ec50
+--- /dev/null
++++ b/tools/grit/grit/testdata/header.html
+@@ -0,0 +1,39 @@
++<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
++<html><head><title>[$~TITLE~$]</title>
++<meta http-equiv="cache-control" content="no-cache">
++<meta http-equiv="content-type" content="text/html; charset=utf-8">
++<meta http-equiv="pragma" content="no-cache">
++<meta http-equiv="expires" content="-1">
++[EXTRA_META]
++<style>
++BODY,TD,DIV,.P,A { FONT-FAMILY: arial,sans-serif}
++DIV,TD { COLOR: #000}
++.f { COLOR: #6f6f6f}
++.fl:link { COLOR: #6f6f6f}
++A:link, .w, A.w:link, .w A:link { COLOR: #00c}
++A:visited { COLOR: #551a8b}
++.fl:visited { COLOR: #551a8b}
++A:active, .fl:active { COLOR: #f00}
++.h { COLOR: #3399CC}
++.i { COLOR: #a90a08}
++.i:link { COLOR: #a90a08}
++.a, .a:link, .a:visited { COLOR: #008000}
++DIV.n { MARGIN-TOP: 1ex}
++.n A { FONT-SIZE: 10pt; COLOR: #000}
++.n .i { FONT-WEIGHT: bold; FONT-SIZE: 10pt}
++.q A:visited { COLOR: #00c}
++.q A:link { COLOR: #00c}
++.q A:active { COLOR: #00c}
++.q { COLOR: #00c}
++.b { FONT-WEIGHT: bold; FONT-SIZE: 12pt; COLOR: #00c}
++.ch { CURSOR: hand}
++.e { MARGIN-TOP: 0.75em; MARGIN-BOTTOM: 0.75em}
++.g { MARGIN-TOP: 1em; MARGIN-BOTTOM: 1em}
++.f { MARGIN-TOP: 0.5em; MARGIN-BOTTOM: 0.25em}
++.s { HEIGHT: 10px }
++.c:active { COLOR: #ff0000}
++.c:visited { COLOR: #551a8b}
++.c:link { COLOR: #7777cc}
++.c { COLOR: #7777cc }
++</style>
++</head>
+\ No newline at end of file
+diff --git a/tools/grit/grit/testdata/homepage.html b/tools/grit/grit/testdata/homepage.html
+new file mode 100644
+index 0000000000..cce4f2cf35
+--- /dev/null
++++ b/tools/grit/grit/testdata/homepage.html
+@@ -0,0 +1,37 @@
++<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
++<html><head><title>Google Desktop Search</title>
++<meta http-equiv="content-type" content="text/html; charset=utf-8">
++<meta http-equiv="cache-control" content="no-cache">
++<meta http-equiv="pragma" content="no-cache">
++<meta http-equiv="expires" content="-1">
++<style>
++BODY,TD,A,P {FONT-FAMILY: arial,sans-serif}
++.q {COLOR: #0000cc}
++</style>
++<script>
++<!--
++function sf(){document.f.q.focus();}
++// -->
++</script>
++</head>
++<BODY text=#000000 vLink=#551a8b aLink=#ff0000 link=#0000cc bgColor=#ffffff onload=sf()>
++<center>
++<TABLE cellSpacing=0 cellPadding=0 border=0>
++<tr><td><a href="[$~HOMEPAGE~$]"><IMG border=0 height=110 alt="Google Desktop Search" src="hp_logo.gif" width=276></a></td></tr></table><BR>
++<FORM name=f method=GET action='[$~SEARCHURL~$]'>
++<TABLE cellSpacing=0 cellPadding=4 border=0>
++<tr>
++<TD class=q noWrap><FONT size=-1>
++[$~LINKS~$]
++</font></td>
++</tr></table>
++<table cellspacing=0 cellpadding=0>
++<tr vAlign=top>
++<td width=25%>&nbsp;</td>
++<td align=center><input maxlength=256 size=62 name=q value="[DISP_QUERY]"><br><input type=submit value="Search Desktop" name=btnG><INPUT type=submit value="Search the Web" name="redir" accesskey=w></td>
++<td align=left valign=top nowrap width=25%><font size=-2>&nbsp;&nbsp;<A href="[$~PREFERENCES~$]">Desktop&nbsp;Preferences</a></font></td>
++</tr></table></FORM>
++<p><FONT color=#224499><B>Search your own computer.</B></font></p>
++<span style='width:29em'>[$~MESSAGE~$]</span><br>
++<br><FONT size=-1>[$~SETHOMEPAGE~$][$~BOTTOMLINE~$]</font></p>
++<p><FONT size=-2>&copy;2005 Google - Searching [NUM_ITEMS] items</font></p></center></body></html>
+\ No newline at end of file
+diff --git a/tools/grit/grit/testdata/hover.html b/tools/grit/grit/testdata/hover.html
+new file mode 100644
+index 0000000000..b8f9ce0200
+--- /dev/null
++++ b/tools/grit/grit/testdata/hover.html
+@@ -0,0 +1,177 @@
++<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
++<html><head>
++<meta http-equiv="content-type" content="text/html; charset=utf-8">
++<meta http-equiv="cache-control" content="no-cache">
++<meta http-equiv="pragma" content="no-cache">
++<meta http-equiv="expires" content="-1">
++<style>
++BODY { FONT-SIZE: 8pt; FONT-FAMILY: verdana,arial,san-serif }
++P { FONT-SIZE: 8pt; FONT-FAMILY: verdana,arial,san-serif }
++TD { FONT-SIZE: 8pt; FONT-FAMILY: verdana,arial,san-serif }
++A { FONT-SIZE: 8pt; FONT-FAMILY: verdana,arial,san-serif }
++DIV { FONT-SIZE: 8pt; TEXT-DECORATION: none }
++A:hover { COLOR: #ffffff }
++.border { BORDER-RIGHT: 0px; BORDER-TOP: 0px; BORDER-LEFT: 0px; BORDER-BOTTOM: 0px }
++</style>
++
++<!-- menu experiment start -->
++
++<style>
++<!--
++.menu1 {
++cursor:default;
++position:absolute;
++left: 10;
++top: 0;
++text-align: left;
++font-family: Arial, Helvetica, sans-serif;
++font-size: 8pt;
++background-color: menu;
++visibility: hidden;
++padding-top: 2px;
++padding-bottom: 2px;
++border: 1 solid;
++border-color: #888888;
++z-index: 100;
++}
++.menuitems {
++padding-left: 5px;
++padding-right: 5px;
++}
++-->
++</style>
++<SCRIPT LANGUAGE="JavaScript1.2">
++<!--
++var menustyle = "menu1";
++
++function showmenu() {
++ // var rightedge = document.body.clientWidth-event.clientX;
++ // var bottomedge = document.body.clientHeight-event.clientY;
++ // if (rightedge < rcmenu.offsetWidth)
++ // rcmenu.style.left = document.body.scrollLeft + event.clientX - rcmenu.offsetWidth;
++ // else
++ // rcmenu.style.left = document.body.scrollLeft + event.clientX;
++ // if (bottomedge < rcmenu.offsetHeight)
++ // rcmenu.style.top = document.body.scrollTop + event.clientY - rcmenu.offsetHeight;
++ // else
++ // rcmenu.style.top = document.body.scrollTop + event.clientY;
++
++ rcmenu.style.visibility = "visible";
++ // rcmenu.style.zindex = 0;
++ // document.all('rcmenu').style.zindex = 20;
++ document.onkeydown=ck;
++ return false;
++}
++
++function hidemenu() {
++ rcmenu.style.visibility = "hidden";
++}
++
++function ck(e){
++ evt=document.all?window.event:e;
++ k=document.all?window.event.keyCode:e.keyCode;
++
++ if(k==27 /*<Esc>*/) {
++ hidemenu();
++ }
++}
++
++function menumouseover() {
++ if (event.srcElement.className == "menuitems") {
++ event.srcElement.style.backgroundColor = "highlight";
++ event.srcElement.style.color = "white";
++ }
++}
++
++function menumouseout() {
++ if (event.srcElement.className == "menuitems") {
++ event.srcElement.style.backgroundColor = "";
++ event.srcElement.style.color = "black";
++ window.status = "";
++ }
++}
++
++function menuselect() {
++ if (event.srcElement.className == "menuitems") {
++ if (event.srcElement.getAttribute("target") != null)
++ window.open(event.srcElement.url, event.srcElement.getAttribute("target"));
++ else if (event.srcElement.url.length)
++ window.location = event.srcElement.url;
++ }
++}
++// -->
++</script>
++
++<!-- menu experiment end -->
++
++</head>
++<BODY bottomMargin=0 bgColor=#3300cc leftMargin=0 topMargin=0 rightMargin=0 marginwidth="0" marginheight="0" border=0 style="border-width:0;" scroll=no>
++
++<!-- <br> -->
++
++<!-- menu experiment start -->
++
++<div id="rcmenu" class="skin0" onMouseover="menumouseover()" onMouseout="menumouseout()" onClick="menuselect();">
++<span class="menuitems" url="[$~SETDISP1~$]">Sidebar</span>
++<span class="menuitems" url="[$~SETDISP4~$]">Minibar</span>
++<span class="menuitems" url="[$~HIDE2~$]">Close</span>
++</div>
++
++<script language="JavaScript1.2">
++if (document.all && window.print) {
++ rcmenu.className = menustyle;
++ document.oncontextmenu = showmenu;
++ document.body.onclick = hidemenu;
++}
++</script>
++
++<!-- menu experiment end -->
++
++<script>
++function hide() {
++ return 1;
++ // return confirm("Are you sure you want to hide the minibar?\nYou can show it again in Google Desktop Search Preferences. ");
++}
++function clear() {
++ document.getElementById('q').value='';
++}
++</script>
++
++<TABLE cellSpacing=0 cellPadding=0 bgColor=#3300cc border=0><TBODY>
++<tr><TD vAlign=top>
++
++<form method=get action="[$~SEARCHURL~$]" [$~SEARCH_TARGET~$] name=f1 ID="f1" onsubmit="window.setTimeout('clear()', 500)">
++<input type=hidden name=src value=3>
++<input type=hidden name=redir value='' ID="redir">
++
++<TABLE cellSpacing=0 cellPadding=0 bgColor=#3300cc border=0><TBODY>
++
++<tr>
++<!-- border-top: #414a4f 0px solid; -->
++<!-- z-index:2; z-order:2; -->
++<TD vAlign=top>&nbsp;<INPUT name=q style="position:relative; height=19px;" class=border size=12>&nbsp;</td>
++
++<TD TABINDEX="2" onkeydown='if(event.keyCode!=16&&event.keyCode!=9)onclick()' onclick="f1.submit();q.value=''" class=ch onmouseover="this.bgColor='6666FF'" style="BORDER-RIGHT: #000000 1px solid; BORDER-TOP: #6666cc 1px solid; BORDER-LEFT: #6666cc 1px solid; BORDER-BOTTOM: #000000 1px solid" onmouseout="this.bgColor='#000099'" vAlign=center align=middle bgColor=#000099><IMG src="logo.gif" align=middle></td>
++
++<TD width=2><IMG height=1 width=1></td>
++
++<TD TABINDEX="3" onkeydown='if(event.keyCode!=16&&event.keyCode!=9)onclick()' onclick="redir.value='google'; f1.submit(); redir.value='';q.value=''" class=ch onmouseover="this.bgColor='6666FF'" style="BORDER-RIGHT: #000000 1px solid; BORDER-TOP: #6666cc 1px solid; BORDER-LEFT: #6666cc 1px solid; BORDER-BOTTOM: #000000 1px solid" onmouseout="this.bgColor='#000099'" vAlign=center align=middle bgColor=#000099><IMG src="gfavicon.ico" align=middle></td></tr>
++</TBODY></table>
++</td>
++
++<TD width=5><IMG height=1 width=1></td>
++
++<TD vAlign=top>
++
++<TABLE cellSpacing=0 cellPadding=1 bgColor=#000099><TBODY>
++<tr><TD TABINDEX="4" onkeydown='if(event.keyCode!=16&&event.keyCode!=9)onclick()' valign=top onclick="location.href='[$~SETDISP1~$]';" class=ch onmouseover="this.bgColor='6666FF'" style="BORDER-RIGHT: #000000 1px solid; BORDER-TOP: #6666cc 1px solid; BORDER-LEFT: #6666cc 1px solid; BORDER-BOTTOM: #000000 1px solid" onmouseout="this.bgColor='#000099'" vAlign=top noWrap bgColor=#000099><IMG src="down.gif"></td></tr></TBODY></table>
++
++</td>
++
++<TD width=1><IMG height=1 width=1></td>
++
++<TD vAlign=top><TABLE cellSpacing=0 cellPadding=1><TBODY>
++<tr><TD TABINDEX="5" onkeydown='if(event.keyCode!=16&&event.keyCode!=9)onclick()' valign=top onclick="if (hide())location.href='[$~HIDE2~$]';" class=ch onmouseover="this.bgColor='6666FF'" style="BORDER-RIGHT: #000000 1px solid; BORDER-TOP: #6666cc 1px solid; BORDER-LEFT: #6666cc 1px solid; BORDER-BOTTOM: #000000 1px solid" onmouseout="this.bgColor='#000099'" vAlign=top noWrap bgColor=#000099><IMG src="close.gif"></td></tr></TBODY></table></td></tr></TBODY></table>
++
++</form>
++</body></html>
+diff --git a/tools/grit/grit/testdata/include_test.html b/tools/grit/grit/testdata/include_test.html
+new file mode 100644
+index 0000000000..e08f2e2e8a
+--- /dev/null
++++ b/tools/grit/grit/testdata/include_test.html
+@@ -0,0 +1,31 @@
++<include src="included_sample.html">
++<if expr="True">
++should be kept
++</if>
++in the middle...
++<if expr="False">
++should be removed
++</if>
++
++<if expr="False">
++should be removed
++ <if expr="True">
++ should be removed because outer expr is False
++ </if>
++should be removed
++</if>
++
++<if expr="True">
++ <if expr="True">
++ <if expr="True">
++ nested true should be kept
++ </if>
++ <if expr="False">
++ should be removed
++ </if>
++ </if>
++ <if expr="True">
++ silbing true should be kept
++ </if>
++</if>
++at the end...
+diff --git a/tools/grit/grit/testdata/included_sample.html b/tools/grit/grit/testdata/included_sample.html
+new file mode 100644
+index 0000000000..7150ffcbea
+--- /dev/null
++++ b/tools/grit/grit/testdata/included_sample.html
+@@ -0,0 +1 @@
++Hello Include!
+diff --git a/tools/grit/grit/testdata/indexing_speed.html b/tools/grit/grit/testdata/indexing_speed.html
+new file mode 100644
+index 0000000000..db1787b0e2
+--- /dev/null
++++ b/tools/grit/grit/testdata/indexing_speed.html
+@@ -0,0 +1,58 @@
++<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
++<html><head><title>Google Desktop Search Index Speed</title>
++<meta http-equiv=content-type content="text/html; charset=utf-8">
++<style>
++BODY {
++ MARGIN-LEFT: 3em; MARGIN-RIGHT: 3em; FONT-FAMILY: arial,sans-serif
++}
++</style>
++</head>
++<BODY text=#000000 vLink=#551a8b aLink=#ff0000 link=#0000cc bgColor=#ffffff>
++<TABLE cellSpacing=2 cellPadding=0 width="100%" border=0>
++ <tr>
++ <TD vAlign=top width="1%"><A href="[$~HOMEPAGE~$]">
++ <IMG alt="Go to Google Desktop Search" src="/logo3.gif" border=0></A></td>
++ <td>&nbsp;</td>
++ <TD noWrap>
++ <TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
++ <tr>
++ <TD bgColor=#3399CC><IMG height=1 alt="" width=1></td>
++ </tr>
++ </table>
++ <TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
++ <tr>
++ <TD noWrap bgColor=#efefef><B>&nbsp;Index Speed</B></td>
++ <TD noWrap align=right bgColor=#efefef><FONT size=-1><A href="/customize.html">Index Speed
++ Help</A> | <A href="[$~ABOUT~$]"> About Google Desktop Search</A></font></td>
++ </tr></table></td></tr></table>
++<FONT size=-1>
++<p>
++To make your emails, files, and previously viewed web pages searchable, Google Desktop Search
++needs to index them. This indexing process is currently occuring in the background
++and your computer performance is minimally impacted.
++<p>
++You have the option of speeding up this process.
++<p><B><FONT color=#FF0000>Warning:</font></B> Speeding up indexing will cause your computer
++to become unusable for many minutes, depending on how many items need to be indexed. FAST INDEXING IS NOT
++RECOMMENDED.
++<BR>&nbsp;<BR>
++<FORM action="[$~SETINDEXSPEED~$]" method=GET>
++<input name=url value="[PREVPAGE]" type=hidden>
++<input type=radio name=FAST value="0" [FAST0-CHECKED] id=f0><label for=f0>Use background indexing (recommended)</label><br>
++<input type=radio name=FAST value="1" [FAST1-CHECKED] id=f1><label for=f1>Use fast indexing</label><br><br>
++<input type=submit value="Set Indexing Speed">
++</FORM>
++<BR>
++
++<p>&nbsp;<BR>
++<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
++<TR bgColor=#3399CC>
++ <TD align=middle height=1><IMG height=1 alt="" width=1></td></tr>
++</table>
++
++<TABLE cellSpacing=0 cellPadding=0 width="100%" align=center bgColor=#efefef border=0>
++<tr>
++ <TD align=middle height=20><FONT size=-1><A href="[$~HOMEPAGE~$]">Google Desktop Search&nbsp;Home</A> - <a href="[$~STATUS~$]">Status</a> - <A href="[$~ABOUT~$]">About Google Desktop Search</A> - [$~BUILDNUMBER~$] - &copy;2005 Google </font> </td></tr>
++</table><BR>
++</body>
++</html>
+\ No newline at end of file
+diff --git a/tools/grit/grit/testdata/install_prefs.html b/tools/grit/grit/testdata/install_prefs.html
+new file mode 100644
+index 0000000000..eca0b56de5
+--- /dev/null
++++ b/tools/grit/grit/testdata/install_prefs.html
+@@ -0,0 +1,92 @@
++<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
++<html><head><title>Google Desktop Search: Initial Preferences</title>
++<meta http-equiv="cache-control" content="no-cache">
++<meta http-equiv="content-type" content="text/html; charset=utf-8">
++<meta http-equiv="pragma" content="no-cache">
++<meta http-equiv="expires" content="-1">
++<style>
++BODY { FONT-FAMILY: arial,sans-serif }
++.c:active { COLOR: #FF0000 }
++.c:visited { COLOR: #7777CC }
++.c:link { COLOR: #7777CC }
++</style>
++<script>
++<!--
++override = 1;
++function ee() {if (override==1) {(new Image()).src="[COMPLETING]";}}
++// -->
++</script>
++</head><body leftmargin=30 rightmargin=30 onresize="stw()" onunload="ee()">
++<form onsubmit='override=0;return true;' action='[STEP2]' name=f method=post>
++<img src="logo3.gif" border=0>
++<div id=c1 style="width:600px">
++<br><font color=#00218a><b>To continue, please set these initial preferences:</b></font><br><br>
++<table border=0 id=t1 width=100%>
++<tr>
++ <td valign=top><input name=AIM id=chat type=checkbox checked></td>
++ <td>&nbsp;</td><td><label for=chat><font size=-1><B>Enable search over Instant Messenger chats</b><br>
++ <font size=-1>Google Desktop Search will store your chats and make them searchable.
++</font></label></td></tr>
++<tr height=1><td height=10px></td></tr>
++<tr>
++ <td valign=top><input name=HTTPS id=https type=checkbox checked></td>
++ <td>&nbsp;</td><td><label for=https><font size=-1><b>Enable search over secure web pages (HTTPS)</b>
++ <br><font size=-1>Google Desktop Search will store secure web pages that you view and make them
++ searchable.</font></label> </td></tr>
++<tr height=1px><td height=10px></td></tr>
++
++<tr>
++ <td valign=top><input name=SEARCHBOX id=SEARCHBOX type=checkbox checked
++ onclick="handleSBClick(this)"></td>
++ <td>&nbsp;</td><td><label for=searchbox><font size=-1><b>Display search box</b></label>
++ <br><table border=0 cellpadding=0><tr><td valign=top>
++
++<input type="radio" name="SBDISPLAY" id="DISPLAYDB" [DB-CHECKED] value="DISPLAYDB"></td><td>
++<label for=DISPLAYDB><font size=-1>Deskbar - A search box in your taskbar</font></label></td></tr>
++<tr><td></td></tr>
++<tr><td></td><td><img src="deskbar.gif" alt="Deskbar" width="268" height="34"></td></tr>
++<tr><td height=2></td></tr>
++<tr><td valign=top>
++
++<input type="radio" name="SBDISPLAY" id="DISPLAYMB" [MB-CHECKED] VALUE="DISPLAYMB"></td><td>
++<label for=DISPLAYMB><font size=-1>Floating Deskbar - A search box that you can put anywhere on your desktop</font></label></td></tr>
++<tr><td></td></tr>
++<tr><td></td><td><img src="minibar.gif" width="137" height="27"></td></tr>
++<tr><td height=2></td></tr>
++
++</table>
++</td></tr>
++
++<tr>
++ <td valign=top><input name=SENDDATA id=usage type=checkbox checked></td>
++ <td>&nbsp;</td><td><label for=usage><font size=-1><b>Help us improve Google Desktop Search by sending usage data and crash reports</b></label>
++</font></td></tr>
++<tr height=8px><td colspan=3 height=8px></td></tr>
++<tr><td colspan=3><font size=-1>You can change these and other preferences at any time.</font></td></tr>
++</table></div>
++<p><input type=submit value="Set Preferences and Continue" id=s><br>
++</form>
++</center>
++[SCRIPT]
++<script>
++<!--
++function handleSBClick(checkbox) {
++ document.getElementById("DISPLAYDB").disabled = !checkbox.checked;
++ document.getElementById("DISPLAYMB").disabled = !checkbox.checked;
++}
++function stw() {
++if (document.all && document.body.clientWidth < 600) {
++ var w = document.body.clientWidth-35;
++ if (w < 10) { w = 10; }
++ w = w + 'px';
++ document.getElementById('c1').style.width=w;
++ return false;
++}
++document.getElementById('c1').style.width='600px';
++}
++stw();
++document.f.s.focus();
++// -->
++</script>
++<img SRC="http://www.google.com" WIDTH="0" HEIGHT="0" ALIGN="right"></img>
++</body></html>
+\ No newline at end of file
+diff --git a/tools/grit/grit/testdata/install_prefs2.html b/tools/grit/grit/testdata/install_prefs2.html
+new file mode 100644
+index 0000000000..18380397c2
+--- /dev/null
++++ b/tools/grit/grit/testdata/install_prefs2.html
+@@ -0,0 +1,52 @@
++<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
++<html><head><title>Indexing has Started</title>
++<meta http-equiv="cache-control" content="no-cache">
++<meta http-equiv="content-type" content="text/html; charset=utf-8">
++<meta http-equiv="pragma" content="no-cache">
++<meta http-equiv="expires" content="-1">
++<style>
++BODY { FONT-FAMILY: arial,sans-serif }
++}
++</style>
++<script>
++<!--
++override = 1;
++function ee() {if (override==1) {(new Image()).src="[COMPLETING]";}}
++// -->
++</script>
++</head><body leftmargin=30 rightmargin=30 onresize="stw()" onunload="ee()">
++<form onsubmit='override=0;return true;' action="[STEP3]" name=f>
++<img src="/logo3.gif" border=0><br><br>
++<div id=c1 style="width:575px">
++<table border=0 id=t1 width=100%><tr><td>
++<font color=#00218a><b>One-time indexing has started.</b></font><br><br>
++<font size=-1>An index is being prepared on your computer to allow you
++to search your information as fast as you can search the web.<br><br>
++<li>This is a one-time process that may take several hours.
++<li>You may continue to use your computer as usual and it is safe to shut down your computer.
++<li>Indexing will be performed only when your computer is idle.
++</ul>
++</font>
++<p><input type=submit value="Go to the Desktop Search homepage" name=s><br>
++</center>
++</td></tr></table>
++</form>
++</div>
++<script>
++<!--
++function stw() {
++if (document.all && document.body.clientWidth < 575) {
++ var w = document.body.clientWidth-35;
++ if (w < 10) { w = 10; }
++ w = w + 'px';
++ document.getElementById('c1').style.width=w;
++ return false;
++}
++document.getElementById('c1').style.width='575px';
++}
++stw();
++// -->
++document.f.s.focus();
++</script>
++[SCRIPT]
++</body></html>
+\ No newline at end of file
+diff --git a/tools/grit/grit/testdata/klonk-alternate-skeleton.rc b/tools/grit/grit/testdata/klonk-alternate-skeleton.rc
+new file mode 100644
+index 0000000000000000000000000000000000000000..5f2c82a55469ddaab246c095826ad9e6743c0015
+GIT binary patch
+literal 1088
+zcmbW0Pfr3t48`AhKZV)z#sGrI5!gjnU?DC>IT2z!82=r_L=!)}zjnmk5b$6oGo9&7
+z+t=4lu9UG-Ujxl_t%b{59ih$9PSBn!lW7`iGrKMm&Q10vTRK5!yRJHlRN`fcWril@
+zv|?uHM))d_NBa7`nW9TQ&PZ3tsax6ojav@U&9TYdHduz6k{G4GFTfpX_hk&`!z0F`
+z!gJ>6WBh&UO&i_oS+VOvUfcD9JR=y&;3OxP2%KT$#JB9W=UtgQpDT@>(E^#^A;oG%
+z4omjIK7rLXcRgmyS+%u_Gl2`MhOxMB{GGM&5o6cXF<vdhEe5Mu-+3OQZF~Ht$8Yl5
+z&=^M*j(xG~y3(sxz{#AtW^kPoyR!dZ9)}Sd$_6;Qjx#V<A+O@5j%7~Al)9jj*71v4
+z<zn{ZUuJA?73tB}iB6fJ)6H}8)1l|&XFq3N%P!P%;Wv{#b&7SVweIxDUCbEh>E~=G
+z`!#F5=z%_bq95y7+aIx?IdcSN`A)xX^vZjCS7lnS#=iZ)E7W%eX8!kr-#RDO36^!o
+Qqn&wY8qX0d7T}2V4SbP+*8l(j
+
+literal 0
+HcmV?d00001
+
+diff --git a/tools/grit/grit/testdata/klonk.ico b/tools/grit/grit/testdata/klonk.ico
+new file mode 100644
+index 0000000000000000000000000000000000000000..d371b214dc366249870efccc5da278d023aa91f1
+GIT binary patch
+literal 766
+zcmeH_F%H5o5CqSFL>Ve71Sxq1;TtsMEB*!Fv6LbmDQFSUL1mXjO7OC0gpl|G?B1ML
+zXS=a1V(2`di0U>FnQ~o{oUDnF5j(}bxAgSuhE8lMu~rkI8Ju%mb%Im^Xd<+ZwEgwd
+zFTg+WQOj5lfvO*4mbEA^bCj9KHh65vjx^zvsKW{eA8|Ah`w&r;%!`QgmEiG3hXCcC
+L+@V2V6oA7MJIRBx
+
+literal 0
+HcmV?d00001
+
+diff --git a/tools/grit/grit/testdata/klonk.rc b/tools/grit/grit/testdata/klonk.rc
+new file mode 100644
+index 0000000000000000000000000000000000000000..35652c4e6dd7cf7b7f62f637e191acf66f487235
+GIT binary patch
+literal 9824
+zcmeI2+j1L48poSUd<zdSObP?FE=Nu{E-aL6%Z`X;t1MXwwb^nxw&R#MQm|was(2h8
+z0*VVRxZyQ;25z?&e*drad1j>1!LS!j6*Z-q>7MTIeClrf{=b{yW=KLKoQA`29(tkA
+z?@<`g*P*W;F2X@LqqP?P!IgxQa2&e)&gmcUJfiQMr{-PocF21|OVCckGsY~31#sNt
+zeuJJaU(OhLWaHAYxy#{kNExfq8uQ5J2xc`jLo2kyURV$HuoL#fZm7|_&ii)Q3SZFE
+z;@$|W^lb4S@e23#yIdxsED4)%Ix5vi$fg&b@^yerB!M>k-sfJ2-!(XtBx>~E;y0>;
+zywqpO@eUBz4c2yv49m3k+_Z88eb3Rg>+A-4?GCk8rmyLEuD7;k@iyBQuQz|u4r}P|
+z1pk!hKgO!w#>STMq~-8ViH-G#etL?RCgF{OzaBBS8aA-k=%+1wau1JP!(#WbwJk2e
+z{FW=3II|6mUA$wTS=-Ei$KrzUMVn6ea?kwXHeRp*%qrtH8Cm5n-|(IYVUu<pe(r=N
+zzO@*)I&s84Ull`c5XBVjPVmJ8W*uVn!oE+xdXM3B1?=zfi}cBtkC36HBDof6y#96&
+zDNK-*c<mwsaiN$Tt;G8iy#LgqQ-aMX7AOxWcPO4D;cMihSg+XijJE^e#f+h-em)#K
+zU}i#pm$ov9MjtR<GnAE-XHJcd#M&7}G3rSx$}4^5MSA<RMTcOD8qE;QGcM((Z-!r=
+z@>HA@wRN;~7h6y+xyz_&R~;+XxM^eZ-_q~|%%b86_{3Asa-8FBk+Z7c-kJgN>UjHR
+zv*J6C_vNv`hUxGkXMvL0T0vKhVQf$p6QjfeUR}fgl_wW2W!gk%O?<jZPZ}19O{d7^
+z*finVDx2ru9D3dIaKoU~fb#-41E46PT;&oc4LDIw7tD-Ohf;>IO<b0BC*h%aN($z?
+zm)50Lg3jeb@}4KgpHn7``|w@I(iDZ;#6d+vaXpT`D6f;D{i-%|`usUfYCfinmyGTN
+zIW7V>a`tbcYLDwE{AY?>BR88vkIj3pcp9ftwy~b;A8i-;T|_p=$nY5yWU!`jTE^ib
+ze*F+mE-Vf$<AuvpIC5FVr`t!>>e;=5g*fg0^w_NUeElxZA2EAWiGRui^1Zl<=<)P1
+zF&Y;=yo$%KVIA>VGwa=@)kgQbrt31jq~WuvvL2VO`$<s`-l~FW4S%T*JzWty@3kqC
+zpB4rFU-(`|ov-8B%D+84yQpbJq|Cy#a=VYFm5(Lg9joHhbBjy*SqUH5^H#VWD)#mP
+zmDd8gX|wiIT+{3pP+PpWiFV4=ZF*H_#xD)})(!p!_EWXI5x?KFnQQblnWI&vvb<)-
+zFIrzJTT2IfU>zNqGSmHCaU;Y2q0yQ$JF7mTwL~ub{sOMb^Vh8GFZ(K1F-x>#wroJR
+z&tF1@??TN-{BD^Hb<bj)tU9hU-SUgid^Mw80(r42u2^L$1ARm5q2(uK*A(fk5cewP
+z9Zr$-B@Y%=OVA@~R*aezo@z;A8C69Z##=4Z+%_6(qSKmXx%;{Kv$<M>gJ;mLeTx&a
+ztSZO1p-!t5NvMLINn_JEi1N%h$mrKfeZ%Sxtv*(<o;FujMW(#py@aoK$>Sq%E`|5`
+zMQa!2rJ*fu!l%|$%^a7pE^XVFE$AM-((pNcct~BK8YqR1Sd~Aqmi*&@D)rQ&bN`YW
+zMPvC%+;<TLnyH+o+P!PzGEPTvj<#1#Q&p3I;<v-i%S09-uHQ3$KQw!lbu5_YDT~KE
+zq3F><0?G_uSf2bldXz_x`Rp$tXUa07m0#5g^O>n*TJE4PW#|}5_jztxOjO*+fAM}<
+zk=Lii5sD#879SKTSIp++>5AlgXumxIv246U-XKqCe;}^ATDIP+P{%8`Yynw2Uilpc
+z$xha}X;{ahB+#YVajq(xJ|2|kCBqoURxZc-PC<V34wS`l@7lObCdzS5sL5l@zQ+BG
+z;+Tl3tUl7t#}1OyYFBw_V3AMzKfW@m<J*t$@OdlXBE$+_TOoq!`H*`aipPX9y8N3z
+zJLpP#o#HyZq-`Au=XaT7{)rj2n4zkrdkJOKOvhNvbdE_@DQ#r;l~PX2VN1f=r#R=S
+z`e>WGR&NeH+c%h>-Yw>z7_{+>=5WWql;yg~F}<jhong+@E{wQv`%$Z$n`LNxVSLVu
+zqX`bJ2rtN9gE2WJxg8d*6Uugv=9gd**I(1S$3)lvXuIe$9VB*sDZi`wUr{S<ASs*o
+zEyw#FTC@PgtLUAvrjGSZrVFRipYc2<9~H+>V-&+XR>jna$uG|ylUKW<KRZ>)Rw*m^
+z_oOjp@vHny>%lMrW)jt@&DG$}J`tOC!twxncz`|R{U9wplS^%1SD7h)zLPS$9KxSJ
+z^(luqF00#DmestFZxDq%2fL5@KE>#H<EVwdOuH`m{4TpYASY`FCbM&`$abwl+vH7a
+za;>I|)#R(g69An@YFD|(_t^K?Y(=LYvGR$s)LKbvaYc(JPp$Xb2G?a>eC9KE-cEhZ
+zHSZ3+_C$Rze-w`BSsn7ZgI%TJO=9FfdDBy)V;pqaYpnOHjNdZ)cZhIWOV;71NPE_b
+z5ZwYd@EV=tI))^?mN>3>KBO~=3-s|NvQu_bO!m`Xy&s`1RS8A9bec9lO`@(ym$)sX
+zMVVq?wjta)kvTJp%Bk>bSh}4@HcmwuW}T<$ta~!gT03ja*d|hI1w9*Uk>}TwPvL12
+z=Q{J$UgQ8RXmu+(2GDd)J#{>6mrEh;W{57|8=6JgB)U>?#`vQXEaBEZgsP}6H0c~I
+zlTn_wQLB~3>U1IQ2y}Rh=cM|##66Rnd!p7F(K=LbM6B`LtO3?OS3Ko>03~gD6g5tu
+zOSRooa>4*SqvO;gSO;d)IuFc4e&rSY3#4arR~e}tmqXie5w!0rzg2#y{KWm2%CD85
+zD?e8LTlv1?UR>st9pKlDtGM^mfuA&df=7MIT`QQ#k8mnxoriygx5#|&^UZ&6F?Nx!
+z2jMH^+zTJm>H?vU#6L!6XLz9~{RHheL_xo4SVUcx*(c|e8ZfVRzJC37^PM7DoUXW9
+zRu0v_b;|ztF`73W!u5N4HWX!l^<O!vStkE0N0PgK{5wU`>ZH1;i+3m{&0Ya4gg*c`
+C>9bG(
+
+literal 0
+HcmV?d00001
+
+diff --git a/tools/grit/grit/testdata/ko_oem_enable_bug.html b/tools/grit/grit/testdata/ko_oem_enable_bug.html
+new file mode 100644
+index 0000000000..f2c199cc15
+--- /dev/null
++++ b/tools/grit/grit/testdata/ko_oem_enable_bug.html
+@@ -0,0 +1 @@
++<IMG style="VERTICAL-ALIGN: middle" height=16 alt=아웃룩 src="/email.gif" width=16>
+\ No newline at end of file
+diff --git a/tools/grit/grit/testdata/ko_oem_non_admin_bug.html b/tools/grit/grit/testdata/ko_oem_non_admin_bug.html
+new file mode 100644
+index 0000000000..b9e8a1f288
+--- /dev/null
++++ b/tools/grit/grit/testdata/ko_oem_non_admin_bug.html
+@@ -0,0 +1 @@
++<INPUT id=s type=submit value="&nbsp;&nbsp;확인&nbsp;&nbsp;">
+\ No newline at end of file
+diff --git a/tools/grit/grit/testdata/mini.html b/tools/grit/grit/testdata/mini.html
+new file mode 100644
+index 0000000000..8ac0a231a0
+--- /dev/null
++++ b/tools/grit/grit/testdata/mini.html
+@@ -0,0 +1,36 @@
++<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
++<html><head>
++<meta http-equiv=content-type content="text/html; charset=windows-1252">
++<meta http-equiv="cache-control" content="no-cache">
++<meta http-equiv="pragma" content="no-cache">
++<meta http-equiv="expires" content="-1">
++<style>
++BODY { FONT-SIZE: 8pt; FONT-FAMILY: verdana,arial,san-serif }
++P { FONT-SIZE: 8pt; FONT-FAMILY: verdana,arial,san-serif }
++TD { FONT-SIZE: 8pt; FONT-FAMILY: verdana,arial,san-serif }
++A { FONT-SIZE: 8pt; FONT-FAMILY: verdana,arial,san-serif }
++DIV { FONT-SIZE: 8pt; TEXT-DECORATION: none }
++A:hover { COLOR: #ffffff }
++.border { BORDER-RIGHT: 0px; BORDER-TOP: 0px; BORDER-LEFT: 0px; BORDER-BOTTOM: 0px }
++</style>
++</head>
++
++<BODY bottomMargin=0 bgColor=#3300cc leftMargin=0 topMargin=0 rightMargin=0 marginwidth="0" marginheight="0">
++
++<TABLE cellSpacing=0 cellPadding=0 bgColor=#3300cc border=0><TBODY>
++<tr><TD vAlign=top>
++
++<TABLE cellSpacing=0 cellPadding=0 bgColor=#3300cc border=0><TBODY>
++
++<tr>
++<TD vAlign=top>&nbsp;<INPUT style="position:relative; height=17px;" class=border size=10>&nbsp;</td>
++
++<TD class=ch onmouseover="this.bgColor='6666FF'" style="BORDER-RIGHT: #000000 1px solid; BORDER-TOP: #6666cc 1px solid; BORDER-LEFT: #6666cc 1px solid; BORDER-BOTTOM: #000000 1px solid" onmouseout="this.bgColor='#000099'" vAlign=center align=middle bgColor=#000099><img height=1 width=1><IMG src="logo.gif" align=middle><img height=1 width=1></td>
++
++</TBODY></table>
++</td>
++
++<TD width=2><IMG height=1 width=1></td>
++
++<TD vAlign=top><TABLE cellSpacing=0 cellPadding=1><TBODY>
++<tr><TD class=ch onmouseover="this.bgColor='6666FF'" style="BORDER-RIGHT: #000000 1px solid; BORDER-TOP: #6666cc 1px solid; BORDER-LEFT: #6666cc 1px solid; BORDER-BOTTOM: #000000 1px solid" onmouseout="this.bgColor='#000099'" vAlign=top noWrap bgColor=#000099><IMG src="mini_close.gif"></td></tr></TBODY></table></td></tr></TBODY></table></body></html>
+diff --git a/tools/grit/grit/testdata/oem_enable.html b/tools/grit/grit/testdata/oem_enable.html
+new file mode 100644
+index 0000000000..db6b85eca6
+--- /dev/null
++++ b/tools/grit/grit/testdata/oem_enable.html
+@@ -0,0 +1,106 @@
++<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
++<html><head><title>Google Desktop Search Download</title>
++<meta http-equiv=content-type content="text/html; charset=utf-8">
++<style>BODY {
++ FONT-FAMILY: arial,sans-serif
++}
++TD {
++ FONT-FAMILY: arial,sans-serif
++}
++DIV {
++ FONT-FAMILY: arial,sans-serif
++}
++.p {
++ FONT-FAMILY: arial,sans-serif
++}
++A {
++ FONT-FAMILY: arial,sans-serif
++}
++DIV {
++ COLOR: #000
++}
++TD {
++ COLOR: #000
++}
++A:link {
++ COLOR: #00c
++}
++A:visited {
++ COLOR: #551a8b
++}
++</style>
++
++<meta content="mshtml 6.00.2800.1476" name=generator></head>
++<body>
++<center>
++<TABLE cellSpacing=0 cellPadding=0 border=0>
++ <TBODY>
++ <TR vAlign=center>
++ <td>
++ <DIV align=center><IMG height=55 alt="Google Desktop Search"
++ src="/logo3.gif" width=150 border=0 search=""
++ desktop=""></DIV></td>
++ <td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
++ <td><FONT size=+1><B>Search your own
++computer.</B></font></td></tr></TBODY></table><BR>
++<TABLE cellSpacing=0 cellPadding=0 width=630 border=0>
++ <TBODY>
++ <tr>
++ <TD vAlign=top width="53%"><FONT size=-1>
++ <LI>Find your email, files, web history and chats instantly <NOBR>
++ <LI>View web pages you've seen, even when you're not online</NOBR>
++ <LI>Search as easily as you do on Google
++ <p><B>Google Desktop Search finds:</B></p></font>
++ <TABLE cellSpacing=1 cellPadding=0 width=325 border=0 valign="center">
++ <TBODY>
++ <TR vAlign=center>
++ <TD colSpan=3><FONT size=-1><IMG style="VERTICAL-ALIGN: middle"
++ height=16 alt=Outlook src="/email.gif"
++ width=16>&nbsp;&nbsp;Email from Outlook, Outlook Express, &amp;
++ Thunderbird</font></td></tr>
++ <tr>
++ <TD noWrap colSpan=3><FONT size=-1><IMG
++ style="VERTICAL-ALIGN: middle" height=16 alt="Internet Explorer"
++ src="/html.gif" width=16>&nbsp;&nbsp;Web history
++ from IE/Firefox/Mozilla/Netscape</font></td></tr>
++ <tr>
++ <TD noWrap colSpan=3><FONT size=-1><IMG
++ style="VERTICAL-ALIGN: middle" height=16 alt=Text
++ src="/file.gif" width=16>&nbsp;&nbsp;Files in Word,
++ Excel, Powerpoint, PDF, &amp; media formats</font></td></tr>
++ <tr>
++ <TD vAlign=top colSpan=3><FONT size=-1><IMG
++ style="VERTICAL-ALIGN: middle" height=16 alt="AOL IM"
++ src="/aim.gif" width=16>&nbsp;&nbsp;Chats from AOL
++ Instant Messenger</font></td></tr>
++ <tr>
++ <TD noWrap><FONT size=-1>&nbsp;</font></td></tr></TBODY></table><FONT
++ size=-1>&nbsp;</font><FONT size=-1><A
++ href="http://desktop.google.com/about.html">About Desktop
++ Search</A>&nbsp;&nbsp; <A
++ href="http://desktop.google.com/screenshots.html">Screenshots</A>&nbsp;&nbsp;
++ <A href="http://desktop.google.com/support">Help</A>&nbsp;&nbsp; <A
++ href="http://desktop.google.com/feedback.html">Contact
++ Us</A><BR></font></LI></td>
++ <td>&nbsp;&nbsp;&nbsp;</td>
++ <TD vAlign=top width="53%">
++ <TABLE cellPadding=2 width="100%" align=center>
++ <TBODY>
++ <tr>
++ <TD
++ style="BORDER-RIGHT: rgb(204,204,204) 1px solid; BORDER-TOP: rgb(204,204,204) 1px solid; BORDER-LEFT: rgb(204,204,204) 1px solid; BORDER-BOTTOM: rgb(204,204,204) 1px solid"
++ width="100%" bgColor=#e7eff7 blah2="#fff8dd" blah="#e7eaf7"><BR>
++ <center><FONT size=-1>By using, you agree to our <A
++ href="http://desktop.google.com/eula.html"><BR>Terms &amp;
++ Conditions</A> and <A
++ href="http://desktop.google.com/privacypolicy.html">Privacy
++ Policy</A></font></center>
++ <p></p>
++ <FORM action='[STEP2]'>
++ <P align=center><INPUT style="PADDING-RIGHT: 3px; PADDING-LEFT: 3px; FONT-WEIGHT: bold; FONT-SIZE: 17px; PADDING-BOTTOM: 4px; PADDING-TOP: 4px" type=submit value="Agree and Start Using" name=Submit>
++ </p></FORM><FONT size=-2>* Automatically starts when you turn on
++ your computer</font> </td></tr></TBODY></table>
++ <p></p></td></tr></TBODY></table></center>
++<p></p>
++<center><FONT color=#666666 size=-2>©2005 Google</font>
++<p></p></center></body></html>
+diff --git a/tools/grit/grit/testdata/oem_non_admin.html b/tools/grit/grit/testdata/oem_non_admin.html
+new file mode 100644
+index 0000000000..8b7ca13e21
+--- /dev/null
++++ b/tools/grit/grit/testdata/oem_non_admin.html
+@@ -0,0 +1,39 @@
++<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
++<html><head><title>Google Desktop Search Preferences</title>
++<meta http-equiv=cache-control content=no-cache>
++<meta http-equiv=content-type content="text/html; charset=utf-8">
++<meta http-equiv=pragma content=no-cache>
++<meta http-equiv=expires content=-1>
++<style>BODY {
++ FONT-FAMILY: arial,sans-serif
++}
++.c:active {
++ COLOR: #ff0000
++}
++.c:visited {
++ COLOR: #7777cc
++}
++.c:link {
++ COLOR: #7777cc
++}
++</style>
++
++<script>
++<!--
++override = 1;
++function ee() {if (override==1) {(new Image()).src="/doneinstallprefs&s=3286011577";}}
++// -->
++</script>
++
++<meta content="mshtml 6.00.2800.1476" name=generator></head>
++<BODY onresize=stw() leftMargin=30 rightMargin=30 onunload=ee()>
++<FORM name=f onsubmit=javascript:window.close();><IMG
++src="/logo3.gif" border=0>
++<DIV id=c1 style="WIDTH: 600px">
++<p><BR><FONT color=#00218a><B>We're sorry, but you need administrator access to
++enable Desktop Search.</B></font></p><FONT size=-1>
++<p>To install or run Google Desktop Search you need administrator access on this
++computer. Please try installing again once you have administrator
++access.</p></font></DIV>
++<p><INPUT id=s type=submit value=&nbsp;&nbsp;OK&nbsp;&nbsp;> <BR></p>
++<center></center></FORM></body></html>
+diff --git a/tools/grit/grit/testdata/onebox.html b/tools/grit/grit/testdata/onebox.html
+new file mode 100644
+index 0000000000..c24ff043a5
+--- /dev/null
++++ b/tools/grit/grit/testdata/onebox.html
+@@ -0,0 +1,21 @@
++<html><head><title>Google Desktop Search Results</title>
++<style><!--
++body,td,div,.p,a{font-family:arial,sans-serif }
++body{ background-color: transparent }
++div,td{color:#000}
++.f,.fl:link{color:#6f6f6f}
++a:link,.w,a.w:link,.w a:link{color:#00c}
++a:visited,.fl:visited{color:#551a8b}
++a:active,.fl:active{color:#f00}
++.t a:link,.t a:active,.t a:visited,.t{color:#000}
++//-->
++</style>
++</head>
++<body>
++<table cellspacing=0 cellpadding=1 border=0 ID="Google Desktop Search">
++<tr><td colspan=2><nobr><a href="http://[WEBSERVER][$~QUERY~$]" target=_parent>[NUMRESULTS] [RESULT-STRING] stored on your computer</a><font size=-1>&nbsp;-&nbsp;<a href="[HIDENOW]" style="color:#7777cc;" target=_parent>Hide</a>&nbsp;-&nbsp;<a href="http://desktop.google.com/integration.html" style="color:#7777cc;" target=_parent>About</a></font></nobr></td></tr>
++<tr><td valign=top width=40><img height=27 style="margin-top:2px;" src="http://[WEBSERVER]/onebox.gif"></td>
++<td valign=top width="99%"><font size=-1>[RESULTS]</font></td></tr>
++</table>
++</body>
++</html>
+diff --git a/tools/grit/grit/testdata/oneclick.html b/tools/grit/grit/testdata/oneclick.html
+new file mode 100644
+index 0000000000..32dc6459dd
+--- /dev/null
++++ b/tools/grit/grit/testdata/oneclick.html
+@@ -0,0 +1,34 @@
++[HEADER]
++
++
++<TABLE cellSpacing=4 cellPadding=0 width="100%" border=0>
++<tr>
++ <TD vAlign=top align=left width=50%>
++ [EMAIL_TOP_CHROME]
++
++ <p class=f>
++ <TABLE cellSpacing=6 cellPadding=0 width="100%" border=0>
++ [EMAIL]
++ </table>
++ </td>
++
++
++ <TD width=1 align=middle bgColor=#cfcfcf><IMG height=1 width=1></td>
++ <TD width=50% vAlign=top align=left>
++ [FREQ_TOP_CHROME]
++ <p class=f>
++ <TABLE cellSpacing=6 cellPadding=0 width="100%" border=0 ID="Table1">
++ [$~FREQ~$]
++ </table>
++ <p class=g>
++ [RECENT_TOP_CHROME]
++ <TABLE cellSpacing=6 cellPadding=0 width="100%" border=0 ID="Table2">
++ [$~RECENT~$]
++ </table>
++ </td>
++ </tr>
++</table>
++<center><BR>
++
++
++[FOOTER]
+diff --git a/tools/grit/grit/testdata/password.html b/tools/grit/grit/testdata/password.html
+new file mode 100644
+index 0000000000..16007a1ac0
+--- /dev/null
++++ b/tools/grit/grit/testdata/password.html
+@@ -0,0 +1,37 @@
++<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
++<html><head><title>Password Required</title>
++<meta http-equiv="content-type" content="text/html; charset=utf-8">
++<meta http-equiv="cache-control" content="no-cache">
++<meta http-equiv="pragma" content="no-cache">
++<meta http-equiv="expires" content="-1">
++<style>
++BODY,TD,A,P {FONT-FAMILY: arial,sans-serif}
++.q {COLOR: #0000cc}
++</style>
++<script>
++<!--
++function sf(){document.f.q.focus();}
++// -->
++</script>
++</head>
++<body text=#000000 vLink=#551a8b aLink=#ff0000 link=#0000cc bgColor=#ffffff onload=sf()>
++<center>
++<table cellSpacing=0 cellPadding=0 border=0>
++<tr><td><a href="[$~HOMEPAGE~$]"><IMG border=0 height=110 alt="Google Desktop Search" src="hp_logo.gif" width=276></a></td></tr></table><BR>
++<form name=f method=GET action='/password'>
++<table cellSpacing=0 cellPadding=4 border=0>
++<tr><td class=q noWrap><font size=-1>
++ <table cellSpacing=0 cellPadding=0>
++ <tr vAlign=top>
++ <td align=middle>Password required:&nbsp;&nbsp;<input maxLength=80 size=30 type=password name=pw value="">
++ <script>
++ document.f.q.focus();
++ </script>
++ &nbsp;<input type=submit value="Submit" name=submit>
++ </td></tr>
++ </table>
++ </form>
++</td></tr>
++</table>
++<br><font size=-1>[$~BOTTOMLINE~$]</font></p>
++<p><font size=-2>&copy;2005 Google</font></p></center></body></html>
+\ No newline at end of file
+diff --git a/tools/grit/grit/testdata/preferences.html b/tools/grit/grit/testdata/preferences.html
+new file mode 100644
+index 0000000000..b37412436b
+--- /dev/null
++++ b/tools/grit/grit/testdata/preferences.html
+@@ -0,0 +1,234 @@
++<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
++"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
++<html><head><title>Google Desktop Search Preferences</title>
++<meta http-equiv=content-type content="text/html; charset=utf-8">
++<style>
++body {
++ margin-left: 2em; margin-right: 2em;
++ font-family: arial,sans-serif;
++ color:#000; background-color:#fff;
++}
++a:active { color:#f00 }
++a:visited { color:#551a8b }
++a:link { color:#00c }
++a.c:active { color: #ff0000 }
++a.c:visited { color: #7777cc }
++a.c:link { color: #7777cc }
++.b { font-weight: bold }
++.shaded-header { background-color: #e8f4f7; border-top: 1px solid #39c;
++margin: 0px; padding: 0px }
++.shaded-subheader { background-color: #e8f4f7; margin: 12px 0px 0px 0px;
++ padding: 0px }
++.plain-subheader { background-color: #fff; margin: 12px 0px 0px 0px;
++ padding: 0px }
++.header-element { margin: 0px; padding: 2px}
++.expand { width: 98% }
++.s { font-size: smaller }
++.prefgroup { border: 2px solid #e8f4f7; width: 100% }
++.phead { font-weight: bold; font-size: smaller; vertical-align: top;
++text-transform: capitalize; border-bottom: 2px solid #e8f4f7; margin: 0px;
++padding: 8px}
++.pbody { border-bottom: 2px solid #e8f4f7; margin: 0px;
++padding: 8px}
++.pref-last { border-bottom: 0px }
++.example { color: gray; font-family: monospace; }
++</style>
++<script>
++<!--
++function validate() {}
++function fnOnClickAll() {for (var i = 0; i < document.langform.lr.length; i++) {
++document.langform.lr[i].checked = false;}}
++function fnOnClickSome() {
++var count = 0;for (var i = 0; i < document.langform.lr.length; i++) {
++if (document.langform.lr[i].checked) {count++;}}
++document.langform.lang[0].checked = (count <= 0);
++document.langform.lang[1].checked = (count > 0);}
++// -->
++</script>
++</head>
++<body onload="checkOffice()">
++<form name=prefs action="[$~SETPREFS~$]" method=post><input name=url
++value="[PREVPAGE]" type=hidden>
++<table cellspacing=2 cellpadding=0 width="100%" border=0>
++<tr>
++<td valign=top width="1%"><a href="[$~HOMEPAGE~$]">
++<img alt="Go to Google Desktop Search" src="logo3.gif" border=0></a></td>
++<td>&nbsp;</td>
++<td nowrap>
++
++<table class="shaded-header"><tr>
++<td class="header-element b expand">Preferences</td>
++<td class="header-element s">
++<a href="http://desktop.google.com/preferences.html">Preferences&nbsp;Help</a>
++</td>
++</tr></table>
++
++</tr></table>
++
++<table class="shaded-subheader"><tr>
++<td class="header-element expand s">
++<span class="b">Save</span> your preferences when finished.</td>
++<td class="header-element"><input type=submit value="Save Preferences"
++name=submit2></td>
++</tr></table>
++
++[STATUS-MESSAGE]
++<table class="plain-subheader"><tr>
++<td class="header-element expand"><span class="b">Preferences</span><span
++class="s"> (changes apply to Google Desktop Search application)</span></td>
++</tr></table>
++
++<table class="prefgroup" cellpadding=0 cellspacing=0>
++
++<!-- -->
++<tr>
++<td class="phead">Search types</td>
++<td class="pbody"><div class="s">Index the following items so that you can
++search for them:<br />&nbsp;</div>
++<div>
++ <table border=0>
++ <tr>
++ <td width=150 nowrap valign=top><span class="s">
++ <input type=checkbox [CHECK-EMAIL] name=EMAIL id=h3><label for=h3>
++ Email</label><br>
++ <input type=checkbox [CHECK-AIM] name=AIM id=h5><label for=h5> Chats
++ (AOL/MSN IM)</label><br>
++ <input type=checkbox onclick='if(!this.checked){h12.checked=0;h12.disabled=1;}
++ else {h12.disabled=0;}' [CHECK-WEB] name=WEB id=h11><label for=h11> Web
++ history</label>
++
++ </span></td>
++ <td width=120 nowrap valign=top><span class="s">
++ <script>
++<!--
++function checkOffice() { var w = document.getElementById("h7");
++var e = document.getElementById("h8"); var o = document.getElementById("h10");
++if (!(w.checked || e.checked)) { o.checked=0;o.disabled=1;} else {o.disabled=0;} }
++// -->
++ </script>
++ <input type=checkbox [CHECK-DOC] name=DOC id=h7 onclick='checkOffice()'>
++ <label for=h7> Word</label><br>
++ <input type=checkbox [CHECK-XLS] name=XLS id=h8 onclick='checkOffice()'>
++ <label for=h8> Excel</label><br>
++ <input type=checkbox [CHECK-PPT] name=PPT id=h9>
++ <label for=h9> PowerPoint</label><br>
++ </span></td><td nowrap valign=top><span class="s">
++ <input type=checkbox [CHECK-PDF] name=PDF id=hpdf>
++ <label for=hpdf> PDF</label><br>
++ <input type=checkbox [CHECK-TXT] name=TXT id=h6>
++ <label for=h6> Text, media, and other files</label><br>
++ </tr>
++ <tr><td nowrap valign=top colspan=3><span class="s"><br />
++ <input type=checkbox [CHECK-SECUREOFFICE] name=SECUREOFFICE id=h10>
++ <label for=h10> Password-protected Office documents (Word, Excel)</label><br />
++ <input type=checkbox [DISABLED-HTTPS] [CHECK-HTTPS] name=HTTPS id=h12><label
++ for=h12> Secure pages (HTTPS) in web history</label></span></td></tr>
++</table>
++</div></td></tr>
++</div>
++</td>
++</tr>
++
++<!-- -->
++<tr>
++<td class="phead">Plug-ins</td>
++<td class="pbody"><div class="s"
++style="display:[ADDIN-DISPLAYSTYLE]">Index these additional items:<p>
++[ADDIN-DO]
++[ADDIN-OPTIONS]</div><div class="s">
++To install plug-ins to index other items, visit the
++<a href="http://desktop.google.com/plugins.html">Plug-ins Download page</a>.</div>
++</tr>
++
++<!-- -->
++<tr>
++<td class="phead">Don't search these items</td>
++<td class="pbody"><div class="s">
++<label for=FORBIDDEN>Do not search web sites with the following URLs or files
++with the following paths. Put each entry on a separate line. Examples:</label><br>
++<span class="example">c:\Documents and Settings\username\Private Stuff</span><br>
++<span class="example">http://www.domain.com/</span><br>
++<div>&nbsp;</div>
++<div><TEXTAREA rows=3 cols=65 name=FORBIDDEN id=FORBIDDEN>[FORBIDDEN]
++</TEXTAREA></div>
++</tr>
++
++<!-- -->
++<tr>
++<td class="phead pref">Search Box Display</td>
++<td class="pbody pref" valign=top>
++
++<table border=0 cellpadding=0><tr><td valign=top>
++
++<input type="radio" name="SBDISPLAY" id="DISPLAYDB" [CHECK-DISPLAYDB] value="DISPLAYDB"></td><td>
++<label for=DISPLAYDB><font size=-1>Deskbar - A search box in your taskbar</font></label></td></tr>
++<tr><td></td></tr>
++<tr><td></td><td><img src="deskbar.gif" alt="Deskbar" width="268" height="34"></td></tr>
++<tr><td height=2></td></tr>
++<tr><td valign=top>
++
++<input type="radio" name="SBDISPLAY" id="DISPLAYMB" [CHECK-DISPLAYMB] VALUE="DISPLAYMB"></td><td>
++<label for=DISPLAYMB><font size=-1>Floating Deskbar - A search box you can put anywhere on your desktop</font></label></td></tr>
++<tr><td></td></tr>
++<tr><td></td><td><img src="minibar.gif" width="137" height="27"></td></tr>
++<tr><td height=2></td></tr>
++<tr><td valign=top>
++
++<input type=radio name="SBDISPLAY" id="DISPLAYNONE" [CHECK-DISPLAYNONE] VALUE="DISPLAYNONE"></td><td valign=top>
++<label for=DISPLAYNONE><font size=-1> None</font></label>
++</td></tr>
++</table>
++
++</td></tr>
++
++<!-- -->
++<tr>
++<td class="phead pref">Number of Results</td>
++<td class="pbody pref"><label for=num><span class="s">
++Display <select name=num id="num">
++<option [CHECK-NUM-10]>10
++<option [CHECK-NUM-20]>20
++<option [CHECK-NUM-30]>30
++<option [CHECK-NUM-50]>50
++<option [CHECK-NUM-100]>100</select>
++ results per page</span></label>
++</td>
++</tr>
++
++<!-- -->
++<tr>
++<td class="phead">Google integration</td>
++<td class="pbody">
++<table border=0 cellpadding=0>
++<tr><td><input type=CHECKBOX name=ONEBOX [CHECK-ONEBOX] id=onebox></td>
++<td><label for=onebox>
++ <span class="s">Show Desktop Search results on Google Web Search result pages.
++ </span></label></td></tr>
++ <tr><td></td><td>
++ <span class="s">Your personal results are private from Google.</span>
++ </td></tr></table>
++</td>
++</tr>
++
++<!-- -->
++<tr>
++<td class="phead pref-last">Help us improve</td>
++<td class="pbody pref-last">
++<input type=CHECKBOX name=SENDDATA id="SENDDATA" [CHECK-SENDDATA]><label for=
++SENDDATA> <span class="s">Send non-personal usage data and crash reports to
++Google to help improve Desktop Search.</span></label>
++</td>
++</tr>
++
++</table>
++
++<table class="shaded-subheader"><tr>
++<td class="header-element expand s"><span class="b">Save</span> your preferences
++when finished.</td>
++<td class="header-element"><input type=submit value="Save Preferences"
++name=submit2></td>
++</tr></table>
++
++<p><div align=center>[$~BOTTOMLINE~$]</div>
++<br><center><span class="s">&copy;2005 Google</span></center>
++</form></body></html>
+\ No newline at end of file
+diff --git a/tools/grit/grit/testdata/preprocess_test.html b/tools/grit/grit/testdata/preprocess_test.html
+new file mode 100644
+index 0000000000..13ece9a9f6
+--- /dev/null
++++ b/tools/grit/grit/testdata/preprocess_test.html
+@@ -0,0 +1,7 @@
++<if expr="True">
++should be kept
++</if>
++in the middle...
++<if expr="False">
++should be removed
++</if>
+diff --git a/tools/grit/grit/testdata/privacy.html b/tools/grit/grit/testdata/privacy.html
+new file mode 100644
+index 0000000000..1d45f4a539
+--- /dev/null
++++ b/tools/grit/grit/testdata/privacy.html
+@@ -0,0 +1,35 @@
++[!]
++title Privacy and Google Desktop Search
++template
++privacy_bottomline
++hp_image
++
++<TABLE CELLSPACING=0 CELLPADDING=5 WIDTH="98%" BORDER=0>
++<TR VALIGN=TOP>
++<td>
++<h4>Privacy and Google Desktop Search</h4>
++
++<p><FONT SIZE=-1>Google is committed to making search on your desktop as easy
++as searching the web. We recognize that privacy is an important issue,
++so we designed and built Google Desktop Search with respect for your privacy.
++<p>
++So that you can easily search your computer, the Google Desktop Search application indexes
++and stores versions of your files and other computer activity,
++such as email, chats, and web history. These versions may also be mixed
++with your Web search results to produce
++results pages for you that integrate relevant content from your computer and
++information from the Web.
++<p>
++Your computer's content is not made accessible to Google or anyone else without your explicit permission.
++
++<p>You can read the
++<A HREF='http://desktop.google.com/privacypolicy.html?hl=[LANG_CODE]'>Privacy Policy</A>
++and <A HREF='http://desktop.google.com/privacyfaq.html?hl=[LANG_CODE]'>Privacy FAQ</A> online.
++</font>
++</td></tr></table>
++
++<center><br>
++<TABLE CELLSPACING=0 CELLPADDING=0 WIDTH="100%" BORDER=0>
++<TR BGCOLOR=#3399CC><TD ALIGN=MIDDLE HEIGHT=1><IMG HEIGHT=1 ALT="" WIDTH=1></td></tr></table>
++<FONT SIZE=-1>[$~PRIVACY_BOTTOMLINE~$] - &copy;2005 Google </font>
++</center>
+\ No newline at end of file
+diff --git a/tools/grit/grit/testdata/quit_apps.html b/tools/grit/grit/testdata/quit_apps.html
+new file mode 100644
+index 0000000000..a501b0e2bf
+--- /dev/null
++++ b/tools/grit/grit/testdata/quit_apps.html
+@@ -0,0 +1,49 @@
++<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
++<html><head><title>Google Desktop Search Preferences</title>
++<meta http-equiv=cache-control content=no-cache>
++<meta http-equiv=content-type content="text/html; charset=utf-8">
++<meta http-equiv=pragma content=no-cache>
++<meta http-equiv=expires content=-1>
++<style>BODY {
++ FONT-FAMILY: arial,sans-serif
++}
++.c:active {
++ COLOR: #ff0000
++}
++.c:visited {
++ COLOR: #7777cc
++}
++.c:link {
++ COLOR: #7777cc
++}
++</style>
++
++<script>
++<!--
++// -->
++</script>
++
++<meta content="mshtml 6.00.2800.1476" name=generator></head>
++<BODY onresize=stw() leftMargin=30 rightMargin=30>
++<FORM name=f action='[NEXTSTEP]' method=post><IMG src="/logo3.gif"
++border=0>
++<DIV id=c1 style="WIDTH: 600px">
++<p><BR><FONT color=#00218a><B>To start using Google Desktop Search, we may need to close the following programs if they are running:</B></font></p>
++<FONT size=-1><p>You can start these programs once Google Desktop Search is running.</p></font>
++
++<LI><FONT size=-1>AOL Instant Messenger</font>
++<LI><FONT size=-1>Firefox</font>
++<LI><FONT size=-1>Internet Explorer</font>
++<LI><FONT size=-1>Microsoft Excel</font>
++<LI><FONT size=-1>Microsoft Outlook </font>
++<LI><FONT size=-1>Microsoft Word </font>
++<LI><FONT size=-1>Mozilla</font>
++<LI><FONT size=-1>Mozilla Thunderbird</font>
++<LI><FONT size=-1>Netscape</font>
++<LI><FONT size=-1>Opera</font>
++<LI><FONT size=-1>Other web browsers</font>
++<FONT size=-1>
++<p>This will take only a few seconds to complete. </p></font></LI></DIV>
++<p><INPUT id=s type=submit name="quit" value="&nbsp;&nbsp;OK.&nbsp;&nbsp;Close&nbsp;these&nbsp;applications&nbsp;&nbsp;">
++ <INPUT id=s type=submit name="redir" value="&nbsp;&nbsp;Cancel.&nbsp;I'll&nbsp;run&nbsp;this&nbsp;later&nbsp;&nbsp;"><BR></p>
++<center></center></FORM></body></html>
+diff --git a/tools/grit/grit/testdata/recrawl.html b/tools/grit/grit/testdata/recrawl.html
+new file mode 100644
+index 0000000000..0401e7c2b0
+--- /dev/null
++++ b/tools/grit/grit/testdata/recrawl.html
+@@ -0,0 +1,30 @@
++<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
++<html><head><title>Refresh index</title>
++<meta http-equiv="content-type" content="text/html; charset=utf-8">
++<meta http-equiv="cache-control" content="no-cache">
++<meta http-equiv="pragma" content="no-cache">
++<meta http-equiv="expires" content="-1">
++<style>
++BODY,TD,A,P {FONT-FAMILY: arial,sans-serif}
++.q {COLOR: #0000cc}
++</style>
++</head>
++<body text="#000000" vLink="#551a8b" aLink="#ff0000" link="#0000cc" bgColor="#ffffff">
++<center>
++<table cellSpacing="0" cellPadding="0" border="0">
++<tr>
++<td><a href="[$~HOMEPAGE~$]"><img border="0" height="110" alt="Google Desktop Search" src="hp_logo.gif" width="276"></a>
++</td>
++</tr>
++</table>
++<br>
++<center>Google Desktop Search is now recrawling your drive to index new files.</center>
++<center>Note that new files are indexed automatically, and this step is generally not needed.</center>
++<center>Click <a href="[$~HOMEPAGE~$]">here</a> to continue.</center>
++</td></tr></table>
++<br>
++<font size="-1">[$~BOTTOMLINE~$]</font>
++<p><font size="-2">&copy;2005 Google</font></p>
++</center>
++</body>
++</html>
+\ No newline at end of file
+diff --git a/tools/grit/grit/testdata/resource_ids b/tools/grit/grit/testdata/resource_ids
+new file mode 100644
+index 0000000000..d5d440d57f
+--- /dev/null
++++ b/tools/grit/grit/testdata/resource_ids
+@@ -0,0 +1,13 @@
++{
++ "SRCDIR": ".",
++ "test.grd": {
++ "messages": [100, 10000],
++ },
++ "substitute_no_ids.grd": {
++ "messages": [10000, 20000],
++ },
++ "<(FOO)/file.grd": {
++ },
++ "<(SHARED_INTERMEDIATE_DIR)/devtools/devtools.grd": {
++ },
++}
+diff --git a/tools/grit/grit/testdata/script.html b/tools/grit/grit/testdata/script.html
+new file mode 100644
+index 0000000000..f177d9c30e
+--- /dev/null
++++ b/tools/grit/grit/testdata/script.html
+@@ -0,0 +1,38 @@
++<script>
++function run(n,cut){
++ var out = "", str = "abcdefghijklmnopqrstuvwxyz 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ,./:;'\"()*!?-_@[]{}#%`+=|\\>";
++ n.innerHTML = 'aa';
++
++ var base = n.scrollWidth;
++ for(var i=0;i<str.length;i++) {
++ n.innerHTML = 'a'+str.charAt(i)+'a';
++ out += str.charAt(i) + (n.scrollWidth-base) +";";
++
++ if(cut && !i && (n.scrollWidth-base == cut)) {
++ return '\x02'+"0;";
++ }
++ }
++ // extra cases for literals
++ n.innerHTML = 'a&lt;a';
++ out += '<' + (n.scrollWidth-base) +";";
++ n.innerHTML = 'a&amp;a';
++ out += '&' + (n.scrollWidth-base) +";";
++
++ var base_height = n.scrollHeight;
++ n.innerHTML += '<br>a';
++ out += '\x01' + (n.scrollHeight-base_height) +";";
++
++ return out;
++}
+
-+#include "nested_class_inline_ctor.h"
-diff --git a/tools/clang/plugins/tests/nested_class_inline_ctor.h b/tools/clang/plugins/tests/nested_class_inline_ctor.h
++function TEST_WIDTH() {
++ var n = document.getElementById('test');
++ var out = run(n[$~CUT~$]);
++ if (out.length>4){
++ n.style.fontWeight='bold';
++ out += run(n);
++ }
++ n.outerHTML = "";
++ (new Image()).src="[$~SETWIDTH~$]?src=[COMPONENT]&data="+escape(out).replace(/\+/g,"%2B");
++}
++</script>
+diff --git a/tools/grit/grit/testdata/searchbox.html b/tools/grit/grit/testdata/searchbox.html
new file mode 100644
-index 0000000000..01cfea9232
+index 0000000000..9eccba99a5
--- /dev/null
-+++ b/tools/clang/plugins/tests/nested_class_inline_ctor.h
++++ b/tools/grit/grit/testdata/searchbox.html
@@ -0,0 +1,22 @@
-+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+// Use of this source code is governed by a BSD-style license that can be
-+// found in the LICENSE file.
++<body bgcolor=#ffffff topmargin=2 marginheight=2>
++<table border=0 cellpadding=0 cellspacing=0 width=1%>
++<tr>
++<td valign=top><a href='[$~HOMEPAGE~$]'><img width=150 height=55 src="/logo3.gif" alt="Go to Google Desktop Search" border=0 vspace=12></a></td>
++<td>&nbsp;&nbsp;</td>
++<td valign=top>
++<table cellpadding=0 cellspacing=0 border=0><tr><td colspan=2 height=14 valign=bottom>
++<table border=0 cellpadding=4 cellspacing=0>
++<tr><td class=q><font size=-1>
++[$~LINKS~$]
++</tr>
++</table>
++</td>
++</tr>
++<tr><td nowrap><form name=gs method=GET action='[$~SEARCHURL~$]'><input type=text name=q size=41 maxlength=2048 value="[DISP_QUERY]"><input type=hidden name=ie value="UTF-8">
++<font size=-1>[$~FLAGS~$]<input type=submit name="btnG" value="Search Desktop"><span id=hf></span></font></td>
++<td><font size=-2>&nbsp;&nbsp;<a href='[$~PREFERENCES~$]'>Desktop&nbsp;Preferences</a><br>&nbsp;&nbsp;<a [DELETE_EXTRA] href=[DELETE_PAGE]><nobr>[DELETE_NAME]</nobr></a></font></td>
++</tr></table>
++<table cellpadding=0 cellspacing=0 border=0>
++<tr><td><font size=-1>&nbsp;</font></td></tr>
++</table>
++</td></tr></form></table>
+\ No newline at end of file
+diff --git a/tools/grit/grit/testdata/sidebar_h.html b/tools/grit/grit/testdata/sidebar_h.html
+new file mode 100644
+index 0000000000..e103e8f8db
+--- /dev/null
++++ b/tools/grit/grit/testdata/sidebar_h.html
+@@ -0,0 +1,82 @@
++<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
++<html><head>
++<meta http-equiv="content-type" content="text/html; charset=utf-8">
++<meta http-equiv="cache-control" content="no-cache">
++<meta http-equiv="pragma" content="no-cache">
++<meta http-equiv="expires" content="-1">
++<style>
++BODY,TD,DIV,A,.p { FONT-FAMILY: arial,sans-serif; SCROLL: no}
++DIV,TD {COLOR: #000}
++.f, .fl:link {COLOR: #6f6f6f}
++A:link { COLOR: #00c}
++A:visited { COLOR: #551a8b}
++A:active { COLOR: #f00}
++.fl:active { COLOR: #f00}
++.h { COLOR: #3399CC}
++.a, .a:link {COLOR: #008000}
++.b { FONT-WEIGHT: bold; FONT-SIZE: 12pt; COLOR: #00c}
++.g { MARGIN-TOP: .5em; MARGIN-BOTTOM: .5em}
++.f { MARGIN-TOP: 0.5em; MARGIN-BOTTOM: 0.25em}
++.c:active, .c:visited, .c:link { COLOR: #6f6f6f}
++.ch {CURSOR: hand}
++</style>
++</head>
++<BODY onload="TEST_WIDTH();" bottomMargin=0 leftMargin=2 topMargin=0 rightMargin=2 marginwidth=0 marginheight=0 SCROLL=NO bgcolor=#E0E0E0 style="border-style:solid; border-width:0;" oncontextmenu="return false;">
++
++<script>
++function hide() {
++ return 1;
++ // return confirm("Are you sure you want to hide the sidebar?\nYou can show it again in Google Desktop Search Preferences. ");
++}
++</script>
+
-+#ifndef NESTED_CLASS_INLINE_CTOR_H_
-+#define NESTED_CLASS_INLINE_CTOR_H_
+
-+#include <string>
-+#include <vector>
++<TABLE border=0 cellPadding=0 cellSpacing=0 width="100%"><tr>
++<TD WIDTH="19%" VALIGN=TOP>
+
-+// See crbug.com/136863.
++<form method=get action="[$~SEARCHURL~$]">
++<input type=hidden name=src value=4>
+
-+class Foo {
-+ class Bar {
-+ Bar() {}
-+ ~Bar() {}
++ <table cellspacing=0 cellpadding=0 width='1%'>
++ <tr><td nowrap align=center valign=middle><nobr><img width=16 height=16 src=logo.gif>&nbsp;<b><i><font color=#6F6F6F>Google Desktop Search</font></i></b><IMG id=ctl src="[CONTROL_IMAGE]" border=0 usemap="#control"></nobr></td></tr>
++ <tr><td nowrap align=center valign=middle><nobr><input TABINDEX="1" style="font-size:10px; width:'100%';" name="q" id="q"></nobr></td></tr>
++ <tr><td nowrap align=center valign=middle><nobr><input TABINDEX="2" style="font-size:10px" type=submit value="Local search" name=btnG> <input TABINDEX="3" style="font-size:9px" type=submit value="Web search" name=redir></nobr></td></tr>
++<MAP name="control">
++<area TABINDEX="4" onkeydown='if(event.keyCode==13)location.href="movesidebar?side=1"' title="Move sidebar to Top" shape="rect" coords="9,0,22,8" href="/movesidebar?side=1" onmouseover="ctl.src='control1.gif'" onmouseout="ctl.src='[CONTROL_IMAGE]'">
++<area TABINDEX="5" onkeydown='if(event.keyCode==13)location.href="movesidebar?side=3"' title="Move sidebar to Bottom" shape="rect" coords="9,9,22,17" href="/movesidebar?side=3" onmouseover="ctl.src='control3.gif'" onmouseout="ctl.src='[CONTROL_IMAGE]'">
++<area TABINDEX="6" onkeydown='if(event.keyCode==13)location.href="movesidebar?side=0"' title="Move sidebar to Left" shape="rect" coords="0,2,8,15" href="/movesidebar?side=0" onmouseover="ctl.src='control0.gif'" onmouseout="ctl.src='[CONTROL_IMAGE]'">
++<area TABINDEX="7" onkeydown='if(event.keyCode==13)location.href="movesidebar?side=2"' title="Move sidebar to Right" shape="rect" coords="23,2,31,15" href="/movesidebar?side=2" onmouseover="ctl.src='control2.gif'" onmouseout="ctl.src='[CONTROL_IMAGE]'">
++</MAP>
++ </table>
++</form>
+
-+ std::vector<std::string> a;
-+ };
-+};
++</td>
++<TD WIDTH="27%" VALIGN=TOP>
++
++[HEADER_SECTION1]
++[CONTENT_INBOX]
++
++</td>
++<TD WIDTH="27%" VALIGN=top>
+
-+#endif // NESTED_CLASS_INLINE_CTOR_H_
-diff --git a/tools/clang/plugins/tests/nested_class_inline_ctor.txt b/tools/clang/plugins/tests/nested_class_inline_ctor.txt
++[HEADER_SECTION2]
++[CONTENT_HIST]
++
++</td>
++<TD WIDTH="27%" VALIGN=top>
++
++[CONTENT_NEWS]
++[CONTENT_OTHER]
++
++</td>
++<TD WIDTH="1%" VALIGN=top>
++
++<a TABINDEX="8" onkeydown='if(event.keyCode!=16&&event.keyCode!=9)onclick()' onclick='return hide()' href='[$~HIDE1~$]' class=ch><img width=12 height=11 style='vertical-align:top;' src=/hide.gif valign=top border=0></a>
++
++</td>
++</tr></table>
++
++<font size=-1><span style="visibility:hidden" id='test'>t</span></font>
++
++[SCRIPT]
++</body></html>
+diff --git a/tools/grit/grit/testdata/sidebar_v.html b/tools/grit/grit/testdata/sidebar_v.html
new file mode 100644
-index 0000000000..39bd6e1dce
+index 0000000000..e040d8ec59
--- /dev/null
-+++ b/tools/clang/plugins/tests/nested_class_inline_ctor.txt
-@@ -0,0 +1,8 @@
-+In file included from nested_class_inline_ctor.cpp:5:
-+./nested_class_inline_ctor.h:15:5: warning: [chromium-style] Complex constructor has an inlined body.
-+ Bar() {}
-+ ^
-+./nested_class_inline_ctor.h:16:5: warning: [chromium-style] Complex destructor has an inline body.
-+ ~Bar() {}
-+ ^
-+2 warnings generated.
-diff --git a/tools/clang/plugins/tests/overridden_methods.cpp b/tools/clang/plugins/tests/overridden_methods.cpp
-new file mode 100644
-index 0000000000..f572a41733
---- /dev/null
-+++ b/tools/clang/plugins/tests/overridden_methods.cpp
-@@ -0,0 +1,38 @@
-+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+// Use of this source code is governed by a BSD-style license that can be
-+// found in the LICENSE file.
++++ b/tools/grit/grit/testdata/sidebar_v.html
+@@ -0,0 +1,267 @@
++<html><head>
++<title>Google Desktop Search Sidebar</title>
++<meta http-equiv="content-type" content="text/html; charset=utf-8">
++<meta http-equiv="cache-control" content="no-cache">
++<meta http-equiv="pragma" content="no-cache">
++<meta http-equiv="expires" content="-1">
++<style>
++BODY,TD,P,A {FONT-FAMILY: verdana,arial,sans-serif;font-size:8pt; color:#fff}
++a:link, a:visited, a:hover, b, q b { color: #ffffff}
++a:active { color: #ff0000}
++a:link,a:active,a:visited,div { text-decoration: none;font-size:8pt }
++.g{margin-top: 1em;}
++.gg{margin-top: .4em;}
++.norepeat { background-repeat: no-repeat }
++.indent{margin-top: 0px; margin-bottom: 0px;margin-left:3px;}
++.c:link, .c:visited, .c:active { color: #959595;font-size:8pt }
++.ch{cursor:pointer;cursor:hand}
++.gap{margin-top: 10px; margin-bottom: 10px;}
++.off {display:none}
++.on {display:on}
++.but { border-top: 1px solid #73787E;border-bottom: 1px solid #000000;border-right: 1px solid #000000;border-left: 1px solid #73787E;margin-top: 0px; margin-bottom: 0px; cursor:pointer;cursor:hand}
++</style>
++<script>
++
++function toggle(i) {
++ var v = document.getElementById(i);
++ var vi = document.getElementById(i+'icon');
++ var c = (v['className'] == 'on');
++ if (c) {
++ v['className'] = 'off';
++ vi.src='up.gif';
++ }
++ else {
++ v['className'] = 'on';
++ vi.src='down.gif';
++ }
++ (new Image()).src="[$~TOGGLE~$]?setting="+i+"&mode="+v['className']+"&rnd="+Math.random();
++
++ if (!c && (v['oclass'] == 'off')) {
++ location.href = location.href;
++ }
++
++ return true;
++}
++function hide() {
++ // return confirm("Are you sure you want to hide the sidebar?\nYou can show it again in Google Desktop Search Preferences.");
++ return 1;
++}
++</script>
++
++<!-- menu experiment start -->
++
++<style>
++<!--
++.menu1 {
++cursor:default;
++position:absolute;
++text-align: left;
++font-family: Arial, Helvetica, sans-serif;
++font-size: 8pt;
++font-color: #000000;
++color: #000000;
++background-color: menu;
++visibility: hidden;
++padding-top: 2px;
++padding-bottom: 2px;
++border: 1 solid;
++border-color: #888888;
++z-index: 100;
++}
++.menuitems {
++padding-left: 5px;
++padding-right: 5px;
++}
++-->
++</style>
++<SCRIPT LANGUAGE="JavaScript1.2">
++<!--
++var menustyle = "menu1";
++
++function showmenu() {
++ var rightedge = document.body.clientWidth-event.clientX;
++ var bottomedge = document.body.clientHeight-event.clientY;
++ // if (rightedge < rcmenu.offsetWidth)
++ // rcmenu.style.left = document.body.scrollLeft + event.clientX - rcmenu.offsetWidth;
++ // else
++ // rcmenu.style.left = document.body.scrollLeft + event.clientX;
++
++ // if (rcmenu.style.left < 0) rcmenu.style.left = 0;
++ rcmenu.style.left = 0;
++
++ if (bottomedge < rcmenu.offsetHeight)
++ rcmenu.style.top = document.body.scrollTop + event.clientY - rcmenu.offsetHeight;
++ else
++ rcmenu.style.top = document.body.scrollTop + event.clientY;
+
-+#include "overridden_methods.h"
++ if (rcmenu.style.top < 0) rcmenu.style.top = 0;
+
-+// Fill in the implementations
-+void DerivedClass::SomeMethod() {}
-+void DerivedClass::SomeOtherMethod() {}
-+void DerivedClass::WebKitModifiedSomething() {}
++ rcmenu.style.visibility = "visible";
++ // rcmenu.style.zindex = 0;
++ // document.all('rcmenu').style.zindex = 20;
++ document.onkeydown=ck;
++ return false;
++}
+
-+class ImplementationInterimClass : public BaseClass {
-+ public:
-+ // Should not warn about pure virtual methods.
-+ virtual void SomeMethod() = 0;
-+};
++function hidemenu() {
++ rcmenu.style.visibility = "hidden";
++}
+
-+class ImplementationDerivedClass : public ImplementationInterimClass,
-+ public webkit_glue::WebKitObserverImpl {
-+ public:
-+ // Should not warn about destructors.
-+ virtual ~ImplementationDerivedClass() {}
-+ // Should warn.
-+ virtual void SomeMethod();
-+ // Should not warn if marked as override.
-+ virtual void SomeOtherMethod() override;
-+ // Should not warn for inline implementations in implementation files.
-+ virtual void SomeInlineMethod() {}
-+ // Should not warn if overriding a method whose origin is WebKit.
-+ virtual void WebKitModifiedSomething();
-+ // Should warn if overridden method isn't pure.
-+ virtual void SomeNonPureBaseMethod() {}
-+};
++function ck(e){
++ evt=document.all?window.event:e;
++ k=document.all?window.event.keyCode:e.keyCode;
++
++ if(k==27 /*<Esc>*/) {
++ hidemenu();
++ }
++}
+
-+int main() {
-+ DerivedClass something;
-+ ImplementationDerivedClass something_else;
++function menumouseover() {
++ if (event.srcElement.className == "menuitems") {
++ event.srcElement.style.backgroundColor = "highlight";
++ event.srcElement.style.color = "white";
++ }
++}
++
++function menumouseout() {
++ if (event.srcElement.className == "menuitems") {
++ event.srcElement.style.backgroundColor = "";
++ event.srcElement.style.color = "black";
++ window.status = "";
++ }
+}
-diff --git a/tools/clang/plugins/tests/overridden_methods.h b/tools/clang/plugins/tests/overridden_methods.h
++
++function menuselect() {
++ if (event.srcElement.className == "menuitems") {
++ if (event.srcElement.getAttribute("target") != null)
++ window.open(event.srcElement.url, event.srcElement.getAttribute("target"));
++ else if (event.srcElement.url.length)
++ window.location = event.srcElement.url;
++ }
++}
++// -->
++</script>
++
++<!-- menu experiment end -->
++
++</head>
++
++<body onload="TEST_WIDTH();" bottommargin=0 leftmargin=0 marginheight=0 marginwidth=0 rightmargin=0 topmargin=0 style="background-color:'#384146'; background-repeat: repeat-y; border-style:solid; border-width:0;" background="greyback.jpg" scroll=NO oncontextmenu="return false;">
++
++<!-- menu experiment start -->
++
++<div id="rcmenu" class="skin0" onMouseover="menumouseover()" onMouseout="menumouseout()" onClick="menuselect();">
++<div class="menuitems" url="[$~SETDISP4~$]">Switch to minibar</div>
++<div class="menuitems" url="[$~SETDISP2~$]">Switch to hoverbar</div>
++<div class="menuitems" url="[$~HIDE1~$]">Close sidebar</div>
++<div class="menuitems" url="">No change</div>
++</div>
++
++<script language="JavaScript1.2">
++if (document.all && window.print) {
++ rcmenu.className = menustyle;
++ document.oncontextmenu = showmenu;
++ document.body.onclick = hidemenu;
++}
++</script>
++
++<!-- menu experiment end -->
++
++<div id="oneliner" style="visibility:hidden; position:absolute; left:0px; top:0px;"></div>
++<script>
++var h = document.getElementById("oneliner").offsetHeight*2;
++document.write("<style type='text/css'>.truncme { overflow:hidden;height: " +h+"px; }</style>");
++</script>
++
++<table cellpadding=0 cellspacing=0 border=0 width='100%'>
++<form method=get action="[$~SEARCHURL~$]" id=f1>
++<input type=hidden name=src value=5>
++<input type=hidden name=redir value=''>
++<tr>
++ <td width='1%'><IMG id=ctl src="[CONTROL_IMAGE]" border=0 usemap="#control"></td>
++ <td width='97%'><input TABINDEX="1" NAME="q" style="width:'100%'; FONT-FAMILY: verdana,arial,sans-serif;font-size:8pt"></td>
++ <td width='1%'><table cellpadding=2 cellspacing=0><tr><td> </td><td TABINDEX="8" onkeydown='if(event.keyCode!=16&&event.keyCode!=9)onclick()' onmouseover="this.bgColor='4C535B'" onmouseout="this.bgColor='#414A4F'" class=but bgcolor=414A4F valign=top onclick="location.href='[$~SETDISP2~$]';"><img src="mini_mini.gif"></td></tr></table></td>
++ <td width='1%'><table cellpadding=2 cellspacing=0><tr><td TABINDEX="9" onkeydown='if(event.keyCode!=16&&event.keyCode!=9)onclick()' onmouseover="this.bgColor='4C535B'" onmouseout="this.bgColor='#414A4F'" class=but bgcolor=414A4F valign=top onclick="if (hide())location.href='[$~HIDE1~$]';"><img src="mini_close.gif"></td></tr></table></td>
++</tr>
++<MAP name="control">
++<area TABINDEX="4" onkeydown='if(event.keyCode==13)location.href="movesidebar?side=1"' title="Move sidebar to Top" shape="rect" coords="9,0,22,8" href="/movesidebar?side=1" onmouseover="ctl.src='control1.gif'" onmouseout="ctl.src='[CONTROL_IMAGE]'">
++<area TABINDEX="5" onkeydown='if(event.keyCode==13)location.href="movesidebar?side=3"' title="Move sidebar to Bottom" shape="rect" coords="9,9,22,17" href="/movesidebar?side=3" onmouseover="ctl.src='control3.gif'" onmouseout="ctl.src='[CONTROL_IMAGE]'">
++<area TABINDEX="6" onkeydown='if(event.keyCode==13)location.href="movesidebar?side=0"' title="Move sidebar to Left" shape="rect" coords="0,2,8,15" href="/movesidebar?side=0" onmouseover="ctl.src='control0.gif'" onmouseout="ctl.src='[CONTROL_IMAGE]'">
++<area TABINDEX="7" onkeydown='if(event.keyCode==13)location.href="movesidebar?side=2"' title="Move sidebar to Right" shape="rect" coords="23,2,31,15" href="/movesidebar?side=2" onmouseover="ctl.src='control2.gif'" onmouseout="ctl.src='[CONTROL_IMAGE]'">
++</MAP>
++</table>
++
++<center>
++<table cellpadding=2 cellspacing=3>
++<tr>
++ <td TABINDEX="2" onkeydown='if(event.keyCode!=16&&event.keyCode!=9)onclick()' onclick="f1.submit()" onmouseover="this.bgColor='4C535B'" onmouseout="this.bgColor='#414A4F'" class=ch nowrap bgcolor=414A4F valign=top style="border-top: 1px solid #73787E;border-bottom: 1px solid #252C30;border-right: 1px solid #252C30;border-left: 1px solid #73787E;"><img src="logo.gif" align="texttop"> <font color=ffffff>Google Desktop Search&nbsp;</td>
++ <td TABINDEX="3" onkeydown='if(event.keyCode!=16&&event.keyCode!=9)onclick()' onclick="redir.value='google'; f1.submit(); redir.value='';" onmouseover="this.bgColor='4C535B'" onmouseout="this.bgColor='#414A4F'" class=ch bgcolor=414A4F nowrap valign=top style="border-top: 1px solid #73787E;border-bottom: 1px solid #252C30;border-right: 1px solid #252C30;border-left: 1px solid #73787E;">&nbsp;<font color=ffffff>Web&nbsp;</td>
++</tr>
++</form>
++</table>
++</center>
++
++<p class=gg>
++<table width=100% cellpadding=0 cellspacing=0><tr><td bgcolor=4E555C><img width=1 height=1></td></tr></table>
++<table class=ch cellpadding=0 cellspacing=0 style="background-color:'#424B50'; background-repeat: repeat-y;" background="section.jpg" width=100% height=18><tr onDblClick='return toggle("news");' onclick='return toggle("news");' onmouseover="this.bgColor='#465055'" onmouseout="this.bgColor=''"><td width=16 align=right><img id=newsicon src="[$~NEWS_MODE~$]" border=0></td><td valign=middle>&nbsp;<font color=ffffff>News</td></tr></table>
++<table width=100% cellpadding=0 cellspacing=0><tr><td bgcolor=313B40><img width=1 height=1></td></tr></table>
++
++<span id="news" class=[$~NEWS_CLASS~$] oclass=[$~NEWS_CLASS~$]>
++[CONTENT_NEWS]
++<p class=g>
++</span>
++
++<table width=100% cellpadding=0 cellspacing=0><tr><td bgcolor=4E555C><img width=1 height=1></td></tr></table>
++<table class=ch cellpadding=0 cellspacing=0 style="background-color:'#424B50'; background-repeat: repeat-y;" background="section.jpg" width=100% height=18><tr onDblClick='return toggle("inbox");' onclick='return toggle("inbox");' onmouseover="this.bgColor='#465055'" onmouseout="this.bgColor=''"><td width=16 align=right><img id=inboxicon src="[$~INBOX_MODE~$]" border=0></td><td valign=middle>&nbsp;<font color=ffffff>Email</td></tr></table>
++<table width=100% cellpadding=0 cellspacing=0><tr><td bgcolor=313B40><img width=1 height=1></td></tr></table>
++
++<span id=inbox class=[$~INBOX_CLASS~$] oclass=[$~INBOX_CLASS~$]>
++[CONTENT_INBOX]
++<p class=g>
++</span>
++
++<table width=100% cellpadding=0 cellspacing=0><tr><td bgcolor=4E555C><img width=1 height=1></td></tr></table>
++<table class=ch cellpadding=0 cellspacing=0 style="background-color:'#424B50'; background-repeat: repeat-y;" background="section.jpg" width=100% height=18><tr onDblClick='return toggle("hist");' onclick='return toggle("hist");' onmouseover="this.bgColor='#465055'" onmouseout="this.bgColor=''"><td width=16 align=right><img id=histicon src="[$~HIST_MODE~$]" border=0></td><td valign=middle>&nbsp;<font color=ffffff>Related History</td></tr></table>
++<table width=100% cellpadding=0 cellspacing=0><tr><td bgcolor=313B40><img width=1 height=1></td></tr></table>
++
++<span id="hist" class=[$~HIST_CLASS~$] oclass=[$~HIST_CLASS~$]>
++[CONTENT_HIST]
++<p class=g>
++</span>
++
++
++<table width=100% cellpadding=0 cellspacing=0><tr><td bgcolor=4E555C><img width=1 height=1></td></tr></table>
++<table class=ch cellpadding=0 cellspacing=0 style="background-color:'#424B50'; background-repeat: repeat-y;" background="section.jpg" width=100% height=18><tr onDblClick='return toggle("recent");' onclick='return toggle("recent");' onmouseover="this.bgColor='#465055'" onmouseout="this.bgColor=''"><td width=16 align=right><img id=recenticon src="[$~RECENT_MODE~$]" border=0></td><td valign=middle>&nbsp;<font color=ffffff>Recent</td></tr></table>
++<table width=100% cellpadding=0 cellspacing=0><tr><td bgcolor=313B40><img width=1 height=1></td></tr></table>
++
++<span id="recent" class=[$~RECENT_CLASS~$] oclass=[$~RECENT_CLASS~$]>
++[CONTENT_RECENT]
++<p class=g>
++</span>
++
++
++<table width=100% cellpadding=0 cellspacing=0><tr><td bgcolor=4E555C><img width=1 height=1></td></tr></table>
++<table class=ch cellpadding=0 cellspacing=0 style="background-color:'#424B50'; background-repeat: repeat-y;" background="section.jpg" width=100% height=18><tr onDblClick='return toggle("popular");' onclick='return toggle("popular");' onmouseover="this.bgColor='#465055'" onmouseout="this.bgColor=''"><td width=16 align=right><img id=popularicon src="[$~POPULAR_MODE~$]" border=0></td><td valign=middle>&nbsp;<font color=ffffff>Frequently Visited</td></tr></table>
++<table width=100% cellpadding=0 cellspacing=0><tr><td bgcolor=313B40><img width=1 height=1></td></tr></table>
++
++<span id="popular" class=[$~POPULAR_CLASS~$] oclass=[$~POPULAR_CLASS~$]>
++[CONTENT_POPULAR]
++<p class=g>
++</span>
++
++
++<table width=100% cellpadding=0 cellspacing=0><tr><td bgcolor=4E555C><img width=1 height=1></td></tr></table>
++<table class=ch cellpadding=0 cellspacing=0 style="background-color:'#424B50'; background-repeat: repeat-y;" background="section.jpg" width=100% height=18><tr onDblClick='return toggle("quib_debug");' onclick='return toggle("quib_debug");' onmouseover="this.bgColor='#465055'" onmouseout="this.bgColor=''"><td width=16 align=right><img id=quib_debugicon src="[$~QUIB_DEBUG_MODE~$]" border=0></td><td valign=middle>&nbsp;<font color=ffffff>Implicit Query Debug</td></tr></table>
++<table width=100% cellpadding=0 cellspacing=0><tr><td bgcolor=313B40><img width=1 height=1></td></tr></table>
++
++<span id="quib_debug" class=[$~QUIB_DEBUG_CLASS~$] oclass=[$~QUIB_DEBUG_CLASS~$]>
++[CONTENT_QUIB_DEBUG]
++</span>
++
++<span style="visibility:hidden" id='test'>t</span>
++
++[CONTENT_OTHER]
++
++[SCRIPT]
++</body>
++</html>
+diff --git a/tools/grit/grit/testdata/simple-input.xml b/tools/grit/grit/testdata/simple-input.xml
+new file mode 100644
+index 0000000000..92827fa4b5
+--- /dev/null
++++ b/tools/grit/grit/testdata/simple-input.xml
+@@ -0,0 +1,52 @@
++<?xml version="1.0" encoding="UTF-8"?>
++<grit base_dir="." latest_public_release="2" current_release="3" source_lang_id="en-US">
++ <release seq="2">
++ <messages>
++ <message name="IDS_OLD_MESSAGE" translateable="true">Hello earthlings!</message>
++ </messages>
++ </release>
++ <release seq="3">
++ <includes>
++ <include name="ID_EDIT_BOX_ICON" type="icon" translateable="false" file="images/edit_box.ico" />
++ <include name="ID_LOGO" type="gif" translateable="true" file="images/logo.gif"/>
++ </includes>
++ <messages>
++ <message name="IDS_BTN_GO" desc="Button text" meaning="verb">Go!</message>
++ <message name="IDS_GREETING" desc="Printed to greet the currently logged in user">
++ Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, how are you doing today?
++ </message>
++ </messages>
++ <structures>
++ <structure type="menu" name="IDM_FOO" file="rc_files/menus.rc" />
++ <structure type="dialog" name="IDD_BLAT" file="rc_files/dialogs.rc" />
++ <structure type="tr_html" name="IDR_HTML_TEMPLATE" file="templates/homepage.html" />
++ <structure type="dialog" name="IDD_NARROW_DIALOG" file="rc_files/dialogs.rc">
++ <skeleton expr="lang == 'fr-FR'" variant_of_revision="3">
++ <![CDATA[IDD_DIALOG1 DIALOGEX 0, 0, 186, 90
++STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION |
++ WS_SYSMENU
++CAPTION "TRANSLATEABLEPLACEHOLDER1"
++FONT 8, "MS Shell Dlg", 400, 0, 0x1
++BEGIN
++ DEFPUSHBUTTON "TRANSLATEABLEPLACEHOLDER2",IDOK,129,7,50,14
++ PUSHBUTTON "TRANSLATEABLEPLACEHOLDER3",IDCANCEL,129,24,50,14
++ LTEXT "TRANSLATEABLEPLACEHOLDER4",IDC_STATIC,23,31,40,8
++END]]>
++ </skeleton>
++ </structure>
++ <structure type="version" name="VS_VERSION_INFO" file="rc_files/version.rc"/>
++ </structures>
++ </release>
++ <translations>
++ <file path="figs_nl_translations.xml" />
++ <file path="cjk_translations.xml" />
++ </translations>
++ <outputs>
++ <output filename="resource.h" type="rc_header" />
++ <output filename="resource_en.rc" type="rc_all" lang="en-US" />
++ <output filename="resource_fr.rc" type="rc_all" lang="fr-FR" />
++ <output filename="resource_it.rc" type="rc_translateable" lang="it-IT" />
++ <output filename="resource_zh_cn.rc" type="rc_translateable" lang="zh-CN" />
++ <output filename="nontranslateable.rc" type="rc_nontranslateable" />
++ </outputs>
++</grit>
+diff --git a/tools/grit/grit/testdata/simple.html b/tools/grit/grit/testdata/simple.html
new file mode 100644
-index 0000000000..150c79913f
+index 0000000000..4392d23e98
--- /dev/null
-+++ b/tools/clang/plugins/tests/overridden_methods.h
++++ b/tools/grit/grit/testdata/simple.html
+@@ -0,0 +1,3 @@
++<p>
++ Hello!
++</p>
+\ No newline at end of file
+diff --git a/tools/grit/grit/testdata/source.rc b/tools/grit/grit/testdata/source.rc
+new file mode 100644
+index 0000000000..fbc72284e9
+--- /dev/null
++++ b/tools/grit/grit/testdata/source.rc
+@@ -0,0 +1,57 @@
++IDC_KLONKMENU MENU
++BEGIN
++ POPUP "&File"
++ BEGIN
++ MENUITEM "E&xit", IDM_EXIT
++ MENUITEM "This be ""Klonk"" me like", ID_FILE_THISBE
++ POPUP "gonk"
++ BEGIN
++ MENUITEM "Klonk && is [good]", ID_GONK_KLONKIS
++ END
++ END
++ POPUP "&Help"
++ BEGIN
++ MENUITEM "&About ...", IDM_ABOUT
++ END
++END
++
++IDD_ABOUTBOX DIALOGEX 22, 17, 230, 75
++STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
++CAPTION "About"
++FONT 8, "System", 0, 0, 0x0
++BEGIN
++ ICON IDI_KLONK,IDC_MYICON,14,9,20,20
++ LTEXT "klonk Version ""yibbee"" 1.0",IDC_STATIC,49,10,119,8,
++ SS_NOPREFIX
++ LTEXT "Copyright (C) 2005",IDC_STATIC,49,20,119,8
++ DEFPUSHBUTTON "OK",IDOK,195,6,30,11,WS_GROUP
++ CONTROL "Jack ""Black"" Daniels",IDC_RADIO1,"Button",
++ BS_AUTORADIOBUTTON,46,51,84,10
++END
++
++IDD_DIFFERENT_LENGTH_IN_TRANSL DIALOGEX 22, 17, 230, 75
++STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
++CAPTION "Bingobobbi"
++FONT 8, "System", 0, 0, 0x0
++BEGIN
++ LTEXT "Howdie dodie!",IDC_STATIC,49,10,119,8,SS_NOPREFIX
++ LTEXT "Yo froodie!",IDC_STATIC,49,20,119,8
++END
++
++STRINGTABLE
++BEGIN
++ IDS_SIMPLE "One"
++ IDS_PLACEHOLDER "%s birds"
++ IDS_PLACEHOLDERS "%d of %d"
++ IDS_REORDERED_PLACEHOLDERS "$1 of $2"
++ // Won't be in translations list because it has changed
++ IDS_CHANGED "This was the old version"
++ IDS_TWIN_1 "Hello"
++ IDS_TWIN_2 "Hello"
++ IDS_NOT_TRANSLATEABLE ":"
++ IDS_LONGER_TRANSLATED "Removed document $1"
++ // Won't appear in the list of translations because it's not in the .grd file
++ IDS_NO_LONGER_USED "Not used"
++ IDS_DIFFERENT_TWIN_1 "Howdie"
++ IDS_DIFFERENT_TWIN_2 "Howdie"
++END
+diff --git a/tools/grit/grit/testdata/special_100_percent/a.png b/tools/grit/grit/testdata/special_100_percent/a.png
+new file mode 100644
+index 0000000000000000000000000000000000000000..5d5089038ca71172e95db9e7aae1e1fa5cebd505
+GIT binary patch
+literal 159
+zcmeAS@N?(olHy`uVBq!ia0vp^oFL4>0wld=oSO}#(mY)pLnNjq|2Y3)zGGzYPN&L+
+zMSC}CcCfp=Dtxv4%6W%G#Q=|R|L;6pCCLUWO)Z<5eoL%TkDTw=s4X!^d(Qa<2khAN
+zZPy!XToBAic1Ss}vcWiD27B3&`Zj^H6CO>7R1{ToQ;=ggdEYbV=IISvfHpFCy85}S
+Ib4q9e0O9jEh5!Hn
+
+literal 0
+HcmV?d00001
+
+diff --git a/tools/grit/grit/testdata/status.html b/tools/grit/grit/testdata/status.html
+new file mode 100644
+index 0000000000..6b997b9369
+--- /dev/null
++++ b/tools/grit/grit/testdata/status.html
+@@ -0,0 +1,44 @@
++[HEADER]
++<table cellspacing=0 cellPadding=0 width="100%" border=0>
++<tr bgcolor=#3399cc><td align=middle height=1><img height=1 width=1></td></tr>
++</table>
++<table cellspacing=0 cellPadding=1 width="100%" bgcolor=#e8f4f7 border=0>
++<tr><td height=20><font size=+1 color=#000000>&nbsp;<b>Desktop Search Status</b></font></td></tr>
++</table>
++<br>
++<center>
++[$~MESSAGE~$]
++<table cellspacing=0 cellPadding=6 width=500 border=0>
++<tr>
++ <td>&nbsp;</td>
++ <td align=right nowrap><i><font size=-1>Number of items</font></i></td>
++ <td align=right nowrap><i><font size=-1>Time of newest item</font></i></td>
++</tr>
++<tr>
++ <td width=1% nowrap><img style="vertical-align:middle" width=16 height=16 src=favicon.ico>&nbsp; Total searchable items</td>
++ <td align=right><b>[TOTAL_COUNT]</b></td>
++ <td align=right><b>[TOTAL_TIME]</b></td>
++</tr>
++<tr>
++ <td><font size=-1>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img style="vertical-align:middle" src=email.gif width=16 height=16>&nbsp; Emails</font></td>
++ <td align=right><font size=-1>[EMAIL_COUNT]</font></td>
++ <td align=right><font size=-1>[EMAIL_TIME]</font></td>
++</tr>
++<tr>
++ <td><font size=-1>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img style="vertical-align:middle" src="16x16_chat.gif" width=16 height=16>&nbsp; Chats</font></td>
++ <td align=right><font size=-1>[IM_COUNT]</font></td>
++ <td align=right><font size=-1>[IM_TIME]</font></td>
++</tr>
++<tr>
++ <td><font size=-1>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img style="vertical-align:middle" src=html.gif width=16 height=16>&nbsp; Web history</font></td>
++ <td align=right><font size=-1>[WEB_COUNT]</font></td>
++ <td align=right><font size=-1>[WEB_TIME]</font></td>
++</tr>
++<tr>
++ <td><font size=-1>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img style="vertical-align:middle" src=file.gif width=16 height=16>&nbsp; Files</font></td>
++ <td align=right><font size=-1>[FILE_COUNT]</font></td>
++ <td align=right><font size=-1>[FILE_TIME]</font></td>
++</tr>
++</table>
++</center>
++[FOOTER]
+\ No newline at end of file
+diff --git a/tools/grit/grit/testdata/structure_variables.html b/tools/grit/grit/testdata/structure_variables.html
+new file mode 100644
+index 0000000000..2a15de8072
+--- /dev/null
++++ b/tools/grit/grit/testdata/structure_variables.html
+@@ -0,0 +1,4 @@
++<h1>[GREETING]!</h1>
++Some cool things are [THINGS].
++Did you know that [EQUATION]?
++<include src="[filename].html">
+diff --git a/tools/grit/grit/testdata/substitute.grd b/tools/grit/grit/testdata/substitute.grd
+new file mode 100644
+index 0000000000..95dcc56e1d
+--- /dev/null
++++ b/tools/grit/grit/testdata/substitute.grd
+@@ -0,0 +1,31 @@
++<?xml version="1.0" encoding="UTF-8"?> <!-- -*- XML -*- -->
++<grit
++ base_dir="."
++ source_lang_id="en"
++ tc_project="GoogleDesktopWindowsClient"
++ latest_public_release="0"
++ current_release="1"
++ enc_check="möl">
++ <outputs>
++ <output filename="resource.h" type="rc_header" />
++ <output filename="en_generated_resources.rc" type="rc_all" lang="en" />
++ <output filename="sv_generated_resources.rc" type="rc_all" lang="sv" />
++ </outputs>
++ <translations>
++ <file path="substitute.xmb" lang="sv" />
++ </translations>
++ <release seq="1" allow_pseudo="false">
++ <messages first_id="8192">
++ <message name="IDS_COPYRIGHT_GOOGLE_LONG" sub_variable="true" desc="Gadget copyright notice. Needs to be updated every year.">
++ Copyright 2008 Google Inc. All Rights Reserved.
++ </message>
++ <message name="IDS_NEWS_PANEL_COPYRIGHT">
++ Google Desktop News gadget
++[IDS_COPYRIGHT_GOOGLE_LONG]
++View news that is personalized based on the articles you read.
++
++For example, if you read lots of sports news, you'll see more sports articles. If you read technology news less often, you'll see fewer of those articles.
++ </message>
++ </messages>
++ </release>
++</grit>
+diff --git a/tools/grit/grit/testdata/substitute.xmb b/tools/grit/grit/testdata/substitute.xmb
+new file mode 100644
+index 0000000000..e592069c8b
+--- /dev/null
++++ b/tools/grit/grit/testdata/substitute.xmb
+@@ -0,0 +1,10 @@
++<?xml version="1.0" encoding="UTF-8"?>
++<!DOCTYPE translationbundle SYSTEM "/home/build/nonconf/google3/i18n/translationbundle.dtd">
++<translationbundle lang="sv">
++<translation id="7239109800378180620">© 2008 Google Inc. Med ensamrätt.</translation>
++<translation id="6212022020330010625">Google Desktop News gadget
++<ph name="IDS_COPYRIGHT_GOOGLE_LONG_1"/>
++Se nyheter som är anpassade till dig, baserat på de artiklar du läser.
++
++Om du t.ex. läser massor av sportnyheter kommer du att se fler sportartiklar. Om du inte läser tekniknyheter lika ofta ser du färre av dessa artiklar.</translation>
++</translationbundle>
+diff --git a/tools/grit/grit/testdata/substitute_no_ids.grd b/tools/grit/grit/testdata/substitute_no_ids.grd
+new file mode 100644
+index 0000000000..d569d1cacd
+--- /dev/null
++++ b/tools/grit/grit/testdata/substitute_no_ids.grd
+@@ -0,0 +1,31 @@
++<?xml version="1.0" encoding="UTF-8"?> <!-- -*- XML -*- -->
++<grit
++ base_dir="."
++ source_lang_id="en"
++ tc_project="GoogleDesktopWindowsClient"
++ latest_public_release="0"
++ current_release="1"
++ enc_check="möl">
++ <outputs>
++ <output filename="resource.h" type="rc_header" />
++ <output filename="en_generated_resources.rc" type="rc_all" lang="en" />
++ <output filename="sv_generated_resources.rc" type="rc_all" lang="sv" />
++ </outputs>
++ <translations>
++ <file path="substitute.xmb" lang="sv" />
++ </translations>
++ <release seq="1" allow_pseudo="false">
++ <messages>
++ <message name="IDS_COPYRIGHT_GOOGLE_LONG" sub_variable="true" desc="Gadget copyright notice. Needs to be updated every year.">
++ Copyright 2008 Google Inc. All Rights Reserved.
++ </message>
++ <message name="IDS_NEWS_PANEL_COPYRIGHT">
++ Google Desktop News gadget
++[IDS_COPYRIGHT_GOOGLE_LONG]
++View news that is personalized based on the articles you read.
++
++For example, if you read lots of sports news, you'll see more sports articles. If you read technology news less often, you'll see fewer of those articles.
++ </message>
++ </messages>
++ </release>
++</grit>
+diff --git a/tools/grit/grit/testdata/substitute_tmpl.grd b/tools/grit/grit/testdata/substitute_tmpl.grd
+new file mode 100644
+index 0000000000..be7b601707
+--- /dev/null
++++ b/tools/grit/grit/testdata/substitute_tmpl.grd
+@@ -0,0 +1,31 @@
++<?xml version="1.0" encoding="UTF-8"?> <!-- -*- XML -*- -->
++<grit
++ base_dir="."
++ source_lang_id="en"
++ tc_project="GoogleDesktopWindowsClient"
++ latest_public_release="0"
++ current_release="1"
++ enc_check="möl">
++ <outputs>
++ <output filename="resource.h" type="rc_header" />
++ <output filename="en_${name}_resources.rc" type="rc_all" lang="en" />
++ <output filename="sv_${name}_resources.rc" type="rc_all" lang="sv" />
++ </outputs>
++ <translations>
++ <file path="substitute.xmb" lang="sv" />
++ </translations>
++ <release seq="1" allow_pseudo="false">
++ <messages first_id="8192">
++ <message name="IDS_COPYRIGHT_GOOGLE_LONG" sub_variable="true" desc="Gadget copyright notice. Needs to be updated every year.">
++ Copyright 2008 Google Inc. All Rights Reserved.
++ </message>
++ <message name="IDS_NEWS_PANEL_COPYRIGHT">
++ Google Desktop News gadget
++[IDS_COPYRIGHT_GOOGLE_LONG]
++View news that is personalized based on the articles you read.
++
++For example, if you read lots of sports news, you'll see more sports articles. If you read technology news less often, you'll see fewer of those articles.
++ </message>
++ </messages>
++ </release>
++</grit>
+diff --git a/tools/grit/grit/testdata/test_css.css b/tools/grit/grit/testdata/test_css.css
+new file mode 100644
+index 0000000000..55d5dd1770
+--- /dev/null
++++ b/tools/grit/grit/testdata/test_css.css
+@@ -0,0 +1 @@
++This is a test!
+diff --git a/tools/grit/grit/testdata/test_html.html b/tools/grit/grit/testdata/test_html.html
+new file mode 100644
+index 0000000000..55d5dd1770
+--- /dev/null
++++ b/tools/grit/grit/testdata/test_html.html
+@@ -0,0 +1 @@
++This is a test!
+diff --git a/tools/grit/grit/testdata/test_js.js b/tools/grit/grit/testdata/test_js.js
+new file mode 100644
+index 0000000000..55d5dd1770
+--- /dev/null
++++ b/tools/grit/grit/testdata/test_js.js
+@@ -0,0 +1 @@
++This is a test!
+diff --git a/tools/grit/grit/testdata/test_svg.svg b/tools/grit/grit/testdata/test_svg.svg
+new file mode 100644
+index 0000000000..55d5dd1770
+--- /dev/null
++++ b/tools/grit/grit/testdata/test_svg.svg
+@@ -0,0 +1 @@
++This is a test!
+diff --git a/tools/grit/grit/testdata/test_text.txt b/tools/grit/grit/testdata/test_text.txt
+new file mode 100644
+index 0000000000..55d5dd1770
+--- /dev/null
++++ b/tools/grit/grit/testdata/test_text.txt
+@@ -0,0 +1 @@
++This is a test!
+diff --git a/tools/grit/grit/testdata/time_related.html b/tools/grit/grit/testdata/time_related.html
+new file mode 100644
+index 0000000000..ee64b1665e
+--- /dev/null
++++ b/tools/grit/grit/testdata/time_related.html
+@@ -0,0 +1,11 @@
++[HEADER]
++[CHROME]
++[NAV_PRE_POST]
++[$~MESSAGE~$]<br>
++<table border=0 cellpadding=2 cellspacing=0 width='100%'>
++[CONTENTS]
++</table><br>
++
++[NAV_PRE_POST]
++[FOOTER]
++
+diff --git a/tools/grit/grit/testdata/toolbar_about.html b/tools/grit/grit/testdata/toolbar_about.html
+new file mode 100644
+index 0000000000..bb4b0eb355
+--- /dev/null
++++ b/tools/grit/grit/testdata/toolbar_about.html
+@@ -0,0 +1,138 @@
++<html id=dlgAbout STYLE="width: 25.8em; height: 17em" [GRITDIR]>
++<head>
++<meta http-equiv="content-type" content="text/html; charset=utf-8">
++<title>About Google Toolbar</title>
++<style>
++.button {
++ width: 7em;
++ height: 2.2em;
++ color: buttontext;
++ font-family: MS Sans Serif;
++ font-size:8pt;
++ cursor: hand;
++}
++</style>
++
++<script> <!--
++ function HandleError(message, url, line) {
++ var L_Dialog_ErrorMessage = "An error has occured in this dialog.";
++ var L_ErrorNumber_Text = "Error: ";
++ var str = L_Dialog_ErrorMessage + "\n\n"
++ + L_ErrorNumber_Text + line + "\n"
++ + message;
++ alert (str);
++ window.close();
++ return true;
++ }
++
++ function OnKeyPress(nCode) {
++ if (nCode == 27) {
++ window.close();
++ return;
++ }
++ }
++
++ function OnLoad() {
++ if ((null != window.dialogArguments) && (window.dialogArguments.indexOf("&") == -1) && (window.dialogArguments.indexOf("<") == -1)) {
++ version.innerHTML = window.dialogArguments;
++ } else {
++ version.innerText = "Version: Unknown";
++ }
++ }
++
++ window.onerror = HandleError;
++ // -->
++</script>
++
++</head>
++
++
++<body bgcolor="#FFFFFF" onload="OnLoad()" onkeydown="OnKeyPress(event.keyCode)" onkeypress="OnKeyPress(event.keyCode)" scroll=no>
++
++<table border=0>
++
++ <tr height=5>
++ <td width=5></td>
++ <td></td>
++ <td></td>
++ <td></td>
++ <td width=5></td>
++ </tr>
++
++ <tr>
++ <td></td>
++ <td colspan=3>
++
++
++<table border="0" cellpadding="0" cellspacing="0" valign="top">
++ <tr>
++ <td valign="top" height="47" width="155">
++ <div align="center"><img src="title_toolbar.gif" width="275" height="59" alt="Google Toolbar"></div>
++ </td>
++ <td valign="middle" height="47" width="713">
++ <hr size=1 color=25479D></td></tr>
++</table>
++
++
++ </td>
++ <!--
++ <TD colspan=2>
++ <span style="COLOR: black; FONT: 18pt Tahoma, MS Shell Dlg"><b>
++ Google Toolbar&trade;</b>
++ </span>
++ </TD>
++ -->
++ <td valign="middle">
++ </td>
++ </tr>
++
++ <tr>
++ <td></td>
++ <td align=center><img src="googly.gif"></td>
++ <td colspan=2 align=left>
++ <span style="WIDTH: 25em; height:6em COLOR: black; FONT: 8pt Tahoma, MS Shell Dlg">
++ <span id=version></span><br>
++ </span>
++ </td>
++ <td></td>
++ </tr>
++
++ <tr height=50>
++ <td></td>
++ <td></td>
++ <td colspan=2 align=left>
++ <span style="WIDTH: 25em; height:6em COLOR: black; FONT: 8pt Tahoma, MS Shell Dlg">
++ <!--$/translate-->
++ <i>De parvis grandis acervus erit</i>
++ <!--$translate-->
++ </span>
++ </td>
++ <td></td>
++ </tr>
++
++ <tr height=40>
++ <td></td>
++ <td></td>
++ <td></td>
++ <td></td>
++ <td></td>
++ </tr>
++
++ <tr>
++ <td></td>
++ <td width=80></td>
++ <td>
++ <!--$/translate-->
++ <span style="WIDTH: 20em; COLOR: black; FONT: 8pt Tahoma, MS Shell Dlg" id="copyright">&copy; 2006 Google</span>
++ <!--$translate-->
++ </td>
++ <td id=ok-button align=right><button tabindex=1 type=submit align=right id="okButton" class=button onClick="window.close();" >OK</button>
++ </td>
++ <td></td>
++ </tr>
++
++</table>
++</span>
++
++</body>
++</html>
+diff --git a/tools/grit/grit/testdata/tools/grit/resource_ids b/tools/grit/grit/testdata/tools/grit/resource_ids
+new file mode 100644
+index 0000000000..8a2b608df1
+--- /dev/null
++++ b/tools/grit/grit/testdata/tools/grit/resource_ids
+@@ -0,0 +1,176 @@
++# Copyright (c) 2011 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++#
++# This file is used to assign starting resource ids for resources and strings
++# used by Chromium. This is done to ensure that resource ids are unique
++# across all the grd files. If you are adding a new grd file, please add
++# a new entry to this file.
++#
++# The first entry in the file, SRCDIR, is special: It is a relative path from
++# this file to the base of your checkout.
++#
++# http://msdn.microsoft.com/en-us/library/t2zechd4(VS.71).aspx says that the
++# range for IDR_ is 1 to 28,671 and the range for IDS_ is 1 to 32,767 and
++# common convention starts practical use of IDs at 100 or 101.
++{
++ "SRCDIR": "../..",
++
++ "chrome/browser/browser_resources.grd": {
++ "includes": [500],
++ },
++ "chrome/browser/resources/component_extension_resources.grd": {
++ "includes": [1000],
++ },
++ "chrome/browser/resources/net_internals_resources.grd": {
++ "includes": [1500],
++ },
++ "chrome/browser/resources/shared_resources.grd": {
++ "includes": [2000],
++ },
++ "chrome/common/common_resources.grd": {
++ "includes": [2500],
++ },
++ "chrome/default_plugin/default_plugin_resources.grd": {
++ "includes": [3000],
++ },
++ "chrome/renderer/renderer_resources.grd": {
++ "includes": [3500],
++ },
++ "net/base/net_resources.grd": {
++ "includes": [4000],
++ },
++ "webkit/glue/webkit_resources.grd": {
++ "includes": [4500],
++ },
++ "webkit/tools/test_shell/test_shell_resources.grd": {
++ "includes": [5000],
++ },
++ "ui/resources/ui_resources.grd": {
++ "includes": [5500],
++ },
++ "chrome/app/theme/theme_resources.grd": {
++ "includes": [6000],
++ },
++ "chrome_frame/resources/chrome_frame_resources.grd": {
++ "includes": [6500],
++ },
++ # WebKit.grd can be in two different places depending on whether we are
++ # in a chromium checkout or a webkit-only checkout.
++ "third_party/WebKit/Source/WebKit/chromium/WebKit.grd": {
++ "includes": [7000],
++ },
++ "WebKit.grd": {
++ "includes": [7000],
++ },
++
++ "ui/base/strings/app_locale_settings.grd": {
++ "META": {"join": 2},
++ "messages": [7500],
++ },
++ "chrome/app/resources/locale_settings.grd": {
++ "includes": [8000],
++ "messages": [8500],
++ },
++ # These each start with the same resource id because we only use one
++ # file for each build (cros, linux, mac, or win).
++ "chrome/app/resources/locale_settings_cros.grd": {
++ "messages": [9000],
++ },
++ "chrome/app/resources/locale_settings_linux.grd": {
++ "messages": [9000],
++ },
++ "chrome/app/resources/locale_settings_mac.grd": {
++ "messages": [9000],
++ },
++ "chrome/app/resources/locale_settings_win.grd": {
++ "messages": [9000],
++ },
++
++ "ui/base/strings/ui_strings.grd": {
++ "META": {"join": 4},
++ "messages": [9500],
++ },
++ # Chromium strings and Google Chrome strings must start at the same id.
++ # We only use one file depending on whether we're building Chromium or
++ # Google Chrome.
++ "chrome/app/chromium_strings.grd": {
++ "messages": [10000],
++ },
++ "chrome/app/google_chrome_strings.grd": {
++ "messages": [10000],
++ },
++ # Leave lots of space for generated_resources since it has most of our
++ # strings.
++ "chrome/app/generated_resources.grd": {
++ "META": {"join": 2},
++ "structures": [10500],
++ "messages": [11000],
++ },
++ # The chrome frame dialogs are also in generated_resources.grd so they
++ # get included by the translation console. We make sure that the ids
++ # for structures here are the same as for generated_resources.grd.
++ "chrome_frame/resources/chrome_frame_dialogs.grd": {
++ "structures": [10500],
++ "includes": [10750],
++ },
++ "webkit/glue/inspector_strings.grd": {
++ "messages": [16000],
++ },
++ "webkit/glue/webkit_strings.grd": {
++ "messages": [16500],
++ },
++
++ "chrome_frame/resources/chrome_frame_resources.grd": {
++ "includes": [17500],
++ "structures": [18000],
++ },
++
++ "ui/gfx/gfx_resources.grd": {
++ "includes": [18500],
++ },
++
++ "chrome/app/policy/policy_templates.grd": {
++ "structures": [19000],
++ "messages": [19010],
++ },
++
++ "chrome/browser/autofill/autofill_resources.grd": {
++ "messages": [19500],
++ },
++ "chrome/browser/resources/sync_internals_resources.grd": {
++ "includes": [20000],
++ },
++ # This file is generated during the build.
++ "<(SHARED_INTERMEDIATE_DIR)/devtools/devtools_resources.grd": {
++ "includes": [20500],
++ },
++ # All standard and large theme resources should have the same IDs.
++ "chrome/app/theme/theme_resources_standard.grd": {
++ "includes": [21000],
++ },
++ "chrome/app/theme/theme_resources_large.grd": {
++ "includes": [21000],
++ },
++ # This file is generated during the build.
++ "chrome/browser/debugger/frontend/devtools_frontend_resources.grd": {
++ "META": {"join": 2},
++ "includes": [21500],
++ },
++ "cloud_print/virtual_driver/win/install/virtual_driver_setup_resources.grd": {
++ "messages": [22500],
++ },
++ "chrome/browser/resources/quota_internals_resources.grd": {
++ "includes": [23000],
++ },
++ "chrome/browser/resources/workers_resources.grd": {
++ "includes": [23500],
++ },
++ # All standard and large theme resources should have the same IDs.
++ "ui/resources/ui_resources_standard.grd": {
++ "includes": [24000],
++ },
++ "ui/resources/ui_resources_large.grd": {
++ "includes": [24000],
++ },
++}
+diff --git a/tools/grit/grit/testdata/transl.rc b/tools/grit/grit/testdata/transl.rc
+new file mode 100644
+index 0000000000..2f2595db3f
+--- /dev/null
++++ b/tools/grit/grit/testdata/transl.rc
+@@ -0,0 +1,56 @@
++IDC_KLONKMENU MENU
++BEGIN
++ POPUP "&Skra"
++ BEGIN
++ MENUITEM "&Haetta", IDM_EXIT
++ MENUITEM "Thetta er ""Klonk"" sem eg fyla", ID_FILE_THISBE
++ POPUP "gonkurinn"
++ BEGIN
++ MENUITEM "Klonk && er [good]", ID_GONK_KLONKIS
++ END
++ END
++ POPUP "&Hjalp"
++ BEGIN
++ MENUITEM "&Um...", IDM_ABOUT
++ END
++END
++
++IDD_ABOUTBOX DIALOGEX 22, 17, 230, 75
++STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
++CAPTION "Um Klonk"
++FONT 8, "System", 0, 0, 0x0
++BEGIN
++ ICON IDI_KLONK,IDC_MYICON,14,9,20,20
++ LTEXT "klonk utgafa ""jibbi"" 1.0",IDC_STATIC,49,10,119,8,
++ SS_NOPREFIX
++ LTEXT "Hofundarrettur (C) 2005",IDC_STATIC,49,20,119,8
++ DEFPUSHBUTTON "I lagi",IDOK,195,6,30,11,WS_GROUP
++ CONTROL "Jack ""Black"" Daniels",IDC_RADIO1,"Button",
++ BS_AUTORADIOBUTTON,46,51,84,10
++END
++
++IDD_DIFFERENT_LENGTH_IN_TRANSL DIALOGEX 22, 17, 230, 75
++STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
++CAPTION "Bingobobbi"
++FONT 8, "System", 0, 0, 0x0
++BEGIN
++ LTEXT "Howdie dodie!",IDC_STATIC,49,10,119,8,SS_NOPREFIX
++END
++
++STRINGTABLE
++BEGIN
++ IDS_SIMPLE "Ein"
++ IDS_PLACEHOLDER "%s Vogeln"
++ IDS_PLACEHOLDERS "%d von %d"
++ // Shouldn't be part of translations list because the translation is
++ // reordered so placeholder fixup fails
++ IDS_REORDERED_PLACEHOLDERS "$2 auf $1"
++ IDS_CHANGED "Dass war die alte Version"
++ IDS_TWIN_1 "Hallo"
++ IDS_TWIN_2 "Hallo"
++ IDS_NOT_TRANSLATEABLE ":"
++ IDS_LONGER_TRANSLATED "Dokument $1 ist entfernt worden"
++ IDS_NO_LONGER_USED "Nicht verwendet"
++ IDS_DIFFERENT_TWIN_1 "Howdie"
++ IDS_DIFFERENT_TWIN_2 "Hallo sagt man"
++END
+diff --git a/tools/grit/grit/testdata/versions.html b/tools/grit/grit/testdata/versions.html
+new file mode 100644
+index 0000000000..d1f40d8d72
+--- /dev/null
++++ b/tools/grit/grit/testdata/versions.html
+@@ -0,0 +1,7 @@
++[HEADER]
++
++[TOP_CHROME]
++[CONTENTS]
++
++[NEXT_PREV]
++[FOOTER]
+diff --git a/tools/grit/grit/testdata/whitelist.txt b/tools/grit/grit/testdata/whitelist.txt
+new file mode 100644
+index 0000000000..5b3aca40b5
+--- /dev/null
++++ b/tools/grit/grit/testdata/whitelist.txt
+@@ -0,0 +1,4 @@
++IDS_MESSAGE_WHITELISTED
++IDR_STRUCTURE_WHITELISTED
++IDR_STRUCTURE_IN_TRUE_IF_WHITELISTED
++IDR_INCLUDE_WHITELISTED
+diff --git a/tools/grit/grit/testdata/whitelist_resources.grd b/tools/grit/grit/testdata/whitelist_resources.grd
+new file mode 100644
+index 0000000000..9925688ff5
+--- /dev/null
++++ b/tools/grit/grit/testdata/whitelist_resources.grd
@@ -0,0 +1,54 @@
-+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-+// Use of this source code is governed by a BSD-style license that can be
-+// found in the LICENSE file.
-+
-+#ifndef OVERRIDDEN_METHODS_H_
-+#define OVERRIDDEN_METHODS_H_
-+
-+// Should warn about overriding of methods.
-+class BaseClass {
-+ public:
-+ virtual ~BaseClass() {}
-+ virtual void SomeMethod() = 0;
-+ virtual void SomeOtherMethod() = 0;
-+ virtual void SomeInlineMethod() = 0;
-+ virtual void SomeNonPureBaseMethod() {}
-+};
++<?xml version="1.0" encoding="UTF-8"?>
++<grit latest_public_release="0"
++ current_release="1"
++ output_all_resource_defines="false">
++ <outputs>
++ <output filename="whitelist_test_resources.h" type="rc_header">
++ <emit emit_type='prepend'></emit>
++ </output>
++ <output filename="whitelist_test_resources_map.cc"
++ type="resource_file_map_source" />
++ <output filename="whitelist_test_resources_map.h"
++ type="resource_map_header" />
++ <output filename="whitelist_test_resources.pak" type="data_package" />
++ </outputs>
++ <translations>
++ <file path="substitute.xmb" lang="sv" />
++ </translations>
++ <release seq="1">
++ <structures>
++ <structure name="IDR_STRUCTURE_WHITELISTED" file="browser.html"
++ type="chrome_html" >
++ </structure>
++ <structure name="IDR_STRUCTURE_NOT_WHITELISTED" file="deleted.html"
++ type="chrome_html" >
++ </structure>
++ <if expr="True">
++ <structure name="IDR_STRUCTURE_IN_TRUE_IF_WHITELISTED"
++ file="details.html"
++ type="chrome_html" >
++ </structure>
++ <structure name="IDR_STRUCTURE_IN_TRUE_IF_NOT_WHITELISTED"
++ file="error.html"
++ type="chrome_html" >
++ </structure>
++ </if>
++ <if expr="False">
++ <structure name="IDR_STRUCTURE_IN_FALSE_IF_WHITELISTED"
++ file="status.html"
++ type="chrome_html" >
++ </structure>
++ <structure name="IDR_STRUCTURE_IN_FALSE_IF_NOT_WHITELISTED"
++ file="simple.html"
++ type="chrome_html" >
++ </structure>
++ </if>
++ </structures>
++ <includes>
++ <include name="IDR_INCLUDE_WHITELISTED" file="klonk.ico"
++ type="BINDATA" />
++ <include name="IDR_INCLUDE_NOT_WHITELISTED" file="klonk.rc"
++ type="BINDATA" />
++ </includes>
++ </release>
++</grit>
+diff --git a/tools/grit/grit/testdata/whitelist_strings.grd b/tools/grit/grit/testdata/whitelist_strings.grd
+new file mode 100644
+index 0000000000..df80f5fd32
+--- /dev/null
++++ b/tools/grit/grit/testdata/whitelist_strings.grd
+@@ -0,0 +1,23 @@
++<?xml version="1.0" encoding="UTF-8"?>
++<grit latest_public_release="0"
++ current_release="1"
++ output_all_resource_defines="false">
++ <outputs >
++ <output filename="whitelist_test_resources.h" type="rc_header">
++ <emit emit_type='prepend'></emit>
++ </output>
++ <output filename="en_whitelist_test_strings.rc" type="rc_all" lang="en" />
++ </outputs>
++ <release seq="1">
++ <messages>
++ <message name="IDS_MESSAGE_WHITELISTED"
++ desc="A message in the whiltelist file.">
++ Whitelisted.
++ </message>
++ <message name="IDS_MESSAGE_NOT_WHITELISTED"
++ desc="A message that isn't in the whiltelist file.">
++ Not whitelisted.
++ </message>
++ </messages>
++ </release>
++</grit>
+diff --git a/tools/grit/grit/tool/__init__.py b/tools/grit/grit/tool/__init__.py
+new file mode 100644
+index 0000000000..cc455b36e7
+--- /dev/null
++++ b/tools/grit/grit/tool/__init__.py
+@@ -0,0 +1,8 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
+
-+class InterimClass : public BaseClass {
-+ // Should not warn about pure virtual methods.
-+ virtual void SomeMethod() = 0;
-+};
++'''Package grit.tool
++'''
+
-+namespace WebKit {
-+class WebKitObserver {
-+ public:
-+ virtual void WebKitModifiedSomething() {};
-+};
-+} // namespace WebKit
++pass
+diff --git a/tools/grit/grit/tool/android2grd.py b/tools/grit/grit/tool/android2grd.py
+new file mode 100644
+index 0000000000..005297bafe
+--- /dev/null
++++ b/tools/grit/grit/tool/android2grd.py
+@@ -0,0 +1,484 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
+
-+namespace webkit_glue {
-+class WebKitObserverImpl : WebKit::WebKitObserver {
-+ public:
-+ virtual void WebKitModifiedSomething() {};
-+};
-+} // namespace webkit_glue
-+
-+class DerivedClass : public InterimClass,
-+ public webkit_glue::WebKitObserverImpl {
-+ public:
-+ // Should not warn about destructors.
-+ virtual ~DerivedClass() {}
-+ // Should warn.
-+ virtual void SomeMethod();
-+ // Should not warn if marked as override.
-+ virtual void SomeOtherMethod() override;
-+ // Should warn for inline implementations.
-+ virtual void SomeInlineMethod() {}
-+ // Should not warn if overriding a method whose origin is WebKit.
-+ virtual void WebKitModifiedSomething();
-+ // Should warn if overridden method isn't pure.
-+ virtual void SomeNonPureBaseMethod() {}
-+};
++"""The 'grit android2grd' tool."""
+
-+#endif // OVERRIDDEN_METHODS_H_
-diff --git a/tools/clang/plugins/tests/overridden_methods.txt b/tools/clang/plugins/tests/overridden_methods.txt
-new file mode 100644
-index 0000000000..7553ade70e
---- /dev/null
-+++ b/tools/clang/plugins/tests/overridden_methods.txt
-@@ -0,0 +1,20 @@
-+In file included from overridden_methods.cpp:5:
-+./overridden_methods.h:43:11: warning: [chromium-style] Overriding method must be marked with OVERRIDE.
-+ virtual void SomeMethod();
-+ ^
-+./overridden_methods.h:47:11: warning: [chromium-style] Overriding method must be marked with OVERRIDE.
-+ virtual void SomeInlineMethod() {}
-+ ^
-+./overridden_methods.h:51:11: warning: [chromium-style] Overriding method must be marked with OVERRIDE.
-+ virtual void SomeNonPureBaseMethod() {}
-+ ^
-+overridden_methods.cpp:24:11: warning: [chromium-style] Overriding method must be marked with OVERRIDE.
-+ virtual void SomeMethod();
-+ ^
-+overridden_methods.cpp:28:11: warning: [chromium-style] Overriding method must be marked with OVERRIDE.
-+ virtual void SomeInlineMethod() {}
-+ ^
-+overridden_methods.cpp:32:11: warning: [chromium-style] Overriding method must be marked with OVERRIDE.
-+ virtual void SomeNonPureBaseMethod() {}
-+ ^
-+6 warnings generated.
-diff --git a/tools/clang/plugins/tests/test.sh b/tools/clang/plugins/tests/test.sh
-new file mode 100755
-index 0000000000..262ebbba29
---- /dev/null
-+++ b/tools/clang/plugins/tests/test.sh
-@@ -0,0 +1,72 @@
-+#!/bin/bash
-+#
-+# Copyright (c) 2011 The Chromium Authors. All rights reserved.
++from __future__ import print_function
++
++import getopt
++import os.path
++import sys
++from xml.dom import Node
++import xml.dom.minidom
++
++import six
++from six import StringIO
++
++import grit.node.empty
++from grit.node import node_io
++from grit.node import message
++
++from grit.tool import interface
++
++from grit import grd_reader
++from grit import lazy_re
++from grit import tclib
++
++
++# The name of a string in strings.xml
++_STRING_NAME = lazy_re.compile(r'[a-z0-9_]+\Z')
++
++# A string's character limit in strings.xml
++_CHAR_LIMIT = lazy_re.compile(r'\[CHAR-LIMIT=(\d+)\]')
++
++# Finds String.Format() style format specifiers such as "%-5.2f".
++_FORMAT_SPECIFIER = lazy_re.compile(
++ r'%'
++ r'([1-9][0-9]*\$|<)?' # argument_index
++ r'([-#+ 0,(]*)' # flags
++ r'([0-9]+)?' # width
++ r'(\.[0-9]+)?' # precision
++ r'([bBhHsScCdoxXeEfgGaAtT%n])') # conversion
++
++
++class Android2Grd(interface.Tool):
++ """Tool for converting Android string.xml files into chrome Grd files.
++
++Usage: grit [global options] android2grd [OPTIONS] STRINGS_XML
++
++The Android2Grd tool will convert an Android strings.xml file (whose path is
++specified by STRINGS_XML) and create a chrome style grd file containing the
++relevant information.
++
++Because grd documents are much richer than strings.xml documents we supplement
++the information required by grds using OPTIONS with sensible defaults.
++
++OPTIONS may be any of the following:
++
++ --name FILENAME Specify the base FILENAME. This should be without
++ any file type suffix. By default
++ "chrome_android_strings" will be used.
++
++ --languages LANGUAGES Comma separated list of ISO language codes (e.g.
++ en-US, en-GB, ru, zh-CN). These codes will be used
++ to determine the names of resource and translations
++ files that will be declared by the output grd file.
++
++ --grd-dir GRD_DIR Specify where the resultant grd file
++ (FILENAME.grd) should be output. By default this
++ will be the present working directory.
++
++ --header-dir HEADER_DIR Specify the location of the directory where grit
++ generated C++ headers (whose name will be
++ FILENAME.h) will be placed. Use an empty string to
++ disable rc generation. Default: empty.
++
++ --rc-dir RC_DIR Specify the directory where resource files will
++ be located relative to grit build's output
++ directory. Use an empty string to disable rc
++ generation. Default: empty.
++
++ --xml-dir XML_DIR Specify where to place localized strings.xml files
++ relative to grit build's output directory. For each
++ language xx a values-xx/strings.xml file will be
++ generated. Use an empty string to disable
++ strings.xml generation. Default: '.'.
++
++ --xtb-dir XTB_DIR Specify where the xtb files containing translations
++ will be located relative to the grd file. Default:
++ '.'.
++"""
++
++ _NAME_FLAG = 'name'
++ _LANGUAGES_FLAG = 'languages'
++ _GRD_DIR_FLAG = 'grd-dir'
++ _RC_DIR_FLAG = 'rc-dir'
++ _HEADER_DIR_FLAG = 'header-dir'
++ _XTB_DIR_FLAG = 'xtb-dir'
++ _XML_DIR_FLAG = 'xml-dir'
++
++ def __init__(self):
++ self.name = 'chrome_android_strings'
++ self.languages = []
++ self.grd_dir = '.'
++ self.rc_dir = None
++ self.xtb_dir = '.'
++ self.xml_res_dir = '.'
++ self.header_dir = None
++
++ def ShortDescription(self):
++ """Returns a short description of the Android2Grd tool.
++
++ Overridden from grit.interface.Tool
++
++ Returns:
++ A string containing a short description of the android2grd tool.
++ """
++ return 'Converts Android string.xml files into Chrome grd files.'
++
++ def ParseOptions(self, args):
++ """Set this objects and return all non-option arguments."""
++ flags = [
++ Android2Grd._NAME_FLAG,
++ Android2Grd._LANGUAGES_FLAG,
++ Android2Grd._GRD_DIR_FLAG,
++ Android2Grd._RC_DIR_FLAG,
++ Android2Grd._HEADER_DIR_FLAG,
++ Android2Grd._XTB_DIR_FLAG,
++ Android2Grd._XML_DIR_FLAG, ]
++ (opts, args) = getopt.getopt(
++ args, None, ['%s=' % o for o in flags] + ['help'])
++
++ for key, val in opts:
++ # Get rid of the preceding hypens.
++ k = key[2:]
++ if k == Android2Grd._NAME_FLAG:
++ self.name = val
++ elif k == Android2Grd._LANGUAGES_FLAG:
++ self.languages = val.split(',')
++ elif k == Android2Grd._GRD_DIR_FLAG:
++ self.grd_dir = val
++ elif k == Android2Grd._RC_DIR_FLAG:
++ self.rc_dir = val
++ elif k == Android2Grd._HEADER_DIR_FLAG:
++ self.header_dir = val
++ elif k == Android2Grd._XTB_DIR_FLAG:
++ self.xtb_dir = val
++ elif k == Android2Grd._XML_DIR_FLAG:
++ self.xml_res_dir = val
++ elif k == 'help':
++ self.ShowUsage()
++ sys.exit(0)
++ return args
++
++ def Run(self, opts, args):
++ """Runs the Android2Grd tool.
++
++ Inherited from grit.interface.Tool.
++
++ Args:
++ opts: List of string arguments that should be parsed.
++ args: String containing the path of the strings.xml file to be converted.
++ """
++ args = self.ParseOptions(args)
++ if len(args) != 1:
++ print('Tool requires one argument, the path to the Android '
++ 'strings.xml resource file to be converted.')
++ return 2
++ self.SetOptions(opts)
++
++ android_path = args[0]
++
++ # Read and parse the Android strings.xml file.
++ with open(android_path) as android_file:
++ android_dom = xml.dom.minidom.parse(android_file)
++
++ # Do the hard work -- convert the Android dom to grd file contents.
++ grd_dom = self.AndroidDomToGrdDom(android_dom)
++ grd_string = six.text_type(grd_dom)
++
++ # Write the grd string to a file in grd_dir.
++ grd_filename = self.name + '.grd'
++ grd_path = os.path.join(self.grd_dir, grd_filename)
++ with open(grd_path, 'w') as grd_file:
++ grd_file.write(grd_string)
++
++ def AndroidDomToGrdDom(self, android_dom):
++ """Converts a strings.xml DOM into a DOM representing the contents of
++ a grd file.
++
++ Args:
++ android_dom: A xml.dom.Document containing the contents of the Android
++ string.xml document.
++ Returns:
++ The DOM for the grd xml document produced by converting the Android DOM.
++ """
++
++ # Start with a basic skeleton for the .grd file.
++ root = grd_reader.Parse(StringIO(
++ '''<?xml version="1.0" encoding="UTF-8"?>
++ <grit base_dir="." latest_public_release="0"
++ current_release="1" source_lang_id="en">
++ <outputs />
++ <translations />
++ <release allow_pseudo="false" seq="1">
++ <messages fallback_to_english="true" />
++ </release>
++ </grit>'''), dir='.')
++ outputs = root.children[0]
++ translations = root.children[1]
++ messages = root.children[2].children[0]
++ assert (isinstance(messages, grit.node.empty.MessagesNode) and
++ isinstance(translations, grit.node.empty.TranslationsNode) and
++ isinstance(outputs, grit.node.empty.OutputsNode))
++
++ if self.header_dir:
++ cpp_header = self.__CreateCppHeaderOutputNode(outputs, self.header_dir)
++ for lang in self.languages:
++ # Create an output element for each language.
++ if self.rc_dir:
++ self.__CreateRcOutputNode(outputs, lang, self.rc_dir)
++ if self.xml_res_dir:
++ self.__CreateAndroidXmlOutputNode(outputs, lang, self.xml_res_dir)
++ if lang != 'en':
++ self.__CreateFileNode(translations, lang)
++ # Convert all the strings.xml strings into grd messages.
++ self.__CreateMessageNodes(messages, android_dom.documentElement)
++
++ return root
++
++ def __CreateMessageNodes(self, messages, resources):
++ """Creates the <message> elements and adds them as children of <messages>.
++
++ Args:
++ messages: the <messages> element in the strings.xml dom.
++ resources: the <resources> element in the grd dom.
++ """
++ # <string> elements contain the definition of the resource.
++ # The description of a <string> element is contained within the comment
++ # node element immediately preceeding the string element in question.
++ description = ''
++ for child in resources.childNodes:
++ if child.nodeType == Node.COMMENT_NODE:
++ # Remove leading/trailing whitespace; collapse consecutive whitespaces.
++ description = ' '.join(child.data.split())
++ elif child.nodeType == Node.ELEMENT_NODE:
++ if child.tagName != 'string':
++ print('Warning: ignoring unknown tag <%s>' % child.tagName)
++ else:
++ translatable = self.IsTranslatable(child)
++ raw_name = child.getAttribute('name')
++ if not _STRING_NAME.match(raw_name):
++ print('Error: illegal string name: %s' % raw_name)
++ grd_name = 'IDS_' + raw_name.upper()
++ # Transform the <string> node contents into a tclib.Message, taking
++ # care to handle whitespace transformations and escaped characters,
++ # and coverting <xliff:g> placeholders into <ph> placeholders.
++ msg = self.CreateTclibMessage(child)
++ msg_node = self.__CreateMessageNode(messages, grd_name, description,
++ msg, translatable)
++ messages.AddChild(msg_node)
++ # Reset the description once a message has been parsed.
++ description = ''
++
++ def CreateTclibMessage(self, android_string):
++ """Transforms a <string/> element from strings.xml into a tclib.Message.
++
++ Interprets whitespace, quotes, and escaped characters in the android_string
++ according to Android's formatting and styling rules for strings. Also
++ converts <xliff:g> placeholders into <ph> placeholders, e.g.:
++
++ <xliff:g id="website" example="google.com">%s</xliff:g>
++ becomes
++ <ph name="website"><ex>google.com</ex>%s</ph>
++
++ Returns:
++ The tclib.Message.
++ """
++ msg = tclib.Message()
++ current_text = '' # Accumulated text that hasn't yet been added to msg.
++ nodes = android_string.childNodes
++
++ for i, node in enumerate(nodes):
++ # Handle text nodes.
++ if node.nodeType in (Node.TEXT_NODE, Node.CDATA_SECTION_NODE):
++ current_text += node.data
++
++ # Handle <xliff:g> and other tags.
++ elif node.nodeType == Node.ELEMENT_NODE:
++ if node.tagName == 'xliff:g':
++ assert node.hasAttribute('id'), 'missing id: ' + node.data()
++ placeholder_id = node.getAttribute('id')
++ placeholder_text = self.__FormatPlaceholderText(node)
++ placeholder_example = node.getAttribute('example')
++ if not placeholder_example:
++ print('Info: placeholder does not contain an example: %s' %
++ node.toxml())
++ placeholder_example = placeholder_id.upper()
++ msg.AppendPlaceholder(tclib.Placeholder(placeholder_id,
++ placeholder_text, placeholder_example))
++ else:
++ print('Warning: removing tag <%s> which must be inside a '
++ 'placeholder: %s' % (node.tagName, node.toxml()))
++ msg.AppendText(self.__FormatPlaceholderText(node))
++
++ # Handle other nodes.
++ elif node.nodeType != Node.COMMENT_NODE:
++ assert False, 'Unknown node type: %s' % node.nodeType
++
++ is_last_node = (i == len(nodes) - 1)
++ if (current_text and
++ (is_last_node or nodes[i + 1].nodeType == Node.ELEMENT_NODE)):
++ # For messages containing just text and comments (no xml tags) Android
++ # strips leading and trailing whitespace. We mimic that behavior.
++ if not msg.GetContent() and is_last_node:
++ current_text = current_text.strip()
++ msg.AppendText(self.__FormatAndroidString(current_text))
++ current_text = ''
++
++ return msg
++
++ def __FormatAndroidString(self, android_string, inside_placeholder=False):
++ r"""Returns android_string formatted for a .grd file.
++
++ * Collapses consecutive whitespaces, except when inside double-quotes.
++ * Replaces \\, \n, \t, \", \' with \, newline, tab, ", '.
++ """
++ backslash_map = {'\\' : '\\', 'n' : '\n', 't' : '\t', '"' : '"', "'" : "'"}
++ is_quoted_section = False # True when we're inside double quotes.
++ is_backslash_sequence = False # True after seeing an unescaped backslash.
++ prev_char = ''
++ output = []
++ for c in android_string:
++ if is_backslash_sequence:
++ # Unescape \\, \n, \t, \", and \'.
++ assert c in backslash_map, 'Illegal escape sequence: \\%s' % c
++ output.append(backslash_map[c])
++ is_backslash_sequence = False
++ elif c == '\\':
++ is_backslash_sequence = True
++ elif c.isspace() and not is_quoted_section:
++ # Turn whitespace into ' ' and collapse consecutive whitespaces.
++ if not prev_char.isspace():
++ output.append(' ')
++ elif c == '"':
++ is_quoted_section = not is_quoted_section
++ else:
++ output.append(c)
++ prev_char = c
++ output = ''.join(output)
++
++ if is_quoted_section:
++ print('Warning: unbalanced quotes in string: %s' % android_string)
++
++ if is_backslash_sequence:
++ print('Warning: trailing backslash in string: %s' % android_string)
++
++ # Check for format specifiers outside of placeholder tags.
++ if not inside_placeholder:
++ format_specifier = _FORMAT_SPECIFIER.search(output)
++ if format_specifier:
++ print('Warning: format specifiers are not inside a placeholder '
++ '<xliff:g/> tag: %s' % output)
++
++ return output
++
++ def __FormatPlaceholderText(self, placeholder_node):
++ """Returns the text inside of an <xliff:g> placeholder node."""
++ text = []
++ for childNode in placeholder_node.childNodes:
++ if childNode.nodeType in (Node.TEXT_NODE, Node.CDATA_SECTION_NODE):
++ text.append(childNode.data)
++ elif childNode.nodeType != Node.COMMENT_NODE:
++ assert False, 'Unknown node type in ' + placeholder_node.toxml()
++ return self.__FormatAndroidString(''.join(text), inside_placeholder=True)
++
++ def __CreateMessageNode(self, messages_node, grd_name, description, msg,
++ translatable):
++ """Creates and initializes a <message> element.
++
++ Message elements correspond to Android <string> elements in that they
++ declare a string resource along with a programmatic id.
++ """
++ if not description:
++ print('Warning: no description for %s' % grd_name)
++ # Check that we actually fit within the character limit we've specified.
++ match = _CHAR_LIMIT.search(description)
++ if match:
++ char_limit = int(match.group(1))
++ msg_content = msg.GetRealContent()
++ if len(msg_content) > char_limit:
++ print('Warning: char-limit for %s is %d, but length is %d: %s' %
++ (grd_name, char_limit, len(msg_content), msg_content))
++ return message.MessageNode.Construct(parent=messages_node,
++ name=grd_name,
++ message=msg,
++ desc=description,
++ translateable=translatable)
++
++ def __CreateFileNode(self, translations_node, lang):
++ """Creates and initializes the <file> elements.
++
++ File elements provide information on the location of translation files
++ (xtbs)
++ """
++ xtb_file = os.path.normpath(os.path.join(
++ self.xtb_dir, '%s_%s.xtb' % (self.name, lang)))
++ fnode = node_io.FileNode()
++ fnode.StartParsing(u'file', translations_node)
++ fnode.HandleAttribute('path', xtb_file)
++ fnode.HandleAttribute('lang', lang)
++ fnode.EndParsing()
++ translations_node.AddChild(fnode)
++ return fnode
++
++ def __CreateCppHeaderOutputNode(self, outputs_node, header_dir):
++ """Creates the <output> element corresponding to the generated c header."""
++ header_file_name = os.path.join(header_dir, self.name + '.h')
++ header_node = node_io.OutputNode()
++ header_node.StartParsing(u'output', outputs_node)
++ header_node.HandleAttribute('filename', header_file_name)
++ header_node.HandleAttribute('type', 'rc_header')
++ emit_node = node_io.EmitNode()
++ emit_node.StartParsing(u'emit', header_node)
++ emit_node.HandleAttribute('emit_type', 'prepend')
++ emit_node.EndParsing()
++ header_node.AddChild(emit_node)
++ header_node.EndParsing()
++ outputs_node.AddChild(header_node)
++ return header_node
++
++ def __CreateRcOutputNode(self, outputs_node, lang, rc_dir):
++ """Creates the <output> element corresponding to various rc file output."""
++ rc_file_name = self.name + '_' + lang + ".rc"
++ rc_path = os.path.join(rc_dir, rc_file_name)
++ node = node_io.OutputNode()
++ node.StartParsing(u'output', outputs_node)
++ node.HandleAttribute('filename', rc_path)
++ node.HandleAttribute('lang', lang)
++ node.HandleAttribute('type', 'rc_all')
++ node.EndParsing()
++ outputs_node.AddChild(node)
++ return node
++
++ def __CreateAndroidXmlOutputNode(self, outputs_node, locale, xml_res_dir):
++ """Creates the <output> element corresponding to various rc file output."""
++ # Need to check to see if the locale has a region, e.g. the GB in en-GB.
++ # When a locale has a region Android expects the region to be prefixed
++ # with an 'r'. For example for en-GB Android expects a values-en-rGB
++ # directory. Also, Android expects nb, tl, in, iw, ji as the language
++ # codes for Norwegian, Tagalog/Filipino, Indonesian, Hebrew, and Yiddish:
++ # http://developer.android.com/reference/java/util/Locale.html
++ if locale == 'es-419':
++ android_locale = 'es-rUS'
++ else:
++ android_lang, dash, region = locale.partition('-')
++ lang_map = {'no': 'nb', 'fil': 'tl', 'id': 'in', 'he': 'iw', 'yi': 'ji'}
++ android_lang = lang_map.get(android_lang, android_lang)
++ android_locale = android_lang + ('-r' + region if region else '')
++ values = 'values-' + android_locale if android_locale != 'en' else 'values'
++ xml_path = os.path.normpath(os.path.join(
++ xml_res_dir, values, 'strings.xml'))
++
++ node = node_io.OutputNode()
++ node.StartParsing(u'output', outputs_node)
++ node.HandleAttribute('filename', xml_path)
++ node.HandleAttribute('lang', locale)
++ node.HandleAttribute('type', 'android')
++ node.EndParsing()
++ outputs_node.AddChild(node)
++ return node
++
++ def IsTranslatable(self, android_string):
++ """Determines if a <string> element is a candidate for translation.
++
++ A <string> element is by default translatable unless otherwise marked.
++ """
++ if android_string.hasAttribute('translatable'):
++ value = android_string.getAttribute('translatable').lower()
++ if value not in ('true', 'false'):
++ print('Warning: translatable attribute has invalid value: %s' % value)
++ return value == 'true'
++ else:
++ return True
+diff --git a/tools/grit/grit/tool/android2grd_unittest.py b/tools/grit/grit/tool/android2grd_unittest.py
+new file mode 100644
+index 0000000000..a6934a707c
+--- /dev/null
++++ b/tools/grit/grit/tool/android2grd_unittest.py
+@@ -0,0 +1,181 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
-+#
-+# Hacky, primitive testing: This runs the style plugin for a set of input files
-+# and compares the output with golden result files.
-+
-+E_BADARGS=65
-+E_FAILEDTEST=1
-+
-+failed_any_test=
-+
-+# Prints usage information.
-+usage() {
-+ echo "Usage: $(basename "${0}")" \
-+ "<Path to the llvm build dir, usually Release+Asserts>"
-+ echo ""
-+ echo " Runs all the libFindBadConstructs unit tests"
-+ echo ""
-+}
-+
-+# Runs a single test case.
-+do_testcase() {
-+ local output="$("${CLANG_DIR}"/bin/clang -c -Wno-c++11-extensions \
-+ -Xclang -load -Xclang "${CLANG_DIR}"/lib/libFindBadConstructs.${LIB} \
-+ -Xclang -plugin -Xclang find-bad-constructs ${1} 2>&1)"
-+ local diffout="$(echo "${output}" | diff - "${2}")"
-+ if [ "${diffout}" = "" ]; then
-+ echo "PASS: ${1}"
-+ else
-+ failed_any_test=yes
-+ echo "FAIL: ${1}"
-+ echo "Output of compiler:"
-+ echo "${output}"
-+ echo "Expected output:"
-+ cat "${2}"
-+ echo
-+ fi
-+}
-+
-+# Validate input to the script.
-+if [[ -z "${1}" ]]; then
-+ usage
-+ exit ${E_BADARGS}
-+elif [[ ! -d "${1}" ]]; then
-+ echo "${1} is not a directory."
-+ usage
-+ exit ${E_BADARGS}
-+else
-+ export CLANG_DIR="${PWD}/${1}"
-+ echo "Using clang directory ${CLANG_DIR}..."
-+
-+ # The golden files assume that the cwd is this directory. To make the script
-+ # work no matter what the cwd is, explicitly cd to there.
-+ cd "$(dirname "${0}")"
-+
-+ if [ "$(uname -s)" = "Linux" ]; then
-+ export LIB=so
-+ elif [ "$(uname -s)" = "Darwin" ]; then
-+ export LIB=dylib
-+ fi
-+fi
-+
-+for input in *.cpp; do
-+ do_testcase "${input}" "${input%cpp}txt"
-+done
-+
-+if [[ "${failed_any_test}" ]]; then
-+ exit ${E_FAILEDTEST}
-+fi
-diff --git a/tools/clang/plugins/tests/virtual_methods.cpp b/tools/clang/plugins/tests/virtual_methods.cpp
-new file mode 100644
-index 0000000000..a07cbe4875
---- /dev/null
-+++ b/tools/clang/plugins/tests/virtual_methods.cpp
-@@ -0,0 +1,36 @@
-+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+// Use of this source code is governed by a BSD-style license that can be
-+// found in the LICENSE file.
-+
-+#include "virtual_methods.h"
-+
-+// Shouldn't warn about method usage in the implementation file.
-+class VirtualMethodsInImplementation {
-+ public:
-+ virtual void MethodIsAbstract() = 0;
-+ virtual void MethodHasNoArguments();
-+ virtual void MethodHasEmptyDefaultImpl() {}
-+ virtual bool ComplainAboutThis() { return true; }
-+};
+
-+// Stubs to fill in the abstract method
-+class ConcreteVirtualMethodsInHeaders : public VirtualMethodsInHeaders {
-+ public:
-+ virtual void MethodIsAbstract() override {}
-+};
++'''Unit tests for grit.tool.android2grd'''
+
-+class ConcreteVirtualMethodsInImplementation
-+ : public VirtualMethodsInImplementation {
-+ public:
-+ virtual void MethodIsAbstract() override {}
-+};
++from __future__ import print_function
++
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++import unittest
++import xml.dom.minidom
++
++from grit import util
++from grit.node import empty
++from grit.node import message
++from grit.node import misc
++from grit.node import node_io
++from grit.tool import android2grd
++
++
++class Android2GrdUnittest(unittest.TestCase):
++
++ def __Parse(self, xml_string):
++ return xml.dom.minidom.parseString(xml_string).childNodes[0]
++
++ def testCreateTclibMessage(self):
++ tool = android2grd.Android2Grd()
++ msg = tool.CreateTclibMessage(self.__Parse(r'''
++ <string name="simple">A simple string</string>'''))
++ self.assertEqual(msg.GetRealContent(), 'A simple string')
++ msg = tool.CreateTclibMessage(self.__Parse(r'''
++ <string name="outer_whitespace">
++ Strip leading/trailing whitespace
++ </string>'''))
++ self.assertEqual(msg.GetRealContent(), 'Strip leading/trailing whitespace')
++ msg = tool.CreateTclibMessage(self.__Parse(r'''
++ <string name="inner_whitespace">Fold multiple spaces</string>'''))
++ self.assertEqual(msg.GetRealContent(), 'Fold multiple spaces')
++ msg = tool.CreateTclibMessage(self.__Parse(r'''
++ <string name="escaped_spaces">Retain \n escaped\t spaces</string>'''))
++ self.assertEqual(msg.GetRealContent(), 'Retain \n escaped\t spaces')
++ msg = tool.CreateTclibMessage(self.__Parse(r'''
++ <string name="quotes"> " Quotes preserve
++ whitespace" but only for "enclosed elements "
++ </string>'''))
++ self.assertEqual(msg.GetRealContent(), ''' Quotes preserve
++ whitespace but only for enclosed elements ''')
++ msg = tool.CreateTclibMessage(self.__Parse(
++ r'''<string name="escaped_characters">Escaped characters: \"\'\\\t\n'''
++ '</string>'))
++ self.assertEqual(msg.GetRealContent(), '''Escaped characters: "'\\\t\n''')
++ msg = tool.CreateTclibMessage(self.__Parse(
++ '<string xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" '
++ 'name="placeholders">'
++ 'Open <xliff:g id="FILENAME" example="internet.html">%s</xliff:g>?'
++ '</string>'))
++ self.assertEqual(msg.GetRealContent(), 'Open %s?')
++ self.assertEqual(len(msg.GetPlaceholders()), 1)
++ self.assertEqual(msg.GetPlaceholders()[0].presentation, 'FILENAME')
++ self.assertEqual(msg.GetPlaceholders()[0].original, '%s')
++ self.assertEqual(msg.GetPlaceholders()[0].example, 'internet.html')
++ msg = tool.CreateTclibMessage(self.__Parse(r'''
++ <string name="comment">Contains a <!-- ignore this --> comment
++ </string>'''))
++ self.assertEqual(msg.GetRealContent(), 'Contains a comment')
++
++ def testIsTranslatable(self):
++ tool = android2grd.Android2Grd()
++ string_el = self.__Parse('<string>Hi</string>')
++ self.assertTrue(tool.IsTranslatable(string_el))
++ string_el = self.__Parse(
++ '<string translatable="true">Hi</string>')
++ self.assertTrue(tool.IsTranslatable(string_el))
++ string_el = self.__Parse(
++ '<string translatable="false">Hi</string>')
++ self.assertFalse(tool.IsTranslatable(string_el))
++
++ def __ParseAndroidXml(self, options = []):
++ tool = android2grd.Android2Grd()
++
++ tool.ParseOptions(options)
++
++ android_path = util.PathFromRoot('grit/testdata/android.xml')
++ with open(android_path) as android_file:
++ android_dom = xml.dom.minidom.parse(android_file)
++
++ grd = tool.AndroidDomToGrdDom(android_dom)
++ self.assertTrue(isinstance(grd, misc.GritNode))
++
++ return grd
++
++ def testAndroidDomToGrdDom(self):
++ grd = self.__ParseAndroidXml(['--languages', 'en-US,en-GB,ru'])
++
++ # Check that the structure of the GritNode is as expected.
++ messages = grd.GetChildrenOfType(message.MessageNode)
++ translations = grd.GetChildrenOfType(empty.TranslationsNode)
++ files = grd.GetChildrenOfType(node_io.FileNode)
++
++ self.assertEqual(len(translations), 1)
++ self.assertEqual(len(files), 3)
++ self.assertEqual(len(messages), 5)
++
++ # Check that a message node is constructed correctly.
++ msg = [x for x in messages if x.GetTextualIds()[0] == 'IDS_PLACEHOLDERS']
++ self.assertTrue(msg)
++ msg = msg[0]
++
++ self.assertTrue(msg.IsTranslateable())
++ self.assertEqual(msg.attrs["desc"], "A string with placeholder.")
++
++ def testTranslatableAttribute(self):
++ grd = self.__ParseAndroidXml([])
++ messages = grd.GetChildrenOfType(message.MessageNode)
++ msgs = [x for x in messages if x.GetTextualIds()[0] == 'IDS_CONSTANT']
++ self.assertTrue(msgs)
++ self.assertFalse(msgs[0].IsTranslateable())
++
++ def testTranslations(self):
++ grd = self.__ParseAndroidXml(['--languages', 'en-US,en-GB,ru,id'])
++
++ files = grd.GetChildrenOfType(node_io.FileNode)
++ us_file = [x for x in files if x.attrs['lang'] == 'en-US']
++ self.assertTrue(us_file)
++ self.assertEqual(us_file[0].GetInputPath(),
++ 'chrome_android_strings_en-US.xtb')
++
++ id_file = [x for x in files if x.attrs['lang'] == 'id']
++ self.assertTrue(id_file)
++ self.assertEqual(id_file[0].GetInputPath(),
++ 'chrome_android_strings_id.xtb')
++
++ def testOutputs(self):
++ grd = self.__ParseAndroidXml(['--languages', 'en-US,ru,id',
++ '--rc-dir', 'rc/dir',
++ '--header-dir', 'header/dir',
++ '--xtb-dir', 'xtb/dir',
++ '--xml-dir', 'xml/dir'])
++
++ outputs = grd.GetChildrenOfType(node_io.OutputNode)
++ self.assertEqual(len(outputs), 7)
++
++ header_outputs = [x for x in outputs if x.GetType() == 'rc_header']
++ rc_outputs = [x for x in outputs if x.GetType() == 'rc_all']
++ xml_outputs = [x for x in outputs if x.GetType() == 'android']
++
++ self.assertEqual(len(header_outputs), 1)
++ self.assertEqual(len(rc_outputs), 3)
++ self.assertEqual(len(xml_outputs), 3)
++
++ # The header node should have an "<emit>" child and the proper filename.
++ self.assertTrue(header_outputs[0].GetChildrenOfType(node_io.EmitNode))
++ self.assertEqual(util.normpath(header_outputs[0].GetFilename()),
++ util.normpath('header/dir/chrome_android_strings.h'))
++
++ id_rc = [x for x in rc_outputs if x.GetLanguage() == 'id']
++ id_xml = [x for x in xml_outputs if x.GetLanguage() == 'id']
++ self.assertTrue(id_rc)
++ self.assertTrue(id_xml)
++ self.assertEqual(util.normpath(id_rc[0].GetFilename()),
++ util.normpath('rc/dir/chrome_android_strings_id.rc'))
++ self.assertEqual(util.normpath(id_xml[0].GetFilename()),
++ util.normpath('xml/dir/values-in/strings.xml'))
++
++ us_rc = [x for x in rc_outputs if x.GetLanguage() == 'en-US']
++ us_xml = [x for x in xml_outputs if x.GetLanguage() == 'en-US']
++ self.assertTrue(us_rc)
++ self.assertTrue(us_xml)
++ self.assertEqual(util.normpath(us_rc[0].GetFilename()),
++ util.normpath('rc/dir/chrome_android_strings_en-US.rc'))
++ self.assertEqual(util.normpath(us_xml[0].GetFilename()),
++ util.normpath('xml/dir/values-en-rUS/strings.xml'))
++
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/tool/build.py b/tools/grit/grit/tool/build.py
+new file mode 100644
+index 0000000000..204592bf0d
+--- /dev/null
++++ b/tools/grit/grit/tool/build.py
+@@ -0,0 +1,556 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''The 'grit build' tool.
++'''
++
++from __future__ import print_function
+
-+// Fill in the implementations
-+void VirtualMethodsInHeaders::MethodHasNoArguments() {}
-+void WarnOnMissingVirtual::MethodHasNoArguments() {}
-+void VirtualMethodsInImplementation::MethodHasNoArguments() {}
++import codecs
++import filecmp
++import getopt
++import gzip
++import os
++import shutil
++import sys
+
-+int main() {
-+ ConcreteVirtualMethodsInHeaders one;
-+ ConcreteVirtualMethodsInImplementation two;
++import six
++
++from grit import grd_reader
++from grit import shortcuts
++from grit import util
++from grit.format import minifier
++from grit.node import brotli_util
++from grit.node import include
++from grit.node import message
++from grit.node import structure
++from grit.tool import interface
++
++
++# It would be cleaner to have each module register itself, but that would
++# require importing all of them on every run of GRIT.
++'''Map from <output> node types to modules under grit.format.'''
++_format_modules = {
++ 'android': 'android_xml',
++ 'c_format': 'c_format',
++ 'chrome_messages_json': 'chrome_messages_json',
++ 'chrome_messages_json_gzip': 'chrome_messages_json',
++ 'data_package': 'data_pack',
++ 'policy_templates': 'policy_templates_json',
++ 'rc_all': 'rc',
++ 'rc_header': 'rc_header',
++ 'rc_nontranslateable': 'rc',
++ 'rc_translateable': 'rc',
++ 'resource_file_map_source': 'resource_map',
++ 'resource_map_header': 'resource_map',
++ 'resource_map_source': 'resource_map',
+}
-diff --git a/tools/clang/plugins/tests/virtual_methods.h b/tools/clang/plugins/tests/virtual_methods.h
++
++def GetFormatter(type):
++ modulename = 'grit.format.' + _format_modules[type]
++ __import__(modulename)
++ module = sys.modules[modulename]
++ try:
++ return module.Format
++ except AttributeError:
++ return module.GetFormatter(type)
++
++
++class RcBuilder(interface.Tool):
++ '''A tool that builds RC files and resource header files for compilation.
++
++Usage: grit build [-o OUTPUTDIR] [-D NAME[=VAL]]*
++
++All output options for this tool are specified in the input file (see
++'grit help' for details on how to specify the input file - it is a global
++option).
++
++Options:
++
++ -a FILE Assert that the given file is an output. There can be
++ multiple "-a" flags listed for multiple outputs. If a "-a"
++ or "--assert-file-list" argument is present, then the list
++ of asserted files must match the output files or the tool
++ will fail. The use-case is for the build system to maintain
++ separate lists of output files and to catch errors if the
++ build system's list and the grit list are out-of-sync.
++
++ --assert-file-list Provide a file listing multiple asserted output files.
++ There is one file name per line. This acts like specifying
++ each file with "-a" on the command line, but without the
++ possibility of running into OS line-length limits for very
++ long lists.
++
++ -o OUTPUTDIR Specify what directory output paths are relative to.
++ Defaults to the current directory.
++
++ -p FILE Specify a file containing a pre-determined mapping from
++ resource names to resource ids which will be used to assign
++ resource ids to those resources. Resources not found in this
++ file will be assigned ids normally. The motivation is to run
++ your app's startup and have it dump the resources it loads,
++ and then pass these via this flag. This will pack startup
++ resources together, thus reducing paging while all other
++ resources are unperturbed. The file should have the format:
++ RESOURCE_ONE_NAME 123
++ RESOURCE_TWO_NAME 124
++
++ -D NAME[=VAL] Specify a C-preprocessor-like define NAME with optional
++ value VAL (defaults to 1) which will be used to control
++ conditional inclusion of resources.
++
++ -E NAME=VALUE Set environment variable NAME to VALUE (within grit).
++
++ -f FIRSTIDSFILE Path to a python file that specifies the first id of
++ value to use for resources. A non-empty value here will
++ override the value specified in the <grit> node's
++ first_ids_file.
++
++ -w WHITELISTFILE Path to a file containing the string names of the
++ resources to include. Anything not listed is dropped.
++
++ -t PLATFORM Specifies the platform the build is targeting; defaults
++ to the value of sys.platform. The value provided via this
++ flag should match what sys.platform would report for your
++ target platform; see grit.node.base.EvaluateCondition.
++
++ --whitelist-support
++ Generate code to support extracting a resource whitelist
++ from executables.
++
++ --write-only-new flag
++ If flag is non-0, write output files to a temporary file
++ first, and copy it to the real output only if the new file
++ is different from the old file. This allows some build
++ systems to realize that dependent build steps might be
++ unnecessary, at the cost of comparing the output data at
++ grit time.
++
++ --depend-on-stamp
++ If specified along with --depfile and --depdir, the depfile
++ generated will depend on a stampfile instead of the first
++ output in the input .grd file.
++
++ --js-minifier A command to run the Javascript minifier. If not set then
++ Javascript won't be minified. The command should read the
++ original Javascript from standard input, and output the
++ minified Javascript to standard output. A non-zero exit
++ status will be taken as indicating failure.
++
++ --css-minifier A command to run the CSS minifier. If not set then CSS won't
++ be minified. The command should read the original CSS from
++ standard input, and output the minified CSS to standard
++ output. A non-zero exit status will be taken as indicating
++ failure.
++
++ --brotli The full path to the brotli executable generated by
++ third_party/brotli/BUILD.gn, required if any entries use
++ compress="brotli".
++
++Conditional inclusion of resources only affects the output of files which
++control which resources get linked into a binary, e.g. it affects .rc files
++meant for compilation but it does not affect resource header files (that define
++IDs). This helps ensure that values of IDs stay the same, that all messages
++are exported to translation interchange files (e.g. XMB files), etc.
++'''
++
++ def ShortDescription(self):
++ return 'A tool that builds RC files for compilation.'
++
++ def Run(self, opts, args):
++ brotli_util.SetBrotliCommand(None)
++ os.environ['cwd'] = os.getcwd()
++ self.output_directory = '.'
++ first_ids_file = None
++ predetermined_ids_file = None
++ whitelist_filenames = []
++ assert_output_files = []
++ target_platform = None
++ depfile = None
++ depdir = None
++ whitelist_support = False
++ write_only_new = False
++ depend_on_stamp = False
++ js_minifier = None
++ css_minifier = None
++ replace_ellipsis = True
++ (own_opts, args) = getopt.getopt(
++ args, 'a:p:o:D:E:f:w:t:',
++ ('depdir=', 'depfile=', 'assert-file-list=', 'help',
++ 'output-all-resource-defines', 'no-output-all-resource-defines',
++ 'no-replace-ellipsis', 'depend-on-stamp', 'js-minifier=',
++ 'css-minifier=', 'write-only-new=', 'whitelist-support', 'brotli='))
++ for (key, val) in own_opts:
++ if key == '-a':
++ assert_output_files.append(val)
++ elif key == '--assert-file-list':
++ with open(val) as f:
++ assert_output_files += f.read().splitlines()
++ elif key == '-o':
++ self.output_directory = val
++ elif key == '-D':
++ name, val = util.ParseDefine(val)
++ self.defines[name] = val
++ elif key == '-E':
++ (env_name, env_value) = val.split('=', 1)
++ os.environ[env_name] = env_value
++ elif key == '-f':
++ # TODO(joi@chromium.org): Remove this override once change
++ # lands in WebKit.grd to specify the first_ids_file in the
++ # .grd itself.
++ first_ids_file = val
++ elif key == '-w':
++ whitelist_filenames.append(val)
++ elif key == '--no-replace-ellipsis':
++ replace_ellipsis = False
++ elif key == '-p':
++ predetermined_ids_file = val
++ elif key == '-t':
++ target_platform = val
++ elif key == '--depdir':
++ depdir = val
++ elif key == '--depfile':
++ depfile = val
++ elif key == '--write-only-new':
++ write_only_new = val != '0'
++ elif key == '--depend-on-stamp':
++ depend_on_stamp = True
++ elif key == '--js-minifier':
++ js_minifier = val
++ elif key == '--css-minifier':
++ css_minifier = val
++ elif key == '--whitelist-support':
++ whitelist_support = True
++ elif key == '--brotli':
++ brotli_util.SetBrotliCommand([os.path.abspath(val)])
++ elif key == '--help':
++ self.ShowUsage()
++ sys.exit(0)
++
++ if len(args):
++ print('This tool takes no tool-specific arguments.')
++ return 2
++ self.SetOptions(opts)
++ self.VerboseOut('Output directory: %s (absolute path: %s)\n' %
++ (self.output_directory,
++ os.path.abspath(self.output_directory)))
++
++ if whitelist_filenames:
++ self.whitelist_names = set()
++ for whitelist_filename in whitelist_filenames:
++ self.VerboseOut('Using whitelist: %s\n' % whitelist_filename);
++ whitelist_contents = util.ReadFile(whitelist_filename, 'utf-8')
++ self.whitelist_names.update(whitelist_contents.strip().split('\n'))
++
++ if js_minifier:
++ minifier.SetJsMinifier(js_minifier)
++
++ if css_minifier:
++ minifier.SetCssMinifier(css_minifier)
++
++ self.write_only_new = write_only_new
++
++ self.res = grd_reader.Parse(opts.input,
++ debug=opts.extra_verbose,
++ first_ids_file=first_ids_file,
++ predetermined_ids_file=predetermined_ids_file,
++ defines=self.defines,
++ target_platform=target_platform)
++
++ # Set an output context so that conditionals can use defines during the
++ # gathering stage; we use a dummy language here since we are not outputting
++ # a specific language.
++ self.res.SetOutputLanguage('en')
++ self.res.SetWhitelistSupportEnabled(whitelist_support)
++ self.res.RunGatherers()
++
++ # Replace ... with the single-character version. http://crbug.com/621772
++ if replace_ellipsis:
++ for node in self.res:
++ if isinstance(node, message.MessageNode):
++ node.SetReplaceEllipsis(True)
++
++ self.Process()
++
++ if assert_output_files:
++ if not self.CheckAssertedOutputFiles(assert_output_files):
++ return 2
++
++ if depfile and depdir:
++ self.GenerateDepfile(depfile, depdir, first_ids_file, depend_on_stamp)
++
++ return 0
++
++ def __init__(self, defines=None):
++ # Default file-creation function is codecs.open(). Only done to allow
++ # overriding by unit test.
++ self.fo_create = codecs.open
++
++ # key/value pairs of C-preprocessor like defines that are used for
++ # conditional output of resources
++ self.defines = defines or {}
++
++ # self.res is a fully-populated resource tree if Run()
++ # has been called, otherwise None.
++ self.res = None
++
++ # The set of names that are whitelisted to actually be included in the
++ # output.
++ self.whitelist_names = None
++
++ # Whether to compare outputs to their old contents before writing.
++ self.write_only_new = False
++
++ @staticmethod
++ def AddWhitelistTags(start_node, whitelist_names):
++ # Walk the tree of nodes added attributes for the nodes that shouldn't
++ # be written into the target files (skip markers).
++ for node in start_node:
++ # Same trick data_pack.py uses to see what nodes actually result in
++ # real items.
++ if (isinstance(node, include.IncludeNode) or
++ isinstance(node, message.MessageNode) or
++ isinstance(node, structure.StructureNode)):
++ text_ids = node.GetTextualIds()
++ # Mark the item to be skipped if it wasn't in the whitelist.
++ if text_ids and text_ids[0] not in whitelist_names:
++ node.SetWhitelistMarkedAsSkip(True)
++
++ @staticmethod
++ def ProcessNode(node, output_node, outfile):
++ '''Processes a node in-order, calling its formatter before and after
++ recursing to its children.
++
++ Args:
++ node: grit.node.base.Node subclass
++ output_node: grit.node.io.OutputNode
++ outfile: open filehandle
++ '''
++ base_dir = util.dirname(output_node.GetOutputFilename())
++
++ formatter = GetFormatter(output_node.GetType())
++ formatted = formatter(node, output_node.GetLanguage(), output_dir=base_dir)
++ # NB: Formatters may be generators or return lists. The writelines API
++ # accepts iterables as a shortcut to calling write directly. That means
++ # you can pass strings (iteration yields characters), but not bytes (as
++ # iteration yields integers). Python 2 worked due to its quirks with
++ # bytes/string implementation, but Python 3 fails. It's also a bit more
++ # inefficient to call write once per character/byte. Handle all of this
++ # ourselves by calling write directly on strings/bytes before falling back
++ # to writelines.
++ if isinstance(formatted, (six.string_types, six.binary_type)):
++ outfile.write(formatted)
++ else:
++ outfile.writelines(formatted)
++ if output_node.GetType() == 'data_package':
++ with open(output_node.GetOutputFilename() + '.info', 'w') as infofile:
++ if node.info:
++ # We terminate with a newline so that when these files are
++ # concatenated later we consistently terminate with a newline so
++ # consumers can account for terminating newlines.
++ infofile.writelines(['\n'.join(node.info), '\n'])
++
++ @staticmethod
++ def _EncodingForOutputType(output_type):
++ # Microsoft's RC compiler can only deal with single-byte or double-byte
++ # files (no UTF-8), so we make all RC files UTF-16 to support all
++ # character sets.
++ if output_type in ('rc_header', 'resource_file_map_source',
++ 'resource_map_header', 'resource_map_source'):
++ return 'cp1252'
++ if output_type in ('android', 'c_format', 'plist', 'plist_strings', 'doc',
++ 'json', 'android_policy', 'chrome_messages_json',
++ 'chrome_messages_json_gzip', 'policy_templates'):
++ return 'utf_8'
++ # TODO(gfeher) modify here to set utf-8 encoding for admx/adml
++ return 'utf_16'
++
++ def Process(self):
++ for output in self.res.GetOutputFiles():
++ output.output_filename = os.path.abspath(os.path.join(
++ self.output_directory, output.GetOutputFilename()))
++
++ # If there are whitelisted names, tag the tree once up front, this way
++ # while looping through the actual output, it is just an attribute check.
++ if self.whitelist_names:
++ self.AddWhitelistTags(self.res, self.whitelist_names)
++
++ for output in self.res.GetOutputFiles():
++ self.VerboseOut('Creating %s...' % output.GetOutputFilename())
++
++ # Set the context, for conditional inclusion of resources
++ self.res.SetOutputLanguage(output.GetLanguage())
++ self.res.SetOutputContext(output.GetContext())
++ self.res.SetFallbackToDefaultLayout(output.GetFallbackToDefaultLayout())
++ self.res.SetDefines(self.defines)
++
++ # Assign IDs only once to ensure that all outputs use the same IDs.
++ if self.res.GetIdMap() is None:
++ self.res.InitializeIds()
++
++ # Make the output directory if it doesn't exist.
++ self.MakeDirectoriesTo(output.GetOutputFilename())
++
++ # Write the results to a temporary file and only overwrite the original
++ # if the file changed. This avoids unnecessary rebuilds.
++ out_filename = output.GetOutputFilename()
++ tmp_filename = out_filename + '.tmp'
++ tmpfile = self.fo_create(tmp_filename, 'wb')
++
++ output_type = output.GetType()
++ if output_type != 'data_package':
++ encoding = self._EncodingForOutputType(output_type)
++ tmpfile = util.WrapOutputStream(tmpfile, encoding)
++
++ # Iterate in-order through entire resource tree, calling formatters on
++ # the entry into a node and on exit out of it.
++ with tmpfile:
++ self.ProcessNode(self.res, output, tmpfile)
++
++ if output_type == 'chrome_messages_json_gzip':
++ gz_filename = tmp_filename + '.gz'
++ with open(tmp_filename, 'rb') as tmpfile, open(gz_filename, 'wb') as f:
++ with gzip.GzipFile(filename='', mode='wb', fileobj=f, mtime=0) as fgz:
++ shutil.copyfileobj(tmpfile, fgz)
++ os.remove(tmp_filename)
++ tmp_filename = gz_filename
++
++ # Now copy from the temp file back to the real output, but on Windows,
++ # only if the real output doesn't exist or the contents of the file
++ # changed. This prevents identical headers from being written and .cc
++ # files from recompiling (which is painful on Windows).
++ if not os.path.exists(out_filename):
++ os.rename(tmp_filename, out_filename)
++ else:
++ # CHROMIUM SPECIFIC CHANGE.
++ # This clashes with gyp + vstudio, which expect the output timestamp
++ # to change on a rebuild, even if nothing has changed, so only do
++ # it when opted in.
++ if not self.write_only_new:
++ write_file = True
++ else:
++ files_match = filecmp.cmp(out_filename, tmp_filename)
++ write_file = not files_match
++ if write_file:
++ shutil.copy2(tmp_filename, out_filename)
++ os.remove(tmp_filename)
++
++ self.VerboseOut(' done.\n')
++
++ # Print warnings if there are any duplicate shortcuts.
++ warnings = shortcuts.GenerateDuplicateShortcutsWarnings(
++ self.res.UberClique(), self.res.GetTcProject())
++ if warnings:
++ print('\n'.join(warnings))
++
++ # Print out any fallback warnings, and missing translation errors, and
++ # exit with an error code if there are missing translations in a non-pseudo
++ # and non-official build.
++ warnings = (self.res.UberClique().MissingTranslationsReport().
++ encode('ascii', 'replace'))
++ if warnings:
++ self.VerboseOut(warnings)
++ if self.res.UberClique().HasMissingTranslations():
++ print(self.res.UberClique().missing_translations_)
++ sys.exit(-1)
++
++
++ def CheckAssertedOutputFiles(self, assert_output_files):
++ '''Checks that the asserted output files are specified in the given list.
++
++ Returns true if the asserted files are present. If they are not, returns
++ False and prints the failure.
++ '''
++ # Compare the absolute path names, sorted.
++ asserted = sorted([os.path.abspath(i) for i in assert_output_files])
++ actual = sorted([
++ os.path.abspath(os.path.join(self.output_directory,
++ i.GetOutputFilename()))
++ for i in self.res.GetOutputFiles()])
++
++ if asserted != actual:
++ missing = list(set(asserted) - set(actual))
++ extra = list(set(actual) - set(asserted))
++ error = '''Asserted file list does not match.
++
++Expected output files:
++%s
++Actual output files:
++%s
++Missing output files:
++%s
++Extra output files:
++%s
++'''
++ print(error % ('\n'.join(asserted), '\n'.join(actual), '\n'.join(missing),
++ ' \n'.join(extra)))
++ return False
++ return True
++
++
++ def GenerateDepfile(self, depfile, depdir, first_ids_file, depend_on_stamp):
++ '''Generate a depfile that contains the imlicit dependencies of the input
++ grd. The depfile will be in the same format as a makefile, and will contain
++ references to files relative to |depdir|. It will be put in |depfile|.
++
++ For example, supposing we have three files in a directory src/
++
++ src/
++ blah.grd <- depends on input{1,2}.xtb
++ input1.xtb
++ input2.xtb
++
++ and we run
++
++ grit -i blah.grd -o ../out/gen \
++ --depdir ../out \
++ --depfile ../out/gen/blah.rd.d
++
++ from the directory src/ we will generate a depfile ../out/gen/blah.grd.d
++ that has the contents
++
++ gen/blah.h: ../src/input1.xtb ../src/input2.xtb
++
++ Where "gen/blah.h" is the first output (Ninja expects the .d file to list
++ the first output in cases where there is more than one). If the flag
++ --depend-on-stamp is specified, "gen/blah.rd.d.stamp" will be used that is
++ 'touched' whenever a new depfile is generated.
++
++ Note that all paths in the depfile are relative to ../out, the depdir.
++ '''
++ depfile = os.path.abspath(depfile)
++ depdir = os.path.abspath(depdir)
++ infiles = self.res.GetInputFiles()
++
++ # We want to trigger a rebuild if the first ids change.
++ if first_ids_file is not None:
++ infiles.append(first_ids_file)
++
++ if (depend_on_stamp):
++ output_file = depfile + ".stamp"
++ # Touch the stamp file before generating the depfile.
++ with open(output_file, 'a'):
++ os.utime(output_file, None)
++ else:
++ # Get the first output file relative to the depdir.
++ outputs = self.res.GetOutputFiles()
++ output_file = os.path.join(self.output_directory,
++ outputs[0].GetOutputFilename())
++
++ output_file = os.path.relpath(output_file, depdir)
++ # The path prefix to prepend to dependencies in the depfile.
++ prefix = os.path.relpath(os.getcwd(), depdir)
++ deps_text = ' '.join([os.path.join(prefix, i) for i in infiles])
++
++ depfile_contents = output_file + ': ' + deps_text
++ self.MakeDirectoriesTo(depfile)
++ outfile = self.fo_create(depfile, 'w', encoding='utf-8')
++ outfile.write(depfile_contents)
++
++ @staticmethod
++ def MakeDirectoriesTo(file):
++ '''Creates directories necessary to contain |file|.'''
++ dir = os.path.split(file)[0]
++ if not os.path.exists(dir):
++ os.makedirs(dir)
+diff --git a/tools/grit/grit/tool/build_unittest.py b/tools/grit/grit/tool/build_unittest.py
new file mode 100644
-index 0000000000..d9fbf96ed3
+index 0000000000..c4a2f2752b
--- /dev/null
-+++ b/tools/clang/plugins/tests/virtual_methods.h
-@@ -0,0 +1,39 @@
-+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-+// Use of this source code is governed by a BSD-style license that can be
-+// found in the LICENSE file.
-+
-+#ifndef VIRTUAL_METHODS_H_
-+#define VIRTUAL_METHODS_H_
-+
-+// Should warn about virtual method usage.
-+class VirtualMethodsInHeaders {
-+ public:
-+ // Don't complain about these.
-+ virtual void MethodIsAbstract() = 0;
-+ virtual void MethodHasNoArguments();
-+ virtual void MethodHasEmptyDefaultImpl() {}
-+
-+ // But complain about this:
-+ virtual bool ComplainAboutThis() { return true; }
-+};
++++ b/tools/grit/grit/tool/build_unittest.py
+@@ -0,0 +1,341 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
+
-+// Complain on missing 'virtual' keyword in overrides.
-+class WarnOnMissingVirtual : public VirtualMethodsInHeaders {
-+ public:
-+ void MethodHasNoArguments() override;
-+};
++'''Unit tests for the 'grit build' tool.
++'''
+
-+// Don't complain about things in a 'testing' namespace.
-+namespace testing {
-+struct TestStruct {};
-+} // namespace testing
-+
-+class VirtualMethodsInHeadersTesting : public VirtualMethodsInHeaders {
-+ public:
-+ // Don't complain about no virtual testing methods.
-+ void MethodHasNoArguments();
-+ private:
-+ testing::TestStruct tester_;
-+};
++from __future__ import print_function
++
++import codecs
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
+
-+#endif // VIRTUAL_METHODS_H_
-diff --git a/tools/clang/plugins/tests/virtual_methods.txt b/tools/clang/plugins/tests/virtual_methods.txt
++import unittest
++
++from grit import util
++from grit.tool import build
++
++
++class BuildUnittest(unittest.TestCase):
++
++ # IDs should not change based on whitelisting.
++ # Android WebView currently relies on this.
++ EXPECTED_ID_MAP = {
++ 'IDS_MESSAGE_WHITELISTED': 6889,
++ 'IDR_STRUCTURE_WHITELISTED': 11546,
++ 'IDR_STRUCTURE_IN_TRUE_IF_WHITELISTED': 11548,
++ 'IDR_INCLUDE_WHITELISTED': 15601,
++ }
++
++ def testFindTranslationsWithSubstitutions(self):
++ # This is a regression test; we had a bug where GRIT would fail to find
++ # messages with substitutions e.g. "Hello [IDS_USER]" where IDS_USER is
++ # another <message>.
++ output_dir = util.TempDir({})
++ builder = build.RcBuilder()
++ class DummyOpts(object):
++ def __init__(self):
++ self.input = util.PathFromRoot('grit/testdata/substitute.grd')
++ self.verbose = False
++ self.extra_verbose = False
++ builder.Run(DummyOpts(), ['-o', output_dir.GetPath()])
++ output_dir.CleanUp()
++
++ def testGenerateDepFile(self):
++ output_dir = util.TempDir({})
++ builder = build.RcBuilder()
++ class DummyOpts(object):
++ def __init__(self):
++ self.input = util.PathFromRoot('grit/testdata/depfile.grd')
++ self.verbose = False
++ self.extra_verbose = False
++ expected_dep_file = output_dir.GetPath('substitute.grd.d')
++ builder.Run(DummyOpts(), ['-o', output_dir.GetPath(),
++ '--depdir', output_dir.GetPath(),
++ '--depfile', expected_dep_file])
++
++ self.failUnless(os.path.isfile(expected_dep_file))
++ with open(expected_dep_file) as f:
++ line = f.readline()
++ (dep_output_file, deps_string) = line.split(': ')
++ deps = deps_string.split(' ')
++
++ self.failUnlessEqual("default_100_percent.pak", dep_output_file)
++ self.failUnlessEqual(deps, [
++ util.PathFromRoot('grit/testdata/default_100_percent/a.png'),
++ util.PathFromRoot('grit/testdata/grit_part.grdp'),
++ util.PathFromRoot('grit/testdata/special_100_percent/a.png'),
++ ])
++ output_dir.CleanUp()
++
++ def testGenerateDepFileWithResourceIds(self):
++ output_dir = util.TempDir({})
++ builder = build.RcBuilder()
++ class DummyOpts(object):
++ def __init__(self):
++ self.input = util.PathFromRoot('grit/testdata/substitute_no_ids.grd')
++ self.verbose = False
++ self.extra_verbose = False
++ expected_dep_file = output_dir.GetPath('substitute_no_ids.grd.d')
++ builder.Run(DummyOpts(),
++ ['-f', util.PathFromRoot('grit/testdata/resource_ids'),
++ '-o', output_dir.GetPath(),
++ '--depdir', output_dir.GetPath(),
++ '--depfile', expected_dep_file])
++
++ self.failUnless(os.path.isfile(expected_dep_file))
++ with open(expected_dep_file) as f:
++ line = f.readline()
++ (dep_output_file, deps_string) = line.split(': ')
++ deps = deps_string.split(' ')
++
++ self.failUnlessEqual("resource.h", dep_output_file)
++ self.failUnlessEqual(2, len(deps))
++ self.failUnlessEqual(deps[0],
++ util.PathFromRoot('grit/testdata/substitute.xmb'))
++ self.failUnlessEqual(deps[1],
++ util.PathFromRoot('grit/testdata/resource_ids'))
++ output_dir.CleanUp()
++
++ def testAssertOutputs(self):
++ output_dir = util.TempDir({})
++ class DummyOpts(object):
++ def __init__(self):
++ self.input = util.PathFromRoot('grit/testdata/substitute.grd')
++ self.verbose = False
++ self.extra_verbose = False
++
++ # Incomplete output file list should fail.
++ builder_fail = build.RcBuilder()
++ self.failUnlessEqual(2,
++ builder_fail.Run(DummyOpts(), [
++ '-o', output_dir.GetPath(),
++ '-a', os.path.abspath(
++ output_dir.GetPath('en_generated_resources.rc'))]))
++
++ # Complete output file list should succeed.
++ builder_ok = build.RcBuilder()
++ self.failUnlessEqual(0,
++ builder_ok.Run(DummyOpts(), [
++ '-o', output_dir.GetPath(),
++ '-a', os.path.abspath(
++ output_dir.GetPath('en_generated_resources.rc')),
++ '-a', os.path.abspath(
++ output_dir.GetPath('sv_generated_resources.rc')),
++ '-a', os.path.abspath(output_dir.GetPath('resource.h'))]))
++ output_dir.CleanUp()
++
++ def testAssertTemplateOutputs(self):
++ output_dir = util.TempDir({})
++ class DummyOpts(object):
++ def __init__(self):
++ self.input = util.PathFromRoot('grit/testdata/substitute_tmpl.grd')
++ self.verbose = False
++ self.extra_verbose = False
++
++ # Incomplete output file list should fail.
++ builder_fail = build.RcBuilder()
++ self.failUnlessEqual(2,
++ builder_fail.Run(DummyOpts(), [
++ '-o', output_dir.GetPath(),
++ '-E', 'name=foo',
++ '-a', os.path.abspath(output_dir.GetPath('en_foo_resources.rc'))]))
++
++ # Complete output file list should succeed.
++ builder_ok = build.RcBuilder()
++ self.failUnlessEqual(0,
++ builder_ok.Run(DummyOpts(), [
++ '-o', output_dir.GetPath(),
++ '-E', 'name=foo',
++ '-a', os.path.abspath(output_dir.GetPath('en_foo_resources.rc')),
++ '-a', os.path.abspath(output_dir.GetPath('sv_foo_resources.rc')),
++ '-a', os.path.abspath(output_dir.GetPath('resource.h'))]))
++ output_dir.CleanUp()
++
++ def _verifyWhitelistedOutput(self,
++ filename,
++ whitelisted_ids,
++ non_whitelisted_ids,
++ encoding='utf8'):
++ self.failUnless(os.path.exists(filename))
++ whitelisted_ids_found = []
++ non_whitelisted_ids_found = []
++ with codecs.open(filename, encoding=encoding) as f:
++ for line in f.readlines():
++ for whitelisted_id in whitelisted_ids:
++ if whitelisted_id in line:
++ whitelisted_ids_found.append(whitelisted_id)
++ if filename.endswith('.h'):
++ numeric_id = int(line.split()[2])
++ expected_numeric_id = self.EXPECTED_ID_MAP.get(whitelisted_id)
++ self.assertEqual(
++ expected_numeric_id, numeric_id,
++ 'Numeric ID for {} was {} should be {}'.format(
++ whitelisted_id, numeric_id, expected_numeric_id))
++ for non_whitelisted_id in non_whitelisted_ids:
++ if non_whitelisted_id in line:
++ non_whitelisted_ids_found.append(non_whitelisted_id)
++ self.longMessage = True
++ self.assertEqual(whitelisted_ids,
++ whitelisted_ids_found,
++ '\nin file {}'.format(os.path.basename(filename)))
++ non_whitelisted_msg = ('Non-Whitelisted IDs {} found in {}'
++ .format(non_whitelisted_ids_found, os.path.basename(filename)))
++ self.assertFalse(non_whitelisted_ids_found, non_whitelisted_msg)
++
++ def testWhitelistStrings(self):
++ output_dir = util.TempDir({})
++ builder = build.RcBuilder()
++ class DummyOpts(object):
++ def __init__(self):
++ self.input = util.PathFromRoot('grit/testdata/whitelist_strings.grd')
++ self.verbose = False
++ self.extra_verbose = False
++ whitelist_file = util.PathFromRoot('grit/testdata/whitelist.txt')
++ builder.Run(DummyOpts(), ['-o', output_dir.GetPath(),
++ '-w', whitelist_file])
++ header = output_dir.GetPath('whitelist_test_resources.h')
++ rc = output_dir.GetPath('en_whitelist_test_strings.rc')
++
++ whitelisted_ids = ['IDS_MESSAGE_WHITELISTED']
++ non_whitelisted_ids = ['IDS_MESSAGE_NOT_WHITELISTED']
++ self._verifyWhitelistedOutput(
++ header,
++ whitelisted_ids,
++ non_whitelisted_ids,
++ )
++ self._verifyWhitelistedOutput(
++ rc,
++ whitelisted_ids,
++ non_whitelisted_ids,
++ encoding='utf16'
++ )
++ output_dir.CleanUp()
++
++ def testWhitelistResources(self):
++ output_dir = util.TempDir({})
++ builder = build.RcBuilder()
++ class DummyOpts(object):
++ def __init__(self):
++ self.input = util.PathFromRoot('grit/testdata/whitelist_resources.grd')
++ self.verbose = False
++ self.extra_verbose = False
++ whitelist_file = util.PathFromRoot('grit/testdata/whitelist.txt')
++ builder.Run(DummyOpts(), ['-o', output_dir.GetPath(),
++ '-w', whitelist_file])
++ header = output_dir.GetPath('whitelist_test_resources.h')
++ map_cc = output_dir.GetPath('whitelist_test_resources_map.cc')
++ map_h = output_dir.GetPath('whitelist_test_resources_map.h')
++ pak = output_dir.GetPath('whitelist_test_resources.pak')
++
++ # Ensure the resource map header and .pak files exist, but don't verify
++ # their content.
++ self.failUnless(os.path.exists(map_h))
++ self.failUnless(os.path.exists(pak))
++
++ whitelisted_ids = [
++ 'IDR_STRUCTURE_WHITELISTED',
++ 'IDR_STRUCTURE_IN_TRUE_IF_WHITELISTED',
++ 'IDR_INCLUDE_WHITELISTED',
++ ]
++ non_whitelisted_ids = [
++ 'IDR_STRUCTURE_NOT_WHITELISTED',
++ 'IDR_STRUCTURE_IN_TRUE_IF_NOT_WHITELISTED',
++ 'IDR_STRUCTURE_IN_FALSE_IF_WHITELISTED',
++ 'IDR_STRUCTURE_IN_FALSE_IF_NOT_WHITELISTED',
++ 'IDR_INCLUDE_NOT_WHITELISTED',
++ ]
++ for output_file in (header, map_cc):
++ self._verifyWhitelistedOutput(
++ output_file,
++ whitelisted_ids,
++ non_whitelisted_ids,
++ )
++ output_dir.CleanUp()
++
++ def testWriteOnlyNew(self):
++ output_dir = util.TempDir({})
++ builder = build.RcBuilder()
++ class DummyOpts(object):
++ def __init__(self):
++ self.input = util.PathFromRoot('grit/testdata/substitute.grd')
++ self.verbose = False
++ self.extra_verbose = False
++ UNCHANGED = 10
++ header = output_dir.GetPath('resource.h')
++
++ builder.Run(DummyOpts(), ['-o', output_dir.GetPath()])
++ self.failUnless(os.path.exists(header))
++ first_mtime = os.stat(header).st_mtime
++
++ os.utime(header, (UNCHANGED, UNCHANGED))
++ builder.Run(DummyOpts(),
++ ['-o', output_dir.GetPath(), '--write-only-new', '0'])
++ self.failUnless(os.path.exists(header))
++ second_mtime = os.stat(header).st_mtime
++
++ os.utime(header, (UNCHANGED, UNCHANGED))
++ builder.Run(DummyOpts(),
++ ['-o', output_dir.GetPath(), '--write-only-new', '1'])
++ self.failUnless(os.path.exists(header))
++ third_mtime = os.stat(header).st_mtime
++
++ self.assertTrue(abs(second_mtime - UNCHANGED) > 5)
++ self.assertTrue(abs(third_mtime - UNCHANGED) < 5)
++ output_dir.CleanUp()
++
++ def testGenerateDepFileWithDependOnStamp(self):
++ output_dir = util.TempDir({})
++ builder = build.RcBuilder()
++ class DummyOpts(object):
++ def __init__(self):
++ self.input = util.PathFromRoot('grit/testdata/substitute.grd')
++ self.verbose = False
++ self.extra_verbose = False
++ expected_dep_file_name = 'substitute.grd.d'
++ expected_stamp_file_name = expected_dep_file_name + '.stamp'
++ expected_dep_file = output_dir.GetPath(expected_dep_file_name)
++ expected_stamp_file = output_dir.GetPath(expected_stamp_file_name)
++ if os.path.isfile(expected_stamp_file):
++ os.remove(expected_stamp_file)
++ builder.Run(DummyOpts(), ['-o', output_dir.GetPath(),
++ '--depdir', output_dir.GetPath(),
++ '--depfile', expected_dep_file,
++ '--depend-on-stamp'])
++ self.failUnless(os.path.isfile(expected_stamp_file))
++ first_mtime = os.stat(expected_stamp_file).st_mtime
++
++ # Reset mtime to very old.
++ OLDTIME = 10
++ os.utime(expected_stamp_file, (OLDTIME, OLDTIME))
++
++ builder.Run(DummyOpts(), ['-o', output_dir.GetPath(),
++ '--depdir', output_dir.GetPath(),
++ '--depfile', expected_dep_file,
++ '--depend-on-stamp'])
++ self.failUnless(os.path.isfile(expected_stamp_file))
++ second_mtime = os.stat(expected_stamp_file).st_mtime
++
++ # Some OS have a 2s stat resolution window, so can't do a direct comparison.
++ self.assertTrue((second_mtime - OLDTIME) > 5)
++ self.assertTrue(abs(second_mtime - first_mtime) < 5)
++
++ self.failUnless(os.path.isfile(expected_dep_file))
++ with open(expected_dep_file) as f:
++ line = f.readline()
++ (dep_output_file, deps_string) = line.split(': ')
++ deps = deps_string.split(' ')
++
++ self.failUnlessEqual(expected_stamp_file_name, dep_output_file)
++ self.failUnlessEqual(deps, [
++ util.PathFromRoot('grit/testdata/substitute.xmb'),
++ ])
++ output_dir.CleanUp()
++
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/tool/buildinfo.py b/tools/grit/grit/tool/buildinfo.py
new file mode 100644
-index 0000000000..571d6d667d
+index 0000000000..7f8d1a3b04
--- /dev/null
-+++ b/tools/clang/plugins/tests/virtual_methods.txt
-@@ -0,0 +1,8 @@
-+In file included from virtual_methods.cpp:5:
-+./virtual_methods.h:17:36: warning: [chromium-style] virtual methods with non-empty bodies shouldn't be declared inline.
-+ virtual bool ComplainAboutThis() { return true; }
-+ ^
-+./virtual_methods.h:23:3: warning: [chromium-style] Overriding method must have "virtual" keyword.
-+ void MethodHasNoArguments() override;
-+ ^
-+2 warnings generated.
-diff --git a/tools/clang/scripts/package.sh b/tools/clang/scripts/package.sh
-new file mode 100755
-index 0000000000..eb345810b9
---- /dev/null
-+++ b/tools/clang/scripts/package.sh
-@@ -0,0 +1,87 @@
-+#!/bin/bash
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+# This script will check out llvm and clang, and then package the results up
-+# to a tgz file.
-+
-+THIS_DIR="$(dirname "${0}")"
-+LLVM_DIR="${THIS_DIR}/../../../third_party/llvm"
-+LLVM_BOOTSTRAP_DIR="${THIS_DIR}/../../../third_party/llvm-bootstrap"
-+LLVM_BUILD_DIR="${THIS_DIR}/../../../third_party/llvm-build"
-+LLVM_BIN_DIR="${LLVM_BUILD_DIR}/Release+Asserts/bin"
-+LLVM_LIB_DIR="${LLVM_BUILD_DIR}/Release+Asserts/lib"
-+
-+echo "Diff in llvm:" | tee buildlog.txt
-+svn stat "${LLVM_DIR}" 2>&1 | tee -a buildlog.txt
-+svn diff "${LLVM_DIR}" 2>&1 | tee -a buildlog.txt
-+echo "Diff in llvm/tools/clang:" | tee -a buildlog.txt
-+svn stat "${LLVM_DIR}/tools/clang" 2>&1 | tee -a buildlog.txt
-+svn diff "${LLVM_DIR}/tools/clang" 2>&1 | tee -a buildlog.txt
-+echo "Diff in llvm/projects/compiler-rt:" | tee -a buildlog.txt
-+svn stat "${LLVM_DIR}/projects/compiler-rt" 2>&1 | tee -a buildlog.txt
-+svn diff "${LLVM_DIR}/projects/compiler-rt" 2>&1 | tee -a buildlog.txt
-+
-+echo "Starting build" | tee -a buildlog.txt
-+
-+set -ex
-+
-+# Do a clobber build.
-+rm -rf "${LLVM_BOOTSTRAP_DIR}"
-+rm -rf "${LLVM_BUILD_DIR}"
-+"${THIS_DIR}"/update.sh --run-tests --bootstrap --force-local-build 2>&1 | \
-+ tee -a buildlog.txt
-+
-+R=$("${LLVM_BIN_DIR}/clang" --version | \
-+ sed -ne 's/clang version .*(trunk \([0-9]*\))/\1/p')
-+
-+PDIR=clang-$R
-+rm -rf $PDIR
-+mkdir $PDIR
-+mkdir $PDIR/bin
-+mkdir $PDIR/lib
-+
-+# Copy buildlog over.
-+cp buildlog.txt $PDIR/
-+
-+# Copy clang into pdir, symlink clang++ to it.
-+cp "${LLVM_BIN_DIR}/clang" $PDIR/bin/
-+(cd $PDIR/bin && ln -sf clang clang++ && cd -)
-+
-+# Copy plugins. Some of the dylibs are pretty big, so copy only the ones we
-+# care about.
-+if [ "$(uname -s)" = "Darwin" ]; then
-+ cp "${LLVM_LIB_DIR}/libFindBadConstructs.dylib" $PDIR/lib
-+else
-+ cp "${LLVM_LIB_DIR}/libFindBadConstructs.so" $PDIR/lib
-+fi
-+
-+# Copy built-in headers (lib/clang/3.2/include).
-+# libcompiler-rt puts all kinds of libraries there too, but we want only ASan.
-+if [ "$(uname -s)" = "Darwin" ]; then
-+ # Keep only Release+Asserts/lib/clang/3.2/lib/darwin/libclang_rt.asan_osx.a
-+ find "${LLVM_LIB_DIR}/clang" -type f -path '*lib/darwin*' | grep -v asan | \
-+ xargs rm
-+else
-+ # Keep only
-+ # Release+Asserts/lib/clang/3.2/lib/linux/libclang_rt.{asan,tsan}-x86_64.a
-+ # TODO(thakis): Make sure the 32bit version of ASan runtime is kept too once
-+ # that's built. TSan runtime exists only for 64 bits.
-+ find "${LLVM_LIB_DIR}/clang" -type f -path '*lib/linux*' | \
-+ grep -v "asan\|tsan" | xargs rm
-+fi
-+
-+cp -R "${LLVM_LIB_DIR}/clang" $PDIR/lib
-+
-+tar zcf $PDIR.tgz -C $PDIR bin lib buildlog.txt
-+
-+if [ "$(uname -s)" = "Darwin" ]; then
-+ PLATFORM=Mac
-+else
-+ PLATFORM=Linux_x64
-+fi
-+
-+echo To upload, run:
-+echo gsutil cp -a public-read $PDIR.tgz \
-+ gs://chromium-browser-clang/$PLATFORM/$PDIR.tgz
-diff --git a/tools/clang/scripts/plugin_flags.sh b/tools/clang/scripts/plugin_flags.sh
-new file mode 100755
-index 0000000000..217c5c3bd6
---- /dev/null
-+++ b/tools/clang/scripts/plugin_flags.sh
-@@ -0,0 +1,24 @@
-+#!/bin/bash
++++ b/tools/grit/grit/tool/buildinfo.py
+@@ -0,0 +1,78 @@
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
-+# This script returns the flags that should be used when GYP_DEFINES contains
-+# clang_use_chrome_plugins. The flags are stored in a script so that they can
-+# be changed on the bots without requiring a master restart.
++"""Output the list of files to be generated by GRIT from an input.
++"""
+
-+THIS_ABS_DIR=$(cd $(dirname $0) && echo $PWD)
-+CLANG_LIB_PATH=$THIS_ABS_DIR/../../../third_party/llvm-build/Release+Asserts/lib
++from __future__ import print_function
+
-+if uname -s | grep -q Darwin; then
-+ LIBSUFFIX=dylib
-+else
-+ LIBSUFFIX=so
-+fi
++import getopt
++import os
++import sys
+
-+echo -Xclang -load -Xclang $CLANG_LIB_PATH/libFindBadConstructs.$LIBSUFFIX \
-+ -Xclang -add-plugin -Xclang find-bad-constructs \
-+ -Xclang -plugin-arg-find-bad-constructs \
-+ -Xclang skip-virtuals-in-implementations \
-+ -Xclang -plugin-arg-find-bad-constructs \
-+ -Xclang check-cc-directory
-diff --git a/tools/clang/scripts/update.py b/tools/clang/scripts/update.py
-new file mode 100755
-index 0000000000..bdc781f715
++from grit import grd_reader
++from grit.node import structure
++from grit.tool import interface
++
++class DetermineBuildInfo(interface.Tool):
++ """Determine what files will be read and output by GRIT.
++Outputs the list of generated files and inputs used to stdout.
++
++Usage: grit buildinfo [-o DIR]
++
++The output directory is used for display only.
++"""
++
++ def __init__(self):
++ pass
++
++ def ShortDescription(self):
++ """Describes this tool for the usage message."""
++ return ('Determine what files will be needed and\n'
++ 'output by GRIT with a given input.')
++
++ def Run(self, opts, args):
++ """Main method for the buildinfo tool."""
++ self.output_directory = '.'
++ (own_opts, args) = getopt.getopt(args, 'o:', ('help',))
++ for (key, val) in own_opts:
++ if key == '-o':
++ self.output_directory = val
++ elif key == '--help':
++ self.ShowUsage()
++ sys.exit(0)
++ if len(args) > 0:
++ print('This tool takes exactly one argument: the output directory via -o')
++ return 2
++ self.SetOptions(opts)
++
++ res_tree = grd_reader.Parse(opts.input, debug=opts.extra_verbose)
++
++ langs = {}
++ for output in res_tree.GetOutputFiles():
++ if output.attrs['lang']:
++ langs[output.attrs['lang']] = os.path.dirname(output.GetFilename())
++
++ for lang, dirname in langs.items():
++ old_output_language = res_tree.output_language
++ res_tree.SetOutputLanguage(lang)
++ for node in res_tree.ActiveDescendants():
++ with node:
++ if (isinstance(node, structure.StructureNode) and
++ node.HasFileForLanguage()):
++ path = node.FileForLanguage(lang, dirname, create_file=False,
++ return_if_not_generated=False)
++ if path:
++ path = os.path.join(self.output_directory, path)
++ path = os.path.normpath(path)
++ print('%s|%s' % ('rc_all', path))
++ res_tree.SetOutputLanguage(old_output_language)
++
++ for output in res_tree.GetOutputFiles():
++ path = os.path.join(self.output_directory, output.GetFilename())
++ path = os.path.normpath(path)
++ print('%s|%s' % (output.GetType(), path))
++
++ for infile in res_tree.GetInputFiles():
++ print('input|%s' % os.path.normpath(infile))
+diff --git a/tools/grit/grit/tool/buildinfo_unittest.py b/tools/grit/grit/tool/buildinfo_unittest.py
+new file mode 100644
+index 0000000000..24e9ddf8d8
--- /dev/null
-+++ b/tools/clang/scripts/update.py
-@@ -0,0 +1,34 @@
++++ b/tools/grit/grit/tool/buildinfo_unittest.py
+@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
-+"""Windows can't run .sh files, so this is a small python wrapper around
-+update.sh.
++"""Unit tests for the 'grit buildinfo' tool.
+"""
+
++from __future__ import print_function
++
+import os
-+import subprocess
+import sys
++import unittest
++
++# This is needed to find some of the imports below.
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++from six import StringIO
++
++# pylint: disable-msg=C6204
++from grit.tool import buildinfo
++
++
++class BuildInfoUnittest(unittest.TestCase):
++ def setUp(self):
++ self.old_cwd = os.getcwd()
++ # Change CWD to make tests work independently of callers CWD.
++ os.chdir(os.path.dirname(__file__))
++ os.chdir('..')
++ self.buf = StringIO()
++ self.old_stdout = sys.stdout
++ sys.stdout = self.buf
++
++ def tearDown(self):
++ sys.stdout = self.old_stdout
++ os.chdir(self.old_cwd)
++
++ def testBuildOutput(self):
++ """Find all of the inputs and outputs for a GRD file."""
++ info_object = buildinfo.DetermineBuildInfo()
++
++ class DummyOpts(object):
++ def __init__(self):
++ self.input = '../grit/testdata/buildinfo.grd'
++ self.print_header = False
++ self.verbose = False
++ self.extra_verbose = False
++ info_object.Run(DummyOpts(), [])
++ output = self.buf.getvalue().replace('\\', '/')
++ self.failUnless(output.count(r'rc_all|sv_sidebar_loading.html'))
++ self.failUnless(output.count(r'rc_header|resource.h'))
++ self.failUnless(output.count(r'rc_all|en_generated_resources.rc'))
++ self.failUnless(output.count(r'rc_all|sv_generated_resources.rc'))
++ self.failUnless(output.count(r'input|../grit/testdata/substitute.xmb'))
++ self.failUnless(output.count(r'input|../grit/testdata/pr.bmp'))
++ self.failUnless(output.count(r'input|../grit/testdata/pr2.bmp'))
++ self.failUnless(
++ output.count(r'input|../grit/testdata/sidebar_loading.html'))
++ self.failUnless(output.count(r'input|../grit/testdata/transl.rc'))
++ self.failUnless(output.count(r'input|../grit/testdata/transl1.rc'))
++
++ def testBuildOutputWithDir(self):
++ """Find all the inputs and outputs for a GRD file with an output dir."""
++ info_object = buildinfo.DetermineBuildInfo()
++
++ class DummyOpts(object):
++ def __init__(self):
++ self.input = '../grit/testdata/buildinfo.grd'
++ self.print_header = False
++ self.verbose = False
++ self.extra_verbose = False
++ info_object.Run(DummyOpts(), ['-o', '../grit/testdata'])
++ output = self.buf.getvalue().replace('\\', '/')
++ self.failUnless(
++ output.count(r'rc_all|../grit/testdata/sv_sidebar_loading.html'))
++ self.failUnless(output.count(r'rc_header|../grit/testdata/resource.h'))
++ self.failUnless(
++ output.count(r'rc_all|../grit/testdata/en_generated_resources.rc'))
++ self.failUnless(
++ output.count(r'rc_all|../grit/testdata/sv_generated_resources.rc'))
++ self.failUnless(output.count(r'input|../grit/testdata/substitute.xmb'))
++ self.failUnlessEqual(0,
++ output.count(r'rc_all|../grit/testdata/sv_welcome_toast.html'))
++ self.failUnless(
++ output.count(r'rc_all|../grit/testdata/en_welcome_toast.html'))
+
+
-+def main():
-+ if sys.platform in ['win32', 'cygwin']:
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/tool/count.py b/tools/grit/grit/tool/count.py
+new file mode 100644
+index 0000000000..ab37f2ddb3
+--- /dev/null
++++ b/tools/grit/grit/tool/count.py
+@@ -0,0 +1,52 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Count number of occurrences of a given message ID.'''
++
++from __future__ import print_function
++
++import getopt
++import sys
++
++from grit import grd_reader
++from grit.tool import interface
++
++
++class CountMessage(interface.Tool):
++ '''Count the number of times a given message ID is used.'''
++
++ def __init__(self):
++ pass
++
++ def ShortDescription(self):
++ return 'Count the number of times a given message ID is used.'
++
++ def ParseOptions(self, args):
++ """Set this objects and return all non-option arguments."""
++ own_opts, args = getopt.getopt(args, '', ('help',))
++ for key, val in own_opts:
++ if key == '--help':
++ self.ShowUsage()
++ sys.exit(0)
++ return args
++
++ def Run(self, opts, args):
++ args = self.ParseOptions(args)
++ if len(args) != 1:
++ print('This tool takes a single tool-specific argument, the message '
++ 'ID to count.')
++ return 2
++ self.SetOptions(opts)
++
++ id = args[0]
++ res_tree = grd_reader.Parse(opts.input, debug=opts.extra_verbose)
++ res_tree.OnlyTheseTranslations([])
++ res_tree.RunGatherers()
++
++ count = 0
++ for c in res_tree.UberClique().AllCliques():
++ if c.GetId() == id:
++ count += 1
++
++ print("There are %d occurrences of message %s." % (count, id))
+diff --git a/tools/grit/grit/tool/diff_structures.py b/tools/grit/grit/tool/diff_structures.py
+new file mode 100644
+index 0000000000..d69e009b58
+--- /dev/null
++++ b/tools/grit/grit/tool/diff_structures.py
+@@ -0,0 +1,119 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''The 'grit sdiff' tool.
++'''
++
++from __future__ import print_function
++
++import os
++import getopt
++import sys
++import tempfile
++
++from grit.node import structure
++from grit.tool import interface
++
++from grit import constants
++from grit import util
++
++# Builds the description for the tool (used as the __doc__
++# for the DiffStructures class).
++_class_doc = """\
++Allows you to view the differences in the structure of two files,
++disregarding their translateable content. Translateable portions of
++each file are changed to the string "TTTTTT" before invoking the diff program
++specified by the P4DIFF environment variable.
++
++Usage: grit sdiff [-t TYPE] [-s SECTION] [-e ENCODING] LEFT RIGHT
++
++LEFT and RIGHT are the files you want to diff. SECTION is required
++for structure types like 'dialog' to identify the part of the file to look at.
++ENCODING indicates the encoding of the left and right files (default 'cp1252').
++TYPE can be one of the following, defaults to 'tr_html':
++"""
++for gatherer in structure._GATHERERS:
++ _class_doc += " - %s\n" % gatherer
++
++
++class DiffStructures(interface.Tool):
++ __doc__ = _class_doc
++
++ def __init__(self):
++ self.section = None
++ self.left_encoding = 'cp1252'
++ self.right_encoding = 'cp1252'
++ self.structure_type = 'tr_html'
++
++ def ShortDescription(self):
++ return 'View differences without regard for translateable portions.'
++
++ def Run(self, global_opts, args):
++ (opts, args) = getopt.getopt(args, 's:e:t:',
++ ('help', 'left_encoding=', 'right_encoding='))
++ for key, val in opts:
++ if key == '-s':
++ self.section = val
++ elif key == '-e':
++ self.left_encoding = val
++ self.right_encoding = val
++ elif key == '-t':
++ self.structure_type = val
++ elif key == '--left_encoding':
++ self.left_encoding = val
++ elif key == '--right_encoding':
++ self.right_encoding == val
++ elif key == '--help':
++ self.ShowUsage()
++ sys.exit(0)
++
++ if len(args) != 2:
++ print("Incorrect usage - 'grit help sdiff' for usage details.")
++ return 2
++
++ if 'P4DIFF' not in os.environ:
++ print("Environment variable P4DIFF not set; defaulting to 'windiff'.")
++ diff_program = 'windiff'
++ else:
++ diff_program = os.environ['P4DIFF']
++
++ left_trans = self.MakeStaticTranslation(args[0], self.left_encoding)
++ try:
++ try:
++ right_trans = self.MakeStaticTranslation(args[1], self.right_encoding)
++
++ os.system('%s %s %s' % (diff_program, left_trans, right_trans))
++ finally:
++ os.unlink(right_trans)
++ finally:
++ os.unlink(left_trans)
++
++ def MakeStaticTranslation(self, original_filename, encoding):
++ """Given the name of the structure type (self.structure_type), the filename
++ of the file holding the original structure, and optionally the "section" key
++ identifying the part of the file to look at (self.section), creates a
++ temporary file holding a "static" translation of the original structure
++ (i.e. one where all translateable parts have been replaced with "TTTTTT")
++ and returns the temporary file name. It is the caller's responsibility to
++ delete the file when finished.
++
++ Args:
++ original_filename: 'c:\\bingo\\bla.rc'
++
++ Return:
++ 'c:\\temp\\werlkjsdf334.tmp'
++ """
++ original = structure._GATHERERS[self.structure_type](original_filename,
++ extkey=self.section,
++ encoding=encoding)
++ original.Parse()
++ translated = original.Translate(constants.CONSTANT_LANGUAGE, False)
++
++ fname = tempfile.mktemp()
++ with util.WrapOutputStream(open(fname, 'wb')) as writer:
++ writer.write("Original filename: %s\n=============\n\n"
++ % original_filename)
++ writer.write(translated) # write in UTF-8
++
++ return fname
+diff --git a/tools/grit/grit/tool/diff_structures_unittest.py b/tools/grit/grit/tool/diff_structures_unittest.py
+new file mode 100644
+index 0000000000..a6d7585761
+--- /dev/null
++++ b/tools/grit/grit/tool/diff_structures_unittest.py
+@@ -0,0 +1,46 @@
++#!/usr/bin/env python
++# Copyright 2020 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit tests for the 'grit newgrd' tool.'''
++
++from __future__ import print_function
++
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++import unittest
++
++from grit.tool import diff_structures
++
++
++class DummyOpts(object):
++ """Options needed by NewGrd."""
++
++
++class DiffStructuresUnittest(unittest.TestCase):
++
++ def testMissingFiles(self):
++ """Verify failure w/out file inputs."""
++ tool = diff_structures.DiffStructures()
++ ret = tool.Run(DummyOpts(), [])
++ self.assertIsNotNone(ret)
++ self.assertGreater(ret, 0)
++
++ ret = tool.Run(DummyOpts(), ['left'])
++ self.assertIsNotNone(ret)
++ self.assertGreater(ret, 0)
++
++ def testTooManyArgs(self):
++ """Verify failure w/too many inputs."""
++ tool = diff_structures.DiffStructures()
++ ret = tool.Run(DummyOpts(), ['a', 'b', 'c'])
++ self.assertIsNotNone(ret)
++ self.assertGreater(ret, 0)
++
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/tool/interface.py b/tools/grit/grit/tool/interface.py
+new file mode 100644
+index 0000000000..e923205223
+--- /dev/null
++++ b/tools/grit/grit/tool/interface.py
+@@ -0,0 +1,62 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Base class and interface for tools.
++'''
++
++from __future__ import print_function
++
++class Tool(object):
++ '''Base class for all tools. Tools should use their docstring (i.e. the
++ class-level docstring) for the help they want to have printed when they
++ are invoked.'''
++
++ #
++ # Interface (abstract methods)
++ #
++
++ def ShortDescription(self):
++ '''Returns a short description of the functionality of the tool.'''
++ raise NotImplementedError()
++
++ def Run(self, global_options, my_arguments):
++ '''Runs the tool.
++
++ Args:
++ global_options: object grit_runner.Options
++ my_arguments: [arg1 arg2 ...]
++
++ Return:
++ 0 for success, non-0 for error
++ '''
++ raise NotImplementedError()
++
++ #
++ # Base class implementation
++ #
++
++ def __init__(self):
++ self.o = None
++
++ def ShowUsage(self):
++ '''Show usage text for this tool.'''
++ print(self.__doc__)
++
++ def SetOptions(self, opts):
++ self.o = opts
++
++ def Out(self, text):
++ '''Always writes out 'text'.'''
++ self.o.output_stream.write(text)
++
++ def VerboseOut(self, text):
++ '''Writes out 'text' if the verbose option is on.'''
++ if self.o.verbose:
++ self.o.output_stream.write(text)
++
++ def ExtraVerboseOut(self, text):
++ '''Writes out 'text' if the extra-verbose option is on.
++ '''
++ if self.o.extra_verbose:
++ self.o.output_stream.write(text)
+diff --git a/tools/grit/grit/tool/menu_from_parts.py b/tools/grit/grit/tool/menu_from_parts.py
+new file mode 100644
+index 0000000000..fcec26c5b1
+--- /dev/null
++++ b/tools/grit/grit/tool/menu_from_parts.py
+@@ -0,0 +1,79 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''The 'grit menufromparts' tool.'''
++
++from __future__ import print_function
++
++import six
++
++from grit import grd_reader
++from grit import util
++from grit import xtb_reader
++from grit.tool import interface
++from grit.tool import transl2tc
++
++import grit.extern.tclib
++
++
++class MenuTranslationsFromParts(interface.Tool):
++ '''One-off tool to generate translated menu messages (where each menu is kept
++in a single message) based on existing translations of the individual menu
++items. Was needed when changing menus from being one message per menu item
++to being one message for the whole menu.'''
++
++ def ShortDescription(self):
++ return ('Create translations of whole menus from existing translations of '
++ 'menu items.')
++
++ def Run(self, globopt, args):
++ self.SetOptions(globopt)
++ assert len(args) == 2, "Need exactly two arguments, the XTB file and the output file"
++
++ xtb_file = args[0]
++ output_file = args[1]
++
++ grd = grd_reader.Parse(self.o.input, debug=self.o.extra_verbose)
++ grd.OnlyTheseTranslations([]) # don't load translations
++ grd.RunGatherers()
++
++ xtb = {}
++ def Callback(msg_id, parts):
++ msg = []
++ for part in parts:
++ if part[0]:
++ msg = []
++ break # it had a placeholder so ignore it
++ else:
++ msg.append(part[1])
++ if len(msg):
++ xtb[msg_id] = ''.join(msg)
++ with open(xtb_file, 'rb') as f:
++ xtb_reader.Parse(f, Callback)
++
++ translations = [] # list of translations as per transl2tc.WriteTranslations
++ for node in grd:
++ if node.name == 'structure' and node.attrs['type'] == 'menu':
++ assert len(node.GetCliques()) == 1
++ message = node.GetCliques()[0].GetMessage()
++ translation = []
++
++ contents = message.GetContent()
++ for part in contents:
++ if isinstance(part, six.string_types):
++ id = grit.extern.tclib.GenerateMessageId(part)
++ if id not in xtb:
++ print("WARNING didn't find all translations for menu %s" %
++ (node.attrs['name'],))
++ translation = []
++ break
++ translation.append(xtb[id])
++ else:
++ translation.append(part.GetPresentation())
++
++ if len(translation):
++ translations.append([message.GetId(), ''.join(translation)])
++
++ with util.WrapOutputStream(open(output_file, 'wb')) as f:
++ transl2tc.TranslationToTc.WriteTranslations(f, translations)
+diff --git a/tools/grit/grit/tool/newgrd.py b/tools/grit/grit/tool/newgrd.py
+new file mode 100644
+index 0000000000..66a18e9c04
+--- /dev/null
++++ b/tools/grit/grit/tool/newgrd.py
+@@ -0,0 +1,85 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Tool to create a new, empty .grd file with all the basic sections.
++'''
++
++from __future__ import print_function
++
++import getopt
++import sys
++
++from grit.tool import interface
++from grit import constants
++from grit import util
++
++# The contents of the new .grd file
++_FILE_CONTENTS = '''\
++<?xml version="1.0" encoding="UTF-8"?>
++<grit base_dir="." latest_public_release="0" current_release="1"
++ source_lang_id="en" enc_check="%s">
++ <outputs>
++ <!-- TODO add each of your output files. Modify the three below, and add
++ your own for your various languages. See the user's guide for more
++ details.
++ Note that all output references are relative to the output directory
++ which is specified at build time. -->
++ <output filename="resource.h" type="rc_header" />
++ <output filename="en_resource.rc" type="rc_all" />
++ <output filename="fr_resource.rc" type="rc_all" />
++ </outputs>
++ <translations>
++ <!-- TODO add references to each of the XTB files (from the Translation
++ Console) that contain translations of messages in your project. Each
++ takes a form like <file path="english.xtb" />. Remember that all file
++ references are relative to this .grd file. -->
++ </translations>
++ <release seq="1">
++ <includes>
++ <!-- TODO add a list of your included resources here, e.g. BMP and GIF
++ resources. -->
++ </includes>
++ <structures>
++ <!-- TODO add a list of all your structured resources here, e.g. HTML
++ templates, menus, dialogs etc. Note that for menus, dialogs and version
++ information resources you reference an .rc file containing them.-->
++ </structures>
++ <messages>
++ <!-- TODO add all of your "string table" messages here. Remember to
++ change nontranslateable parts of the messages into placeholders (using the
++ <ph> element). You can also use the 'grit add' tool to help you identify
++ nontranslateable parts and create placeholders for them. -->
++ </messages>
++ </release>
++</grit>''' % constants.ENCODING_CHECK
++
++
++class NewGrd(interface.Tool):
++ '''Usage: grit newgrd OUTPUT_FILE
++
++Creates a new, empty .grd file OUTPUT_FILE with comments about what to put
++where in the file.'''
++
++ def ShortDescription(self):
++ return 'Create a new empty .grd file.'
++
++ def ParseOptions(self, args):
++ """Set this objects and return all non-option arguments."""
++ own_opts, args = getopt.getopt(args, '', ('help',))
++ for key, val in own_opts:
++ if key == '--help':
++ self.ShowUsage()
++ sys.exit(0)
++ return args
++
++ def Run(self, opts, args):
++ args = self.ParseOptions(args)
++ if len(args) != 1:
++ print('This tool requires exactly one argument, the name of the output '
++ 'file.')
++ return 2
++ filename = args[0]
++ with util.WrapOutputStream(open(filename, 'wb'), 'utf-8') as out:
++ out.write(_FILE_CONTENTS)
++ print("Wrote file %s" % filename)
+diff --git a/tools/grit/grit/tool/newgrd_unittest.py b/tools/grit/grit/tool/newgrd_unittest.py
+new file mode 100644
+index 0000000000..f7c8831df5
+--- /dev/null
++++ b/tools/grit/grit/tool/newgrd_unittest.py
+@@ -0,0 +1,51 @@
++#!/usr/bin/env python
++# Copyright 2020 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit tests for the 'grit newgrd' tool.'''
++
++from __future__ import print_function
++
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++import unittest
++
++from grit import util
++from grit.tool import newgrd
++
++
++class DummyOpts(object):
++ """Options needed by NewGrd."""
++
++
++class NewgrdUnittest(unittest.TestCase):
++
++ def testNewFile(self):
++ """Create a new file."""
++ tool = newgrd.NewGrd()
++ with util.TempDir({}) as output_dir:
++ output_file = os.path.join(output_dir.GetPath(), 'new.grd')
++ self.assertIsNone(tool.Run(DummyOpts(), [output_file]))
++ self.assertTrue(os.path.exists(output_file))
++
++ def testMissingFile(self):
++ """Verify failure w/out file output."""
++ tool = newgrd.NewGrd()
++ ret = tool.Run(DummyOpts(), [])
++ self.assertIsNotNone(ret)
++ self.assertGreater(ret, 0)
++
++ def testTooManyArgs(self):
++ """Verify failure w/too many outputs."""
++ tool = newgrd.NewGrd()
++ ret = tool.Run(DummyOpts(), ['a', 'b'])
++ self.assertIsNotNone(ret)
++ self.assertGreater(ret, 0)
++
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/tool/postprocess_interface.py b/tools/grit/grit/tool/postprocess_interface.py
+new file mode 100644
+index 0000000000..4bb8c5871f
+--- /dev/null
++++ b/tools/grit/grit/tool/postprocess_interface.py
+@@ -0,0 +1,29 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++''' Base class for postprocessing of RC files.
++'''
++
++from __future__ import print_function
++
++class PostProcessor(object):
++ ''' Base class for postprocessing of the RC file data before being
++ output through the RC2GRD tool. You should implement this class if
++ you want GRIT to do specific things to the RC files after it has
++ converted the data into GRD format, i.e. change the content of the
++ RC file, and put it into a P4 changelist, etc.'''
++
++
++ def Process(self, rctext, rcpath, grdnode):
++ ''' Processes the data in rctext and grdnode.
++ Args:
++ rctext: string containing the contents of the RC file being processed.
++ rcpath: the path used to access the file.
++ grdtext: the root node of the grd xml data generated by
++ the rc2grd tool.
++
++ Return:
++ The root node of the processed GRD tree.
++ '''
++ raise NotImplementedError()
+diff --git a/tools/grit/grit/tool/postprocess_unittest.py b/tools/grit/grit/tool/postprocess_unittest.py
+new file mode 100644
+index 0000000000..77fe228bbe
+--- /dev/null
++++ b/tools/grit/grit/tool/postprocess_unittest.py
+@@ -0,0 +1,64 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit test that checks postprocessing of files.
++ Tests postprocessing by having the postprocessor
++ modify the grd data tree, changing the message name attributes.
++'''
++
++from __future__ import print_function
++
++import os
++import re
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++import unittest
++
++import grit.tool.postprocess_interface
++from grit.tool import rc2grd
++
++
++class PostProcessingUnittest(unittest.TestCase):
++
++ def testPostProcessing(self):
++ rctext = '''STRINGTABLE
++BEGIN
++ DUMMY_STRING_1 "String 1"
++ // Some random description
++ DUMMY_STRING_2 "This text was added during preprocessing"
++END
++ '''
++ tool = rc2grd.Rc2Grd()
++ class DummyOpts(object):
++ verbose = False
++ extra_verbose = False
++ tool.o = DummyOpts()
++ tool.post_process = 'grit.tool.postprocess_unittest.DummyPostProcessor'
++ result = tool.Process(rctext, '.\resource.rc')
++
++ self.failUnless(
++ result.children[2].children[2].children[0].attrs['name'] == 'SMART_STRING_1')
++ self.failUnless(
++ result.children[2].children[2].children[1].attrs['name'] == 'SMART_STRING_2')
++
++class DummyPostProcessor(grit.tool.postprocess_interface.PostProcessor):
++ '''
++ Post processing replaces all message name attributes containing "DUMMY" to
++ "SMART".
++ '''
++ def Process(self, rctext, rcpath, grdnode):
++ smarter = re.compile(r'(DUMMY)(.*)')
++ messages = grdnode.children[2].children[2]
++ for node in messages.children:
++ name_attr = node.attrs['name']
++ m = smarter.search(name_attr)
++ if m:
++ node.attrs['name'] = 'SMART' + m.group(2)
++ return grdnode
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/tool/preprocess_interface.py b/tools/grit/grit/tool/preprocess_interface.py
+new file mode 100644
+index 0000000000..67974e704e
+--- /dev/null
++++ b/tools/grit/grit/tool/preprocess_interface.py
+@@ -0,0 +1,25 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++''' Base class for preprocessing of RC files.
++'''
++
++from __future__ import print_function
++
++class PreProcessor(object):
++ ''' Base class for preprocessing of the RC file data before being
++ output through the RC2GRD tool. You should implement this class if
++ you have specific constructs in your RC files that GRIT cannot handle.'''
++
++
++ def Process(self, rctext, rcpath):
++ ''' Processes the data in rctext.
++ Args:
++ rctext: string containing the contents of the RC file being processed
++ rcpath: the path used to access the file.
++
++ Return:
++ The processed text.
++ '''
++ raise NotImplementedError()
+diff --git a/tools/grit/grit/tool/preprocess_unittest.py b/tools/grit/grit/tool/preprocess_unittest.py
+new file mode 100644
+index 0000000000..40b95cd6f8
+--- /dev/null
++++ b/tools/grit/grit/tool/preprocess_unittest.py
+@@ -0,0 +1,50 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit test that checks preprocessing of files.
++ Tests preprocessing by adding having the preprocessor
++ provide the actual rctext data.
++'''
++
++from __future__ import print_function
++
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++import unittest
++
++import grit.tool.preprocess_interface
++from grit.tool import rc2grd
++
++
++class PreProcessingUnittest(unittest.TestCase):
++
++ def testPreProcessing(self):
++ tool = rc2grd.Rc2Grd()
++ class DummyOpts(object):
++ verbose = False
++ extra_verbose = False
++ tool.o = DummyOpts()
++ tool.pre_process = 'grit.tool.preprocess_unittest.DummyPreProcessor'
++ result = tool.Process('', '.\resource.rc')
++
++ self.failUnless(
++ result.children[2].children[2].children[0].attrs['name'] == 'DUMMY_STRING_1')
++
++class DummyPreProcessor(grit.tool.preprocess_interface.PreProcessor):
++ def Process(self, rctext, rcpath):
++ rctext = '''STRINGTABLE
++BEGIN
++ DUMMY_STRING_1 "String 1"
++ // Some random description
++ DUMMY_STRING_2 "This text was added during preprocessing"
++END
++ '''
++ return rctext
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/tool/rc2grd.py b/tools/grit/grit/tool/rc2grd.py
+new file mode 100644
+index 0000000000..3195b39000
+--- /dev/null
++++ b/tools/grit/grit/tool/rc2grd.py
+@@ -0,0 +1,418 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''The 'grit rc2grd' tool.'''
++
++from __future__ import print_function
++
++import os.path
++import getopt
++import re
++import sys
++
++import six
++from six import StringIO
++
++import grit.node.empty
++from grit.node import include
++from grit.node import structure
++from grit.node import message
++
++from grit.gather import rc
++from grit.gather import tr_html
++
++from grit.tool import interface
++from grit.tool import postprocess_interface
++from grit.tool import preprocess_interface
++
++from grit import grd_reader
++from grit import lazy_re
++from grit import tclib
++from grit import util
++
++
++# Matches files referenced from an .rc file
++_FILE_REF = lazy_re.compile(r'''
++ ^(?P<id>[A-Z_0-9.]+)[ \t]+
++ (?P<type>[A-Z_0-9]+)[ \t]+
++ "(?P<file>.*?([^"]|""))"[ \t]*$''', re.VERBOSE | re.MULTILINE)
++
++
++# Matches a dialog section
++_DIALOG = lazy_re.compile(
++ r'^(?P<id>[A-Z0-9_]+)\s+DIALOG(EX)?\s.+?^BEGIN\s*$.+?^END\s*$',
++ re.MULTILINE | re.DOTALL)
++
++
++# Matches a menu section
++_MENU = lazy_re.compile(r'^(?P<id>[A-Z0-9_]+)\s+MENU.+?^BEGIN\s*$.+?^END\s*$',
++ re.MULTILINE | re.DOTALL)
++
++
++# Matches a versioninfo section
++_VERSIONINFO = lazy_re.compile(
++ r'^(?P<id>[A-Z0-9_]+)\s+VERSIONINFO\s.+?^BEGIN\s*$.+?^END\s*$',
++ re.MULTILINE | re.DOTALL)
++
++
++# Matches a stringtable
++_STRING_TABLE = lazy_re.compile(
++ (r'^STRINGTABLE(\s+(PRELOAD|DISCARDABLE|CHARACTERISTICS.+|LANGUAGE.+|'
++ r'VERSION.+))*\s*\nBEGIN\s*$(?P<body>.+?)^END\s*$'),
++ re.MULTILINE | re.DOTALL)
++
++
++# Matches each message inside a stringtable, breaking it up into comments,
++# the ID of the message, and the (RC-escaped) message text.
++_MESSAGE = lazy_re.compile(r'''
++ (?P<comment>(^\s+//.+?)*) # 0 or more lines of comments preceding the message
++ ^\s*
++ (?P<id>[A-Za-z0-9_]+) # id
++ \s+
++ "(?P<text>.*?([^"]|""))"([^"]|$) # The message itself
++ ''', re.MULTILINE | re.DOTALL | re.VERBOSE)
++
++
++# Matches each line of comment text in a multi-line comment.
++_COMMENT_TEXT = lazy_re.compile(r'^\s*//\s*(?P<text>.+?)$', re.MULTILINE)
++
++
++# Matches a string that is empty or all whitespace
++_WHITESPACE_ONLY = lazy_re.compile(r'\A\s*\Z', re.MULTILINE)
++
++
++# Finds printf and FormatMessage style format specifiers
++# Uses non-capturing groups except for the outermost group, so the output of
++# re.split() should include both the normal text and what we intend to
++# replace with placeholders.
++# TODO(joi) Check documentation for printf (and Windows variants) and FormatMessage
++_FORMAT_SPECIFIER = lazy_re.compile(
++ r'(%[-# +]?(?:[0-9]*|\*)(?:\.(?:[0-9]+|\*))?(?:h|l|L)?' # printf up to last char
++ r'(?:d|i|o|u|x|X|e|E|f|F|g|G|c|r|s|ls|ws)' # printf last char
++ r'|\$[1-9][0-9]*)') # FormatMessage
++
++
++class Rc2Grd(interface.Tool):
++ '''A tool for converting .rc files to .grd files. This tool is only for
++converting the source (nontranslated) .rc file to a .grd file. For importing
++existing translations, use the rc2xtb tool.
++
++Usage: grit [global options] rc2grd [OPTIONS] RCFILE
++
++The tool takes a single argument, which is the path to the .rc file to convert.
++It outputs a .grd file with the same name in the same directory as the .rc file.
++The .grd file may have one or more TODO comments for things that have to be
++cleaned up manually.
++
++OPTIONS may be any of the following:
++
++ -e ENCODING Specify the ENCODING of the .rc file. Default is 'cp1252'.
++
++ -h TYPE Specify the TYPE attribute for HTML structures.
++ Default is 'tr_html'.
++
++ -u ENCODING Specify the ENCODING of HTML files. Default is 'utf-8'.
++
++ -n MATCH Specify the regular expression to match in comments that will
++ indicate that the resource the comment belongs to is not
++ translateable. Default is 'Not locali(s|z)able'.
++
++ -r GRDFILE Specify that GRDFILE should be used as a "role model" for
++ any placeholders that otherwise would have had TODO names.
++ This attempts to find an identical message in the GRDFILE
++ and uses that instead of the automatically placeholderized
++ message.
++
++ --pre CLASS Specify an optional, fully qualified classname, which
++ has to be a subclass of grit.tool.PreProcessor, to
++ run on the text of the RC file before conversion occurs.
++ This can be used to support constructs in the RC files
++ that GRIT cannot handle on its own.
++
++ --post CLASS Specify an optional, fully qualified classname, which
++ has to be a subclass of grit.tool.PostProcessor, to
++ run on the text of the converted RC file.
++ This can be used to alter the content of the RC file
++ based on the conversion that occured.
++
++For menus, dialogs and version info, the .grd file will refer to the original
++.rc file. Once conversion is complete, you can strip the original .rc file
++of its string table and all comments as these will be available in the .grd
++file.
++
++Note that this tool WILL NOT obey C preprocessor rules, so even if something
++is #if 0-ed out it will still be included in the output of this tool
++Therefore, if your .rc file contains sections like this, you should run the
++C preprocessor on the .rc file or manually edit it before using this tool.
++'''
++
++ def ShortDescription(self):
++ return 'A tool for converting .rc source files to .grd files.'
++
++ def __init__(self):
++ self.input_encoding = 'cp1252'
++ self.html_type = 'tr_html'
++ self.html_encoding = 'utf-8'
++ self.not_localizable_re = re.compile('Not locali(s|z)able')
++ self.role_model = None
++ self.pre_process = None
++ self.post_process = None
++
++ def ParseOptions(self, args, help_func=None):
++ '''Given a list of arguments, set this object's options and return
++ all non-option arguments.
++ '''
++ (own_opts, args) = getopt.getopt(args, 'e:h:u:n:r',
++ ('help', 'pre=', 'post='))
++ for (key, val) in own_opts:
++ if key == '-e':
++ self.input_encoding = val
++ elif key == '-h':
++ self.html_type = val
++ elif key == '-u':
++ self.html_encoding = val
++ elif key == '-n':
++ self.not_localizable_re = re.compile(val)
++ elif key == '-r':
++ self.role_model = grd_reader.Parse(val)
++ elif key == '--pre':
++ self.pre_process = val
++ elif key == '--post':
++ self.post_process = val
++ elif key == '--help':
++ if help_func is None:
++ self.ShowUsage()
++ else:
++ help_func()
++ sys.exit(0)
++ return args
++
++ def Run(self, opts, args):
++ args = self.ParseOptions(args)
++ if len(args) != 1:
++ print('This tool takes a single tool-specific argument, the path to the\n'
++ '.rc file to process.')
++ return 2
++ self.SetOptions(opts)
++
++ path = args[0]
++ out_path = os.path.join(util.dirname(path),
++ os.path.splitext(os.path.basename(path))[0] + '.grd')
++
++ rctext = util.ReadFile(path, self.input_encoding)
++ grd_text = six.text_type(self.Process(rctext, path))
++ with util.WrapOutputStream(open(out_path, 'wb'), 'utf-8') as outfile:
++ outfile.write(grd_text)
++
++ print('Wrote output file %s.\nPlease check for TODO items in the file.' %
++ (out_path,))
++
++
++ def Process(self, rctext, rc_path):
++ '''Processes 'rctext' and returns a resource tree corresponding to it.
++
++ Args:
++ rctext: complete text of the rc file
++ rc_path: 'resource\resource.rc'
++
++ Return:
++ grit.node.base.Node subclass
++ '''
++
++ if self.pre_process:
++ preprocess_class = util.NewClassInstance(self.pre_process,
++ preprocess_interface.PreProcessor)
++ if preprocess_class:
++ rctext = preprocess_class.Process(rctext, rc_path)
++ else:
++ self.Out(
++ 'PreProcessing class could not be found. Skipping preprocessing.\n')
++
++ # Start with a basic skeleton for the .grd file
++ root = grd_reader.Parse(StringIO(
++ '''<?xml version="1.0" encoding="UTF-8"?>
++ <grit base_dir="." latest_public_release="0"
++ current_release="1" source_lang_id="en">
++ <outputs />
++ <translations />
++ <release seq="1">
++ <includes />
++ <structures />
++ <messages />
++ </release>
++ </grit>'''), util.dirname(rc_path))
++ includes = root.children[2].children[0]
++ structures = root.children[2].children[1]
++ messages = root.children[2].children[2]
++ assert (isinstance(includes, grit.node.empty.IncludesNode) and
++ isinstance(structures, grit.node.empty.StructuresNode) and
++ isinstance(messages, grit.node.empty.MessagesNode))
++
++ self.AddIncludes(rctext, includes)
++ self.AddStructures(rctext, structures, os.path.basename(rc_path))
++ self.AddMessages(rctext, messages)
++
++ self.VerboseOut('Validating that all IDs are unique...\n')
++ root.ValidateUniqueIds()
++ self.ExtraVerboseOut('Done validating that all IDs are unique.\n')
++
++ if self.post_process:
++ postprocess_class = util.NewClassInstance(self.post_process,
++ postprocess_interface.PostProcessor)
++ if postprocess_class:
++ root = postprocess_class.Process(rctext, rc_path, root)
++ else:
++ self.Out(
++ 'PostProcessing class could not be found. Skipping postprocessing.\n')
++
++ return root
++
++
++ def IsHtml(self, res_type, fname):
++ '''Check whether both the type and file extension indicate HTML'''
++ fext = fname.split('.')[-1].lower()
++ return res_type == 'HTML' and fext in ('htm', 'html')
++
++
++ def AddIncludes(self, rctext, node):
++ '''Scans 'rctext' for included resources (e.g. BITMAP, ICON) and
++ adds each included resource as an <include> child node of 'node'.'''
++ for m in _FILE_REF.finditer(rctext):
++ id = m.group('id')
++ res_type = m.group('type').upper()
++ fname = rc.Section.UnEscape(m.group('file'))
++ assert fname.find('\n') == -1
++ if not self.IsHtml(res_type, fname):
++ self.VerboseOut('Processing %s with ID %s (filename: %s)\n' %
++ (res_type, id, fname))
++ node.AddChild(include.IncludeNode.Construct(node, id, res_type, fname))
++
++
++ def AddStructures(self, rctext, node, rc_filename):
++ '''Scans 'rctext' for structured resources (e.g. menus, dialogs, version
++ information resources and HTML templates) and adds each as a <structure>
++ child of 'node'.'''
++ # First add HTML includes
++ for m in _FILE_REF.finditer(rctext):
++ id = m.group('id')
++ res_type = m.group('type').upper()
++ fname = rc.Section.UnEscape(m.group('file'))
++ if self.IsHtml(type, fname):
++ node.AddChild(structure.StructureNode.Construct(
++ node, id, self.html_type, fname, self.html_encoding))
++
++ # Then add all RC includes
++ def AddStructure(res_type, id):
++ self.VerboseOut('Processing %s with ID %s\n' % (res_type, id))
++ node.AddChild(structure.StructureNode.Construct(node, id, res_type,
++ rc_filename,
++ encoding=self.input_encoding))
++ for m in _MENU.finditer(rctext):
++ AddStructure('menu', m.group('id'))
++ for m in _DIALOG.finditer(rctext):
++ AddStructure('dialog', m.group('id'))
++ for m in _VERSIONINFO.finditer(rctext):
++ AddStructure('version', m.group('id'))
++
++
++ def AddMessages(self, rctext, node):
++ '''Scans 'rctext' for all messages in string tables, preprocesses them as
++ much as possible for placeholders (e.g. messages containing $1, $2 or %s, %d
++ type format specifiers get those specifiers replaced with placeholders, and
++ HTML-formatted messages get run through the HTML-placeholderizer). Adds
++ each message as a <message> node child of 'node'.'''
++ for tm in _STRING_TABLE.finditer(rctext):
++ table = tm.group('body')
++ for mm in _MESSAGE.finditer(table):
++ comment_block = mm.group('comment')
++ comment_text = []
++ for cm in _COMMENT_TEXT.finditer(comment_block):
++ comment_text.append(cm.group('text'))
++ comment_text = ' '.join(comment_text)
++
++ id = mm.group('id')
++ text = rc.Section.UnEscape(mm.group('text'))
++
++ self.VerboseOut('Processing message %s (text: "%s")\n' % (id, text))
++
++ msg_obj = self.Placeholderize(text)
++
++ # Messages that contain only placeholders do not need translation.
++ is_translateable = False
++ for item in msg_obj.GetContent():
++ if isinstance(item, six.string_types):
++ if not _WHITESPACE_ONLY.match(item):
++ is_translateable = True
++
++ if self.not_localizable_re.search(comment_text):
++ is_translateable = False
++
++ message_meaning = ''
++ internal_comment = ''
++
++ # If we have a "role model" (existing GRD file) and this node exists
++ # in the role model, use the description, meaning and translateable
++ # attributes from the role model.
++ if self.role_model:
++ role_node = self.role_model.GetNodeById(id)
++ if role_node:
++ is_translateable = role_node.IsTranslateable()
++ message_meaning = role_node.attrs['meaning']
++ comment_text = role_node.attrs['desc']
++ internal_comment = role_node.attrs['internal_comment']
++
++ # For nontranslateable messages, we don't want the complexity of
++ # placeholderizing everything.
++ if not is_translateable:
++ msg_obj = tclib.Message(text=text)
++
++ msg_node = message.MessageNode.Construct(node, msg_obj, id,
++ desc=comment_text,
++ translateable=is_translateable,
++ meaning=message_meaning)
++ msg_node.attrs['internal_comment'] = internal_comment
++
++ node.AddChild(msg_node)
++ self.ExtraVerboseOut('Done processing message %s\n' % id)
++
++
++ def Placeholderize(self, text):
++ '''Creates a tclib.Message object from 'text', attempting to recognize
++ a few different formats of text that can be automatically placeholderized
++ (HTML code, printf-style format strings, and FormatMessage-style format
++ strings).
++ '''
++
++ try:
++ # First try HTML placeholderizing.
++ # TODO(joi) Allow use of non-TotalRecall flavors of HTML placeholderizing
++ msg = tr_html.HtmlToMessage(text, True)
++ for item in msg.GetContent():
++ if not isinstance(item, six.string_types):
++ return msg # Contained at least one placeholder, so we're done
++
++ # HTML placeholderization didn't do anything, so try to find printf or
++ # FormatMessage format specifiers and change them into placeholders.
++ msg = tclib.Message()
++ parts = _FORMAT_SPECIFIER.split(text)
++ todo_counter = 1 # We make placeholder IDs 'TODO_0001' etc.
++ for part in parts:
++ if _FORMAT_SPECIFIER.match(part):
++ msg.AppendPlaceholder(tclib.Placeholder(
++ 'TODO_%04d' % todo_counter, part, 'TODO'))
++ todo_counter += 1
++ elif part != '':
++ msg.AppendText(part)
++
++ if self.role_model and len(parts) > 1: # there are TODO placeholders
++ role_model_msg = self.role_model.UberClique().BestCliqueByOriginalText(
++ msg.GetRealContent(), '')
++ if role_model_msg:
++ # replace wholesale to get placeholder names and examples
++ msg = role_model_msg
++
++ return msg
++ except:
++ print('Exception processing message with text "%s"' % text)
++ raise
+diff --git a/tools/grit/grit/tool/rc2grd_unittest.py b/tools/grit/grit/tool/rc2grd_unittest.py
+new file mode 100644
+index 0000000000..6d53794c27
+--- /dev/null
++++ b/tools/grit/grit/tool/rc2grd_unittest.py
+@@ -0,0 +1,163 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit tests for grit.tool.rc2grd'''
++
++from __future__ import print_function
++
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++import re
++import unittest
++
++from six import StringIO
++
++from grit import grd_reader
++from grit import util
++from grit.node import base
++from grit.tool import rc2grd
++
++
++class Rc2GrdUnittest(unittest.TestCase):
++ def testPlaceholderize(self):
++ tool = rc2grd.Rc2Grd()
++ original = "Hello %s, how are you? I'm $1 years old!"
++ msg = tool.Placeholderize(original)
++ self.failUnless(msg.GetPresentableContent() == "Hello TODO_0001, how are you? I'm TODO_0002 years old!")
++ self.failUnless(msg.GetRealContent() == original)
++
++ def testHtmlPlaceholderize(self):
++ tool = rc2grd.Rc2Grd()
++ original = "Hello <b>[USERNAME]</b>, how are you? I'm [AGE] years old!"
++ msg = tool.Placeholderize(original)
++ self.failUnless(msg.GetPresentableContent() ==
++ "Hello BEGIN_BOLDX_USERNAME_XEND_BOLD, how are you? I'm X_AGE_X years old!")
++ self.failUnless(msg.GetRealContent() == original)
++
++ def testMenuWithoutWhitespaceRegression(self):
++ # There was a problem in the original regular expression for parsing out
++ # menu sections, that would parse the following block of text as a single
++ # menu instead of two.
++ two_menus = '''
++// Hyper context menus
++IDR_HYPERMENU_FOLDER MENU
++BEGIN
++ POPUP "HyperFolder"
++ BEGIN
++ MENUITEM "Open Containing Folder", IDM_OPENFOLDER
++ END
++END
++
++IDR_HYPERMENU_FILE MENU
++BEGIN
++ POPUP "HyperFile"
++ BEGIN
++ MENUITEM "Open Folder", IDM_OPENFOLDER
++ END
++END
++
++'''
++ self.failUnless(len(rc2grd._MENU.findall(two_menus)) == 2)
++
++ def testRegressionScriptWithTranslateable(self):
++ tool = rc2grd.Rc2Grd()
++
++ # test rig
++ class DummyNode(base.Node):
++ def AddChild(self, item):
++ self.node = item
++ verbose = False
++ extra_verbose = False
++ tool.not_localizable_re = re.compile('')
++ tool.o = DummyNode()
++
++ rc_text = '''STRINGTABLE\nBEGIN\nID_BINGO "<SPAN id=hp style='BEHAVIOR: url(#default#homepage)'></SPAN><script>if (!hp.isHomePage('[$~HOMEPAGE~$]')) {document.write(""<a href=\\""[$~SETHOMEPAGEURL~$]\\"" >Set As Homepage</a> - "");}</script>"\nEND\n'''
++ tool.AddMessages(rc_text, tool.o)
++ self.failUnless(tool.o.node.GetCdata().find('Set As Homepage') != -1)
++
++ # TODO(joi) Improve the HTML parser to support translateables inside
++ # <script> blocks?
++ self.failUnless(tool.o.node.attrs['translateable'] == 'false')
++
++ def testRoleModel(self):
++ rc_text = ('STRINGTABLE\n'
++ 'BEGIN\n'
++ ' // This should not show up\n'
++ ' IDS_BINGO "Hello %s, how are you?"\n'
++ ' // The first description\n'
++ ' IDS_BONGO "Hello %s, my name is %s, and yours?"\n'
++ ' IDS_PROGRAMS_SHUTDOWN_TEXT "Google Desktop Search needs to close the following programs:\\n\\n$1\\nThe installation will not proceed if you choose to cancel."\n'
++ 'END\n')
++ tool = rc2grd.Rc2Grd()
++ tool.role_model = grd_reader.Parse(StringIO(
++ '''<?xml version="1.0" encoding="UTF-8"?>
++ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
++ <release seq="3">
++ <messages>
++ <message name="IDS_BINGO">
++ Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, how are you?
++ </message>
++ <message name="IDS_BONGO" desc="The other description">
++ Hello <ph name="USERNAME">%s<ex>Jakob</ex></ph>, my name is <ph name="ADMINNAME">%s<ex>Joi</ex></ph>, and yours?
++ </message>
++ <message name="IDS_PROGRAMS_SHUTDOWN_TEXT" desc="LIST_OF_PROGRAMS is replaced by a bulleted list of program names.">
++ Google Desktop Search needs to close the following programs:
++
++<ph name="LIST_OF_PROGRAMS">$1<ex>Program 1, Program 2</ex></ph>
++The installation will not proceed if you choose to cancel.
++ </message>
++ </messages>
++ </release>
++ </grit>'''), dir='.')
++
++ # test rig
++ class DummyOpts(object):
++ verbose = False
++ extra_verbose = False
++ tool.o = DummyOpts()
++ result = tool.Process(rc_text, '.\resource.rc')
++ self.failUnless(
++ result.children[2].children[2].children[0].attrs['desc'] == '')
++ self.failUnless(
++ result.children[2].children[2].children[0].children[0].attrs['name'] == 'USERNAME')
++ self.failUnless(
++ result.children[2].children[2].children[1].attrs['desc'] == 'The other description')
++ self.failUnless(
++ result.children[2].children[2].children[1].attrs['meaning'] == '')
++ self.failUnless(
++ result.children[2].children[2].children[1].children[0].attrs['name'] == 'USERNAME')
++ self.failUnless(
++ result.children[2].children[2].children[1].children[1].attrs['name'] == 'ADMINNAME')
++ self.failUnless(
++ result.children[2].children[2].children[2].children[0].attrs['name'] == 'LIST_OF_PROGRAMS')
++
++ def testRunOutput(self):
++ """Verify basic correct Run behavior."""
++ tool = rc2grd.Rc2Grd()
++ class DummyOpts(object):
++ verbose = False
++ extra_verbose = False
++ with util.TempDir({}) as output_dir:
++ rcfile = os.path.join(output_dir.GetPath(), 'foo.rc')
++ open(rcfile, 'w').close()
++ self.assertIsNone(tool.Run(DummyOpts(), [rcfile]))
++ self.assertTrue(os.path.exists(os.path.join(output_dir.GetPath(), 'foo.grd')))
++
++ def testMissingOutput(self):
++ """Verify failure with no args."""
++ tool = rc2grd.Rc2Grd()
++ class DummyOpts(object):
++ verbose = False
++ extra_verbose = False
++ ret = tool.Run(DummyOpts(), [])
++ self.assertIsNotNone(ret)
++ self.assertGreater(ret, 0)
++
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/tool/resize.py b/tools/grit/grit/tool/resize.py
+new file mode 100644
+index 0000000000..6a897c077e
+--- /dev/null
++++ b/tools/grit/grit/tool/resize.py
+@@ -0,0 +1,295 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''The 'grit resize' tool.
++'''
++
++from __future__ import print_function
++
++import getopt
++import os
++import sys
++
++from grit import grd_reader
++from grit import pseudo
++from grit import util
++from grit.format import rc
++from grit.format import rc_header
++from grit.node import include
++from grit.tool import interface
++
++
++# Template for the .vcproj file, with a couple of [[REPLACEABLE]] parts.
++PROJECT_TEMPLATE = '''\
++<?xml version="1.0" encoding="Windows-1252"?>
++<VisualStudioProject
++ ProjectType="Visual C++"
++ Version="7.10"
++ Name="[[DIALOG_NAME]]"
++ ProjectGUID="[[PROJECT_GUID]]"
++ Keyword="Win32Proj">
++ <Platforms>
++ <Platform
++ Name="Win32"/>
++ </Platforms>
++ <Configurations>
++ <Configuration
++ Name="Debug|Win32"
++ OutputDirectory="Debug"
++ IntermediateDirectory="Debug"
++ ConfigurationType="1"
++ CharacterSet="2">
++ </Configuration>
++ </Configurations>
++ <References>
++ </References>
++ <Files>
++ <Filter
++ Name="Resource Files"
++ Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx"
++ UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}">
++ <File
++ RelativePath=".\\[[DIALOG_NAME]].rc">
++ </File>
++ </Filter>
++ </Files>
++ <Globals>
++ </Globals>
++</VisualStudioProject>'''
++
++
++# Template for the .rc file with a couple of [[REPLACEABLE]] parts.
++# TODO(joi) Improve this (and the resource.h template) to allow saving and then
++# reopening of the RC file in Visual Studio. Currently you can only open it
++# once and change it, then after you close it you won't be able to reopen it.
++RC_TEMPLATE = '''\
++// This file is automatically generated by GRIT and intended for editing
++// the layout of the dialogs contained in it. Do not edit anything but the
++// dialogs. Any changes made to translateable portions of the dialogs will
++// be ignored by GRIT.
++
++#include "resource.h"
++#include <winresrc.h>
++#ifdef IDC_STATIC
++#undef IDC_STATIC
++#endif
++#define IDC_STATIC (-1)
++
++LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
++
++#pragma code_page([[CODEPAGE_NUM]])
++
++[[INCLUDES]]
++
++[[DIALOGS]]
++'''
++
++
++# Template for the resource.h file with a couple of [[REPLACEABLE]] parts.
++HEADER_TEMPLATE = '''\
++// This file is automatically generated by GRIT. Do not edit.
++
++#pragma once
++
++// Edit commands
++#define ID_EDIT_CLEAR 0xE120
++#define ID_EDIT_CLEAR_ALL 0xE121
++#define ID_EDIT_COPY 0xE122
++#define ID_EDIT_CUT 0xE123
++#define ID_EDIT_FIND 0xE124
++#define ID_EDIT_PASTE 0xE125
++#define ID_EDIT_PASTE_LINK 0xE126
++#define ID_EDIT_PASTE_SPECIAL 0xE127
++#define ID_EDIT_REPEAT 0xE128
++#define ID_EDIT_REPLACE 0xE129
++#define ID_EDIT_SELECT_ALL 0xE12A
++#define ID_EDIT_UNDO 0xE12B
++#define ID_EDIT_REDO 0xE12C
++
++
++[[DEFINES]]
++'''
++
++
++class ResizeDialog(interface.Tool):
++ '''Generates an RC file, header and Visual Studio project that you can use
++with Visual Studio's GUI resource editor to modify the layout of dialogs for
++the language of your choice. You then use the RC file, after you resize the
++dialog, for the language or languages of your choice, using the <skeleton> child
++of the <structure> node for the dialog. The translateable bits of the dialog
++will be ignored when you use the <skeleton> node (GRIT will instead use the
++translateable bits from the original dialog) but the layout changes you make
++will be used. Note that your layout changes must preserve the order of the
++translateable elements in the RC file.
++
++Usage: grit resize [-f BASEFOLDER] [-l LANG] [-e RCENCODING] DIALOGID*
++
++Arguments:
++ DIALOGID The 'name' attribute of a dialog to output for resizing. Zero
++ or more of these parameters can be used. If none are
++ specified, all dialogs from the input .grd file are output.
++
++Options:
++
++ -f BASEFOLDER The project will be created in a subfolder of BASEFOLDER.
++ The name of the subfolder will be the first DIALOGID you
++ specify. Defaults to '.'
++
++ -l LANG Specifies that the RC file should contain a dialog translated
++ into the language LANG. The default is a cp1252-representable
++ pseudotranslation, because Visual Studio's GUI RC editor only
++ supports single-byte encodings.
++
++ -c CODEPAGE Code page number to indicate to the RC compiler the encoding
++ of the RC file, default is something reasonable for the
++ language you selected (but this does not work for every single
++ language). See details on codepages below. NOTE that you do
++ not need to specify the codepage unless the tool complains
++ that it's not sure which codepage to use. See the following
++ page for codepage numbers supported by Windows:
++ http://www.microsoft.com/globaldev/reference/wincp.mspx
++
++ -D NAME[=VAL] Specify a C-preprocessor-like define NAME with optional
++ value VAL (defaults to 1) which will be used to control
++ conditional inclusion of resources.
++
++
++IMPORTANT NOTE: For now, the tool outputs a UTF-8 encoded file for any language
++that can not be represented in cp1252 (i.e. anything other than Western
++European languages). You will need to open this file in a text editor and
++save it using the codepage indicated in the #pragma code_page(XXXX) command
++near the top of the file, before you open it in Visual Studio.
++
++'''
++
++ # TODO(joi) It would be cool to have this tool note the Perforce revision
++ # of the original RC file somewhere, such that the <skeleton> node could warn
++ # if the original RC file gets updated without the skeleton file being updated.
++
++ # TODO(joi) Would be cool to have option to add the files to Perforce
++
++ def __init__(self):
++ self.lang = pseudo.PSEUDO_LANG
++ self.defines = {}
++ self.base_folder = '.'
++ self.codepage_number = 1252
++ self.codepage_number_specified_explicitly = False
++
++ def SetLanguage(self, lang):
++ '''Sets the language code to output things in.
++ '''
++ self.lang = lang
++ if not self.codepage_number_specified_explicitly:
++ self.codepage_number = util.LanguageToCodepage(lang)
++
++ def GetEncoding(self):
++ if self.codepage_number == 1200:
++ return 'utf_16'
++ if self.codepage_number == 65001:
++ return 'utf_8'
++ return 'cp%d' % self.codepage_number
++
++ def ShortDescription(self):
++ return 'Generate a file where you can resize a given dialog.'
++
++ def Run(self, opts, args):
++ self.SetOptions(opts)
++
++ own_opts, args = getopt.getopt(args, 'l:f:c:D:', ('help',))
++ for key, val in own_opts:
++ if key == '-l':
++ self.SetLanguage(val)
++ if key == '-f':
++ self.base_folder = val
++ if key == '-c':
++ self.codepage_number = int(val)
++ self.codepage_number_specified_explicitly = True
++ if key == '-D':
++ name, val = util.ParseDefine(val)
++ self.defines[name] = val
++ elif key == '--help':
++ self.ShowUsage()
++ sys.exit(0)
++
++ res_tree = grd_reader.Parse(opts.input, debug=opts.extra_verbose)
++ res_tree.OnlyTheseTranslations([self.lang])
++ res_tree.RunGatherers()
++
++ # Dialog IDs are either explicitly listed, or we output all dialogs from the
++ # .grd file
++ dialog_ids = args
++ if not len(dialog_ids):
++ for node in res_tree:
++ if node.name == 'structure' and node.attrs['type'] == 'dialog':
++ dialog_ids.append(node.attrs['name'])
++
++ self.Process(res_tree, dialog_ids)
++
++ def Process(self, grd, dialog_ids):
++ '''Outputs an RC file and header file for the dialog 'dialog_id' stored in
++ resource tree 'grd', to self.base_folder, as discussed in this class's
++ documentation.
++
++ Arguments:
++ grd: grd = grd_reader.Parse(...); grd.RunGatherers()
++ dialog_ids: ['IDD_MYDIALOG', 'IDD_OTHERDIALOG']
++ '''
++ grd.SetOutputLanguage(self.lang)
++ grd.SetDefines(self.defines)
++
++ project_name = dialog_ids[0]
++
++ dir_path = os.path.join(self.base_folder, project_name)
++ if not os.path.isdir(dir_path):
++ os.mkdir(dir_path)
++
++ # If this fails then we're not on Windows (or you don't have the required
++ # win32all Python libraries installed), so what are you doing mucking
++ # about with RC files anyway? :)
++ # pylint: disable=import-error
++ import pythoncom
++
++ # Create the .vcproj file
++ project_text = PROJECT_TEMPLATE.replace(
++ '[[PROJECT_GUID]]', str(pythoncom.CreateGuid())
++ ).replace('[[DIALOG_NAME]]', project_name)
++ fname = os.path.join(dir_path, '%s.vcproj' % project_name)
++ self.WriteFile(fname, project_text)
++ print("Wrote %s" % fname)
++
++ # Create the .rc file
++ # Output all <include> nodes since the dialogs might depend on them (e.g.
++ # for icons and bitmaps).
++ include_items = []
++ for node in grd.ActiveDescendants():
++ if isinstance(node, include.IncludeNode):
++ include_items.append(rc.FormatInclude(node, self.lang, '.'))
++ rc_text = RC_TEMPLATE.replace('[[CODEPAGE_NUM]]',
++ str(self.codepage_number))
++ rc_text = rc_text.replace('[[INCLUDES]]', ''.join(include_items))
++
++ # Then output the dialogs we have been asked to output.
++ dialogs = []
++ for dialog_id in dialog_ids:
++ node = grd.GetNodeById(dialog_id)
++ assert node.name == 'structure' and node.attrs['type'] == 'dialog'
++ # TODO(joi) Add exception handling for better error reporting
++ dialogs.append(rc.FormatStructure(node, self.lang, '.'))
++ rc_text = rc_text.replace('[[DIALOGS]]', ''.join(dialogs))
++
++ fname = os.path.join(dir_path, '%s.rc' % project_name)
++ self.WriteFile(fname, rc_text, self.GetEncoding())
++ print("Wrote %s" % fname)
++
++ # Create the resource.h file
++ header_defines = ''.join(rc_header.FormatDefines(grd))
++ header_text = HEADER_TEMPLATE.replace('[[DEFINES]]', header_defines)
++ fname = os.path.join(dir_path, 'resource.h')
++ self.WriteFile(fname, header_text)
++ print("Wrote %s" % fname)
++
++ def WriteFile(self, filename, contents, encoding='cp1252'):
++ with open(filename, 'wb') as f:
++ writer = util.WrapOutputStream(f, encoding)
++ writer.write(contents)
+diff --git a/tools/grit/grit/tool/test.py b/tools/grit/grit/tool/test.py
+new file mode 100644
+index 0000000000..241a976d74
+--- /dev/null
++++ b/tools/grit/grit/tool/test.py
+@@ -0,0 +1,24 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++from __future__ import print_function
++
++from grit.tool import interface
++
++class TestTool(interface.Tool):
++ '''This tool does nothing except print out the global options and
++tool-specific arguments that it receives. It is intended only for testing,
++hence the name :)
++'''
++
++ def ShortDescription(self):
++ return 'A do-nothing tool for testing command-line parsing.'
++
++ def Run(self, global_options, my_arguments):
++ print('NOTE This tool is only for testing the parsing of global options and')
++ print('tool-specific arguments that it receives. You may have intended to')
++ print('run "grit unit" which is the unit-test suite for GRIT.')
++ print('Options: %s' % repr(global_options))
++ print('Arguments: %s' % repr(my_arguments))
+ return 0
+diff --git a/tools/grit/grit/tool/transl2tc.py b/tools/grit/grit/tool/transl2tc.py
+new file mode 100644
+index 0000000000..45301bbf58
+--- /dev/null
++++ b/tools/grit/grit/tool/transl2tc.py
+@@ -0,0 +1,251 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
+
-+ # This script is called by gclient. gclient opens its hooks subprocesses with
-+ # (stdout=subprocess.PIPE, stderr=subprocess.STDOUT) and then does custom
-+ # output processing that breaks printing '\r' characters for single-line
-+ # updating status messages as printed by curl and wget.
-+ # Work around this by setting stderr of the update.sh process to stdin (!):
-+ # gclient doesn't redirect stdin, and while stdin itself is read-only, a
-+ # dup()ed sys.stdin is writable, try
-+ # fd2 = os.dup(sys.stdin.fileno()); os.write(fd2, 'hi')
-+ # TODO: Fix gclient instead, http://crbug.com/95350
-+ return subprocess.call(
-+ [os.path.join(os.path.dirname(__file__), 'update.sh')] + sys.argv[1:],
-+ stderr=os.fdopen(os.dup(sys.stdin.fileno())))
++'''The 'grit transl2tc' tool.
++'''
++
++from __future__ import print_function
++
++from grit import grd_reader
++from grit import util
++from grit.tool import interface
++from grit.tool import rc2grd
++
++from grit.extern import tclib
++
++
++class TranslationToTc(interface.Tool):
++ '''A tool for importing existing translations in RC format into the
++Translation Console.
++
++Usage:
++
++grit -i GRD transl2tc [-l LIMITS] [RCOPTS] SOURCE_RC TRANSLATED_RC OUT_FILE
++
++The tool needs a "source" RC file, i.e. in English, and an RC file that is a
++translation of precisely the source RC file (not of an older or newer version).
++
++The tool also requires you to provide a .grd file (input file) e.g. using the
++-i global option or the GRIT_INPUT environment variable. The tool uses
++information from your .grd file to correct placeholder names in the
++translations and ensure that only translatable items and translations still
++being used are output.
++
++This tool will accept all the same RCOPTS as the 'grit rc2grd' tool. To get
++a list of these options, run 'grit help rc2grd'.
++
++Additionally, you can use the -l option (which must be the first option to the
++tool) to specify a file containing a list of message IDs to which output should
++be limited. This is only useful if you are limiting the output to your XMB
++files using the 'grit xmb' tool's -l option. See 'grit help xmb' for how to
++generate a file containing a list of the message IDs in an XMB file.
++
++The tool will scan through both of the RC files as well as any HTML files they
++refer to, and match together the source messages and translated messages. It
++will output a file (OUTPUT_FILE) you can import directly into the TC using the
++Bulk Translation Upload tool.
++'''
++
++ def ShortDescription(self):
++ return 'Import existing translations in RC format into the TC'
++
++ def Setup(self, globopt, args):
++ '''Sets the instance up for use.
++ '''
++ self.SetOptions(globopt)
++ self.rc2grd = rc2grd.Rc2Grd()
++ self.rc2grd.SetOptions(globopt)
++ self.limits = None
++ if len(args) and args[0] == '-l':
++ self.limits = util.ReadFile(args[1], 'utf-8').splitlines()
++ args = args[2:]
++ return self.rc2grd.ParseOptions(args, help_func=self.ShowUsage)
++
++ def Run(self, globopt, args):
++ args = self.Setup(globopt, args)
++
++ if len(args) != 3:
++ self.Out('This tool takes exactly three arguments:\n'
++ ' 1. The path to the original RC file\n'
++ ' 2. The path to the translated RC file\n'
++ ' 3. The output file path.\n')
++ return 2
++
++ grd = grd_reader.Parse(self.o.input, debug=self.o.extra_verbose)
++ grd.RunGatherers()
++
++ source_rc = util.ReadFile(args[0], self.rc2grd.input_encoding)
++ transl_rc = util.ReadFile(args[1], self.rc2grd.input_encoding)
++ translations = self.ExtractTranslations(grd,
++ source_rc, args[0],
++ transl_rc, args[1])
++
++ with util.WrapOutputStream(open(args[2], 'wb')) as output_file:
++ self.WriteTranslations(output_file, translations.items())
++
++ self.Out('Wrote output file %s' % args[2])
++
++ def ExtractTranslations(self, current_grd, source_rc, source_path,
++ transl_rc, transl_path):
++ '''Extracts translations from the translated RC file, matching them with
++ translations in the source RC file to calculate their ID, and correcting
++ placeholders, limiting output to translateables, etc. using the supplied
++ .grd file which is the current .grd file for your project.
++
++ If this object's 'limits' attribute is not None but a list, the output of
++ this function will be further limited to include only messages that have
++ message IDs in the 'limits' list.
++
++ Args:
++ current_grd: grit.node.base.Node child, that has had RunGatherers() run
++ on it
++ source_rc: Complete text of source RC file
++ source_path: Path to the source RC file
++ transl_rc: Complete text of translated RC file
++ transl_path: Path to the translated RC file
++
++ Return:
++ { id1 : text1, '12345678' : 'Hello USERNAME, howzit?' }
++ '''
++ source_grd = self.rc2grd.Process(source_rc, source_path)
++ self.VerboseOut('Read %s into GRIT format, running gatherers.\n' % source_path)
++ source_grd.SetOutputLanguage(current_grd.output_language)
++ source_grd.SetDefines(current_grd.defines)
++ source_grd.RunGatherers(debug=self.o.extra_verbose)
++ transl_grd = self.rc2grd.Process(transl_rc, transl_path)
++ transl_grd.SetOutputLanguage(current_grd.output_language)
++ transl_grd.SetDefines(current_grd.defines)
++ self.VerboseOut('Read %s into GRIT format, running gatherers.\n' % transl_path)
++ transl_grd.RunGatherers(debug=self.o.extra_verbose)
++ self.VerboseOut('Done running gatherers for %s.\n' % transl_path)
++
++ # Proceed to create a map from ID to translation, getting the ID from the
++ # source GRD and the translation from the translated GRD.
++ id2transl = {}
++ for source_node in source_grd:
++ source_cliques = source_node.GetCliques()
++ if not len(source_cliques):
++ continue
++
++ assert 'name' in source_node.attrs, 'All nodes with cliques should have an ID'
++ node_id = source_node.attrs['name']
++ self.ExtraVerboseOut('Processing node %s\n' % node_id)
++ transl_node = transl_grd.GetNodeById(node_id)
++
++ if transl_node:
++ transl_cliques = transl_node.GetCliques()
++ if not len(transl_cliques) == len(source_cliques):
++ self.Out(
++ 'Warning: Translation for %s has wrong # of cliques, skipping.\n' %
++ node_id)
++ continue
++ else:
++ self.Out('Warning: No translation for %s, skipping.\n' % node_id)
++ continue
++
++ if source_node.name == 'message':
++ # Fixup placeholders as well as possible based on information from
++ # the current .grd file if they are 'TODO_XXXX' placeholders. We need
++ # to fixup placeholders in the translated message so that it looks right
++ # and we also need to fixup placeholders in the source message so that
++ # its calculated ID will match the current message.
++ current_node = current_grd.GetNodeById(node_id)
++ if current_node:
++ assert len(source_cliques) == len(current_node.GetCliques()) == 1
++
++ source_msg = source_cliques[0].GetMessage()
++ current_msg = current_node.GetCliques()[0].GetMessage()
++
++ # Only do this for messages whose source version has not changed.
++ if (source_msg.GetRealContent() != current_msg.GetRealContent()):
++ self.VerboseOut('Info: Message %s has changed; skipping\n' % node_id)
++ else:
++ transl_msg = transl_cliques[0].GetMessage()
++ transl_content = transl_msg.GetContent()
++ current_content = current_msg.GetContent()
++ source_content = source_msg.GetContent()
++
++ ok_to_fixup = True
++ if (len(transl_content) != len(current_content)):
++ # message structure of translation is different, don't try fixup
++ ok_to_fixup = False
++ if ok_to_fixup:
++ for ix in range(len(transl_content)):
++ if isinstance(transl_content[ix], tclib.Placeholder):
++ if not isinstance(current_content[ix], tclib.Placeholder):
++ ok_to_fixup = False # structure has changed
++ break
++ if (transl_content[ix].GetOriginal() !=
++ current_content[ix].GetOriginal()):
++ ok_to_fixup = False # placeholders have likely been reordered
++ break
++ else: # translated part is not a placeholder but a string
++ if isinstance(current_content[ix], tclib.Placeholder):
++ ok_to_fixup = False # placeholders have likely been reordered
++ break
++
++ if not ok_to_fixup:
++ self.VerboseOut(
++ 'Info: Structure of message %s has changed; skipping.\n' % node_id)
++ else:
++ def Fixup(content, ix):
++ if (isinstance(content[ix], tclib.Placeholder) and
++ content[ix].GetPresentation().startswith('TODO_')):
++ assert isinstance(current_content[ix], tclib.Placeholder)
++ # Get the placeholder ID and example from the current message
++ content[ix] = current_content[ix]
++ for ix in range(len(transl_content)):
++ Fixup(transl_content, ix)
++ Fixup(source_content, ix)
++
++ # Only put each translation once into the map. Warn if translations
++ # for the same message are different.
++ for ix in range(len(transl_cliques)):
++ source_msg = source_cliques[ix].GetMessage()
++ source_msg.GenerateId() # needed to refresh ID based on new placeholders
++ message_id = source_msg.GetId()
++ translated_content = transl_cliques[ix].GetMessage().GetPresentableContent()
++
++ if message_id in id2transl:
++ existing_translation = id2transl[message_id]
++ if existing_translation != translated_content:
++ original_text = source_cliques[ix].GetMessage().GetPresentableContent()
++ self.Out('Warning: Two different translations for "%s":\n'
++ ' Translation 1: "%s"\n'
++ ' Translation 2: "%s"\n' %
++ (original_text, existing_translation, translated_content))
++ else:
++ id2transl[message_id] = translated_content
++
++ # Remove translations for messages that do not occur in the current .grd
++ # or have been marked as not translateable, or do not occur in the 'limits'
++ # list (if it has been set).
++ current_message_ids = current_grd.UberClique().AllMessageIds()
++ for message_id in list(id2transl.keys()):
++ if (message_id not in current_message_ids or
++ not current_grd.UberClique().BestClique(message_id).IsTranslateable() or
++ (self.limits and message_id not in self.limits)):
++ del id2transl[message_id]
++
++ return id2transl
++
++ @staticmethod
++ def WriteTranslations(output_file, translations):
++ '''Writes the provided list of translations to the provided output file
++ in the format used by the TC's Bulk Translation Upload tool. The file
++ must be UTF-8 encoded.
++
++ Args:
++ output_file: util.WrapOutputStream(open('bingo.out', 'wb'))
++ translations: [ [id1, text1], ['12345678', 'Hello USERNAME, howzit?'] ]
++
++ Return:
++ None
++ '''
++ for id, text in translations:
++ text = text.replace('<', '&lt;').replace('>', '&gt;')
++ output_file.write(id)
++ output_file.write(' ')
++ output_file.write(text)
++ output_file.write('\n')
+diff --git a/tools/grit/grit/tool/transl2tc_unittest.py b/tools/grit/grit/tool/transl2tc_unittest.py
+new file mode 100644
+index 0000000000..22e937f9f2
+--- /dev/null
++++ b/tools/grit/grit/tool/transl2tc_unittest.py
+@@ -0,0 +1,133 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit tests for the 'grit transl2tc' tool.'''
++
++from __future__ import print_function
++
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++import unittest
++
++from six import StringIO
++
++from grit.tool import transl2tc
++from grit import grd_reader
++from grit import util
++
++
++def MakeOptions():
++ from grit import grit_runner
++ return grit_runner.Options()
++
++
++class TranslationToTcUnittest(unittest.TestCase):
++
++ def testOutput(self):
++ buf = StringIO()
++ tool = transl2tc.TranslationToTc()
++ translations = [
++ ['1', 'Hello USERNAME, how are you?'],
++ ['12', 'Howdie doodie!'],
++ ['123', 'Hello\n\nthere\n\nhow are you?'],
++ ['1234', 'Hello is > goodbye but < howdie pardner'],
++ ]
++ tool.WriteTranslations(buf, translations)
++ output = buf.getvalue()
++ self.failUnless(output.strip() == '''
++1 Hello USERNAME, how are you?
++12 Howdie doodie!
++123 Hello
++
++there
++
++how are you?
++1234 Hello is &gt; goodbye but &lt; howdie pardner
++'''.strip())
++
++ def testExtractTranslations(self):
++ path = util.PathFromRoot('grit/testdata')
++ current_grd = grd_reader.Parse(StringIO('''<?xml version="1.0" encoding="UTF-8"?>
++ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
++ <release seq="3">
++ <messages>
++ <message name="IDS_SIMPLE">
++ One
++ </message>
++ <message name="IDS_PLACEHOLDER">
++ <ph name="NUMBIRDS">%s<ex>3</ex></ph> birds
++ </message>
++ <message name="IDS_PLACEHOLDERS">
++ <ph name="ITEM">%d<ex>1</ex></ph> of <ph name="COUNT">%d<ex>3</ex></ph>
++ </message>
++ <message name="IDS_REORDERED_PLACEHOLDERS">
++ <ph name="ITEM">$1<ex>1</ex></ph> of <ph name="COUNT">$2<ex>3</ex></ph>
++ </message>
++ <message name="IDS_CHANGED">
++ This is the new version
++ </message>
++ <message name="IDS_TWIN_1">Hello</message>
++ <message name="IDS_TWIN_2">Hello</message>
++ <message name="IDS_NOT_TRANSLATEABLE" translateable="false">:</message>
++ <message name="IDS_LONGER_TRANSLATED">
++ Removed document <ph name="FILENAME">$1<ex>c:\temp</ex></ph>
++ </message>
++ <message name="IDS_DIFFERENT_TWIN_1">Howdie</message>
++ <message name="IDS_DIFFERENT_TWIN_2">Howdie</message>
++ </messages>
++ <structures>
++ <structure type="dialog" name="IDD_ABOUTBOX" encoding="utf-16" file="klonk.rc" />
++ <structure type="menu" name="IDC_KLONKMENU" encoding="utf-16" file="klonk.rc" />
++ </structures>
++ </release>
++ </grit>'''), path)
++ current_grd.SetOutputLanguage('en')
++ current_grd.RunGatherers()
++
++ source_rc_path = util.PathFromRoot('grit/testdata/source.rc')
++ source_rc = util.ReadFile(source_rc_path, 'utf-8')
++ transl_rc_path = util.PathFromRoot('grit/testdata/transl.rc')
++ transl_rc = util.ReadFile(transl_rc_path, 'utf-8')
++
++ tool = transl2tc.TranslationToTc()
++ output_buf = StringIO()
++ globopts = MakeOptions()
++ globopts.verbose = True
++ globopts.output_stream = output_buf
++ tool.Setup(globopts, [])
++ translations = tool.ExtractTranslations(current_grd,
++ source_rc, source_rc_path,
++ transl_rc, transl_rc_path)
++
++ values = list(translations.values())
++ output = output_buf.getvalue()
++
++ self.failUnless('Ein' in values)
++ self.failUnless('NUMBIRDS Vogeln' in values)
++ self.failUnless('ITEM von COUNT' in values)
++ self.failUnless(values.count('Hallo') == 1)
++ self.failIf('Dass war die alte Version' in values)
++ self.failIf(':' in values)
++ self.failIf('Dokument FILENAME ist entfernt worden' in values)
++ self.failIf('Nicht verwendet' in values)
++ self.failUnless(('Howdie' in values or 'Hallo sagt man' in values) and not
++ ('Howdie' in values and 'Hallo sagt man' in values))
++
++ self.failUnless('XX01XX&SkraXX02XX&HaettaXX03XXThetta er "Klonk" sem eg fylaXX04XXgonkurinnXX05XXKlonk && er [good]XX06XX&HjalpXX07XX&Um...XX08XX' in values)
++
++ self.failUnless('I lagi' in values)
++
++ self.failUnless(output.count('Structure of message IDS_REORDERED_PLACEHOLDERS has changed'))
++ self.failUnless(output.count('Message IDS_CHANGED has changed'))
++ self.failUnless(output.count('Structure of message IDS_LONGER_TRANSLATED has changed'))
++ self.failUnless(output.count('Two different translations for "Howdie"'))
++ self.failUnless(output.count('IDD_DIFFERENT_LENGTH_IN_TRANSL has wrong # of cliques'))
+
+
+if __name__ == '__main__':
-+ sys.exit(main())
-diff --git a/tools/clang/scripts/update.sh b/tools/clang/scripts/update.sh
-new file mode 100755
-index 0000000000..e9448236c8
++ unittest.main()
+diff --git a/tools/grit/grit/tool/unit.py b/tools/grit/grit/tool/unit.py
+new file mode 100644
+index 0000000000..7e96b699c3
+--- /dev/null
++++ b/tools/grit/grit/tool/unit.py
+@@ -0,0 +1,43 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''GRIT tool that runs the unit test suite for GRIT.'''
++
++from __future__ import print_function
++
++import getopt
++import sys
++import unittest
++
++try:
++ import grit.test_suite_all
++except ImportError:
++ pass
++from grit.tool import interface
++
++
++class UnitTestTool(interface.Tool):
++ '''By using this tool (e.g. 'grit unit') you run all the unit tests for GRIT.
++This happens in the environment that is set up by the basic GRIT runner.'''
++
++ def ShortDescription(self):
++ return 'Use this tool to run all the unit tests for GRIT.'
++
++ def ParseOptions(self, args):
++ """Set this objects and return all non-option arguments."""
++ own_opts, args = getopt.getopt(args, '', ('help',))
++ for key, val in own_opts:
++ if key == '--help':
++ self.ShowUsage()
++ sys.exit(0)
++ return args
++
++ def Run(self, opts, args):
++ args = self.ParseOptions(args)
++ if args:
++ print('This tool takes no arguments.')
++ return 2
++
++ return unittest.TextTestRunner(verbosity=2).run(
++ grit.test_suite_all.TestSuiteAll())
+diff --git a/tools/grit/grit/tool/update_resource_ids/__init__.py b/tools/grit/grit/tool/update_resource_ids/__init__.py
+new file mode 100644
+index 0000000000..3006fbffab
+--- /dev/null
++++ b/tools/grit/grit/tool/update_resource_ids/__init__.py
+@@ -0,0 +1,305 @@
++# Copyright 2019 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++"""Package grit.tool.update_resource_ids
++
++Updates GRID resource_ids from linked GRD files, while preserving structure.
++
++A resource_ids file is a JSON dict (with Python comments) that maps GRD paths
++to *items*. Item order is ignored by GRIT, but is important since it establishes
++a narrative of item dependency (needs non-overlapping IDs) and mutual exclusion
++(allows ID overlap). Example:
++
++{
++ # The first entry in the file, SRCDIR, is special: It is a relative path from
++ # this file to the base of your checkout.
++ "SRCDIR": "../..",
++
++ # First GRD file. This entry is an "Item".
++ "1.grd": {
++ "messages": [400], # "Tag".
++ },
++ # Depends on 1.grd, i.e., 500 >= 400 + (# of IDs used in 1.grd).
++ "2a.grd": {
++ "includes": [500], # "Tag".
++ "structures": [510], # "Tag" (etc.).
++ },
++ # Depends on 2a.grd.
++ "3a.grd": {
++ "includes": [1000],
++ },
++ # Depends on 2a.grd, but overlaps with 3b.grd due to mutually exclusivity.
++ "3b.grd": {
++ "includes": [1000],
++ },
++ # Depends on {3a.grd, 3b.grd}.
++ "4.grd": {
++ "META": {"join": 2}, # Hint for update_resource_ids.
++ "structures": [1500],
++ },
++ # Depends on 1.grd but overlaps with 2a.grd.
++ "2b.grd": {
++ "includes": [500],
++ "structures": [540],
++ },
++ # Depends on {4.grd, 2b.grd}.
++ "5.grd": {
++ "META": {"join": 2}, # Hint for update_resource_ids.
++ "includes": [600],
++ },
++ # Depends on 5.grd. File is generated, so hint is needed for sizes.
++ "<(SHARED_INTERMEDIATE_DIR)/6.grd": {
++ "META": {"sizes": {"includes": [10]}},
++ "includes": [700],
++ },
++}
++
++The "structure" within a resouces_ids file are as follows:
++1. Comments and spacing.
++2. Item ordering, to establish dependency and grouping.
++3. Special provision to allow ID overlaps from mutual exclusion.
++
++This module parses a resource_ids file, reads ID usages from GRD files it refers
++to, and generates an updated version of the resource_ids file while preserving
++structure elements 1-3 stated above.
++"""
++
++from __future__ import print_function
++
++import collections
++import getopt
++import os
++import shutil
++import sys
++import tempfile
++
++from grit.tool import interface
++from grit.tool.update_resource_ids import assigner, common, parser, reader
++
++
++def _ReadData(input_file):
++ if input_file == '-':
++ data = sys.stdin.read()
++ file_dir = os.getcwd()
++ else:
++ with open(input_file, 'rt') as f:
++ data = f.read()
++ file_dir = os.path.dirname(input_file)
++ return data, file_dir
++
++
++def _MultiReplace(data, repl):
++ """Multi-replacement of text |data| by ranges and replacement text.
++
++ Args:
++ data: Original text.
++ repl: List of (lo, hi, s) tuples, specifying that |data[lo:hi]| should be
++ replaced with |s|. The ranges must be inside |data|, and not overlap.
++ Returns: New text.
++ """
++ res = []
++ prev = 0
++ for (lo, hi, s) in sorted(repl):
++ if prev < lo:
++ res.append(data[prev:lo])
++ res.append(s)
++ prev = hi
++ res.append(data[prev:])
++ return ''.join(res)
++
++
++def _WriteFileIfChanged(output, new_data):
++ if not output:
++ sys.stdout.write(new_data)
++ return
++
++ # Avoid touching outputs if file contents has not changed so that ninja
++ # does not rebuild dependent when not necessary.
++ if os.path.exists(output) and _ReadData(output)[0] == new_data:
++ return
++
++ # Write to a temporary file to ensure atomic changes.
++ with tempfile.NamedTemporaryFile('wt', delete=False) as f:
++ f.write(new_data)
++ shutil.move(f.name, output)
++
++
++class _Args:
++ """Encapsulated arguments for this module."""
++ def __init__(self):
++ self.add_header = False
++ self.analyze_inputs = False
++ self.count = False
++ self.depfile = None
++ self.fake = False
++ self.input = None
++ self.naive = False
++ self.output = None
++ self.parse = False
++ self.tokenize = False
++
++ @staticmethod
++ def Parse(raw_args):
++ own_opts, raw_args = getopt.getopt(raw_args, 'o:cpt', [
++ 'add-header',
++ 'analyze-inputs',
++ 'count',
++ 'depfile=',
++ 'fake',
++ 'naive',
++ 'parse',
++ 'tokenize',
++ ])
++ args = _Args();
++ if not len(raw_args) == 1:
++ print('grit update_resource_ids takes exactly one argument, the path to '
++ 'the resource ids file.')
++ return 2
++ args.input = raw_args[0]
++ for (key, val) in own_opts:
++ if key == '-o':
++ args.output = val
++ elif key == '--add-header':
++ args.add_header = True
++ elif key == '--analyze-inputs':
++ args.analyze_inputs = True
++ elif key in ('--count', '-c'):
++ args.count = True
++ elif key == '--depfile':
++ args.depfile = val
++ elif key == '--fake':
++ args.fake = True
++ elif key == '--naive':
++ args.naive = True
++ elif key in ('--parse', '-p'):
++ args.parse = True
++ elif key in ('--tokenize', '-t'):
++ args.tokenize = True
++ return args
++
++
++class UpdateResourceIds(interface.Tool):
++ """Updates all start IDs in an resource_ids file by reading all GRD files it
++refers to, estimating the number of required IDs of each type, then rewrites
++start IDs while preserving structure.
++
++Usage: grit update_resource_ids [--parse|-p] [--read-grd|-r] [--tokenize|-t]
++ [--naive] [--fake] [-o OUTPUT_FILE]
++ [--analyze-inputs] [--depfile DEPFILE]
++ [--add-header] RESOURCE_IDS_FILE
++
++RESOURCE_IDS_FILE is the path of the input resource_ids file.
++
++The -o option redirects output (default stdout) to OUPTUT_FILE, which can also
++be RESOURCE_IDS_FILE.
++
++Other options:
++
++ -E NAME=VALUE Sets environment variable NAME to VALUE (within grit).
++
++ --count|-c Parses RESOURCE_IDS_FILE, reads the GRD files, and prints
++ required sizes.
++
++ --fake For testing: Skips reading GRD files, and assigns 10 as the
++ usage of every tag.
++
++ --naive Use naive coarse assigner.
++
++ --parse|-p Parses RESOURCE_IDS_FILE and dumps its nodes to console.
++
++ --tokenize|-t Tokenizes RESOURCE_IDS_FILE and reprints it as syntax-
++ highlighted output.
++
++ --depfile=DEPFILE Write out a depfile for ninja to know about dependencies.
++ --analyze-inputs Writes dependencies to stdout.
++ --add-header Adds a "THIS FILE IS GENERATED" header to the output.
++"""
++
++ def __init(self):
++ super(UpdateResourceIds, self).__init__()
++
++ def ShortDescription(self):
++ return 'Updates a resource_ids file based on usage, preserving structure'
++
++ def _DumpTokens(self, data, tok_gen):
++ # Reprint |data| with syntax highlight.
++ color_map = {
++ '#': common.Color.GRAY,
++ 'S': common.Color.CYAN,
++ '0': common.Color.RED,
++ '{': common.Color.YELLOW,
++ '}': common.Color.YELLOW,
++ '[': common.Color.GREEN,
++ ']': common.Color.GREEN,
++ ':': common.Color.MAGENTA,
++ ',': common.Color.MAGENTA,
++ }
++ for t, lo, hi in tok_gen:
++ c = color_map.get(t, common.Color.NONE)
++ sys.stdout.write(c(data[lo:hi]))
++
++ def _DumpRootObj(self, root_obj):
++ print(root_obj)
++
++ def _DumpResourceCounts(self, usage_gen):
++ tot = collections.Counter()
++ for item, tag_name_to_usage in usage_gen:
++ c = common.Color.YELLOW if item.grd.startswith('<') else common.Color.CYAN
++ print('%s: %r' % (c(item.grd), dict(tag_name_to_usage)))
++ tot += collections.Counter(tag_name_to_usage)
++ print(common.Color.GRAY('-' * 80))
++ print('%s: %r' % (common.Color.GREEN('Total'), dict(tot)))
++ print('%s: %d' % (common.Color.GREEN('Grand Total'), sum(tot.values())))
++
++ def Run(self, opts, raw_args):
++ self.SetOptions(opts)
++
++ args = _Args.Parse(raw_args)
++ data, file_dir = _ReadData(args.input)
++
++ tok_gen = parser.Tokenize(data)
++ if args.tokenize:
++ return self._DumpTokens(data, tok_gen)
++
++ root_obj = parser.ResourceIdParser(data, tok_gen).Parse()
++ if args.parse:
++ return self._DumpRootObj(root_obj)
++ item_list = common.BuildItemList(root_obj)
++
++ src_dir = os.path.normpath(os.path.join(file_dir, root_obj['SRCDIR'].val))
++ seen_files = set()
++ usage_gen = reader.GenerateResourceUsages(item_list, src_dir, args.fake,
++ seen_files)
++ if args.count:
++ return self._DumpResourceCounts(usage_gen)
++ for item, tag_name_to_usage in usage_gen:
++ item.SetUsages(tag_name_to_usage)
++
++ if args.analyze_inputs:
++ print('\n'.join(sorted(seen_files)))
++ return 0
++
++ new_ids_gen = assigner.GenerateNewIds(item_list, args.naive)
++ # Create replacement specs usable by _MultiReplace().
++ repl = [(tag.lo, tag.hi, str(new_id)) for tag, new_id in new_ids_gen]
++ rel_input_dir = args.input
++ # Update "SRCDIR" entry if output is specified.
++ if args.output:
++ new_srcdir = os.path.relpath(src_dir, os.path.dirname(args.output))
++ repl.append((root_obj['SRCDIR'].lo, root_obj['SRCDIR'].hi,
++ repr(new_srcdir)))
++ rel_input_dir = os.path.join('$SRCDIR',
++ os.path.relpath(rel_input_dir, new_srcdir))
++
++ new_data = _MultiReplace(data, repl)
++ if args.add_header:
++ header = []
++ header.append('# GENERATED FILE.')
++ header.append('# Edit %s instead.' % rel_input_dir)
++ header.append('#' * 80)
++ new_data = '\n'.join(header + ['']) + new_data
++ _WriteFileIfChanged(args.output, new_data)
++
++ if args.depfile:
++ deps_data = '{}: {}'.format(args.output, ' '.join(sorted(seen_files)))
++ _WriteFileIfChanged(args.depfile, deps_data)
+diff --git a/tools/grit/grit/tool/update_resource_ids/assigner.py b/tools/grit/grit/tool/update_resource_ids/assigner.py
+new file mode 100644
+index 0000000000..6cd46031a6
--- /dev/null
-+++ b/tools/clang/scripts/update.sh
++++ b/tools/grit/grit/tool/update_resource_ids/assigner.py
@@ -0,0 +1,286 @@
-+#!/usr/bin/env bash
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+# This script will check out llvm and clang into third_party/llvm and build it.
-+
-+# Do NOT CHANGE this if you don't know what you're doing -- see
-+# https://code.google.com/p/chromium/wiki/UpdatingClang
-+# Reverting problematic clang rolls is safe, though.
-+CLANG_REVISION=163674
-+
-+THIS_DIR="$(dirname "${0}")"
-+LLVM_DIR="${THIS_DIR}/../../../third_party/llvm"
-+LLVM_BUILD_DIR="${LLVM_DIR}/../llvm-build"
-+LLVM_BOOTSTRAP_DIR="${LLVM_DIR}/../llvm-bootstrap"
-+CLANG_DIR="${LLVM_DIR}/tools/clang"
-+COMPILER_RT_DIR="${LLVM_DIR}/projects/compiler-rt"
-+STAMP_FILE="${LLVM_BUILD_DIR}/cr_build_revision"
-+
-+# ${A:-a} returns $A if it's set, a else.
-+LLVM_REPO_URL=${LLVM_URL:-https://llvm.org/svn/llvm-project}
-+
-+# Die if any command dies.
-+set -e
-+
-+OS="$(uname -s)"
-+
-+# Parse command line options.
-+force_local_build=
-+mac_only=
-+run_tests=
-+bootstrap=
-+while [[ $# > 0 ]]; do
-+ case $1 in
-+ --bootstrap)
-+ bootstrap=yes
-+ ;;
-+ --force-local-build)
-+ force_local_build=yes
-+ ;;
-+ --mac-only)
-+ mac_only=yes
-+ ;;
-+ --run-tests)
-+ run_tests=yes
-+ ;;
-+ --help)
-+ echo "usage: $0 [--force-local-build] [--mac-only] [--run-tests] "
-+ echo "--bootstrap: First build clang with CC, then with itself."
-+ echo "--force-local-build: Don't try to download prebuilt binaries."
-+ echo "--mac-only: Do initial download only on Mac systems."
-+ echo "--run-tests: Run tests after building. Only for local builds."
-+ exit 1
-+ ;;
-+ esac
-+ shift
-+done
-+
-+# --mac-only prevents the initial download on non-mac systems, but if clang has
-+# already been downloaded in the past, this script keeps it up to date even if
-+# --mac-only is passed in and the system isn't a mac. People who don't like this
-+# can just delete their third_party/llvm-build directory.
-+if [[ -n "$mac_only" ]] && [[ "${OS}" != "Darwin" ]] &&
-+ [[ "$GYP_DEFINES" != *clang=1* ]] && ! [[ -d "${LLVM_BUILD_DIR}" ]]; then
-+ exit 0
-+fi
-+
-+# Xcode and clang don't get along when predictive compilation is enabled.
-+# http://crbug.com/96315
-+if [[ "${OS}" = "Darwin" ]] && xcodebuild -version | grep -q 'Xcode 3.2' ; then
-+ XCONF=com.apple.Xcode
-+ if [[ "${GYP_GENERATORS}" != "make" ]] && \
-+ [ "$(defaults read "${XCONF}" EnablePredictiveCompilation)" != "0" ]; then
-+ echo
-+ echo " HEARKEN!"
-+ echo "You're using Xcode3 and you have 'Predictive Compilation' enabled."
-+ echo "This does not work well with clang (http://crbug.com/96315)."
-+ echo "Disable it in Preferences->Building (lower right), or run"
-+ echo " defaults write ${XCONF} EnablePredictiveCompilation -boolean NO"
-+ echo "while Xcode is not running."
-+ echo
-+ fi
-+
-+ SUB_VERSION=$(xcodebuild -version | sed -Ene 's/Xcode 3\.2\.([0-9]+)/\1/p')
-+ if [[ "${SUB_VERSION}" < 6 ]]; then
-+ echo
-+ echo " YOUR LD IS BUGGY!"
-+ echo "Please upgrade Xcode to at least 3.2.6."
-+ echo
-+ fi
-+fi
-+
-+
-+# Check if there's anything to be done, exit early if not.
-+if [[ -f "${STAMP_FILE}" ]]; then
-+ PREVIOUSLY_BUILT_REVISON=$(cat "${STAMP_FILE}")
-+ if [[ -z "$force_local_build" ]] && \
-+ [[ "${PREVIOUSLY_BUILT_REVISON}" = "${CLANG_REVISION}" ]]; then
-+ echo "Clang already at ${CLANG_REVISION}"
-+ exit 0
-+ fi
-+fi
-+# To always force a new build if someone interrupts their build half way.
-+rm -f "${STAMP_FILE}"
-+
-+# Clobber pch files, since they only work with the compiler version that
-+# created them. Also clobber .o files, to make sure everything will be built
-+# with the new compiler.
-+if [[ "${OS}" = "Darwin" ]]; then
-+ XCODEBUILD_DIR="${THIS_DIR}/../../../xcodebuild"
-+
-+ # Xcode groups .o files by project first, configuration second.
-+ if [[ -d "${XCODEBUILD_DIR}" ]]; then
-+ echo "Clobbering .o files for Xcode build"
-+ find "${XCODEBUILD_DIR}" -name '*.o' -exec rm {} +
-+ fi
-+fi
-+
-+if [ -f "${THIS_DIR}/../../../WebKit.gyp" ]; then
-+ # We're inside a WebKit checkout.
-+ # TODO(thakis): try to unify the directory layout of the xcode- and
-+ # make-based builds. http://crbug.com/110455
-+ MAKE_DIR="${THIS_DIR}/../../../../../../out"
-+else
-+ # We're inside a Chromium checkout.
-+ MAKE_DIR="${THIS_DIR}/../../../out"
-+fi
-+
-+for CONFIG in Debug Release; do
-+ if [[ -d "${MAKE_DIR}/${CONFIG}/obj.target" ||
-+ -d "${MAKE_DIR}/${CONFIG}/obj.host" ]]; then
-+ echo "Clobbering ${CONFIG} PCH and .o files for make build"
-+ if [[ -d "${MAKE_DIR}/${CONFIG}/obj.target" ]]; then
-+ find "${MAKE_DIR}/${CONFIG}/obj.target" -name '*.gch' -exec rm {} +
-+ find "${MAKE_DIR}/${CONFIG}/obj.target" -name '*.o' -exec rm {} +
-+ fi
-+ if [[ -d "${MAKE_DIR}/${CONFIG}/obj.host" ]]; then
-+ find "${MAKE_DIR}/${CONFIG}/obj.host" -name '*.o' -exec rm {} +
-+ fi
-+ fi
-+
-+ # ninja puts its output below ${MAKE_DIR} as well.
-+ if [[ -d "${MAKE_DIR}/${CONFIG}/obj" ]]; then
-+ echo "Clobbering ${CONFIG} PCH and .o files for ninja build"
-+ find "${MAKE_DIR}/${CONFIG}/obj" -name '*.gch' -exec rm {} +
-+ find "${MAKE_DIR}/${CONFIG}/obj" -name '*.o' -exec rm {} +
-+ find "${MAKE_DIR}/${CONFIG}/obj" -name '*.o.d' -exec rm {} +
-+ fi
-+
-+ if [[ "${OS}" = "Darwin" ]]; then
-+ if [[ -d "${XCODEBUILD_DIR}/${CONFIG}/SharedPrecompiledHeaders" ]]; then
-+ echo "Clobbering ${CONFIG} PCH files for Xcode build"
-+ rm -rf "${XCODEBUILD_DIR}/${CONFIG}/SharedPrecompiledHeaders"
-+ fi
-+ fi
-+done
-+
-+if [[ -z "$force_local_build" ]]; then
-+ # Check if there's a prebuilt binary and if so just fetch that. That's faster,
-+ # and goma relies on having matching binary hashes on client and server too.
-+ CDS_URL=https://commondatastorage.googleapis.com/chromium-browser-clang
-+ CDS_FILE="clang-${CLANG_REVISION}.tgz"
-+ CDS_OUT_DIR=$(mktemp -d -t clang_download.XXXXXX)
-+ CDS_OUTPUT="${CDS_OUT_DIR}/${CDS_FILE}"
-+ if [ "${OS}" = "Linux" ]; then
-+ CDS_FULL_URL="${CDS_URL}/Linux_x64/${CDS_FILE}"
-+ elif [ "${OS}" = "Darwin" ]; then
-+ CDS_FULL_URL="${CDS_URL}/Mac/${CDS_FILE}"
-+ fi
-+ echo Trying to download prebuilt clang
-+ if which curl > /dev/null; then
-+ curl -L --fail "${CDS_FULL_URL}" -o "${CDS_OUTPUT}" || \
-+ rm -rf "${CDS_OUT_DIR}"
-+ elif which wget > /dev/null; then
-+ wget "${CDS_FULL_URL}" -O "${CDS_OUTPUT}" || rm -rf "${CDS_OUT_DIR}"
-+ else
-+ echo "Neither curl nor wget found. Please install one of these."
-+ exit 1
-+ fi
-+ if [ -f "${CDS_OUTPUT}" ]; then
-+ rm -rf "${LLVM_BUILD_DIR}/Release+Asserts"
-+ mkdir -p "${LLVM_BUILD_DIR}/Release+Asserts"
-+ tar -xzf "${CDS_OUTPUT}" -C "${LLVM_BUILD_DIR}/Release+Asserts"
-+ echo clang "${CLANG_REVISION}" unpacked
-+ echo "${CLANG_REVISION}" > "${STAMP_FILE}"
-+ rm -rf "${CDS_OUT_DIR}"
-+ exit 0
-+ else
-+ echo Did not find prebuilt clang at r"${CLANG_REVISION}", building
-+ fi
-+fi
-+
-+echo Getting LLVM r"${CLANG_REVISION}" in "${LLVM_DIR}"
-+if ! svn co --force "${LLVM_REPO_URL}/llvm/trunk@${CLANG_REVISION}" \
-+ "${LLVM_DIR}"; then
-+ echo Checkout failed, retrying
-+ rm -rf "${LLVM_DIR}"
-+ svn co --force "${LLVM_REPO_URL}/llvm/trunk@${CLANG_REVISION}" "${LLVM_DIR}"
-+fi
-+
-+echo Getting clang r"${CLANG_REVISION}" in "${CLANG_DIR}"
-+svn co --force "${LLVM_REPO_URL}/cfe/trunk@${CLANG_REVISION}" "${CLANG_DIR}"
-+
-+echo Getting compiler-rt r"${CLANG_REVISION}" in "${COMPILER_RT_DIR}"
-+svn co --force "${LLVM_REPO_URL}/compiler-rt/trunk@${CLANG_REVISION}" \
-+ "${COMPILER_RT_DIR}"
-+
-+# Echo all commands.
-+set -x
-+
-+NUM_JOBS=3
-+if [[ "${OS}" = "Linux" ]]; then
-+ NUM_JOBS="$(grep -c "^processor" /proc/cpuinfo)"
-+elif [ "${OS}" = "Darwin" ]; then
-+ NUM_JOBS="$(sysctl -n hw.ncpu)"
-+fi
-+
-+# Build bootstrap clang if requested.
-+if [[ -n "${bootstrap}" ]]; then
-+ echo "Building bootstrap compiler"
-+ mkdir -p "${LLVM_BOOTSTRAP_DIR}"
-+ cd "${LLVM_BOOTSTRAP_DIR}"
-+ if [[ ! -f ./config.status ]]; then
-+ # The bootstrap compiler only needs to be able to build the real compiler,
-+ # so it needs no cross-compiler output support. In general, the host
-+ # compiler should be as similar to the final compiler as possible, so do
-+ # keep --disable-threads & co.
-+ ../llvm/configure \
-+ --enable-optimized \
-+ --enable-targets=host-only \
-+ --disable-threads \
-+ --disable-pthreads \
-+ --without-llvmgcc \
-+ --without-llvmgxx
-+ MACOSX_DEPLOYMENT_TARGET=10.5 make -j"${NUM_JOBS}"
-+ fi
-+ if [[ -n "${run_tests}" ]]; then
-+ make check-all
-+ fi
-+ cd -
-+ export CC="${PWD}/${LLVM_BOOTSTRAP_DIR}/Release+Asserts/bin/clang"
-+ export CXX="${PWD}/${LLVM_BOOTSTRAP_DIR}/Release+Asserts/bin/clang++"
-+ echo "Building final compiler"
-+fi
-+
-+# Build clang (in a separate directory).
-+# The clang bots have this path hardcoded in built/scripts/slave/compile.py,
-+# so if you change it you also need to change these links.
-+mkdir -p "${LLVM_BUILD_DIR}"
-+cd "${LLVM_BUILD_DIR}"
-+if [[ ! -f ./config.status ]]; then
-+ ../llvm/configure \
-+ --enable-optimized \
-+ --disable-threads \
-+ --disable-pthreads \
-+ --without-llvmgcc \
-+ --without-llvmgxx
-+fi
-+
-+MACOSX_DEPLOYMENT_TARGET=10.5 make -j"${NUM_JOBS}"
-+cd -
-+
-+# Build plugin.
-+# Copy it into the clang tree and use clang's build system to compile the
-+# plugin.
-+PLUGIN_SRC_DIR="${THIS_DIR}/../plugins"
-+PLUGIN_DST_DIR="${LLVM_DIR}/tools/clang/tools/chrome-plugin"
-+PLUGIN_BUILD_DIR="${LLVM_BUILD_DIR}/tools/clang/tools/chrome-plugin"
-+rm -rf "${PLUGIN_DST_DIR}"
-+cp -R "${PLUGIN_SRC_DIR}" "${PLUGIN_DST_DIR}"
-+rm -rf "${PLUGIN_BUILD_DIR}"
-+mkdir -p "${PLUGIN_BUILD_DIR}"
-+cp "${PLUGIN_SRC_DIR}/Makefile" "${PLUGIN_BUILD_DIR}"
-+MACOSX_DEPLOYMENT_TARGET=10.5 make -j"${NUM_JOBS}" -C "${PLUGIN_BUILD_DIR}"
-+
-+if [[ -n "$run_tests" ]]; then
-+ # Run a few tests.
-+ "${PLUGIN_SRC_DIR}/tests/test.sh" "${LLVM_BUILD_DIR}/Release+Asserts"
-+ cd "${LLVM_BUILD_DIR}"
-+ make check-all
-+ cd -
-+fi
-+
-+# After everything is done, log success for this revision.
-+echo "${CLANG_REVISION}" > "${STAMP_FILE}"
++# Copyright 2019 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++"""Assign IDs to resource_ids file based on usage, while preserving structure.
++
++resource_ids assignment is divided into two parts:
++(A) Coarse assignment: Assigns start IDs of items.
++(B) Quota assignment: Assigns per-tag ID allotments for a given item, allowing
++ padding for IDs.
++
++These parts are interdependent: Start IDs (A) of an item depends on ID
++allotments (B) of *other* items; and ID allotment (B) of an item depends on its
++start IDs (A) to compute alignment.
++
++(B) hides padding and alignment details of tags so that (A) can be abstracted
++into the graph construction and traversal problem in DagCoarseIdAssigner.
++"""
++
++import math
++
++from grit.tool.update_resource_ids import common
++
++
++class Aligner:
++ """Helper to allot IDs, given start ID and ID usage.
++
++ Args:
++ expand: Scale factor relative to ID usage. Must be >= 1.0.
++ slack: Minimum number of reserved ID at end. Must be >= 0.
++ align: ID alignment of results. Must be >= 1.
++ """
++
++ def __init__(self, expand=1.0, slack=0, align=1):
++ assert expand >= 1.0 and slack >= 0 and align >= 1
++ self._expand = expand
++ self._slack = slack
++ self._align = align
++
++ def Calc(self, cur_id, usage):
++ quota = max(int(math.ceil(usage * self._expand)), usage + self._slack)
++ return common.AlignUp(cur_id + quota, self._align)
++
++
++class QuotaAssigner:
++ """Main class for (B), for ID allotment of tags for an item."""
++
++ def __init__(self, aligner):
++ self._aligner = aligner
++
++ def Gen(self, item, start_id):
++ """Generates per-tag *end* ID in |item|, succeeding |start_id|."""
++ cur_id = start_id
++ for tag in item.tags: # Sorted by |tag.lo|.
++ cur_id = self._aligner.Calc(cur_id, tag.usage)
++ yield tag, cur_id
++
++
++class BaseCoarseIdAssigner(object):
++ """Base class for coarse assignment."""
++
++ def __init__(self, item_list, align):
++ self._item_list = item_list
++ self._align = align
++
++ def GenStartIds(self):
++ """Visits |_item_list| and yields (|item|, new |start_id|).
++
++ Visit follows dependency order: If item B succeeds item A, then A is visited
++ before B. Caller must call FeedWeight() to assign ID allotment.
++ """
++ raise NotImplementedError()
++
++ def FeedWeight(self, item, weight):
++ """Callback to assign number of IDs allotted to |item|."""
++ raise NotImplementedError()
++
++
++class NaiveCoarseIdAssigner(BaseCoarseIdAssigner):
++ """CoarseIdAssigner that assigns item with non-overlapping start IDs."""
++
++ def __init__(self, item_list, align):
++ super(NaiveCoarseIdAssigner, self).__init__(item_list, align)
++ first_id = self._item_list[0].tags[0].id
++ self._cur_id = common.AlignUp(first_id, self._align)
++
++ def GenStartIds(self):
++ """Visits items in array order."""
++ for item in self._item_list:
++ yield item, self._cur_id
++
++ def FeedWeight(self, item, weight):
++ self._cur_id = common.AlignUp(self._cur_id + weight, self._align)
++
++
++class DagCoarseIdAssigner(BaseCoarseIdAssigner):
++ """CoarseIdAssigner that preserves existing structure.
++
++Start ID assignment in resource_ids is structured a Series-Parallel Graph, which
++is a directed, acyclic multi-graph generated by the following:
++* Start: Single directed edge. S <-- T.
++* Operation 1: A <-- B becomes A <-- C <-- B.
++* Operation 2: A <-- B becomes A <== B (add parallel edge).
++
++Each vertex (A, B, ...) is a start ID. S = globally minimal ID. T = \infty is an
++implicit sentinel. Each edge maps to an item, and edge weight is ID allotment.
++The edge A <-- B means "A's ID assignment needs to be determined before B's"
++(i.e., "B depends on A"), and requires A < B.
++
++resource_ids stores a "flattened" representation of the graph, as a list of
++items (with meta data). Thus coarse ID assignment consists of the following:
++(1) Process list of items (with old start ID and meta data) to rebuild graph.
++(2) Traverse graph in an order that satisfies dependencies.
++(3) When vertex A has its ID assigned, the weight of each edge "A <--" (i.e., an
++ item) can have its weight (ID allotment) computed via quota assignment of A.
++(4) New start IDs satisfy A + w <= B for each edge A <-- B with weight w > 0.
++
++The key algorithm challenge is (1). Note that it does not need weight details,
++so we only assume A < B whenever A <-- B. Now we're faced with 2 subproblems:
++(1a) How to represent the graph as a list of integers (with meta data)?
++(1b) Given the list representation, how to recover the graph?
++
++For (1a), we start with DFS traversal of the (transposed, i.e., reversed) graph
++starting from S, and apply the following:
++* For each edge A <-- B traversed, emit A into sequence,
++* Traverse a B <-- Y only when all X <-- B have been traversed.
++
++The resulting sequence has the length as the number of edges, and has the useful
++property that a vertex's dependencies always appear before the vertex itself!
++Note this the sentinel T is omitted.
++
++Example 1:
++ S <-- A <-- B <-- C <-- T => "SABC".
++
++Example 2:
++ S <-- A <-- B <-- C <-- T => "SA|AB|SDEC|SF",
++ | | | | | or "SF|SA|AB|SDEC",
++ | + <-- + | | or "SDE|SA|ABC|SF",
++ | | | or "SF|SDE|SA|ABC".
++ + <---D <-- E <---+ |
++ | |
++ + <-- F <---------------+
++
++Here, "X|Y" denotes backtracking between visiting X and visiting Y. This appears
++if and only if X >= Y, so "|" an optional (but illustrative) character that's
++not in the actual output. We will use it consistently in comments, and so the
++absence of "|" denotes the converse. For example, "XY" implies X < Y.
++
++In terms of the basic operations:
++* Start: S <-- T => "S".
++* Operation 1: "...AB..." => "...ACB..." (or "...A" => "...AB").
++* Operation 2: "...AB..." => "...A|AB..." (or "...A" => "...A|A").
++
++For Example 2, a viable "evolution path" is:
++"S" => "S|S" => "SC|S" => "S|SC|S" => "SA|SC|S" => "SAB|SC|S" => "SA|AB|SC|S"
++ => "SA|AB|SDC|S" => "SA|AB|SDEC|S" => "SA|AB|SDEC|SF".
++(Alternative: "S|S" => "S|SC" => etc.).
++
++Note: {A, ...} are *unlabelled* integers, and "spurious equalities" such as
++A = D or A = F can occur!
++
++For (1b), we wish to build the graph from the sequence. This requires (1a) to be
++injective (up to isomorphism). Unfortunately, this does not always hold.
++Example:
++ S <-- A <-- C <-- D <-- T => "SA|SBCD".
++ | |
++ + <-- B <---+
++vs.
++ S <----- A <----- D <-- T => "SA|SBCD".
++ | |
++ + <-- B <-- C <---+
++
++To fix this, we prepend a "join" label (*) to each vertex that has multiple
++dependencies. With this, the example above produce different results:
++ "SA|SB*CD" != "SA|SBC*D".
++
++Unfortunately, this is also inadequate. Example:
++ S <-------- B <-- T => "S|S|S|S*A*B",
++ | |
++ + <---------+
++ | |
++ + <-- A <---+
++ | |
++ + <---+
++vs.
++ S <-------- B <-- T => "S|S|S|S*A*B".
++ | |
++ + <---A <---+
++ | |
++ + <---+
++ | |
++ + <---+
++
++To fix this, we also label the number of dependencies. In text representation,
++we just show multiple (#dependencies - 1) copies of '*'. Now we have:
++ "S|S|S|S*A**B" != "S|S|S|S**A*B".
++
++The "join" label with count adequately addresses the issue (proof omitted). In
++the resource_ids files, these are stored as the "join" field of an item's meta
++data.
++
++Additional comments for (1b) and other steps are detailed below.
++"""
++
++ class DagNode:
++ """An element of the implicit graph, corresponding to an item.
++
++ This actually represents an edge-vertex pair, corresponding to an item.
++ A vertex is represented by a collection of DagNode that uses |sib| to link
++ to a "representative node". The representative node, in turn, holds the list
++ of all |deps| (dependencies) of the vertex.
++ """
++
++ def __init__(self, item, old_id):
++ self.item = item
++ self.old_id = old_id
++ self.new_id = None
++ self.weight = None
++
++ def __init__(self, item_list, align):
++ super(DagCoarseIdAssigner, self).__init__(item_list, align)
++ self._node_dict = {} # Maps from |lo| to item.
++
++ def GenStartIds(self):
++ """Traverses implicit graph and yields new IDs.
++
++ Algorithm: Process |old_id| of items sequentially. Successive items A and B
++ can be "AB" (A < B), "A*...B" (A < B), or "A|B" (A >= B). "AB" and "A*...B"
++ imply A <-- B, and are accumulated in |trace|. "A|B" are jumps that rewinds
++ |trace| to the latest B (must exist), and A is pushed into |jumps|. A join
++ "A*...B" also pops |num_join - 1| items {X_i} in |jump|, and X_i <-- B. In
++ the end, unprocessed elements in |jumps| all link to sentinel T, and can be
++ ignored.
++ """
++ # DagNode stack of "A" when "AB" is found (increasing |old_id|).
++ trace = []
++ # DagNode stack of "A" when "A|B" jumps is found.
++ jumps = []
++ for item in self._item_list: # Sorted by |lo|.
++ meta = item.meta
++ # |num_join| indicates "*" in "A*...B", and specify B's dependencies: +1
++ # from A, and +count("*") from |jumps|.
++ num_join = meta['join'].val if meta and 'join' in meta else None
++ node = DagCoarseIdAssigner.DagNode(item, item.tags[0].id)
++ self._node_dict[item.lo] = node
++ if trace:
++ if trace[-1].old_id >= node.old_id: # "A|B".
++ if num_join:
++ raise ValueError('Cannot join on jump: %d' % node.old_id)
++ jumps.append(trace[-1]) # Add A to |jumps|, for later join.
++ while trace and trace[-1].old_id > node.old_id: # Rewind to find B.
++ trace.pop()
++ if not trace or trace[-1].old_id != node.old_id: #
++ raise ValueError('Cannot jump to unvisited: %d' % node.old_id)
++ node.new_id = trace.pop().new_id # Copy B & remove. Will re-add B.
++ else: # "AB" or "A*...B".
++ node.new_id = trace[-1].new_id + trace[-1].weight # A --> B
++ if num_join: # "A*...B".
++ for _ in range(1, num_join):
++ t = jumps.pop()
++ node.new_id = max(node.new_id, t.new_id + t.weight) # X_i --> B.
++ else:
++ node.new_id = node.old_id # Initial S.
++ trace.append(node) # Add B.
++ align = meta['align'].val if meta and 'align' in meta else self._align
++ node.new_id = common.AlignUp(node.new_id, align)
++ yield node.item, node.new_id
++ # Expect caller to calling FreedWeight() and update |node.weight|.
++
++ def FeedWeight(self, item, weight):
++ self._node_dict[item.lo].weight = weight
++
++
++def GenerateNewIds(item_list, use_naive):
++ """Visits all tags in |item_list| and generates new ids.
++
++ New ids are generated based on old ids and usages.
++ """
++ Assigner = NaiveCoarseIdAssigner if use_naive else DagCoarseIdAssigner
++ coarse_id_assigner = Assigner(item_list, 10)
++ quota_assigner = QuotaAssigner(Aligner(expand=1.15, slack=3, align=10))
++ for item, start_id in coarse_id_assigner.GenStartIds(): # Topo-sorted.
++ cur_id = start_id
++ for tag, next_id in quota_assigner.Gen(item, start_id): # Sorted by |lo|.
++ yield tag, cur_id
++ cur_id = next_id
++ coarse_id_assigner.FeedWeight(item, next_id - start_id)
+diff --git a/tools/grit/grit/tool/update_resource_ids/assigner_unittest.py b/tools/grit/grit/tool/update_resource_ids/assigner_unittest.py
+new file mode 100644
+index 0000000000..164d820762
+--- /dev/null
++++ b/tools/grit/grit/tool/update_resource_ids/assigner_unittest.py
+@@ -0,0 +1,154 @@
++#!/usr/bin/env python
++# Copyright 2019 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++from __future__ import print_function
++
++import os
++import sys
++import traceback
++import unittest
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../../..'))
++
++from grit.tool.update_resource_ids import assigner, common, parser
++
++# |spec| format: A comma-separated list of (old) start IDs. Modifiers:
++# * Prefix with n '*' to assign the item's META "join" field to n + 1.
++# * Suffix with "+[usage]" to assign |usage| for the item (else default=10)
++
++
++def _RenderTestResourceId(spec):
++ """Renders barebone resource_ids data based on |spec|."""
++ data = '{"SRCDIR": ".",'
++ for i, tok in enumerate(spec.split(',')):
++ num_star = len(tok) - len(tok.lstrip('*'))
++ tok = tok[num_star:]
++ meta = '"META":{"join": %d},' % (num_star + 1) if num_star else ''
++ start_id = tok.split('+')[0] # Strip '+usage'.
++ data += '"foo%d.grd": {%s"includes": [%s]},' % (i, meta, start_id)
++ data += '}'
++ return data
++
++
++def _CreateTestItemList(spec):
++ """Creates list of ItemInfo based on |spec|."""
++ data = _RenderTestResourceId(spec)
++ item_list = common.BuildItemList(
++ parser.ResourceIdParser(data, parser.Tokenize(data)).Parse())
++ # Assign usages from "id+usage", default to 10.
++ for i, tok in enumerate(spec.split(',')):
++ item_list[i].tags[0].usage = int((tok.split('+') + ['10'])[1])
++ return item_list
++
++
++def _RunCoarseIdAssigner(spec):
++ item_list = _CreateTestItemList(spec)
++ coarse_id_assigner = assigner.DagCoarseIdAssigner(item_list, 1)
++ new_id_list = [] # List of new IDs, to check ID assignment.
++ new_spec_list = [] # List of new tokens to construct new |spec|.
++ for item, start_id in coarse_id_assigner.GenStartIds(): # Topo-sorted..
++ new_id_list.append(str(start_id))
++ meta = item.meta
++ num_join = meta['join'].val if meta and 'join' in meta else 0
++ t = '*' * max(0, num_join - 1)
++ t += str(start_id)
++ t += '' if item.tags[0].usage == 10 else '+' + str(item.tags[0].usage)
++ new_spec_list.append((item.lo, t))
++ coarse_id_assigner.FeedWeight(item, item.tags[0].usage)
++ new_spec = ','.join(s for _, s in sorted(new_spec_list))
++ return ','.join(new_id_list), new_spec
++
++
++class AssignerUnittest(unittest.TestCase):
++
++ def testDagAssigner(self):
++ test_cases = [
++ # Trivial.
++ ('0', '0'),
++ ('137', '137'),
++ ('5,15', '5,6'),
++ ('11,18', '11+7,12'),
++ ('5,5', '5,5'),
++ # Series only.
++ ('0,10,20,30,40', '0,1,2,3,4'),
++ ('5,15,25,35,45,55', '5,6,7,8,9,10'),
++ ('5,15,25,35,45,55', '5,7,100,101,256,1001'),
++ ('0,10,20,45,85', '0,1,2+25,3+40,4'),
++ # Branching with and without join.
++ ('0,0,10,20,20,30,40', '0,0,1,2,2,3,4'),
++ ('0,0,10,20,20,30,40', '0,0,*1,2,2,*3,4'),
++ ('0,0,2,12,12,16,26', '0+4,0+2,1,2+8,2+4,3,4'),
++ ('0,0,4,14,14,22,32', '0+4,0+2,*1,2+8,2+4,*3,4'),
++ # Wide branching with and without join.
++ ('0,10,10,10,10,10,10,20,30', '0,1,1,1,1,1,1,2,3'),
++ ('0,10,10,10,10,10,10,20,30', '0,1,1,1,1,1,1,*****2,3'),
++ ('0,2,2,2,2,2,2,7,17', '0+2,1+4,1+19,1,1+4,1+2,1+5,2,3'),
++ ('0,2,2,2,2,2,2,21,31', '0+2,1+4,1+19,1,1+4,1+2,1+5,*****2,3'),
++ # Expanding different branch, without join.
++ ('0,10,10,10,60,70,80', '0,1+15,1+15,1+50,2,3,4'),
++ ('0,10,10,10,25,35,45', '0,1+15,1+50,1+15,2,3,4'),
++ ('0,10,10,10,25,35,45', '0,1+50,1+15,1+15,2,3,4'),
++ # ... with join.
++ ('0,10,10,10,60,70,80', '0,1+15,1+15,1+50,**2,3,4'),
++ ('0,10,10,10,60,70,80', '0,1+15,1+50,1+15,**2,3,4'),
++ ('0,10,10,10,60,70,80', '0,1+50,1+15,1+15,**2,3,4'),
++ # ... with alternative join.
++ ('0,10,10,10,60,70,80', '0,1+15,1+15,1+50,2,**3,4'),
++ ('0,10,10,10,25,60,70', '0,1+15,1+50,1+15,2,**3,4'),
++ ('0,10,10,10,25,60,70', '0,1+50,1+15,1+15,2,**3,4'),
++ # Examples from assigner.py.
++ ('0,10,10,20,0,10,20,30,0,10',
++ '0,1,1,*2,0,4,5,*6,0,7'), # SA|AB|SDEC|SF
++ ('0,10,0,10,20,30', '0,1,0,2,*3,4'), # SA|SB*CD
++ ('0,10,0,10,20,30', '0,1,0,2,3,*4'), # SA|SBC*D
++ ('0,7,0,5,11,21', '0+7,1+4,0+5,2+3,*3,4'), # SA|SB*CD
++ ('0,7,0,5,8,18', '0+7,1+4,0+5,2+3,3,*4'), # SA|SBC*D
++ ('0,0,0,0,10,20', '0,0,0,0,*1,**2'), # S|S|S|S*A**B
++ ('0,0,0,0,10,20', '0,0,0,0,**1,*2'), # S|S|S|S**A*B
++ ('0,0,0,0,6,16', '0+8,0+7,0+6,0+5,*1,**2'), # S|S|S|S*A**B
++ ('0,0,0,0,7,17', '0+8,0+7,0+6,0+5,**1,*2'), # S|S|S|S**A*B
++ # Long branches without join.
++ ('0,10,0,0,10,20,0,10,20,30', '0,1,0,0,1,2,0,1,2,3'),
++ ('0,30,0,0,20,30,0,10,13,28', '0+30,1,0+50,0+20,1,2+17,0,1+3,2+15,3'),
++ # Long branches with join.
++ ('0,10,0,0,10,20,0,10,20,30', '0,1,0,0,1,2,0,1,2,***3'),
++ ('0,30,0,0,20,30,0,10,13,50',
++ '0+30,1,0+50,0+20,1,2+17,0,1+3,2+15,***3'),
++ # 2-level hierarchy.
++ ('0,10,10,20,0,10,10,20,30', '0,1,1,*2,0,1,1,*2,*3'),
++ ('0,2,2,10,0,3,3,6,34', '0+2,1+5,1+8,*2+24,0+3,1+2,1+3,*2+27,*3'),
++ ('0,2,2,10,0,3,3,6,34', '0+2,1+5,1+8,*2+24,0+3,1+2,1+3,*2+28,*3'),
++ ('0,2,2,10,0,3,3,6,35', '0+2,1+5,1+8,*2+24,0+3,1+2,1+3,*2+29,*3'),
++ # Binary hierarchy.
++ ('0,0,10,0,0,10,20,0,0,10,0,0,10,20,30',
++ '0,0,*1,0,0,*1,*2,0,0,*1,0,0,*1,*2,*3'),
++ ('0,0,2,0,0,5,11,0,0,8,0,0,5,14,18',
++ '0+1,0+2,*1+3,0+4,0+5,*1+6,*2+7,0+8,0+7,*1+6,0+5,0+4,*1+3,*2+2,*3+1'),
++ # Joining from different heads.
++ ('0,10,20,30,40,30,20,10,0,50', '0,1,2,3,4,3,2,1,0,****5'),
++ # Miscellaneous.
++ ('0,1,0,11', '0+1,1,0,*1'),
++ ]
++ for exp, spec in test_cases:
++ try:
++ actual, new_spec = _RunCoarseIdAssigner(spec)
++ self.failUnlessEqual(exp, actual)
++ # Test that assignment is idempotent.
++ actual2, new_spec2 = _RunCoarseIdAssigner(new_spec)
++ self.failUnlessEqual(actual, actual2)
++ self.failUnlessEqual(new_spec, new_spec2)
++ except Exception as e:
++ print(common.Color.RED(traceback.format_exc().rstrip()))
++ print('Failed spec: %s' % common.Color.CYAN(spec))
++ print(' Expected: %s' % common.Color.YELLOW(exp))
++ print(' Actual: %s' % common.Color.YELLOW(actual))
++ if new_spec != new_spec2:
++ print('Not idempotent')
++ if isinstance(e, AssertionError):
++ raise e
++
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/tool/update_resource_ids/common.py b/tools/grit/grit/tool/update_resource_ids/common.py
+new file mode 100644
+index 0000000000..004d8aa0e3
+--- /dev/null
++++ b/tools/grit/grit/tool/update_resource_ids/common.py
+@@ -0,0 +1,101 @@
++# Copyright 2019 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++
++def AlignUp(v, align):
++ return (v + align - 1) // align * align
++
++
++def StripPlural(s):
++ assert s.endswith('s'), 'Expect %s to be plural' % s
++ return s[:-1]
++
++
++class Color:
++
++ def _MakeColor(code):
++ t = '\033[' + code + 'm%s\033[0m'
++ return lambda s: t % s
++
++ NONE = staticmethod(lambda s: s)
++ RED = staticmethod(_MakeColor('31'))
++ GREEN = staticmethod(_MakeColor('32'))
++ YELLOW = staticmethod(_MakeColor('33'))
++ BLUE = staticmethod(_MakeColor('34'))
++ MAGENTA = staticmethod(_MakeColor('35'))
++ CYAN = staticmethod(_MakeColor('36'))
++ WHITE = staticmethod(_MakeColor('37'))
++ GRAY = staticmethod(_MakeColor('30;1'))
++
++
++class TagInfo:
++ """Stores resource_ids tag entry (e.g., {"includes": 100} pair)."""
++
++ def __init__(self, raw_key, raw_value):
++ """TagInfo Constructor.
++
++ Args:
++ raw_key: parser.AnnotatedValue for the parsed key, e.g., "includes".
++ raw_value: parser.AnnotatedValue for the parsed value, e.g., 100.
++ """
++ # Tag name, e.g., 'include' (no "s" at end).
++ self.name = StripPlural(raw_key.val)
++ # |len(raw_value) > 1| is possible, e.g., see grd_reader_unittest.py's
++ # testAssignFirstIdsMultipleMessages. This feature seems unused though.
++ # TODO(huangs): Reconcile this (may end up removing multi-value feature).
++ assert len(raw_value) == 1
++ # Inclusive start *position* of the tag's start ID in resource_ids.
++ self.lo = raw_value[0].lo
++ # Exclusive end *position* of the tag's start ID in resource_ids.
++ self.hi = raw_value[0].hi
++ # The tag's start ID. Initially the old value, but may be reassigned to new.
++ self.id = raw_value[0].val
++ # The number of IDs the tag uses, to be assigned by ItemInfo.SetUsages().
++ self.usage = None
++
++
++class ItemInfo:
++ """resource_ids item, containing multiple TagInfo."""
++
++ def __init__(self, lo, grd, raw_item):
++ # Inclusive start position of the item's key. Serve as unique identifier.
++ self.lo = lo
++ # The GRD filename for the item.
++ self.grd = grd
++ # Optional META information for the item.
++ self.meta = None
++ # List of TagInfo associated witih the item.
++ self.tags = []
++ for k, v in raw_item.items():
++ if k.val == 'META':
++ assert self.meta is None
++ self.meta = v # Not flattened.
++ else:
++ self.tags.append(TagInfo(k, v))
++ self.tags.sort(key=lambda tag: tag.lo)
++
++ def SetUsages(self, tag_name_to_usage):
++ for tag in self.tags:
++ tag.usage = tag_name_to_usage.get(tag.name, 0)
++
++
++def BuildItemList(root_obj):
++ """Extracts ID assignments and structure from parsed resource_ids.
++
++ Returns: A list of ItemInfo, ordered by |lo|.
++ """
++ item_list = []
++ grd_seen = set()
++ for raw_key, raw_item in root_obj.items(): # Unordered.
++ grd = raw_key.val
++ if grd == 'SRCDIR':
++ continue
++ if not grd.endswith('.grd'):
++ raise ValueError('Invalid GRD file: %s' % grd)
++ if grd in grd_seen:
++ raise ValueError('Duplicate GRD: %s' % grd)
++ grd_seen.add(grd)
++ item_list.append(ItemInfo(raw_key.lo, grd, raw_item))
++ item_list.sort(key=lambda item: item.lo)
++ return item_list
+diff --git a/tools/grit/grit/tool/update_resource_ids/parser.py b/tools/grit/grit/tool/update_resource_ids/parser.py
+new file mode 100644
+index 0000000000..da956bbd1c
+--- /dev/null
++++ b/tools/grit/grit/tool/update_resource_ids/parser.py
+@@ -0,0 +1,231 @@
++# Copyright 2019 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++"""Structure-preserving parser for resource_ids files.
++
++Naive usage of eval() destroys resource_ids structure. This module provides a
++custom parser that annotates source byte ranges of "leaf values" (strings and
++integers).
++"""
++
++from __future__ import print_function
++
++_isWhitespace = lambda ch: ch in ' \t\n'
++_isNotNewline = lambda ch: ch != '\n'
++_isDigit = lambda ch: ch.isdigit()
++
++
++def _RenderLineCol(data, pos):
++ """Renders |pos| within text |data| in as text showing line and column."""
++ # This is used to pinpoint fatal parse errors, so okay to be inefficient.
++ new_lines = [i for i in range(pos) if data[i] == '\n']
++ row = 1 + len(new_lines)
++ col = (pos - new_lines[-1]) if new_lines else 1 + pos
++ return 'line %d, column %d' % (row, col)
++
++
++def Tokenize(data):
++ """Generator to split |data| into tokens.
++
++ Each token is specified as |(t, lo, hi)|:
++ * |t|: Type, with '#' = space / comments, '0' = int, 'S' = string, 'E' = end,
++ and other characters denoting themselves.
++ * |lo, hi|: Token's range within |data| (as |data[lo:hi]|).
++ """
++
++ class ctx: # Local context for mutable data shared across inner functions.
++ pos = 0
++
++ def _HasData():
++ return ctx.pos < len(data)
++
++ # Returns True if ended by |not pred()|, or False if ended by EOF.
++ def _EatWhile(pred):
++ while _HasData():
++ if pred(data[ctx.pos]):
++ ctx.pos += 1
++ else:
++ return True
++ return False
++
++ def _NextBlank():
++ lo = ctx.pos
++ while True:
++ if not _EatWhile(_isWhitespace) or data[ctx.pos] != '#':
++ break
++ ctx.pos += 1
++ if not _EatWhile(_isNotNewline):
++ break
++ ctx.pos += 1
++ return None if ctx.pos == lo else (lo, ctx.pos)
++
++ def _EatString():
++ lo = ctx.pos
++ delim = data[ctx.pos]
++ is_escaped = False
++ ctx.pos += 1
++ while _HasData():
++ ch = data[ctx.pos]
++ ctx.pos += 1
++ if is_escaped:
++ is_escaped = False
++ elif ch == '\\':
++ is_escaped = True
++ elif ch == delim:
++ return
++ raise ValueError('Unterminated string at %s' % _RenderLineCol(data, lo))
++
++ while _HasData():
++ blank = _NextBlank()
++ if blank is not None:
++ yield ('#', blank[0], blank[1])
++ if not _HasData():
++ break
++ lo = ctx.pos
++ ch = data[ctx.pos]
++ if ch in '{}[],:':
++ ctx.pos += 1
++ t = ch
++ elif ch.isdigit():
++ _EatWhile(_isDigit)
++ t = '0'
++ elif ch in '+-':
++ ctx.pos += 1
++ if not _HasData() or not data[ctx.pos].isdigit():
++ raise ValueError('Invalid int at %s' % _RenderLineCol(data, lo))
++ _EatWhile(_isDigit)
++ t = '0'
++ elif ch in '"\'':
++ _EatString()
++ t = 'S'
++ else:
++ raise ValueError('Unknown char %s at %s' %
++ (repr(ch), _RenderLineCol(data, lo)))
++ yield (t, lo, ctx.pos)
++ yield ('E', ctx.pos, ctx.pos) # End sentinel.
++
++
++def _SkipBlanks(toks):
++ """Generator to remove whitespace and comments from Tokenize()."""
++ for t, lo, hi in toks:
++ if t != '#':
++ yield t, lo, hi
++
++
++class AnnotatedValue:
++ """Container for leaf values (ints or strings) with an annotated range."""
++
++ def __init__(self, val, lo, hi):
++ self.val = val
++ self.lo = lo
++ self.hi = hi
++
++ def __str__(self):
++ return '<%s@%d:%d>' % (str(self.val), self.lo, self.hi)
++
++ def __repr__(self):
++ return '<%r@%d:%d>' % (self.val, self.lo, self.hi)
++
++ def __hash__(self):
++ return hash(self.val)
++
++ def __eq__(self, other):
++ return self.val == other
++
++
++class ResourceIdParser:
++ """resource_ids parser that stores leaf values as AnnotatedValue.
++
++ Algorithm: Use Tokenize() to split |data| into tokens and _SkipBlanks() to
++ ignore comments and spacing, then apply a recursive parsing, using a one-token
++ look-ahead for decision making.
++ """
++
++ def __init__(self, data, tok_gen):
++ self.data = data
++ self.state = []
++ self.toks = _SkipBlanks(tok_gen)
++ self.tok_look_ahead = None
++
++ def _MakeErr(self, msg, pos):
++ return ValueError(msg + ' at ' + _RenderLineCol(self.data, pos))
++
++ def _PeekTok(self):
++ if self.tok_look_ahead is None:
++ self.tok_look_ahead = next(self.toks)
++ return self.tok_look_ahead
++
++ def _NextTok(self):
++ if self.tok_look_ahead is None:
++ return next(self.toks)
++ ret = self.tok_look_ahead
++ self.tok_look_ahead = None
++ return ret
++
++ def _EatTok(self, exp_t, tok_name=None):
++ t, lo, _ = self._NextTok()
++ if t != exp_t:
++ raise self._MakeErr('Bad token: Expect \'%s\'' % (tok_name or exp_t), lo)
++
++ def _NextIntOrString(self):
++ t, lo, hi = self._NextTok()
++ if t != '0' and t != 'S':
++ raise self._MakeErr('Expected number or string', lo)
++ value = eval(self.data[lo:hi])
++ return AnnotatedValue(value, lo, hi)
++
++ # Consumes separator ',' and returns whether |end_ch| is encountered.
++ def _EatSep(self, end_ch):
++ t, lo, _ = self._PeekTok()
++ if t == ',':
++ self._EatTok(',')
++ # Allow trailing ','.
++ t, _, _ = self._PeekTok()
++ return t == end_ch
++ elif t == end_ch:
++ return True
++ else:
++ raise self._MakeErr('Expect \',\' or \'%s\'' % end_ch, lo)
++
++ def _NextList(self):
++ self._EatTok('[')
++ ret = []
++ t, _, _ = self._PeekTok()
++ if t != ']':
++ while True:
++ ret.append(self._NextObject())
++ if self._EatSep(']'):
++ break
++ self._EatTok(']')
++ return ret
++
++ def _NextDict(self):
++ self._EatTok('{')
++ ret = {}
++ t, _, _ = self._PeekTok()
++ if t != '}':
++ while True:
++ k = self._NextIntOrString()
++ self._EatTok(':')
++ v = self._NextObject()
++ ret[k] = v
++ if self._EatSep('}'):
++ break
++ self._EatTok('}')
++ return ret
++
++ def _NextObject(self):
++ t, lo, _ = self._PeekTok()
++ if t == '[':
++ return self._NextList()
++ elif t == '{':
++ return self._NextDict()
++ elif t == '0' or t == 'S':
++ return self._NextIntOrString()
++ else:
++ raise self._MakeErr('Bad token: Type = %s' % t, lo)
++
++ def Parse(self):
++ root_obj = self._NextObject()
++ self._EatTok('E', 'EOF')
++ return root_obj
+diff --git a/tools/grit/grit/tool/update_resource_ids/reader.py b/tools/grit/grit/tool/update_resource_ids/reader.py
+new file mode 100644
+index 0000000000..0a156d2deb
+--- /dev/null
++++ b/tools/grit/grit/tool/update_resource_ids/reader.py
+@@ -0,0 +1,83 @@
++# Copyright 2019 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++"""Helpers to read GRD files and estimate resource ID usages.
++
++This module uses grit.grd_reader to estimate resource ID usages in GRD
++(and GRDP) files by counting the occurrences of {include, message, structure}
++tags. This approach avoids the complexties of conditional inclusions, but
++produces a conservative estimate of ID usages.
++"""
++
++from __future__ import print_function
++
++import collections
++import os
++
++from grit import grd_reader
++from grit import util
++from grit.tool.update_resource_ids import common
++
++TAGS_OF_INTEREST = set(['include', 'message', 'structure'])
++
++def _CountResourceUsage(grd, seen_files):
++ tag_name_to_count = {tag: set() for tag in TAGS_OF_INTEREST}
++ # Pass '_chromium', but '_google_chrome' would produce the same result.
++ root = grd_reader.Parse(grd, defines={'_chromium': True})
++ seen_files.add(grd)
++ # Count all descendant tags, regardless of whether they're active.
++ for node in root.Preorder():
++ if node.name in TAGS_OF_INTEREST:
++ tag_name_to_count[node.name].add(node.attrs['name'])
++ elif node.name == 'part':
++ part_path = os.path.join(os.path.dirname(grd), node.GetInputPath())
++ seen_files.add(util.normpath(part_path))
++ return {k: len(v) for k, v in tag_name_to_count.items() if v}
++
++
++def GenerateResourceUsages(item_list, src_dir, fake, seen_files):
++ """Visits a list of ItemInfo to generate maps from tag name to usage.
++
++ Args:
++ root_obj: Root dict of a resource_ids file.
++ src_dir: Absolute directory of Chrome's src/ directory.
++ fake: For testing: Sets 10 as usages for all tags, to avoid reading GRD.
++ seen_files: A set to collect paths of files read.
++ Yields:
++ Tuple (item, tag_name_to_usage), where |item| is from |item_list| and
++ |tag_name_to_usage| is a dict() mapping tag name to (int) usage.
++ """
++ if fake:
++ for item in item_list:
++ tag_name_to_usage = collections.Counter({t.name: 10 for t in item.tags})
++ yield item, tag_name_to_usage
++ return
++ for item in item_list:
++ supported_tag_names = set(tag.name for tag in item.tags)
++ if item.meta and 'sizes' in item.meta:
++ # If META has "sizes" field, use it instead of reading GRD.
++ tag_name_to_usage = collections.Counter()
++ for k, vlist in item.meta['sizes'].items():
++ tag_name_to_usage[common.StripPlural(k.val)] = sum(v.val for v in vlist)
++ tag_names = set(tag_name_to_usage.keys())
++ if tag_names != supported_tag_names:
++ raise ValueError('META "sizes" field have identical fields as actual '
++ '"sizes" field.')
++ else:
++ # Generated GRD start with '<(SHARED_INTERMEDIATE_DIR)'. Just check '<'.
++ if item.grd.startswith('<'):
++ raise ValueError('%s: Generated GRD must use META with "sizes" field '
++ 'to specify size bounds.' % item.grd)
++ grd_file = os.path.join(src_dir, item.grd)
++ if not os.path.exists(grd_file):
++ # Silently skip missing files so that src-internal files do not break
++ # public checkouts.
++ yield item, {}
++ continue
++ tag_name_to_usage = _CountResourceUsage(grd_file, seen_files)
++ tag_names = set(tag_name_to_usage.keys())
++ if not tag_names.issubset(supported_tag_names):
++ missing = [t + 's' for t in tag_names - supported_tag_names]
++ raise ValueError(
++ 'Resource ids for %s needs entry for %s' % (item.grd, missing))
++ yield item, tag_name_to_usage
+diff --git a/tools/grit/grit/tool/xmb.py b/tools/grit/grit/tool/xmb.py
+new file mode 100644
+index 0000000000..b821308369
+--- /dev/null
++++ b/tools/grit/grit/tool/xmb.py
+@@ -0,0 +1,295 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++"""The 'grit xmb' tool.
++"""
++
++from __future__ import print_function
++
++import getopt
++import os
++import sys
++
++from xml.sax import saxutils
++
++import six
++
++from grit import grd_reader
++from grit import lazy_re
++from grit import tclib
++from grit import util
++from grit.tool import interface
++
++
++# Used to collapse presentable content to determine if
++# xml:space="preserve" is needed.
++_WHITESPACES_REGEX = lazy_re.compile(r'\s\s*')
++
++
++# See XmlEscape below.
++_XML_QUOTE_ESCAPES = {
++ u"'": u'&apos;',
++ u'"': u'&quot;',
++}
++
++def _XmlEscape(s):
++ """Returns text escaped for XML in a way compatible with Google's
++ internal Translation Console tool. May be used for attributes as
++ well as for contents.
++ """
++ return saxutils.escape(six.text_type(s), _XML_QUOTE_ESCAPES).encode('utf-8')
++
++
++def _WriteAttribute(file, name, value):
++ """Writes an XML attribute to the specified file.
++
++ Args:
++ file: file to write to
++ name: name of the attribute
++ value: (unescaped) value of the attribute
++ """
++ name = name.encode('utf-8')
++ if value:
++ file.write(b' %s="%s"' % (name, _XmlEscape(value)))
++
++
++def _WriteMessage(file, message):
++ presentable_content = message.GetPresentableContent()
++ assert (isinstance(presentable_content, six.string_types) or
++ (len(message.parts) == 1 and
++ type(message.parts[0] == tclib.Placeholder)))
++ preserve_space = presentable_content != _WHITESPACES_REGEX.sub(
++ u' ', presentable_content.strip())
++
++ file.write(b'<msg')
++ _WriteAttribute(file, 'desc', message.GetDescription())
++ _WriteAttribute(file, 'id', message.GetId())
++ _WriteAttribute(file, 'meaning', message.GetMeaning())
++ if preserve_space:
++ _WriteAttribute(file, 'xml:space', 'preserve')
++ file.write(b'>')
++ if not preserve_space:
++ file.write(b'\n ')
++
++ parts = message.GetContent()
++ for part in parts:
++ if isinstance(part, tclib.Placeholder):
++ file.write(b'<ph')
++ _WriteAttribute(file, 'name', part.GetPresentation())
++ file.write(b'><ex>')
++ file.write(_XmlEscape(part.GetExample()))
++ file.write(b'</ex>')
++ file.write(_XmlEscape(part.GetOriginal()))
++ file.write(b'</ph>')
++ else:
++ file.write(_XmlEscape(part))
++ if not preserve_space:
++ file.write(b'\n')
++ file.write(b'</msg>\n')
++
++
++def WriteXmbFile(file, messages):
++ """Writes the given grit.tclib.Message items to the specified open
++ file-like object in the XMB format.
++ """
++ file.write(b"""<?xml version="1.0" encoding="UTF-8"?>
++<!DOCTYPE messagebundle [
++<!ELEMENT messagebundle (msg)*>
++<!ATTLIST messagebundle class CDATA #IMPLIED>
++
++<!ELEMENT msg (#PCDATA|ph|source)*>
++<!ATTLIST msg id CDATA #IMPLIED>
++<!ATTLIST msg seq CDATA #IMPLIED>
++<!ATTLIST msg name CDATA #IMPLIED>
++<!ATTLIST msg desc CDATA #IMPLIED>
++<!ATTLIST msg meaning CDATA #IMPLIED>
++<!ATTLIST msg obsolete (obsolete) #IMPLIED>
++<!ATTLIST msg xml:space (default|preserve) "default">
++<!ATTLIST msg is_hidden CDATA #IMPLIED>
++
++<!ELEMENT source (#PCDATA)>
++
++<!ELEMENT ph (#PCDATA|ex)*>
++<!ATTLIST ph name CDATA #REQUIRED>
++
++<!ELEMENT ex (#PCDATA)>
++]>
++<messagebundle>
++""")
++ for message in messages:
++ _WriteMessage(file, message)
++ file.write(b'</messagebundle>')
++
++
++class OutputXmb(interface.Tool):
++ """Outputs all translateable messages in the .grd input file to an
++.xmb file, which is the format used to give source messages to
++Google's internal Translation Console tool. The format could easily
++be used for other systems.
++
++Usage: grit xmb [-i|-h] [-l LIMITFILE] OUTPUTPATH
++
++OUTPUTPATH is the path you want to output the .xmb file to.
++
++The -l option can be used to output only some of the resources to the .xmb file.
++LIMITFILE is the path to a file that is used to limit the items output to the
++xmb file. If the filename extension is .grd, the file must be a .grd file
++and the tool only output the contents of nodes from the input file that also
++exist in the limit file (as compared on the 'name' attribute). Otherwise it must
++contain a list of the IDs that output should be limited to, one ID per line, and
++the tool will only output nodes with 'name' attributes that match one of the
++IDs.
++
++The -i option causes 'grit xmb' to output an "IDs only" file instead of an XMB
++file. The "IDs only" file contains the message ID of each message that would
++normally be output to the XMB file, one message ID per line. It is designed for
++use with the 'grit transl2tc' tool's -l option.
++
++Other options:
++
++ -D NAME[=VAL] Specify a C-preprocessor-like define NAME with optional
++ value VAL (defaults to 1) which will be used to control
++ conditional inclusion of resources.
++
++ -E NAME=VALUE Set environment variable NAME to VALUE (within grit).
++
++"""
++ # The different output formats supported by this tool
++ FORMAT_XMB = 0
++ FORMAT_IDS_ONLY = 1
++
++ def __init__(self, defines=None):
++ super(OutputXmb, self).__init__()
++ self.format = self.FORMAT_XMB
++ self.defines = defines or {}
++
++ def ShortDescription(self):
++ return 'Exports all translateable messages into an XMB file.'
++
++ def Run(self, opts, args):
++ os.environ['cwd'] = os.getcwd()
++
++ self.SetOptions(opts)
++
++ limit_file = None
++ limit_is_grd = False
++ limit_file_dir = None
++ own_opts, args = getopt.getopt(args, 'l:D:ih', ('help',))
++ for key, val in own_opts:
++ if key == '-l':
++ limit_file = open(val, 'r')
++ limit_file_dir = util.dirname(val)
++ if not len(limit_file_dir):
++ limit_file_dir = '.'
++ limit_is_grd = os.path.splitext(val)[1] == '.grd'
++ elif key == '-i':
++ self.format = self.FORMAT_IDS_ONLY
++ elif key == '-D':
++ name, val = util.ParseDefine(val)
++ self.defines[name] = val
++ elif key == '-E':
++ (env_name, env_value) = val.split('=', 1)
++ os.environ[env_name] = env_value
++ elif key == '--help':
++ self.ShowUsage()
++ sys.exit(0)
++ if not len(args) == 1:
++ print('grit xmb takes exactly one argument, the path to the XMB file '
++ 'to output.')
++ return 2
++
++ xmb_path = args[0]
++ res_tree = grd_reader.Parse(opts.input, debug=opts.extra_verbose, defines=self.defines)
++ res_tree.SetOutputLanguage('en')
++ res_tree.SetDefines(self.defines)
++ res_tree.OnlyTheseTranslations([])
++ res_tree.RunGatherers()
++
++ with open(xmb_path, 'wb') as output_file:
++ self.Process(
++ res_tree, output_file, limit_file, limit_is_grd, limit_file_dir)
++ if limit_file:
++ limit_file.close()
++ print("Wrote %s" % xmb_path)
++
++ def Process(self, res_tree, output_file, limit_file=None, limit_is_grd=False,
++ dir=None):
++ """Writes a document with the contents of res_tree into output_file,
++ limiting output to the IDs specified in limit_file, which is a GRD file if
++ limit_is_grd is true, otherwise a file with one ID per line.
++
++ The format of the output document depends on this object's format attribute.
++ It can be FORMAT_XMB or FORMAT_IDS_ONLY.
++
++ The FORMAT_IDS_ONLY format causes this function to write just a list
++ of the IDs of all messages that would have been added to the XMB file, one
++ ID per line.
++
++ The FORMAT_XMB format causes this function to output the (default) XMB
++ format.
++
++ Args:
++ res_tree: base.Node()
++ output_file: file open for writing
++ limit_file: None or file open for reading
++ limit_is_grd: True | False
++ dir: Directory of the limit file
++ """
++ if limit_file:
++ if limit_is_grd:
++ limit_list = []
++ limit_tree = grd_reader.Parse(limit_file,
++ dir=dir,
++ debug=self.o.extra_verbose)
++ for node in limit_tree:
++ if 'name' in node.attrs:
++ limit_list.append(node.attrs['name'])
++ else:
++ # Not a GRD file, so it's just a file with one ID per line
++ limit_list = [item.strip() for item in limit_file.read().split('\n')]
++
++ ids_already_done = {}
++ messages = []
++ for node in res_tree:
++ if (limit_file and
++ not ('name' in node.attrs and node.attrs['name'] in limit_list)):
++ continue
++ if not node.IsTranslateable():
++ continue
++
++ for clique in node.GetCliques():
++ if not clique.IsTranslateable():
++ continue
++ if not clique.GetMessage().GetRealContent():
++ continue
++
++ # Some explanation is in order here. Note that we can have
++ # many messages with the same ID.
++ #
++ # The way we work around this is to maintain a list of cliques
++ # per message ID (in the UberClique) and select the "best" one
++ # (the first one that has a description, or an arbitrary one
++ # if there is no description) for inclusion in the XMB file.
++ # The translations are all going to be the same for messages
++ # with the same ID, although the way we replace placeholders
++ # might be slightly different.
++ id = clique.GetMessage().GetId()
++ if id in ids_already_done:
++ continue
++ ids_already_done[id] = 1
++
++ message = node.UberClique().BestClique(id).GetMessage()
++ messages += [message]
++
++ # Ensure a stable order of messages, to help regression testing.
++ messages.sort(key=lambda x:x.GetId())
++
++ if self.format == self.FORMAT_IDS_ONLY:
++ # We just print the list of IDs to the output file.
++ for msg in messages:
++ output_file.write(msg.GetId())
++ output_file.write('\n')
++ else:
++ assert self.format == self.FORMAT_XMB
++ WriteXmbFile(output_file, messages)
+diff --git a/tools/grit/grit/tool/xmb_unittest.py b/tools/grit/grit/tool/xmb_unittest.py
+new file mode 100644
+index 0000000000..3c7e92cee7
+--- /dev/null
++++ b/tools/grit/grit/tool/xmb_unittest.py
+@@ -0,0 +1,132 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit tests for 'grit xmb' tool.'''
++
++from __future__ import print_function
++
++import io
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
++
++import unittest
++import xml.sax
++
++from six import StringIO
++
++from grit import grd_reader
++from grit import util
++from grit.tool import xmb
++
++
++class XmbUnittest(unittest.TestCase):
++ def setUp(self):
++ self.res_tree = grd_reader.Parse(
++ io.BytesIO(u'''<?xml version="1.0" encoding="UTF-8"?>
++ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
++ <release seq="3">
++ <includes>
++ <include type="gif" name="ID_LOGO" file="images/logo.gif" />
++ </includes>
++ <messages>
++ <message name="GOOD" desc="sub" sub_variable="true">
++ excellent
++ </message>
++ <message name="IDS_GREETING" desc="Printed to greet the currently logged in user">
++ Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, are you doing [GOOD] today?
++ </message>
++ <message name="IDS_BONGOBINGO">
++ Yibbee
++ </message>
++ <message name="IDS_UNICODE">
++ Ol\xe1, \u4eca\u65e5\u306f! \U0001F60A
++ </message>
++ </messages>
++ <structures>
++ <structure type="dialog" name="IDD_SPACYBOX" encoding="utf-16" file="grit/testdata/klonk.rc" />
++ </structures>
++ </release>
++ </grit>'''.encode('utf-8')), '.')
++ self.xmb_file = io.BytesIO()
++
++ def testNormalOutput(self):
++ xmb.OutputXmb().Process(self.res_tree, self.xmb_file)
++ output = self.xmb_file.getvalue().decode('utf-8')
++ self.failUnless(output.count('Joi'))
++ self.failUnless(output.count('Yibbee'))
++ self.failUnless(output.count(u'Ol\xe1, \u4eca\u65e5\u306f! \U0001F60A'))
++
++ def testLimitList(self):
++ limit_file = StringIO(
++ 'IDS_BONGOBINGO\nIDS_DOES_NOT_EXIST\nIDS_ALSO_DOES_NOT_EXIST')
++ xmb.OutputXmb().Process(self.res_tree, self.xmb_file, limit_file, False)
++ output = self.xmb_file.getvalue().decode('utf-8')
++ self.failUnless(output.count('Yibbee'))
++ self.failUnless(not output.count('Joi'))
++
++ def testLimitGrd(self):
++ limit_file = StringIO('''<?xml version="1.0" encoding="UTF-8"?>
++ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
++ <release seq="3">
++ <messages>
++ <message name="IDS_GREETING" desc="Printed to greet the currently logged in user">
++ Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, how are you doing today?
++ </message>
++ </messages>
++ </release>
++ </grit>''')
++ tool = xmb.OutputXmb()
++ class DummyOpts(object):
++ extra_verbose = False
++ tool.o = DummyOpts()
++ tool.Process(self.res_tree, self.xmb_file, limit_file, True, dir='.')
++ output = self.xmb_file.getvalue().decode('utf-8')
++ self.failUnless(output.count('Joi'))
++ self.failUnless(not output.count('Yibbee'))
++
++ def testSubstitution(self):
++ self.res_tree.SetOutputLanguage('en')
++ os.chdir(util.PathFromRoot('.')) # so it can find klonk.rc
++ self.res_tree.RunGatherers()
++ xmb.OutputXmb().Process(self.res_tree, self.xmb_file)
++ output = self.xmb_file.getvalue().decode('utf-8')
++ self.failUnless(output.count(
++ '<ph name="GOOD_1"><ex>excellent</ex>[GOOD]</ph>'))
++
++ def testLeadingTrailingWhitespace(self):
++ # Regression test for problems outputting messages with leading or
++ # trailing whitespace (these come in via structures only, as
++ # message nodes already strip and store whitespace).
++ self.res_tree.SetOutputLanguage('en')
++ os.chdir(util.PathFromRoot('.')) # so it can find klonk.rc
++ self.res_tree.RunGatherers()
++ xmb.OutputXmb().Process(self.res_tree, self.xmb_file)
++ output = self.xmb_file.getvalue().decode('utf-8')
++ self.failUnless(output.count('OK ? </msg>'))
++
++ def testDisallowedChars(self):
++ # Validate that the invalid unicode is not accepted. Since it's not valid,
++ # we can't specify it in a string literal, so write as a byte sequence.
++ bad_xml = io.BytesIO()
++ bad_xml.write(b'''<?xml version="1.0" encoding="UTF-8"?>
++ <grit latest_public_release="2" source_lang_id="en-US"
++ current_release="3" base_dir=".">
++ <release seq="3">
++ <messages>
++ <message name="ID_FOO">''')
++ # UTF-8 corresponding to to \U00110000
++ # http://apps.timwhitlock.info/unicode/inspect/hex/110000
++ bad_xml.write(b'\xF4\x90\x80\x80')
++ bad_xml.write(b'''</message>
++ </messages>
++ </release>
++ </grit>''')
++ bad_xml.seek(0)
++ self.assertRaises(xml.sax.SAXParseException, grd_reader.Parse, bad_xml, '.')
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/util.py b/tools/grit/grit/util.py
+new file mode 100644
+index 0000000000..98433d154c
+--- /dev/null
++++ b/tools/grit/grit/util.py
+@@ -0,0 +1,691 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Utilities used by GRIT.
++'''
++
++from __future__ import print_function
++
++import codecs
++import io
++import os
++import re
++import shutil
++import sys
++import tempfile
++from xml.sax import saxutils
++
++import six
++from six import StringIO
++from six.moves import html_entities as entities
++
++from grit import lazy_re
++
++_root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
++
++
++# Unique constants for use by ReadFile().
++BINARY = 0
++
++
++# Unique constants representing data pack encodings.
++_, UTF8, UTF16 = range(3)
++
++
++def Encode(message, encoding):
++ '''Returns a byte stream that represents |message| in the given |encoding|.'''
++ # |message| is a python unicode string, so convert to a byte stream that
++ # has the correct encoding requested for the datapacks. We skip the first
++ # 2 bytes of text resources because it is the BOM.
++ if encoding == UTF8:
++ return message.encode('utf8')
++ if encoding == UTF16:
++ return message.encode('utf16')[2:]
++ # Default is BINARY
++ return message
++
++
++# Matches all different types of linebreaks.
++LINEBREAKS = re.compile('\r\n|\n|\r')
++
++def MakeRelativePath(base_path, path_to_make_relative):
++ """Returns a relative path such from the base_path to
++ the path_to_make_relative.
++
++ In other words, os.join(base_path,
++ MakeRelativePath(base_path, path_to_make_relative))
++ is the same location as path_to_make_relative.
++
++ Args:
++ base_path: the root path
++ path_to_make_relative: an absolute path that is on the same drive
++ as base_path
++ """
++
++ def _GetPathAfterPrefix(prefix_path, path_with_prefix):
++ """Gets the subpath within in prefix_path for the path_with_prefix
++ with no beginning or trailing path separators.
++
++ Args:
++ prefix_path: the base path
++ path_with_prefix: a path that starts with prefix_path
++ """
++ assert path_with_prefix.startswith(prefix_path)
++ path_without_prefix = path_with_prefix[len(prefix_path):]
++ normalized_path = os.path.normpath(path_without_prefix.strip(os.path.sep))
++ if normalized_path == '.':
++ normalized_path = ''
++ return normalized_path
++
++ def _GetCommonBaseDirectory(*args):
++ """Returns the common prefix directory for the given paths
++
++ Args:
++ The list of paths (at least one of which should be a directory)
++ """
++ prefix = os.path.commonprefix(args)
++ # prefix is a character-by-character prefix (i.e. it does not end
++ # on a directory bound, so this code fixes that)
++
++ # if the prefix ends with the separator, then it is prefect.
++ if len(prefix) > 0 and prefix[-1] == os.path.sep:
++ return prefix
++
++ # We need to loop through all paths or else we can get
++ # tripped up by "c:\a" and "c:\abc". The common prefix
++ # is "c:\a" which is a directory and looks good with
++ # respect to the first directory but it is clear that
++ # isn't a common directory when the second path is
++ # examined.
++ for path in args:
++ assert len(path) >= len(prefix)
++ # If the prefix the same length as the path,
++ # then the prefix must be a directory (since one
++ # of the arguements should be a directory).
++ if path == prefix:
++ continue
++ # if the character after the prefix in the path
++ # is the separator, then the prefix appears to be a
++ # valid a directory as well for the given path
++ if path[len(prefix)] == os.path.sep:
++ continue
++ # Otherwise, the prefix is not a directory, so it needs
++ # to be shortened to be one
++ index_sep = prefix.rfind(os.path.sep)
++ # The use "index_sep + 1" because it includes the final sep
++ # and it handles the case when the index_sep is -1 as well
++ prefix = prefix[:index_sep + 1]
++ # At this point we backed up to a directory bound which is
++ # common to all paths, so we can quit going through all of
++ # the paths.
++ break
++ return prefix
++
++ prefix = _GetCommonBaseDirectory(base_path, path_to_make_relative)
++ # If the paths had no commonality at all, then return the absolute path
++ # because it is the best that can be done. If the path had to be relative
++ # then eventually this absolute path will be discovered (when a build breaks)
++ # and an appropriate fix can be made, but having this allows for the best
++ # backward compatibility with the absolute path behavior in the past.
++ if len(prefix) <= 0:
++ return path_to_make_relative
++ # Build a path from the base dir to the common prefix
++ remaining_base_path = _GetPathAfterPrefix(prefix, base_path)
++
++ # The follow handles two case: "" and "foo\\bar"
++ path_pieces = remaining_base_path.split(os.path.sep)
++ base_depth_from_prefix = len([d for d in path_pieces if len(d)])
++ base_to_prefix = (".." + os.path.sep) * base_depth_from_prefix
++
++ # Put add in the path from the prefix to the path_to_make_relative
++ remaining_other_path = _GetPathAfterPrefix(prefix, path_to_make_relative)
++ return base_to_prefix + remaining_other_path
++
++
++KNOWN_SYSTEM_IDENTIFIERS = set()
++
++SYSTEM_IDENTIFIERS = None
++
++def SetupSystemIdentifiers(ids):
++ '''Adds ids to a regexp of known system identifiers.
++
++ Can be called many times, ids will be accumulated.
++
++ Args:
++ ids: an iterable of strings
++ '''
++ KNOWN_SYSTEM_IDENTIFIERS.update(ids)
++ global SYSTEM_IDENTIFIERS
++ SYSTEM_IDENTIFIERS = lazy_re.compile(
++ ' | '.join([r'\b%s\b' % i for i in KNOWN_SYSTEM_IDENTIFIERS]),
++ re.VERBOSE)
++
++
++# Matches all of the resource IDs predefined by Windows.
++SetupSystemIdentifiers((
++ 'IDOK', 'IDCANCEL', 'IDC_STATIC', 'IDYES', 'IDNO',
++ 'ID_FILE_NEW', 'ID_FILE_OPEN', 'ID_FILE_CLOSE', 'ID_FILE_SAVE',
++ 'ID_FILE_SAVE_AS', 'ID_FILE_PAGE_SETUP', 'ID_FILE_PRINT_SETUP',
++ 'ID_FILE_PRINT', 'ID_FILE_PRINT_DIRECT', 'ID_FILE_PRINT_PREVIEW',
++ 'ID_FILE_UPDATE', 'ID_FILE_SAVE_COPY_AS', 'ID_FILE_SEND_MAIL',
++ 'ID_FILE_MRU_FIRST', 'ID_FILE_MRU_LAST',
++ 'ID_EDIT_CLEAR', 'ID_EDIT_CLEAR_ALL', 'ID_EDIT_COPY',
++ 'ID_EDIT_CUT', 'ID_EDIT_FIND', 'ID_EDIT_PASTE', 'ID_EDIT_PASTE_LINK',
++ 'ID_EDIT_PASTE_SPECIAL', 'ID_EDIT_REPEAT', 'ID_EDIT_REPLACE',
++ 'ID_EDIT_SELECT_ALL', 'ID_EDIT_UNDO', 'ID_EDIT_REDO',
++ 'VS_VERSION_INFO', 'IDRETRY',
++ 'ID_APP_ABOUT', 'ID_APP_EXIT',
++ 'ID_NEXT_PANE', 'ID_PREV_PANE',
++ 'ID_WINDOW_NEW', 'ID_WINDOW_ARRANGE', 'ID_WINDOW_CASCADE',
++ 'ID_WINDOW_TILE_HORZ', 'ID_WINDOW_TILE_VERT', 'ID_WINDOW_SPLIT',
++ 'ATL_IDS_SCSIZE', 'ATL_IDS_SCMOVE', 'ATL_IDS_SCMINIMIZE',
++ 'ATL_IDS_SCMAXIMIZE', 'ATL_IDS_SCNEXTWINDOW', 'ATL_IDS_SCPREVWINDOW',
++ 'ATL_IDS_SCCLOSE', 'ATL_IDS_SCRESTORE', 'ATL_IDS_SCTASKLIST',
++ 'ATL_IDS_MDICHILD', 'ATL_IDS_IDLEMESSAGE', 'ATL_IDS_MRU_FILE' ))
++
++
++# Matches character entities, whether specified by name, decimal or hex.
++_HTML_ENTITY = lazy_re.compile(
++ '&(#(?P<decimal>[0-9]+)|#x(?P<hex>[a-fA-F0-9]+)|(?P<named>[a-z0-9]+));',
++ re.IGNORECASE)
++
++# Matches characters that should be HTML-escaped. This is <, > and &, but only
++# if the & is not the start of an HTML character entity.
++_HTML_CHARS_TO_ESCAPE = lazy_re.compile(
++ '"|<|>|&(?!#[0-9]+|#x[0-9a-z]+|[a-z]+;)',
++ re.IGNORECASE | re.MULTILINE)
++
++
++def ReadFile(filename, encoding):
++ '''Reads and returns the entire contents of the given file.
++
++ Args:
++ filename: The path to the file.
++ encoding: A Python codec name or the special value: BINARY to read
++ the file in binary mode.
++ '''
++ if encoding == BINARY:
++ mode = 'rb'
++ encoding = None
++ else:
++ mode = 'rU'
++
++ with io.open(filename, mode, encoding=encoding) as f:
++ return f.read()
++
++
++def WrapOutputStream(stream, encoding = 'utf-8'):
++ '''Returns a stream that wraps the provided stream, making it write
++ characters using the specified encoding.'''
++ return codecs.getwriter(encoding)(stream)
++
++
++def ChangeStdoutEncoding(encoding = 'utf-8'):
++ '''Changes STDOUT to print characters using the specified encoding.'''
++ # If we're unittesting, don't reconfigure.
++ if isinstance(sys.stdout, StringIO):
++ return
++
++ if sys.version_info.major < 3:
++ # Python 2 has binary streams by default, so reconfigure directly.
++ sys.stdout = WrapOutputStream(sys.stdout, encoding)
++ sys.stderr = WrapOutputStream(sys.stderr, encoding)
++ elif sys.version_info < (3, 7):
++ # Python 3 has text streams by default, so we have to detach them first.
++ sys.stdout = WrapOutputStream(sys.stdout.detach(), encoding)
++ sys.stderr = WrapOutputStream(sys.stderr.detach(), encoding)
++ else:
++ # Python 3.7+ provides an API for this specifically.
++ sys.stdout.reconfigure(encoding=encoding)
++ sys.stderr.reconfigure(encoding=encoding)
++
++
++def EscapeHtml(text, escape_quotes = False):
++ '''Returns 'text' with <, > and & (and optionally ") escaped to named HTML
++ entities. Any existing named entity or HTML entity defined by decimal or
++ hex code will be left untouched. This is appropriate for escaping text for
++ inclusion in HTML, but not for XML.
++ '''
++ def Replace(match):
++ if match.group() == '&': return '&amp;'
++ elif match.group() == '<': return '&lt;'
++ elif match.group() == '>': return '&gt;'
++ elif match.group() == '"':
++ if escape_quotes: return '&quot;'
++ else: return match.group()
++ else: assert False
++ out = _HTML_CHARS_TO_ESCAPE.sub(Replace, text)
++ return out
++
++
++def UnescapeHtml(text, replace_nbsp=True):
++ '''Returns 'text' with all HTML character entities (both named character
++ entities and those specified by decimal or hexadecimal Unicode ordinal)
++ replaced by their Unicode characters (or latin1 characters if possible).
++
++ The only exception is that &nbsp; will not be escaped if 'replace_nbsp' is
++ False.
++ '''
++ def Replace(match):
++ groups = match.groupdict()
++ if groups['hex']:
++ return six.unichr(int(groups['hex'], 16))
++ elif groups['decimal']:
++ return six.unichr(int(groups['decimal'], 10))
++ else:
++ name = groups['named']
++ if name == 'nbsp' and not replace_nbsp:
++ return match.group() # Don't replace &nbsp;
++ assert name != None
++ if name in entities.name2codepoint:
++ return six.unichr(entities.name2codepoint[name])
++ else:
++ return match.group() # Unknown HTML character entity - don't replace
++
++ out = _HTML_ENTITY.sub(Replace, text)
++ return out
++
++
++def EncodeCdata(cdata):
++ '''Returns the provided cdata in either escaped format or <![CDATA[xxx]]>
++ format, depending on which is more appropriate for easy editing. The data
++ is escaped for inclusion in an XML element's body.
++
++ Args:
++ cdata: 'If x < y and y < z then x < z'
++
++ Return:
++ '<![CDATA[If x < y and y < z then x < z]]>'
++ '''
++ if cdata.count('<') > 1 or cdata.count('>') > 1 and cdata.count(']]>') == 0:
++ return '<![CDATA[%s]]>' % cdata
++ else:
++ return saxutils.escape(cdata)
++
++
++def FixupNamedParam(function, param_name, param_value):
++ '''Returns a closure that is identical to 'function' but ensures that the
++ named parameter 'param_name' is always set to 'param_value' unless explicitly
++ set by the caller.
++
++ Args:
++ function: callable
++ param_name: 'bingo'
++ param_value: 'bongo' (any type)
++
++ Return:
++ callable
++ '''
++ def FixupClosure(*args, **kw):
++ if not param_name in kw:
++ kw[param_name] = param_value
++ return function(*args, **kw)
++ return FixupClosure
++
++
++def PathFromRoot(path):
++ r'''Takes a path relative to the root directory for GRIT (the one that grit.py
++ resides in) and returns a path that is either absolute or relative to the
++ current working directory (i.e .a path you can use to open the file).
++
++ Args:
++ path: 'rel_dir\file.ext'
++
++ Return:
++ 'c:\src\tools\rel_dir\file.ext
++ '''
++ return os.path.normpath(os.path.join(_root_dir, path))
++
++
++def ParseGrdForUnittest(body, base_dir=None, predetermined_ids_file=None,
++ run_gatherers=False):
++ '''Parse a skeleton .grd file and return it, for use in unit tests.
++
++ Args:
++ body: XML that goes inside the <release> element.
++ base_dir: The base_dir attribute of the <grit> tag.
++ '''
++ from grit import grd_reader
++ if isinstance(body, six.text_type):
++ body = body.encode('utf-8')
++ if base_dir is None:
++ base_dir = PathFromRoot('.')
++ lines = [b'<?xml version="1.0" encoding="UTF-8"?>']
++ lines.append(b'<grit latest_public_release="2" current_release="3" '
++ b'source_lang_id="en" base_dir="%s">' % base_dir.encode('utf-8'))
++ if b'<outputs>' in body:
++ lines.append(body)
++ else:
++ lines.append(b' <outputs></outputs>')
++ lines.append(b' <release seq="3">')
++ lines.append(body)
++ lines.append(b' </release>')
++ lines.append(b'</grit>')
++ ret = grd_reader.Parse(io.BytesIO(b'\n'.join(lines)), dir='.')
++ ret.SetOutputLanguage('en')
++ if run_gatherers:
++ ret.RunGatherers()
++ ret.SetPredeterminedIdsFile(predetermined_ids_file)
++ ret.InitializeIds()
++ return ret
++
++
++def StripBlankLinesAndComments(text):
++ '''Strips blank lines and comments from C source code, for unit tests.'''
++ return '\n'.join(line for line in text.splitlines()
++ if line and not line.startswith('//'))
++
++
++def dirname(filename):
++ '''Version of os.path.dirname() that never returns empty paths (returns
++ '.' if the result of os.path.dirname() is empty).
++ '''
++ ret = os.path.dirname(filename)
++ if ret == '':
++ ret = '.'
++ return ret
++
++
++def normpath(path):
++ '''Version of os.path.normpath that also changes backward slashes to
++ forward slashes when not running on Windows.
++ '''
++ # This is safe to always do because the Windows version of os.path.normpath
++ # will replace forward slashes with backward slashes.
++ path = path.replace('\\', '/')
++ return os.path.normpath(path)
++
++
++_LANGUAGE_SPLIT_RE = lazy_re.compile('-|_|/')
++
++
++def CanonicalLanguage(code):
++ '''Canonicalizes two-part language codes by using a dash and making the
++ second part upper case. Returns one-part language codes unchanged.
++
++ Args:
++ code: 'zh_cn'
++
++ Return:
++ code: 'zh-CN'
++ '''
++ parts = _LANGUAGE_SPLIT_RE.split(code)
++ code = [ parts[0] ]
++ for part in parts[1:]:
++ code.append(part.upper())
++ return '-'.join(code)
++
++
++_LANG_TO_CODEPAGE = {
++ 'en' : 1252,
++ 'fr' : 1252,
++ 'it' : 1252,
++ 'de' : 1252,
++ 'es' : 1252,
++ 'nl' : 1252,
++ 'sv' : 1252,
++ 'no' : 1252,
++ 'da' : 1252,
++ 'fi' : 1252,
++ 'pt-BR' : 1252,
++ 'ru' : 1251,
++ 'ja' : 932,
++ 'zh-TW' : 950,
++ 'zh-CN' : 936,
++ 'ko' : 949,
++}
++
++
++def LanguageToCodepage(lang):
++ '''Returns the codepage _number_ that can be used to represent 'lang', which
++ may be either in formats such as 'en', 'pt_br', 'pt-BR', etc.
++
++ The codepage returned will be one of the 'cpXXXX' codepage numbers.
++
++ Args:
++ lang: 'de'
++
++ Return:
++ 1252
++ '''
++ lang = CanonicalLanguage(lang)
++ if lang in _LANG_TO_CODEPAGE:
++ return _LANG_TO_CODEPAGE[lang]
++ else:
++ print("Not sure which codepage to use for %s, assuming cp1252" % lang)
++ return 1252
++
++def NewClassInstance(class_name, class_type):
++ '''Returns an instance of the class specified in classname
++
++ Args:
++ class_name: the fully qualified, dot separated package + classname,
++ i.e. "my.package.name.MyClass". Short class names are not supported.
++ class_type: the class or superclass this object must implement
++
++ Return:
++ An instance of the class, or None if none was found
++ '''
++ lastdot = class_name.rfind('.')
++ module_name = ''
++ if lastdot >= 0:
++ module_name = class_name[0:lastdot]
++ if module_name:
++ class_name = class_name[lastdot+1:]
++ module = __import__(module_name, globals(), locals(), [''])
++ if hasattr(module, class_name):
++ class_ = getattr(module, class_name)
++ class_instance = class_()
++ if isinstance(class_instance, class_type):
++ return class_instance
++ return None
++
++
++def FixLineEnd(text, line_end):
++ # First normalize
++ text = text.replace('\r\n', '\n')
++ text = text.replace('\r', '\n')
++ # Then fix
++ text = text.replace('\n', line_end)
++ return text
++
++
++def BoolToString(bool):
++ if bool:
++ return 'true'
++ else:
++ return 'false'
++
++
++verbose = False
++extra_verbose = False
++
++def IsVerbose():
++ return verbose
++
++def IsExtraVerbose():
++ return extra_verbose
++
++def ParseDefine(define):
++ '''Parses a define argument and returns the name and value.
++
++ The format is either "NAME=VAL" or "NAME", using True as the default value.
++ Values of "1"/"true" and "0"/"false" are transformed to True and False
++ respectively.
++
++ Args:
++ define: a string of the form "NAME=VAL" or "NAME".
++
++ Returns:
++ A (name, value) pair. name is a string, value a string or boolean.
++ '''
++ parts = [part.strip() for part in define.split('=', 1)]
++ assert len(parts) >= 1
++ name = parts[0]
++ val = True
++ if len(parts) > 1:
++ val = parts[1]
++ if val == "1" or val == "true": val = True
++ elif val == "0" or val == "false": val = False
++ return (name, val)
++
++
++class Substituter(object):
++ '''Finds and substitutes variable names in text strings.
++
++ Given a dictionary of variable names and values, prepares to
++ search for patterns of the form [VAR_NAME] in a text.
++ The value will be substituted back efficiently.
++ Also applies to tclib.Message objects.
++ '''
++
++ def __init__(self):
++ '''Create an empty substituter.'''
++ self.substitutions_ = {}
++ self.dirty_ = True
++
++ def AddSubstitutions(self, subs):
++ '''Add new values to the substitutor.
++
++ Args:
++ subs: A dictionary of new substitutions.
++ '''
++ self.substitutions_.update(subs)
++ self.dirty_ = True
++
++ def AddMessages(self, messages, lang):
++ '''Adds substitutions extracted from node.Message objects.
++
++ Args:
++ messages: a list of node.Message objects.
++ lang: The translation language to use in substitutions.
++ '''
++ subs = [(str(msg.attrs['name']), msg.Translate(lang)) for msg in messages]
++ self.AddSubstitutions(dict(subs))
++ self.dirty_ = True
++
++ def GetExp(self):
++ '''Obtain a regular expression that will find substitution keys in text.
++
++ Create and cache if the substituter has been updated. Use the cached value
++ otherwise. Keys will be enclosed in [square brackets] in text.
++
++ Returns:
++ A regular expression object.
++ '''
++ if self.dirty_:
++ components = [r'\[%s\]' % (k,) for k in self.substitutions_]
++ self.exp = re.compile(r'(%s)' % ('|'.join(components),))
++ self.dirty_ = False
++ return self.exp
++
++ def Substitute(self, text):
++ '''Substitute the variable values in the given text.
++
++ Text of the form [message_name] will be replaced by the message's value.
++
++ Args:
++ text: A string of text.
++
++ Returns:
++ A string of text with substitutions done.
++ '''
++ return ''.join([self._SubFragment(f) for f in self.GetExp().split(text)])
++
++ def _SubFragment(self, fragment):
++ '''Utility function for Substitute.
++
++ Performs a simple substitution if the fragment is exactly of the form
++ [message_name].
++
++ Args:
++ fragment: A simple string.
++
++ Returns:
++ A string with the substitution done.
++ '''
++ if len(fragment) > 2 and fragment[0] == '[' and fragment[-1] == ']':
++ sub = self.substitutions_.get(fragment[1:-1], None)
++ if sub is not None:
++ return sub
++ return fragment
++
++ def SubstituteMessage(self, msg):
++ '''Apply substitutions to a tclib.Message object.
++
++ Text of the form [message_name] will be replaced by a new placeholder,
++ whose presentation will take the form the message_name_{UsageCount}, and
++ whose example will be the message's value. Existing placeholders are
++ not affected.
++
++ Args:
++ msg: A tclib.Message object.
++
++ Returns:
++ A tclib.Message object, with substitutions done.
++ '''
++ from grit import tclib # avoid circular import
++ counts = {}
++ text = msg.GetPresentableContent()
++ placeholders = []
++ newtext = ''
++ for f in self.GetExp().split(text):
++ sub = self._SubFragment(f)
++ if f != sub:
++ f = str(f)
++ count = counts.get(f, 0) + 1
++ counts[f] = count
++ name = "%s_%d" % (f[1:-1], count)
++ placeholders.append(tclib.Placeholder(name, f, sub))
++ newtext += name
++ else:
++ newtext += f
++ if placeholders:
++ return tclib.Message(newtext, msg.GetPlaceholders() + placeholders,
++ msg.GetDescription(), msg.GetMeaning())
++ else:
++ return msg
++
++
++class TempDir(object):
++ '''Creates files with the specified contents in a temporary directory,
++ for unit testing.
++ '''
++
++ def __init__(self, file_data, mode='w'):
++ self._tmp_dir_name = tempfile.mkdtemp()
++ assert not os.listdir(self.GetPath())
++ for name, contents in file_data.items():
++ file_path = self.GetPath(name)
++ dir_path = os.path.split(file_path)[0]
++ if not os.path.exists(dir_path):
++ os.makedirs(dir_path)
++ with open(file_path, mode) as f:
++ f.write(file_data[name])
++
++ def __enter__(self):
++ return self
++
++ def __exit__(self, *exc_info):
++ self.CleanUp()
++
++ def CleanUp(self):
++ shutil.rmtree(self.GetPath())
++
++ def GetPath(self, name=''):
++ name = os.path.join(self._tmp_dir_name, name)
++ assert name.startswith(self._tmp_dir_name)
++ return name
++
++ def AsCurrentDir(self):
++ return self._AsCurrentDirClass(self.GetPath())
++
++ class _AsCurrentDirClass(object):
++ def __init__(self, path):
++ self.path = path
++ def __enter__(self):
++ self.oldpath = os.getcwd()
++ os.chdir(self.path)
++ def __exit__(self, *exc_info):
++ os.chdir(self.oldpath)
+diff --git a/tools/grit/grit/util_unittest.py b/tools/grit/grit/util_unittest.py
+new file mode 100644
+index 0000000000..7d6efaf858
+--- /dev/null
++++ b/tools/grit/grit/util_unittest.py
+@@ -0,0 +1,118 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit test that checks some of util functions.
++'''
++
++from __future__ import print_function
++
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
++
++import unittest
++
++import six
++
++from grit import util
++
++
++class UtilUnittest(unittest.TestCase):
++ ''' Tests functions from util
++ '''
++
++ def testNewClassInstance(self):
++ # Test short class name with no fully qualified package name
++ # Should fail, it is not supported by the function now (as documented)
++ cls = util.NewClassInstance('grit.util.TestClassToLoad',
++ TestBaseClassToLoad)
++ self.failUnless(cls == None)
++
++ # Test non existent class name
++ cls = util.NewClassInstance('grit.util_unittest.NotExistingClass',
++ TestBaseClassToLoad)
++ self.failUnless(cls == None)
++
++ # Test valid class name and valid base class
++ cls = util.NewClassInstance('grit.util_unittest.TestClassToLoad',
++ TestBaseClassToLoad)
++ self.failUnless(isinstance(cls, TestBaseClassToLoad))
++
++ # Test valid class name with wrong hierarchy
++ cls = util.NewClassInstance('grit.util_unittest.TestClassNoBase',
++ TestBaseClassToLoad)
++ self.failUnless(cls == None)
++
++ def testCanonicalLanguage(self):
++ self.failUnless(util.CanonicalLanguage('en') == 'en')
++ self.failUnless(util.CanonicalLanguage('pt_br') == 'pt-BR')
++ self.failUnless(util.CanonicalLanguage('pt-br') == 'pt-BR')
++ self.failUnless(util.CanonicalLanguage('pt-BR') == 'pt-BR')
++ self.failUnless(util.CanonicalLanguage('pt/br') == 'pt-BR')
++ self.failUnless(util.CanonicalLanguage('pt/BR') == 'pt-BR')
++ self.failUnless(util.CanonicalLanguage('no_no_bokmal') == 'no-NO-BOKMAL')
++
++ def testUnescapeHtml(self):
++ self.failUnless(util.UnescapeHtml('&#1010;') == six.unichr(1010))
++ self.failUnless(util.UnescapeHtml('&#xABcd;') == six.unichr(43981))
++
++ def testRelativePath(self):
++ """ Verify that MakeRelativePath works in some tricky cases."""
++
++ def TestRelativePathCombinations(base_path, other_path, expected_result):
++ """ Verify that the relative path function works for
++ the given paths regardless of whether or not they end with
++ a trailing slash."""
++ for path1 in [base_path, base_path + os.path.sep]:
++ for path2 in [other_path, other_path + os.path.sep]:
++ result = util.MakeRelativePath(path1, path2)
++ self.failUnless(result == expected_result)
++
++ # set-up variables
++ root_dir = 'c:%sa' % os.path.sep
++ result1 = '..%sabc' % os.path.sep
++ path1 = root_dir + 'bc'
++ result2 = 'bc'
++ path2 = '%s%s%s' % (root_dir, os.path.sep, result2)
++ # run the tests
++ TestRelativePathCombinations(root_dir, path1, result1)
++ TestRelativePathCombinations(root_dir, path2, result2)
++
++ def testReadFile(self):
++ def Test(data, encoding, expected_result):
++ with open('testfile', 'wb') as f:
++ f.write(data)
++ self.assertEqual(util.ReadFile('testfile', encoding), expected_result)
++
++ test_std_newline = b'\xEF\xBB\xBFabc\ndef' # EF BB BF is UTF-8 BOM
++ newlines = [b'\n', b'\r\n', b'\r']
++
++ with util.TempDir({}) as tmp_dir:
++ with tmp_dir.AsCurrentDir():
++ for newline in newlines:
++ test = test_std_newline.replace(b'\n', newline)
++ Test(test, util.BINARY, test)
++ # utf-8 doesn't strip BOM
++ Test(test, 'utf-8', test_std_newline.decode('utf-8'))
++ # utf-8-sig strips BOM
++ Test(test, 'utf-8-sig', test_std_newline.decode('utf-8')[1:])
++ # test another encoding
++ Test(test, 'cp1252', test_std_newline.decode('cp1252'))
++ self.assertRaises(UnicodeDecodeError, Test, b'\x80', 'utf-8', None)
++
++
++class TestBaseClassToLoad(object):
++ pass
++
++class TestClassToLoad(TestBaseClassToLoad):
++ pass
++
++class TestClassNoBase(object):
++ pass
++
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit/xtb_reader.py b/tools/grit/grit/xtb_reader.py
+new file mode 100644
+index 0000000000..e0f842588a
+--- /dev/null
++++ b/tools/grit/grit/xtb_reader.py
+@@ -0,0 +1,140 @@
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Fast and efficient parser for XTB files.
++'''
++
++from __future__ import print_function
++
++import sys
++import xml.sax
++import xml.sax.handler
++
++import grit.node.base
++
++
++class XtbContentHandler(xml.sax.handler.ContentHandler):
++ '''A content handler that calls a given callback function for each
++ translation in the XTB file.
++ '''
++
++ def __init__(self, callback, defs=None, debug=False, target_platform=None):
++ self.callback = callback
++ self.debug = debug
++ # 0 if we are not currently parsing a translation, otherwise the message
++ # ID of that translation.
++ self.current_id = 0
++ # Empty if we are not currently parsing a translation, otherwise the
++ # parts we have for that translation - a list of tuples
++ # (is_placeholder, text)
++ self.current_structure = []
++ # Set to the language ID when we see the <translationbundle> node.
++ self.language = ''
++ # Keep track of the if block we're inside. We can't nest ifs.
++ self.if_expr = None
++ # Root defines to be used with if expr.
++ if defs:
++ self.defines = defs
++ else:
++ self.defines = {}
++ # Target platform for build.
++ if target_platform:
++ self.target_platform = target_platform
++ else:
++ self.target_platform = sys.platform
++
++ def startElement(self, name, attrs):
++ if name == 'translation':
++ assert self.current_id == 0 and len(self.current_structure) == 0, (
++ "Didn't expect a <translation> element here.")
++ self.current_id = attrs.getValue('id')
++ elif name == 'ph':
++ assert self.current_id != 0, "Didn't expect a <ph> element here."
++ self.current_structure.append((True, attrs.getValue('name')))
++ elif name == 'translationbundle':
++ self.language = attrs.getValue('lang')
++ elif name in ('if', 'then', 'else'):
++ assert self.if_expr is None, "Can't nest <if> or use <else> in xtb files"
++ self.if_expr = attrs.getValue('expr')
++
++ def endElement(self, name):
++ if name == 'translation':
++ assert self.current_id != 0
++
++ defs = self.defines
++ def pp_ifdef(define):
++ return define in defs
++ def pp_if(define):
++ return define in defs and defs[define]
++
++ # If we're in an if block, only call the callback (add the translation)
++ # if the expression is True.
++ should_run_callback = True
++ if self.if_expr:
++ should_run_callback = grit.node.base.Node.EvaluateExpression(
++ self.if_expr, self.defines, self.target_platform)
++ if should_run_callback:
++ self.callback(self.current_id, self.current_structure)
++
++ self.current_id = 0
++ self.current_structure = []
++ elif name == 'if':
++ assert self.if_expr is not None
++ self.if_expr = None
++
++ def characters(self, content):
++ if self.current_id != 0:
++ # We are inside a <translation> node so just add the characters to our
++ # structure.
++ #
++ # This naive way of handling characters is OK because in the XTB format,
++ # <ph> nodes are always empty (always <ph name="XXX"/>) and whitespace
++ # inside the <translation> node should be preserved.
++ self.current_structure.append((False, content))
++
++
++class XtbErrorHandler(xml.sax.handler.ErrorHandler):
++ def error(self, exception):
++ pass
++
++ def fatalError(self, exception):
++ raise exception
++
++ def warning(self, exception):
++ pass
++
++
++def Parse(xtb_file, callback_function, defs=None, debug=False,
++ target_platform=None):
++ '''Parse xtb_file, making a call to callback_function for every translation
++ in the XTB file.
++
++ The callback function must have the signature as described below. The 'parts'
++ parameter is a list of tuples (is_placeholder, text). The 'text' part is
++ either the raw text (if is_placeholder is False) or the name of the placeholder
++ (if is_placeholder is True).
++
++ Args:
++ xtb_file: open('fr.xtb', 'rb')
++ callback_function: def Callback(msg_id, parts): pass
++ defs: None, or a dictionary of preprocessor definitions.
++ debug: Default False. Set True for verbose debug output.
++ target_platform: None, or a sys.platform-like identifier of the build
++ target platform.
++
++ Return:
++ The language of the XTB, e.g. 'fr'
++ '''
++ # Start by advancing the file pointer past the DOCTYPE thing, as the TC
++ # uses a path to the DTD that only works in Unix.
++ # TODO(joi) Remove this ugly hack by getting the TC gang to change the
++ # XTB files somehow?
++ front_of_file = xtb_file.read(1024)
++ xtb_file.seek(front_of_file.find(b'<translationbundle'))
++
++ handler = XtbContentHandler(callback=callback_function, defs=defs,
++ debug=debug, target_platform=target_platform)
++ xml.sax.parse(xtb_file, handler)
++ assert handler.language != ''
++ return handler.language
+diff --git a/tools/grit/grit/xtb_reader_unittest.py b/tools/grit/grit/xtb_reader_unittest.py
+new file mode 100644
+index 0000000000..79c0ac9ef1
+--- /dev/null
++++ b/tools/grit/grit/xtb_reader_unittest.py
+@@ -0,0 +1,110 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Unit tests for grit.xtb_reader'''
++
++from __future__ import print_function
++
++import io
++import os
++import sys
++if __name__ == '__main__':
++ sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
++
++import unittest
++
++from grit import util
++from grit import xtb_reader
++from grit.node import empty
++
++
++class XtbReaderUnittest(unittest.TestCase):
++ def testParsing(self):
++ xtb_file = io.BytesIO(b'''<?xml version="1.0" encoding="UTF-8"?>
++ <!DOCTYPE translationbundle>
++ <translationbundle lang="fr">
++ <translation id="5282608565720904145">Bingo.</translation>
++ <translation id="2955977306445326147">Bongo longo.</translation>
++ <translation id="238824332917605038">Hullo</translation>
++ <translation id="6629135689895381486"><ph name="PROBLEM_REPORT"/> peut <ph name="START_LINK"/>utilisation excessive de majuscules<ph name="END_LINK"/>.</translation>
++ <translation id="7729135689895381486">Hello
++this is another line
++and another
++
++and another after a blank line.</translation>
++ </translationbundle>''')
++
++ messages = []
++ def Callback(id, structure):
++ messages.append((id, structure))
++ xtb_reader.Parse(xtb_file, Callback)
++ self.failUnless(len(messages[0][1]) == 1)
++ self.failUnless(messages[3][1][0]) # PROBLEM_REPORT placeholder
++ self.failUnless(messages[4][0] == '7729135689895381486')
++ self.failUnless(messages[4][1][7][1] == 'and another after a blank line.')
++
++ def testParsingIntoMessages(self):
++ root = util.ParseGrdForUnittest('''
++ <messages>
++ <message name="ID_MEGA">Fantastic!</message>
++ <message name="ID_HELLO_USER">Hello <ph name="USERNAME">%s<ex>Joi</ex></ph></message>
++ </messages>''')
++
++ msgs, = root.GetChildrenOfType(empty.MessagesNode)
++ clique_mega = msgs.children[0].GetCliques()[0]
++ msg_mega = clique_mega.GetMessage()
++ clique_hello_user = msgs.children[1].GetCliques()[0]
++ msg_hello_user = clique_hello_user.GetMessage()
++
++ xtb_file = io.BytesIO(b'''<?xml version="1.0" encoding="UTF-8"?>
++ <!DOCTYPE translationbundle>
++ <translationbundle lang="is">
++ <translation id="%s">Meirihattar!</translation>
++ <translation id="%s">Saelir <ph name="USERNAME"/></translation>
++ </translationbundle>''' % (
++ msg_mega.GetId().encode('utf-8'),
++ msg_hello_user.GetId().encode('utf-8')))
++
++ xtb_reader.Parse(xtb_file,
++ msgs.UberClique().GenerateXtbParserCallback('is'))
++ self.assertEqual('Meirihattar!',
++ clique_mega.MessageForLanguage('is').GetRealContent())
++ self.failUnless('Saelir %s',
++ clique_hello_user.MessageForLanguage('is').GetRealContent())
++
++ def testIfNodesWithUseNameForId(self):
++ root = util.ParseGrdForUnittest('''
++ <messages>
++ <message name="ID_BINGO" use_name_for_id="true">Bingo!</message>
++ </messages>''')
++ msgs, = root.GetChildrenOfType(empty.MessagesNode)
++ clique = msgs.children[0].GetCliques()[0]
++ msg = clique.GetMessage()
++
++ xtb_file = io.BytesIO(b'''<?xml version="1.0" encoding="UTF-8"?>
++ <!DOCTYPE translationbundle>
++ <translationbundle lang="is">
++ <if expr="is_linux">
++ <translation id="ID_BINGO">Bongo!</translation>
++ </if>
++ <if expr="not is_linux">
++ <translation id="ID_BINGO">Congo!</translation>
++ </if>
++ </translationbundle>''')
++ xtb_reader.Parse(xtb_file,
++ msgs.UberClique().GenerateXtbParserCallback('is'),
++ target_platform='darwin')
++ self.assertEqual('Congo!', clique.MessageForLanguage('is').GetRealContent())
++
++ def testParseLargeFile(self):
++ def Callback(id, structure):
++ pass
++ path = util.PathFromRoot('grit/testdata/generated_resources_fr.xtb')
++ with open(path, 'rb') as xtb:
++ xtb_reader.Parse(xtb, Callback)
++
++
++if __name__ == '__main__':
++ unittest.main()
+diff --git a/tools/grit/grit_info.py b/tools/grit/grit_info.py
+new file mode 100644
+index 0000000000..55738f25f6
+--- /dev/null
++++ b/tools/grit/grit_info.py
+@@ -0,0 +1,173 @@
++#!/usr/bin/env python
++# Copyright (c) 2012 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++'''Tool to determine inputs and outputs of a grit file.
++'''
++
++from __future__ import print_function
++
++import optparse
++import os
++import posixpath
++import sys
++
++from grit import grd_reader
++from grit import util
++
++class WrongNumberOfArguments(Exception):
++ pass
++
++
++def Outputs(filename, defines, ids_file, target_platform=None):
++ grd = grd_reader.Parse(
++ filename, defines=defines, tags_to_ignore=set(['messages']),
++ first_ids_file=ids_file, target_platform=target_platform)
++
++ target = []
++ lang_folders = {}
++ # Add all explicitly-specified output files
++ for output in grd.GetOutputFiles():
++ path = output.GetFilename()
++ target.append(path)
++
++ if path.endswith('.h'):
++ path, filename = os.path.split(path)
++ if output.attrs['lang']:
++ lang_folders[output.attrs['lang']] = os.path.dirname(path)
++
++ return [t.replace('\\', '/') for t in target]
++
++
++def GritSourceFiles():
++ files = []
++ grit_root_dir = os.path.relpath(os.path.dirname(__file__), os.getcwd())
++ for root, dirs, filenames in os.walk(grit_root_dir):
++ grit_src = [os.path.join(root, f) for f in filenames
++ if f.endswith('.py') and not f.endswith('_unittest.py')]
++ files.extend(grit_src)
++ return sorted(files)
++
++
++def Inputs(filename, defines, ids_file, target_platform=None):
++ grd = grd_reader.Parse(
++ filename, debug=False, defines=defines, tags_to_ignore=set(['message']),
++ first_ids_file=ids_file, target_platform=target_platform)
++ files = set()
++ for lang, ctx, fallback in grd.GetConfigurations():
++ # TODO(tdanderson): Refactor all places which perform the action of setting
++ # output attributes on the root. See crbug.com/503637.
++ grd.SetOutputLanguage(lang or grd.GetSourceLanguage())
++ grd.SetOutputContext(ctx)
++ grd.SetFallbackToDefaultLayout(fallback)
++ for node in grd.ActiveDescendants():
++ with node:
++ if (node.name == 'structure' or node.name == 'skeleton' or
++ (node.name == 'file' and node.parent and
++ node.parent.name == 'translations')):
++ path = node.GetInputPath()
++ if path is not None:
++ files.add(grd.ToRealPath(path))
++
++ # If it's a flattened node, grab inlined resources too.
++ if node.name == 'structure' and node.attrs['flattenhtml'] == 'true':
++ node.RunPreSubstitutionGatherer()
++ files.update(node.GetHtmlResourceFilenames())
++ elif node.name == 'grit':
++ first_ids_file = node.GetFirstIdsFile()
++ if first_ids_file:
++ files.add(first_ids_file)
++ elif node.name == 'include':
++ files.add(grd.ToRealPath(node.GetInputPath()))
++ # If it's a flattened node, grab inlined resources too.
++ if node.attrs['flattenhtml'] == 'true':
++ files.update(node.GetHtmlResourceFilenames())
++ elif node.name == 'part':
++ files.add(util.normpath(os.path.join(os.path.dirname(filename),
++ node.GetInputPath())))
++
++ cwd = os.getcwd()
++ return [os.path.relpath(f, cwd) for f in sorted(files)]
++
++
++def PrintUsage():
++ print('USAGE: ./grit_info.py --inputs [-D foo] [-f resource_ids] <grd-file>')
++ print(' ./grit_info.py --outputs [-D foo] [-f resource_ids] ' +
++ '<out-prefix> <grd-file>')
++
++
++def DoMain(argv):
++ os.environ['cwd'] = os.getcwd()
++
++ parser = optparse.OptionParser()
++ parser.add_option("--inputs", action="store_true", dest="inputs")
++ parser.add_option("--outputs", action="store_true", dest="outputs")
++ parser.add_option("-D", action="append", dest="defines", default=[])
++ # grit build also supports '-E KEY=VALUE', support that to share command
++ # line flags.
++ parser.add_option("-E", action="append", dest="build_env", default=[])
++ parser.add_option("-p", action="store", dest="predetermined_ids_file")
++ parser.add_option("-w", action="append", dest="whitelist_files", default=[])
++ parser.add_option("-f", dest="ids_file", default="")
++ parser.add_option("-t", dest="target_platform", default=None)
++
++ options, args = parser.parse_args(argv)
++
++ defines = {}
++ for define in options.defines:
++ name, val = util.ParseDefine(define)
++ defines[name] = val
++
++ for env_pair in options.build_env:
++ (env_name, env_value) = env_pair.split('=', 1)
++ os.environ[env_name] = env_value
++
++ if options.inputs:
++ if len(args) > 1:
++ raise WrongNumberOfArguments("Expected 0 or 1 arguments for --inputs.")
++
++ inputs = []
++ if len(args) == 1:
++ filename = args[0]
++ inputs = Inputs(filename, defines, options.ids_file,
++ options.target_platform)
++
++ # Add in the grit source files. If one of these change, we want to re-run
++ # grit.
++ inputs.extend(GritSourceFiles())
++ inputs = [f.replace('\\', '/') for f in inputs]
++
++ if len(args) == 1:
++ # Include grd file as second input (works around gyp expecting it).
++ inputs.insert(1, args[0])
++ if options.whitelist_files:
++ inputs.extend(options.whitelist_files)
++ return '\n'.join(inputs)
++ elif options.outputs:
++ if len(args) != 2:
++ raise WrongNumberOfArguments(
++ "Expected exactly 2 arguments for --outputs.")
++
++ prefix, filename = args
++ outputs = [posixpath.join(prefix, f)
++ for f in Outputs(filename, defines,
++ options.ids_file, options.target_platform)]
++ return '\n'.join(outputs)
++ else:
++ raise WrongNumberOfArguments("Expected --inputs or --outputs.")
++
++
++def main(argv):
++ try:
++ result = DoMain(argv[1:])
++ except WrongNumberOfArguments as e:
++ PrintUsage()
++ print(e)
++ return 1
++ print(result)
++ return 0
++
++
++if __name__ == '__main__':
++ sys.exit(main(sys.argv))
+diff --git a/tools/grit/grit_rule.gni b/tools/grit/grit_rule.gni
+new file mode 100644
+index 0000000000..fb107ef1a3
+--- /dev/null
++++ b/tools/grit/grit_rule.gni
+@@ -0,0 +1,485 @@
++# Copyright 2014 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++# Instantiate grit. This will produce a script target to run grit (named
++# ${target_name}_grit), and a static library that compiles the .cc files.
++#
++# In general, code should depend on the static library. However, if the
++# generated files are only processed by other actions to generate other
++# files, it is possible to depend on the script target directly.
++#
++# Parameters
++#
++# source (required)
++# Path to .grd file.
++#
++# enable_input_discovery_for_gn_analyze (default=true)
++# Runs grit_info.py via exec_script() when compute_inputs_for_analyze=true
++# in order to discover all files that affect this target.
++# Turn this off when the .grd file is generated, or an <include> with
++# flattenhtml=true points to a generated file.
++# For "gn analyze" to be correct with this arg disabled, all inputs
++# must be listed via |inputs|.
++#
++# inputs (optional)
++# List of additional files, required for grit to process source file.
++#
++# outputs (required)
++# List of outputs from grit, relative to the target_gen_dir. Grit will
++# verify at build time that this list is correct and will fail if there
++# is a mismatch between the outputs specified by the .grd file and the
++# outputs list here.
++#
++# To get this list, you can look in the .grd file for
++# <output filename="..." and put those filename here. The base directory
++# of the list in Grit and the output list specified in the GN grit target
++# are the same (the target_gen_dir) so you can generally copy the names
++# exactly.
++#
++# To get the list of outputs programatically, run:
++# python tools/grit/grit_info.py --outputs . path/to/your.grd
++# And strip the leading "./" from the output files.
++#
++# defines (optional)
++# Extra defines to pass to grit (on top of the global grit_defines list).
++#
++# grit_flags (optional)
++# List of strings containing extra command-line flags to pass to Grit.
++#
++# resource_ids (optional)
++# Path to a grit "firstidsfile". Default is
++# //tools/gritsettings/resource_ids. Set to "" to use the value specified
++# in the <grit> nodes of the processed files.
++#
++# output_dir (optional)
++# Directory for generated files. If you specify this, you will often
++# want to specify output_name if the target name is not particularly
++# unique, since this can cause files from multiple grit targets to
++# overwrite each other.
++#
++# output_name (optional)
++# Provide an alternate base name for the generated files, like the .d
++# files. Normally these are based on the target name and go in the
++# output_dir, but if multiple targets with the same name end up in
++# the same output_dir, they can collide.
++#
++# configs (optional)
++# List of additional configs to be applied to the generated target.
++#
++# deps (optional)
++# testonly (optional)
++# visibility (optional)
++# Normal meaning.
++#
++# Example
++#
++# grit("my_resources") {
++# # Source and outputs are required.
++# source = "myfile.grd"
++# outputs = [
++# "foo_strings.h",
++# "foo_strings.pak",
++# ]
++#
++# grit_flags = [ "-E", "foo=bar" ] # Optional extra flags.
++# # You can also put deps here if the grit source depends on generated
++# # files.
++# }
++import("//build/config/chrome_build.gni")
++import("//build/config/chromeos/ui_mode.gni")
++import("//build/config/compiler/compiler.gni")
++import("//build/config/compute_inputs_for_analyze.gni")
++import("//build/config/crypto.gni")
++import("//build/config/features.gni")
++import("//build/config/sanitizers/sanitizers.gni")
++import("//build/config/ui.gni")
++import("//build/toolchain/gcc_toolchain.gni")
++
++declare_args() {
++ enable_resource_whitelist_generation = is_android && is_official_build
++}
++
++if (enable_resource_whitelist_generation) {
++ assert(target_os == "android" || target_os == "win",
++ "unsupported platform for resource whitelist generation")
++ assert(
++ symbol_level > 0 && !strip_debug_info && !is_component_build,
++ "resource whitelist generation only works on non-component builds with debug info enabled.")
++}
++
++grit_defines = []
++
++if (is_mac || is_win || is_linux || is_chromeos || is_ios) {
++ grit_defines += [
++ "-D",
++ "scale_factors=2x",
++ ]
++}
++
++# Mac and iOS want Title Case strings.
++use_titlecase_in_grd_files = is_apple
++if (use_titlecase_in_grd_files) {
++ grit_defines += [
++ "-D",
++ "use_titlecase",
++ ]
++}
++
++if (is_chrome_branded) {
++ grit_defines += [
++ "-D",
++ "_google_chrome",
++ "-E",
++ "CHROMIUM_BUILD=google_chrome",
++ ]
++} else {
++ grit_defines += [
++ "-D",
++ "_chromium",
++ "-E",
++ "CHROMIUM_BUILD=chromium",
++ ]
++}
++
++if (is_chromeos) {
++ grit_defines += [
++ "-D",
++ "chromeos",
++ ]
++}
++
++if (chromeos_is_browser_only) {
++ grit_defines += [
++ "-D",
++ "lacros",
++ ]
++}
++
++if (is_desktop_linux) {
++ grit_defines += [
++ "-D",
++ "desktop_linux",
++ ]
++}
++
++if (toolkit_views) {
++ grit_defines += [
++ "-D",
++ "toolkit_views",
++ ]
++}
++
++if (use_aura) {
++ grit_defines += [
++ "-D",
++ "use_aura",
++ ]
++}
++
++if (use_nss_certs) {
++ grit_defines += [
++ "-D",
++ "use_nss_certs",
++ ]
++}
++
++if (use_ozone) {
++ grit_defines += [
++ "-D",
++ "use_ozone",
++ ]
++}
++
++if (is_android) {
++ grit_defines += [
++ "-E",
++ "ANDROID_JAVA_TAGGED_ONLY=true",
++ ]
++}
++
++# When cross-compiling, explicitly pass the target system to grit.
++if (current_toolchain != host_toolchain) {
++ if (is_android) {
++ grit_defines += [
++ "-t",
++ "android",
++ ]
++ }
++ if (is_ios) {
++ grit_defines += [
++ "-t",
++ "ios",
++ ]
++ }
++ if (is_linux || is_chromeos) {
++ grit_defines += [
++ "-t",
++ "linux2",
++ ]
++ }
++ if (is_mac) {
++ grit_defines += [
++ "-t",
++ "darwin",
++ ]
++ }
++ if (is_win) {
++ grit_defines += [
++ "-t",
++ "win32",
++ ]
++ }
++}
++
++_strip_resource_files = is_android && is_official_build
++_js_minifier = "//tools/grit/minify_with_uglify.py"
++_css_minifier = "//tools/grit/minimize_css.py"
++
++grit_resource_id_target = "//tools/gritsettings:default_resource_ids"
++grit_resource_id_file =
++ get_label_info(grit_resource_id_target, "target_gen_dir") +
++ "/default_resource_ids"
++grit_info_script = "//tools/grit/grit_info.py"
++
++# TODO(asvitkine): Add predetermined ids files for other platforms.
++grit_predetermined_resource_ids_file = ""
++if (is_mac) {
++ grit_predetermined_resource_ids_file =
++ "//tools/gritsettings/startup_resources_mac.txt"
++}
++if (is_win) {
++ grit_predetermined_resource_ids_file =
++ "//tools/gritsettings/startup_resources_win.txt"
++}
++
++template("grit") {
++ if (defined(invoker.output_dir)) {
++ _output_dir = invoker.output_dir
++ } else {
++ _output_dir = target_gen_dir
++ }
++
++ _grit_outputs =
++ get_path_info(rebase_path(invoker.outputs, ".", _output_dir), "abspath")
++
++ # Add .info output for all pak files
++ _pak_info_outputs = []
++ foreach(output, _grit_outputs) {
++ if (get_path_info(output, "extension") == "pak") {
++ _pak_info_outputs += [ output + ".info" ]
++ }
++ }
++
++ if (defined(invoker.output_name)) {
++ _grit_output_name = invoker.output_name
++ } else {
++ _grit_output_name = target_name
++ }
++
++ _grit_custom_target = target_name + "_grit"
++ action(_grit_custom_target) {
++ testonly = defined(invoker.testonly) && invoker.testonly
++
++ script = "//tools/grit/grit.py"
++ depfile = "$target_gen_dir/$target_name.d"
++
++ inputs = [ invoker.source ]
++ deps = [ "//tools/grit:grit_sources" ]
++ outputs = [ "${depfile}.stamp" ] + _grit_outputs + _pak_info_outputs
++
++ _grit_flags = grit_defines
++
++ # Add extra defines with -D flags.
++ if (defined(invoker.defines)) {
++ foreach(i, invoker.defines) {
++ _grit_flags += [
++ "-D",
++ i,
++ ]
++ }
++ }
++
++ if (defined(invoker.grit_flags)) {
++ _grit_flags += invoker.grit_flags
++ }
++
++ _rebased_source_path = rebase_path(invoker.source, root_build_dir)
++ _enable_grit_info =
++ !defined(invoker.enable_input_discovery_for_gn_analyze) ||
++ invoker.enable_input_discovery_for_gn_analyze
++ if (_enable_grit_info && compute_inputs_for_analyze) {
++ # Only call exec_script when the user has explicitly opted into greater
++ # precision at the expense of performance.
++ _rel_inputs = exec_script("//tools/grit/grit_info.py",
++ [
++ "--inputs",
++ _rebased_source_path,
++ ] + _grit_flags,
++ "list lines")
++ inputs += rebase_path(_rel_inputs, ".", root_build_dir)
++ }
++
++ args = [
++ "-i",
++ _rebased_source_path,
++ "build",
++ "-o",
++ rebase_path(_output_dir, root_build_dir),
++ "--depdir",
++ ".",
++ "--depfile",
++ rebase_path(depfile, root_build_dir),
++ "--write-only-new=1",
++ "--depend-on-stamp",
++ ] + _grit_flags
++
++ # Add brotli executable if using brotli.
++ if (defined(invoker.use_brotli) && invoker.use_brotli) {
++ _brotli_target = "//third_party/brotli:brotli($host_toolchain)"
++ _brotli_executable = get_label_info(_brotli_target, "root_out_dir") +
++ "/" + get_label_info(_brotli_target, "name")
++ if (host_os == "win") {
++ _brotli_executable += ".exe"
++ }
++
++ inputs += [ _brotli_executable ]
++ args += [
++ "--brotli",
++ rebase_path(_brotli_executable, root_build_dir),
++ ]
++ }
++
++ _resource_ids = grit_resource_id_file
++ if (defined(invoker.resource_ids)) {
++ _resource_ids = invoker.resource_ids
++ }
++
++ if (_resource_ids != "") {
++ inputs += [ _resource_ids ]
++ args += [
++ "-f",
++ rebase_path(_resource_ids, root_build_dir),
++ ]
++ if (_resource_ids == grit_resource_id_file) {
++ deps += [ grit_resource_id_target ]
++ }
++ }
++ if (grit_predetermined_resource_ids_file != "") {
++ inputs += [ grit_predetermined_resource_ids_file ]
++ args += [
++ "-p",
++ rebase_path(grit_predetermined_resource_ids_file, root_build_dir),
++ ]
++ }
++
++ # We want to make sure the declared outputs actually match what Grit is
++ # writing. We write the list to a file (some of the output lists are long
++ # enough to not fit on a Windows command line) and ask Grit to verify those
++ # are the actual outputs at runtime.
++ _asserted_list_file =
++ "$target_out_dir/${_grit_output_name}_expected_outputs.txt"
++ write_file(_asserted_list_file,
++ rebase_path(invoker.outputs, root_build_dir, _output_dir))
++ inputs += [ _asserted_list_file ]
++ args += [
++ "--assert-file-list",
++ rebase_path(_asserted_list_file, root_build_dir),
++ ]
++
++ if (enable_resource_whitelist_generation) {
++ _rc_grit_outputs = []
++ foreach(output, _grit_outputs) {
++ if (get_path_info(output, "extension") == "rc") {
++ _rc_grit_outputs += [ output ]
++ }
++ }
++
++ if (_rc_grit_outputs != []) {
++ # Resource whitelisting cannot be used with .rc files.
++ # Make sure that there aren't any .pak outputs which would require
++ # whitelist annotations.
++ assert(_pak_info_outputs == [], "can't combine .pak and .rc outputs")
++ } else {
++ args += [ "--whitelist-support" ]
++ }
++ }
++ if (_strip_resource_files) {
++ _js_minifier_command = rebase_path(_js_minifier, root_build_dir)
++ _css_minifier_command = rebase_path(_css_minifier, root_build_dir)
++ args += [
++ "--js-minifier",
++ _js_minifier_command,
++ "--css-minifier",
++ _css_minifier_command,
++ ]
++ inputs += [
++ _js_minifier,
++ _css_minifier,
++ ]
++ }
++
++ if (defined(invoker.visibility)) {
++ # This needs to include both what the invoker specified (since they
++ # probably include generated headers from this target), as well as the
++ # generated source set (since there's no guarantee that the visibility
++ # specified by the invoker includes our target).
++ #
++ # Only define visibility at all if the invoker specified it. Otherwise,
++ # we want to keep the public "no visibility specified" default.
++ visibility = [ ":${invoker.target_name}" ] + invoker.visibility
++ }
++
++ if (defined(invoker.use_brotli) && invoker.use_brotli) {
++ if (is_mac && is_asan) {
++ deps += [ "//tools/grit:brotli_mac_asan_workaround" ]
++ } else {
++ deps += [ "//third_party/brotli:brotli($host_toolchain)" ]
++ }
++ }
++ if (defined(invoker.deps)) {
++ deps += invoker.deps
++ }
++ if (defined(invoker.inputs)) {
++ inputs += invoker.inputs
++ }
++ }
++
++ # This is the thing that people actually link with, it must be named the
++ # same as the argument the template was invoked with.
++ source_set(target_name) {
++ testonly = defined(invoker.testonly) && invoker.testonly
++
++ # Since we generate a file, we need to be run before the targets that
++ # depend on us.
++ sources = []
++ foreach(_output, _grit_outputs) {
++ _extension = get_path_info(_output, "extension")
++ if (_extension != "json" && _extension != "gz" && _extension != "pak" &&
++ _extension != "xml") {
++ sources += [ _output ]
++ }
++ }
++
++ # Deps set on the template invocation will go on the action that runs
++ # grit above rather than this library. This target needs to depend on the
++ # action publicly so other scripts can take the outputs from the grit
++ # script as inputs.
++ public_deps = [ ":$_grit_custom_target" ]
++
++ deps = [ "//base" ]
++
++ if (defined(invoker.public_configs)) {
++ public_configs += invoker.public_configs
++ }
++
++ if (defined(invoker.configs)) {
++ configs += invoker.configs
++ }
++
++ if (defined(invoker.visibility)) {
++ visibility = invoker.visibility
++ }
++ output_name = _grit_output_name
++ }
++}
+diff --git a/tools/grit/minify_with_uglify.py b/tools/grit/minify_with_uglify.py
+new file mode 100644
+index 0000000000..788ffa6a75
+--- /dev/null
++++ b/tools/grit/minify_with_uglify.py
+@@ -0,0 +1,44 @@
++#!/usr/bin/env python
++# Copyright 2019 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++from __future__ import print_function
++
++import os
++import sys
++import tempfile
++
++_HERE_PATH = os.path.dirname(__file__)
++_SRC_PATH = os.path.normpath(os.path.join(_HERE_PATH, '..', '..'))
++sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'node'))
++
++import node
++import node_modules
++
++def Minify(source):
++ # Open two temporary files, so that uglify can read the input from one and
++ # write its output to the other.
++ with tempfile.NamedTemporaryFile(suffix='.js') as infile, \
++ tempfile.NamedTemporaryFile(suffix='.js') as outfile:
++ infile.write(source)
++ infile.flush();
++ node.RunNode([
++ node_modules.PathToUglify(), infile.name, '--output', outfile.name])
++ result = outfile.read()
++ return result
++
++
++def main():
++ orig_stdout = sys.stdout
++ result = ''
++ try:
++ sys.stdout = sys.stderr
++ result = Minify(sys.stdin.read())
++ finally:
++ sys.stdout = orig_stdout
++ print(result)
++
++
++if __name__ == '__main__':
++ main()
+diff --git a/tools/grit/minimize_css.py b/tools/grit/minimize_css.py
+new file mode 100644
+index 0000000000..2c3b8aeb1e
+--- /dev/null
++++ b/tools/grit/minimize_css.py
+@@ -0,0 +1,105 @@
++#!/usr/bin/env python
++# Copyright 2016 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++import re
++import sys
++
++class CSSMinimizer(object):
++
++ INITIAL = 0
++ MAYBE_COMMENT_START = 1
++ INSIDE_COMMENT = 2
++ MAYBE_COMMENT_END = 3
++ INSIDE_SINGLE_QUOTE = 4
++ INSIDE_SINGLE_QUOTE_ESCAPE = 5
++ INSIDE_DOUBLE_QUOTE = 6
++ INSIDE_DOUBLE_QUOTE_ESCAPE = 7
++
++ def __init__(self):
++ self._output = ''
++ self._codeblock = ''
++
++ def flush_codeblock(self):
++ stripped = re.sub(r"\s+", ' ', self._codeblock)
++ stripped = re.sub(r";?\s*(?P<op>[{};])\s*", r'\g<op>', stripped)
++ self._output += stripped
++ self._codeblock = ''
++
++ def parse(self, content):
++ state = self.INITIAL
++ for char in content:
++ if state == self.INITIAL:
++ if char == '/':
++ state = self.MAYBE_COMMENT_START
++ elif char == "'":
++ self.flush_codeblock()
++ self._output += char
++ state = self.INSIDE_SINGLE_QUOTE
++ elif char == '"':
++ self.flush_codeblock()
++ self._output += char
++ state = self.INSIDE_DOUBLE_QUOTE
++ else:
++ self._codeblock += char
++ elif state == self.MAYBE_COMMENT_START:
++ if char == '*':
++ self.flush_codeblock()
++ state = self.INSIDE_COMMENT
++ else:
++ self._codeblock += '/' + char
++ state = self.INITIAL
++ elif state == self.INSIDE_COMMENT:
++ if char == '*':
++ state = self.MAYBE_COMMENT_END
++ else:
++ pass
++ elif state == self.MAYBE_COMMENT_END:
++ if char == '/':
++ state = self.INITIAL
++ else:
++ state = self.INSIDE_COMMENT
++ elif state == self.INSIDE_SINGLE_QUOTE:
++ if char == '\\':
++ self._output += char
++ state = self.INSIDE_SINGLE_QUOTE_ESCAPE
++ elif char == "'":
++ self._output += char
++ state = self.INITIAL
++ else:
++ self._output += char
++ elif state == self.INSIDE_SINGLE_QUOTE_ESCAPE:
++ self._output += char
++ state = self.INSIDE_SINGLE_QUOTE
++ elif state == self.INSIDE_DOUBLE_QUOTE:
++ if char == '\\':
++ self._output += char
++ state = self.INSIDE_DOUBLE_QUOTE_ESCAPE
++ elif char == '"':
++ self._output += char
++ state = self.INITIAL
++ else:
++ self._output += char
++ elif state == self.INSIDE_DOUBLE_QUOTE_ESCAPE:
++ self._output += char
++ state = self.INSIDE_DOUBLE_QUOTE
++
++ self.flush_codeblock()
++ self._output = self._output.strip()
++ return self._output
++
++ @classmethod
++ def minimize_css(cls, content):
++ minimizer = CSSMinimizer()
++ return minimizer.parse(content)
++
++def main():
++ result = ''
++ try:
++ result = CSSMinimizer.minimize_css(sys.stdin.read())
++ finally:
++ print(result)
++
++if __name__ == '__main__':
++ main()
+diff --git a/tools/grit/minimize_css_unittest.py b/tools/grit/minimize_css_unittest.py
+new file mode 100644
+index 0000000000..cddc313078
+--- /dev/null
++++ b/tools/grit/minimize_css_unittest.py
+@@ -0,0 +1,58 @@
++#!/usr/bin/env python
++# Copyright 2016 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++import unittest
++
++import minimize_css
++
++
++class CSSMinimizerTest(unittest.TestCase):
++
++ def test_simple(self):
++ source = """
++ div {
++ color: blue;
++ }
++ """
++ minimized = minimize_css.CSSMinimizer.minimize_css(source)
++ self.assertEquals(minimized, "div{color: blue}")
++
++ def test_attribute_selectors(self):
++ source = """
++ input[type="search" i]::-webkit-textfield-decoration-container {
++ direction: ltr;
++ }
++ """
++ minimized = minimize_css.CSSMinimizer.minimize_css(source)
++ self.assertEquals(
++ minimized,
++ # pylint: disable=line-too-long
++ """input[type="search" i]::-webkit-textfield-decoration-container{direction: ltr}""")
++
++ def test_strip_comment(self):
++ source = """
++ /* header */
++ html {
++ /* inside block */
++ display: block;
++ }
++ /* footer */
++ """
++ minimized = minimize_css.CSSMinimizer.minimize_css(source)
++ self.assertEquals(minimized, "html{ display: block}")
++
++ def test_no_strip_inside_quotes(self):
++ source = """div[foo=' bar ']"""
++ minimized = minimize_css.CSSMinimizer.minimize_css(source)
++ self.assertEquals(minimized, source)
++
++ source = """div[foo=" bar "]"""
++ minimized = minimize_css.CSSMinimizer.minimize_css(source)
++ self.assertEquals(minimized, source)
++
++ def test_escape_string(self):
++ source = """content: " <a onclick=\\\"javascript: alert ( 'foobar' ); \\\">";"""
++ minimized = minimize_css.CSSMinimizer.minimize_css(source)
++ self.assertEquals(minimized, source)
+diff --git a/tools/grit/pak_util.py b/tools/grit/pak_util.py
+new file mode 100644
+index 0000000000..ede638bbe1
+--- /dev/null
++++ b/tools/grit/pak_util.py
+@@ -0,0 +1,223 @@
++#!/usr/bin/env python
++# Copyright 2017 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++"""A tool for interacting with .pak files.
++
++For details on the pak file format, see:
++https://dev.chromium.org/developers/design-documents/linuxresourcesandlocalizedstrings
++"""
++
++from __future__ import print_function
++
++import argparse
++import gzip
++import hashlib
++import os
++import shutil
++import sys
++import tempfile
++
++# Import grit first to get local third_party modules.
++import grit # pylint: disable=ungrouped-imports,unused-import
++
++import six
++
++from grit.format import data_pack
++
++
++def _RepackMain(args):
++ output_info_filepath = args.output_pak_file + '.info'
++ if args.compress:
++ # If the file needs to be compressed, call RePack with a tempfile path,
++ # then compress the tempfile to args.output_pak_file.
++ temp_outfile = tempfile.NamedTemporaryFile()
++ out_path = temp_outfile.name
++ # Strip any non .pak extension from the .info output file path.
++ splitext = os.path.splitext(args.output_pak_file)
++ if splitext[1] != '.pak':
++ output_info_filepath = splitext[0] + '.info'
++ else:
++ out_path = args.output_pak_file
++ data_pack.RePack(out_path, args.input_pak_files, args.whitelist,
++ args.suppress_removed_key_output,
++ output_info_filepath=output_info_filepath)
++ if args.compress:
++ with open(args.output_pak_file, 'wb') as out:
++ with gzip.GzipFile(filename='', mode='wb', fileobj=out, mtime=0) as outgz:
++ shutil.copyfileobj(temp_outfile, outgz)
++
++
++def _ExtractMain(args):
++ pak = data_pack.ReadDataPack(args.pak_file)
++ if args.textual_id:
++ info_dict = data_pack.ReadGrdInfo(args.pak_file)
++ for resource_id, payload in pak.resources.items():
++ filename = (
++ info_dict[resource_id].textual_id
++ if args.textual_id else str(resource_id))
++ path = os.path.join(args.output_dir, filename)
++ with open(path, 'w') as f:
++ f.write(payload)
++
++
++def _CreateMain(args):
++ pak = {}
++ for name in os.listdir(args.input_dir):
++ try:
++ resource_id = int(name)
++ except:
++ continue
++ filename = os.path.join(args.input_dir, name)
++ if os.path.isfile(filename):
++ with open(filename, 'rb') as f:
++ pak[resource_id] = f.read()
++ data_pack.WriteDataPack(pak, args.output_pak_file, data_pack.UTF8)
++
++
++def _PrintMain(args):
++ pak = data_pack.ReadDataPack(args.pak_file)
++ if args.textual_id:
++ info_dict = data_pack.ReadGrdInfo(args.pak_file)
++ output = args.output
++ encoding = 'binary'
++ if pak.encoding == 1:
++ encoding = 'utf-8'
++ elif pak.encoding == 2:
++ encoding = 'utf-16'
++ else:
++ encoding = '?' + str(pak.encoding)
++
++ output.write('version: {}\n'.format(pak.version))
++ output.write('encoding: {}\n'.format(encoding))
++ output.write('num_resources: {}\n'.format(len(pak.resources)))
++ output.write('num_aliases: {}\n'.format(len(pak.aliases)))
++ breakdown = ', '.join('{}: {}'.format(*x) for x in pak.sizes)
++ output.write('total_size: {} ({})\n'.format(pak.sizes.total, breakdown))
++
++ try_decode = args.decode and encoding.startswith('utf')
++ # Print IDs in ascending order, since that's the order in which they appear in
++ # the file (order is lost by Python dict).
++ for resource_id in sorted(pak.resources):
++ data = pak.resources[resource_id]
++ canonical_id = pak.aliases.get(resource_id, resource_id)
++ desc = '<data>'
++ if try_decode:
++ try:
++ desc = six.text_type(data, encoding)
++ if len(desc) > 60:
++ desc = desc[:60] + u'...'
++ desc = desc.replace('\n', '\\n')
++ except UnicodeDecodeError:
++ pass
++ sha1 = hashlib.sha1(data).hexdigest()[:10]
++ if args.textual_id:
++ textual_id = info_dict[resource_id].textual_id
++ canonical_textual_id = info_dict[canonical_id].textual_id
++ output.write(
++ u'Entry(id={}, canonical_id={}, size={}, sha1={}): {}\n'.format(
++ textual_id, canonical_textual_id, len(data), sha1,
++ desc).encode('utf-8'))
++ else:
++ output.write(
++ u'Entry(id={}, canonical_id={}, size={}, sha1={}): {}\n'.format(
++ resource_id, canonical_id, len(data), sha1, desc).encode('utf-8'))
++
++
++def _ListMain(args):
++ pak = data_pack.ReadDataPack(args.pak_file)
++ if args.textual_id or args.path:
++ info_dict = data_pack.ReadGrdInfo(args.pak_file)
++ fmt = ''.join([
++ '{id}', ' = {textual_id}' if args.textual_id else '',
++ ' @ {path}' if args.path else '', '\n'
++ ])
++ for resource_id in sorted(pak.resources):
++ item = info_dict[resource_id]
++ args.output.write(
++ fmt.format(textual_id=item.textual_id, id=item.id, path=item.path))
++ else:
++ for resource_id in sorted(pak.resources):
++ args.output.write('%d\n' % resource_id)
++
++
++def main():
++ parser = argparse.ArgumentParser(
++ description=__doc__, formatter_class=argparse.RawTextHelpFormatter)
++ # Subparsers are required by default under Python 2. Python 3 changed to
++ # not required, but didn't include a required option until 3.7. Setting
++ # the required member works in all versions (and setting dest name).
++ sub_parsers = parser.add_subparsers(dest='action')
++ sub_parsers.required = True
++
++ sub_parser = sub_parsers.add_parser('repack',
++ help='Combines several .pak files into one.')
++ sub_parser.add_argument('output_pak_file', help='File to create.')
++ sub_parser.add_argument('input_pak_files', nargs='+',
++ help='Input .pak files.')
++ sub_parser.add_argument('--whitelist',
++ help='Path to a whitelist used to filter output pak file resource IDs.')
++ sub_parser.add_argument('--suppress-removed-key-output', action='store_true',
++ help='Do not log which keys were removed by the whitelist.')
++ sub_parser.add_argument('--compress', dest='compress', action='store_true',
++ default=False, help='Compress output_pak_file using gzip.')
++ sub_parser.set_defaults(func=_RepackMain)
++
++ sub_parser = sub_parsers.add_parser('extract', help='Extracts pak file')
++ sub_parser.add_argument('pak_file')
++ sub_parser.add_argument('-o', '--output-dir', default='.',
++ help='Directory to extract to.')
++ sub_parser.add_argument(
++ '-t',
++ '--textual-id',
++ action='store_true',
++ help='Use textual resource ID (name) (from .info file) as filenames.')
++ sub_parser.set_defaults(func=_ExtractMain)
++
++ sub_parser = sub_parsers.add_parser('create',
++ help='Creates pak file from extracted directory.')
++ sub_parser.add_argument('output_pak_file', help='File to create.')
++ sub_parser.add_argument('-i', '--input-dir', default='.',
++ help='Directory to create from.')
++ sub_parser.set_defaults(func=_CreateMain)
++
++ sub_parser = sub_parsers.add_parser('print',
++ help='Prints all pak IDs and contents. Useful for diffing.')
++ sub_parser.add_argument('pak_file')
++ sub_parser.add_argument('--output', type=argparse.FileType('w'),
++ default=sys.stdout,
++ help='The resource list path to write (default stdout)')
++ sub_parser.add_argument('--no-decode', dest='decode', action='store_false',
++ default=True, help='Do not print entry data.')
++ sub_parser.add_argument(
++ '-t',
++ '--textual-id',
++ action='store_true',
++ help='Print textual ID (name) (from .info file) instead of the ID.')
++ sub_parser.set_defaults(func=_PrintMain)
++
++ sub_parser = sub_parsers.add_parser('list-id',
++ help='Outputs all resource IDs to a file.')
++ sub_parser.add_argument('pak_file')
++ sub_parser.add_argument('--output', type=argparse.FileType('w'),
++ default=sys.stdout,
++ help='The resource list path to write (default stdout)')
++ sub_parser.add_argument(
++ '-t',
++ '--textual-id',
++ action='store_true',
++ help='Print the textual resource ID (from .info file).')
++ sub_parser.add_argument(
++ '-p',
++ '--path',
++ action='store_true',
++ help='Print the resource path (from .info file).')
++ sub_parser.set_defaults(func=_ListMain)
++
++ args = parser.parse_args()
++ args.func(args)
++
++
++if __name__ == '__main__':
++ main()
+diff --git a/tools/grit/repack.gni b/tools/grit/repack.gni
+new file mode 100644
+index 0000000000..193f2dc43f
+--- /dev/null
++++ b/tools/grit/repack.gni
+@@ -0,0 +1,189 @@
++# Copyright 2014 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++import("//tools/grit/grit_rule.gni")
++
++# This file defines a template to invoke grit repack in a consistent manner.
++#
++# Parameters:
++# sources [required]
++# List of pak files that need to be combined.
++#
++# output [required]
++# File name (single string) of the output file.
++#
++# copy_data_to_bundle [optional]
++# Whether to define a bundle_data() for the resulting pak.
++#
++# bundle_output [optional]
++# Path of the file in the application bundle, defaults to
++# {{bundle_resources_dir}}/{{source_file_part}}.
++#
++# compress [optional]
++# Gzip the resulting bundle (and append .gz to the output name).
++#
++# deps [optional]
++# public_deps [optional]
++# visibility [optional]
++# Normal meaning.
++template("repack") {
++ _copy_data_to_bundle =
++ defined(invoker.copy_data_to_bundle) && invoker.copy_data_to_bundle
++ _repack_target_name = target_name
++ if (_copy_data_to_bundle) {
++ _repack_target_name = "${target_name}__repack"
++ }
++
++ _compress = defined(invoker.compress) && invoker.compress
++
++ action(_repack_target_name) {
++ forward_variables_from(invoker,
++ [
++ "deps",
++ "public_deps",
++ "testonly",
++ "visibility",
++ ])
++ if (defined(visibility) && _copy_data_to_bundle) {
++ visibility += [ ":${invoker.target_name}" ]
++ }
++ assert(defined(invoker.sources), "Need sources for $target_name")
++ assert(defined(invoker.output), "Need output for $target_name")
++
++ script = "//tools/grit/pak_util.py"
++
++ inputs = invoker.sources
++ outputs = [
++ invoker.output,
++ "${invoker.output}.info",
++ ]
++
++ args = [ "repack" ]
++ if (defined(invoker.repack_whitelist)) {
++ inputs += [ invoker.repack_whitelist ]
++ _rebased_whitelist = rebase_path(invoker.repack_whitelist)
++ args += [ "--whitelist=$_rebased_whitelist" ]
++ args += [ "--suppress-removed-key-output" ]
++ }
++ args += [ rebase_path(invoker.output, root_build_dir) ]
++ args += rebase_path(invoker.sources, root_build_dir)
++ if (_compress) {
++ args += [ "--compress" ]
++ }
++ }
++
++ if (_copy_data_to_bundle) {
++ bundle_data(target_name) {
++ forward_variables_from(invoker,
++ [
++ "testonly",
++ "visibility",
++ ])
++
++ public_deps = [ ":$_repack_target_name" ]
++ sources = [ invoker.output ]
++ if (defined(invoker.bundle_output)) {
++ outputs = [ invoker.bundle_output ]
++ } else {
++ outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ]
++ }
++ }
++ }
++}
++
++# Repacks a set of .pak files for each locale.
++#
++# Parameters:
++#
++# input_locales [required]
++# List of locale names to use as inputs.
++#
++# output_locales [required]
++# A list containing the corresponding output names for each of the
++# input names. Mac and iOS use different names in some cases.
++#
++# source_patterns [required]
++# The pattern for pak files which need repacked. The filenames always end
++# with "${locale}.pak".
++# E.g.:
++# ${root_gen_dir}/foo_ expands to ${root_gen_dir}/foo_zh-CN.pak
++# when locale is zh-CN.
++#
++# output_dir [optional]
++# Directory in which to put all pak files.
++#
++# deps [optional]
++# visibility [optional]
++# testonly [optional]
++# copy_data_to_bundle [optional]
++# repack_whitelist [optional]
++# Normal meaning.
++template("repack_locales") {
++ if (defined(invoker.output_dir)) {
++ _output_dir = invoker.output_dir
++ } else if (is_ios) {
++ _output_dir = "$target_gen_dir"
++ } else {
++ _output_dir = "$target_gen_dir/$target_name"
++ }
++
++ # GN can't handle invoker.output_locales[foo] (http://crbug.com/614747).
++ _output_locales = invoker.output_locales
++
++ # Collects all targets the loop generates.
++ _locale_targets = []
++
++ # This loop iterates over the input locales and also keeps a counter so it
++ # can simultaneously iterate over the output locales (using GN's very
++ # limited looping capabilities).
++ _current_index = 0
++ foreach(_input_locale, invoker.input_locales) {
++ _output_locale = _output_locales[_current_index]
++
++ # Compute the name of the target for the current file. Save it for the deps.
++ _current_name = "${target_name}_${_input_locale}"
++ _locale_targets += [ ":$_current_name" ]
++
++ repack(_current_name) {
++ forward_variables_from(invoker,
++ [
++ "copy_data_to_bundle",
++ "bundle_output",
++ "compress",
++ "deps",
++ "repack_whitelist",
++ "testonly",
++ ])
++ visibility = [ ":${invoker.target_name}" ]
++ if (is_ios) {
++ output = "$_output_dir/${_output_locale}.lproj/locale.pak"
++ } else {
++ output = "$_output_dir/${_output_locale}.pak"
++ }
++ if (defined(copy_data_to_bundle) && copy_data_to_bundle) {
++ bundle_output =
++ "{{bundle_resources_dir}}/${_output_locale}.lproj/locale.pak"
++ }
++ sources = []
++ foreach(_pattern, invoker.source_patterns) {
++ sources += [ "${_pattern}${_input_locale}.pak" ]
++ }
++ }
++
++ _current_index = _current_index + 1
++ }
++
++ # The group that external targets depend on which collects all deps.
++ group(target_name) {
++ forward_variables_from(invoker,
++ [
++ "visibility",
++ "testonly",
++ ])
++ public_deps = _locale_targets
++ if (!defined(invoker.copy_data_to_bundle) || !invoker.copy_data_to_bundle) {
++ data_deps = public_deps
++ }
++ }
++}
+diff --git a/tools/grit/setup.py b/tools/grit/setup.py
+new file mode 100644
+index 0000000000..5d86dfc2fc
+--- /dev/null
++++ b/tools/grit/setup.py
+@@ -0,0 +1,46 @@
++#!/usr/bin/env python3
++# Copyright 2020 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++"""Install the package!"""
++
++from __future__ import absolute_import
++
++import setuptools
++
++
++setuptools.setup(
++ name='grit',
++ version='0',
++ entry_points={
++ 'console_scripts': ['grit = grit.grit_runner:Main'],
++ },
++ packages=setuptools.find_packages(),
++ install_requires=[
++ 'six >= 1.10',
++ ],
++ author='The Chromium Authors',
++ author_email='chromium-dev@chromium.org',
++ description='Google Resource and Internationalization Tool for managing '
++ 'translations & resource files',
++ license='BSD-3',
++ url='https://chromium.googlesource.com/chromium/src/tools/grit/',
++ classifiers=[
++ 'Development Status :: 6 - Mature',
++ 'Environment :: Console',
++ 'Intended Audience :: Developers',
++ 'License :: OSI Approved :: BSD License',
++ 'Operating System :: MacOS',
++ 'Operating System :: Microsoft :: Windows',
++ 'Operating System :: POSIX :: Linux',
++ 'Programming Language :: Python',
++ 'Programming Language :: Python :: 2.7',
++ 'Programming Language :: Python :: 3',
++ 'Programming Language :: Python :: 3.6',
++ 'Programming Language :: Python :: 3.7',
++ 'Programming Language :: Python :: 3.8',
++ 'Programming Language :: Python :: 3.9',
++ 'Topic :: Utilities',
++ ],
++)
+diff --git a/tools/grit/stamp_grit_sources.py b/tools/grit/stamp_grit_sources.py
+new file mode 100644
+index 0000000000..bc7265c6cb
+--- /dev/null
++++ b/tools/grit/stamp_grit_sources.py
+@@ -0,0 +1,57 @@
++# Copyright 2014 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++# This script enumerates the files in the given directory, writing an empty
++# stamp file and a .d file listing the inputs required to make the stamp. This
++# allows us to dynamically depend on the grit sources without enumerating the
++# grit directory for every invocation of grit (which is what adding the source
++# files to every .grd file's .d file would entail) or shelling out to grit
++# synchronously during GN execution to get the list (which would be slow).
++#
++# Usage:
++# stamp_grit_sources.py <directory> <stamp-file> <.d-file>
++
++from __future__ import print_function
++
++import os
++import sys
++
++def GritSourceFiles(grit_root_dir):
++ files = []
++ for root, _, filenames in os.walk(grit_root_dir):
++ grit_src = [os.path.join(root, f) for f in filenames
++ if f.endswith('.py') and not f.endswith('_unittest.py')]
++ files.extend(grit_src)
++ files = [f.replace('\\', '/') for f in files]
++ return sorted(files)
++
++
++def WriteDepFile(dep_file, stamp_file, source_files):
++ with open(dep_file, "w") as f:
++ f.write(stamp_file)
++ f.write(": ")
++ f.write(' '.join(source_files))
++
++
++def WriteStampFile(stamp_file):
++ with open(stamp_file, "w"):
++ pass
++
++
++def main(argv):
++ if len(argv) != 4:
++ print("Error: expecting 3 args.")
++ return 1
++
++ grit_root_dir = sys.argv[1]
++ stamp_file = sys.argv[2]
++ dep_file = sys.argv[3]
++
++ WriteStampFile(stamp_file)
++ WriteDepFile(dep_file, stamp_file, GritSourceFiles(grit_root_dir))
++ return 0
++
++
++if __name__ == '__main__':
++ sys.exit(main(sys.argv))
+diff --git a/tools/grit/third_party/six/LICENSE b/tools/grit/third_party/six/LICENSE
+new file mode 100644
+index 0000000000..e558f9d494
+--- /dev/null
++++ b/tools/grit/third_party/six/LICENSE
+@@ -0,0 +1,18 @@
++Copyright (c) 2010-2015 Benjamin Peterson
++
++Permission is hereby granted, free of charge, to any person obtaining a copy of
++this software and associated documentation files (the "Software"), to deal in
++the Software without restriction, including without limitation the rights to
++use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
++the Software, and to permit persons to whom the Software is furnished to do so,
++subject to the following conditions:
++
++The above copyright notice and this permission notice shall be included in all
++copies or substantial portions of the Software.
++
++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
++FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
++COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
++IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
++CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+diff --git a/tools/grit/third_party/six/README b/tools/grit/third_party/six/README
+new file mode 100644
+index 0000000000..ee628a9db6
+--- /dev/null
++++ b/tools/grit/third_party/six/README
+@@ -0,0 +1,16 @@
++Six is a Python 2 and 3 compatibility library. It provides utility functions
++for smoothing over the differences between the Python versions with the goal of
++writing Python code that is compatible on both Python versions. See the
++documentation for more information on what is provided.
++
++Six supports every Python version since 2.6. It is contained in only one Python
++file, so it can be easily copied into your project. (The copyright and license
++notice must be retained.)
++
++Online documentation is at https://pythonhosted.org/six/.
++
++Bugs can be reported to https://bitbucket.org/gutworth/six. The code can also
++be found there.
++
++For questions about six or porting in general, email the python-porting mailing
++list: https://mail.python.org/mailman/listinfo/python-porting
+diff --git a/tools/grit/third_party/six/README.chromium b/tools/grit/third_party/six/README.chromium
+new file mode 100644
+index 0000000000..100b24d046
+--- /dev/null
++++ b/tools/grit/third_party/six/README.chromium
+@@ -0,0 +1,13 @@
++Name: six
++Short Name: six
++URL: https://bitbucket.org/gutworth/six/commits/tag/1.10.0
++Version: 1.10.0
++Revision: 403:e5218c3f66a2
++License: Apache License, Version 2.0
++
++Description:
++Six is a Python 2 and 3 compatibility library.
++
++Local Modifications:
++- Copied six.py as __init__.py.
++- Kept LICENSE and README.
+diff --git a/tools/grit/third_party/six/__init__.py b/tools/grit/third_party/six/__init__.py
+new file mode 100644
+index 0000000000..56e4272cb3
+--- /dev/null
++++ b/tools/grit/third_party/six/__init__.py
+@@ -0,0 +1,868 @@
++# Copyright (c) 2010-2015 Benjamin Peterson
++#
++# Permission is hereby granted, free of charge, to any person obtaining a copy
++# of this software and associated documentation files (the "Software"), to deal
++# in the Software without restriction, including without limitation the rights
++# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++# copies of the Software, and to permit persons to whom the Software is
++# furnished to do so, subject to the following conditions:
++#
++# The above copyright notice and this permission notice shall be included in all
++# copies or substantial portions of the Software.
++#
++# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++# SOFTWARE.
++
++"""Utilities for writing code that runs on Python 2 and 3"""
++
++from __future__ import absolute_import
++
++import functools
++import itertools
++import operator
++import sys
++import types
++
++__author__ = "Benjamin Peterson <benjamin@python.org>"
++__version__ = "1.10.0"
++
++
++# Useful for very coarse version differentiation.
++PY2 = sys.version_info[0] == 2
++PY3 = sys.version_info[0] == 3
++PY34 = sys.version_info[0:2] >= (3, 4)
++
++if PY3:
++ string_types = str,
++ integer_types = int,
++ class_types = type,
++ text_type = str
++ binary_type = bytes
++
++ MAXSIZE = sys.maxsize
++else:
++ string_types = basestring,
++ integer_types = (int, long)
++ class_types = (type, types.ClassType)
++ text_type = unicode
++ binary_type = str
++
++ if sys.platform.startswith("java"):
++ # Jython always uses 32 bits.
++ MAXSIZE = int((1 << 31) - 1)
++ else:
++ # It's possible to have sizeof(long) != sizeof(Py_ssize_t).
++ class X(object):
++
++ def __len__(self):
++ return 1 << 31
++ try:
++ len(X())
++ except OverflowError:
++ # 32-bit
++ MAXSIZE = int((1 << 31) - 1)
++ else:
++ # 64-bit
++ MAXSIZE = int((1 << 63) - 1)
++ del X
++
++
++def _add_doc(func, doc):
++ """Add documentation to a function."""
++ func.__doc__ = doc
++
++
++def _import_module(name):
++ """Import module, returning the module after the last dot."""
++ __import__(name)
++ return sys.modules[name]
++
++
++class _LazyDescr(object):
++
++ def __init__(self, name):
++ self.name = name
++
++ def __get__(self, obj, tp):
++ result = self._resolve()
++ setattr(obj, self.name, result) # Invokes __set__.
++ try:
++ # This is a bit ugly, but it avoids running this again by
++ # removing this descriptor.
++ delattr(obj.__class__, self.name)
++ except AttributeError:
++ pass
++ return result
++
++
++class MovedModule(_LazyDescr):
++
++ def __init__(self, name, old, new=None):
++ super(MovedModule, self).__init__(name)
++ if PY3:
++ if new is None:
++ new = name
++ self.mod = new
++ else:
++ self.mod = old
++
++ def _resolve(self):
++ return _import_module(self.mod)
++
++ def __getattr__(self, attr):
++ _module = self._resolve()
++ value = getattr(_module, attr)
++ setattr(self, attr, value)
++ return value
++
++
++class _LazyModule(types.ModuleType):
++
++ def __init__(self, name):
++ super(_LazyModule, self).__init__(name)
++ self.__doc__ = self.__class__.__doc__
++
++ def __dir__(self):
++ attrs = ["__doc__", "__name__"]
++ attrs += [attr.name for attr in self._moved_attributes]
++ return attrs
++
++ # Subclasses should override this
++ _moved_attributes = []
++
++
++class MovedAttribute(_LazyDescr):
++
++ def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
++ super(MovedAttribute, self).__init__(name)
++ if PY3:
++ if new_mod is None:
++ new_mod = name
++ self.mod = new_mod
++ if new_attr is None:
++ if old_attr is None:
++ new_attr = name
++ else:
++ new_attr = old_attr
++ self.attr = new_attr
++ else:
++ self.mod = old_mod
++ if old_attr is None:
++ old_attr = name
++ self.attr = old_attr
++
++ def _resolve(self):
++ module = _import_module(self.mod)
++ return getattr(module, self.attr)
++
++
++class _SixMetaPathImporter(object):
++
++ """
++ A meta path importer to import six.moves and its submodules.
++
++ This class implements a PEP302 finder and loader. It should be compatible
++ with Python 2.5 and all existing versions of Python3
++ """
++
++ def __init__(self, six_module_name):
++ self.name = six_module_name
++ self.known_modules = {}
++
++ def _add_module(self, mod, *fullnames):
++ for fullname in fullnames:
++ self.known_modules[self.name + "." + fullname] = mod
++
++ def _get_module(self, fullname):
++ return self.known_modules[self.name + "." + fullname]
++
++ def find_module(self, fullname, path=None):
++ if fullname in self.known_modules:
++ return self
++ return None
++
++ def __get_module(self, fullname):
++ try:
++ return self.known_modules[fullname]
++ except KeyError:
++ raise ImportError("This loader does not know module " + fullname)
++
++ def load_module(self, fullname):
++ try:
++ # in case of a reload
++ return sys.modules[fullname]
++ except KeyError:
++ pass
++ mod = self.__get_module(fullname)
++ if isinstance(mod, MovedModule):
++ mod = mod._resolve()
++ else:
++ mod.__loader__ = self
++ sys.modules[fullname] = mod
++ return mod
++
++ def is_package(self, fullname):
++ """
++ Return true, if the named module is a package.
++
++ We need this method to get correct spec objects with
++ Python 3.4 (see PEP451)
++ """
++ return hasattr(self.__get_module(fullname), "__path__")
++
++ def get_code(self, fullname):
++ """Return None
++
++ Required, if is_package is implemented"""
++ self.__get_module(fullname) # eventually raises ImportError
++ return None
++ get_source = get_code # same as get_code
++
++_importer = _SixMetaPathImporter(__name__)
++
++
++class _MovedItems(_LazyModule):
++
++ """Lazy loading of moved objects"""
++ __path__ = [] # mark as package
++
++
++_moved_attributes = [
++ MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
++ MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
++ MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"),
++ MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
++ MovedAttribute("intern", "__builtin__", "sys"),
++ MovedAttribute("map", "itertools", "builtins", "imap", "map"),
++ MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"),
++ MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"),
++ MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
++ MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"),
++ MovedAttribute("reduce", "__builtin__", "functools"),
++ MovedAttribute("shlex_quote", "pipes", "shlex", "quote"),
++ MovedAttribute("StringIO", "StringIO", "io"),
++ MovedAttribute("UserDict", "UserDict", "collections"),
++ MovedAttribute("UserList", "UserList", "collections"),
++ MovedAttribute("UserString", "UserString", "collections"),
++ MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
++ MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
++ MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
++ MovedModule("builtins", "__builtin__"),
++ MovedModule("configparser", "ConfigParser"),
++ MovedModule("copyreg", "copy_reg"),
++ MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
++ MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"),
++ MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
++ MovedModule("http_cookies", "Cookie", "http.cookies"),
++ MovedModule("html_entities", "htmlentitydefs", "html.entities"),
++ MovedModule("html_parser", "HTMLParser", "html.parser"),
++ MovedModule("http_client", "httplib", "http.client"),
++ MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
++ MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"),
++ MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
++ MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
++ MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
++ MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
++ MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
++ MovedModule("cPickle", "cPickle", "pickle"),
++ MovedModule("queue", "Queue"),
++ MovedModule("reprlib", "repr"),
++ MovedModule("socketserver", "SocketServer"),
++ MovedModule("_thread", "thread", "_thread"),
++ MovedModule("tkinter", "Tkinter"),
++ MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
++ MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
++ MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
++ MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
++ MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
++ MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"),
++ MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
++ MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
++ MovedModule("tkinter_colorchooser", "tkColorChooser",
++ "tkinter.colorchooser"),
++ MovedModule("tkinter_commondialog", "tkCommonDialog",
++ "tkinter.commondialog"),
++ MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
++ MovedModule("tkinter_font", "tkFont", "tkinter.font"),
++ MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
++ MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
++ "tkinter.simpledialog"),
++ MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"),
++ MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"),
++ MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"),
++ MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
++ MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"),
++ MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"),
++]
++# Add windows specific modules.
++if sys.platform == "win32":
++ _moved_attributes += [
++ MovedModule("winreg", "_winreg"),
++ ]
++
++for attr in _moved_attributes:
++ setattr(_MovedItems, attr.name, attr)
++ if isinstance(attr, MovedModule):
++ _importer._add_module(attr, "moves." + attr.name)
++del attr
++
++_MovedItems._moved_attributes = _moved_attributes
++
++moves = _MovedItems(__name__ + ".moves")
++_importer._add_module(moves, "moves")
++
++
++class Module_six_moves_urllib_parse(_LazyModule):
++
++ """Lazy loading of moved objects in six.moves.urllib_parse"""
++
++
++_urllib_parse_moved_attributes = [
++ MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
++ MovedAttribute("SplitResult", "urlparse", "urllib.parse"),
++ MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
++ MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
++ MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
++ MovedAttribute("urljoin", "urlparse", "urllib.parse"),
++ MovedAttribute("urlparse", "urlparse", "urllib.parse"),
++ MovedAttribute("urlsplit", "urlparse", "urllib.parse"),
++ MovedAttribute("urlunparse", "urlparse", "urllib.parse"),
++ MovedAttribute("urlunsplit", "urlparse", "urllib.parse"),
++ MovedAttribute("quote", "urllib", "urllib.parse"),
++ MovedAttribute("quote_plus", "urllib", "urllib.parse"),
++ MovedAttribute("unquote", "urllib", "urllib.parse"),
++ MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
++ MovedAttribute("urlencode", "urllib", "urllib.parse"),
++ MovedAttribute("splitquery", "urllib", "urllib.parse"),
++ MovedAttribute("splittag", "urllib", "urllib.parse"),
++ MovedAttribute("splituser", "urllib", "urllib.parse"),
++ MovedAttribute("uses_fragment", "urlparse", "urllib.parse"),
++ MovedAttribute("uses_netloc", "urlparse", "urllib.parse"),
++ MovedAttribute("uses_params", "urlparse", "urllib.parse"),
++ MovedAttribute("uses_query", "urlparse", "urllib.parse"),
++ MovedAttribute("uses_relative", "urlparse", "urllib.parse"),
++]
++for attr in _urllib_parse_moved_attributes:
++ setattr(Module_six_moves_urllib_parse, attr.name, attr)
++del attr
++
++Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes
++
++_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"),
++ "moves.urllib_parse", "moves.urllib.parse")
++
++
++class Module_six_moves_urllib_error(_LazyModule):
++
++ """Lazy loading of moved objects in six.moves.urllib_error"""
++
++
++_urllib_error_moved_attributes = [
++ MovedAttribute("URLError", "urllib2", "urllib.error"),
++ MovedAttribute("HTTPError", "urllib2", "urllib.error"),
++ MovedAttribute("ContentTooShortError", "urllib", "urllib.error"),
++]
++for attr in _urllib_error_moved_attributes:
++ setattr(Module_six_moves_urllib_error, attr.name, attr)
++del attr
++
++Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes
++
++_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"),
++ "moves.urllib_error", "moves.urllib.error")
++
++
++class Module_six_moves_urllib_request(_LazyModule):
++
++ """Lazy loading of moved objects in six.moves.urllib_request"""
++
++
++_urllib_request_moved_attributes = [
++ MovedAttribute("urlopen", "urllib2", "urllib.request"),
++ MovedAttribute("install_opener", "urllib2", "urllib.request"),
++ MovedAttribute("build_opener", "urllib2", "urllib.request"),
++ MovedAttribute("pathname2url", "urllib", "urllib.request"),
++ MovedAttribute("url2pathname", "urllib", "urllib.request"),
++ MovedAttribute("getproxies", "urllib", "urllib.request"),
++ MovedAttribute("Request", "urllib2", "urllib.request"),
++ MovedAttribute("OpenerDirector", "urllib2", "urllib.request"),
++ MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"),
++ MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"),
++ MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"),
++ MovedAttribute("ProxyHandler", "urllib2", "urllib.request"),
++ MovedAttribute("BaseHandler", "urllib2", "urllib.request"),
++ MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"),
++ MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"),
++ MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"),
++ MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"),
++ MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"),
++ MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"),
++ MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"),
++ MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"),
++ MovedAttribute("HTTPHandler", "urllib2", "urllib.request"),
++ MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"),
++ MovedAttribute("FileHandler", "urllib2", "urllib.request"),
++ MovedAttribute("FTPHandler", "urllib2", "urllib.request"),
++ MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"),
++ MovedAttribute("UnknownHandler", "urllib2", "urllib.request"),
++ MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
++ MovedAttribute("urlretrieve", "urllib", "urllib.request"),
++ MovedAttribute("urlcleanup", "urllib", "urllib.request"),
++ MovedAttribute("URLopener", "urllib", "urllib.request"),
++ MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
++ MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
++]
++for attr in _urllib_request_moved_attributes:
++ setattr(Module_six_moves_urllib_request, attr.name, attr)
++del attr
++
++Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes
++
++_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"),
++ "moves.urllib_request", "moves.urllib.request")
++
++
++class Module_six_moves_urllib_response(_LazyModule):
++
++ """Lazy loading of moved objects in six.moves.urllib_response"""
++
++
++_urllib_response_moved_attributes = [
++ MovedAttribute("addbase", "urllib", "urllib.response"),
++ MovedAttribute("addclosehook", "urllib", "urllib.response"),
++ MovedAttribute("addinfo", "urllib", "urllib.response"),
++ MovedAttribute("addinfourl", "urllib", "urllib.response"),
++]
++for attr in _urllib_response_moved_attributes:
++ setattr(Module_six_moves_urllib_response, attr.name, attr)
++del attr
++
++Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes
++
++_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"),
++ "moves.urllib_response", "moves.urllib.response")
++
++
++class Module_six_moves_urllib_robotparser(_LazyModule):
++
++ """Lazy loading of moved objects in six.moves.urllib_robotparser"""
++
++
++_urllib_robotparser_moved_attributes = [
++ MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"),
++]
++for attr in _urllib_robotparser_moved_attributes:
++ setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
++del attr
++
++Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes
++
++_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"),
++ "moves.urllib_robotparser", "moves.urllib.robotparser")
++
++
++class Module_six_moves_urllib(types.ModuleType):
++
++ """Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
++ __path__ = [] # mark as package
++ parse = _importer._get_module("moves.urllib_parse")
++ error = _importer._get_module("moves.urllib_error")
++ request = _importer._get_module("moves.urllib_request")
++ response = _importer._get_module("moves.urllib_response")
++ robotparser = _importer._get_module("moves.urllib_robotparser")
++
++ def __dir__(self):
++ return ['parse', 'error', 'request', 'response', 'robotparser']
++
++_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"),
++ "moves.urllib")
++
++
++def add_move(move):
++ """Add an item to six.moves."""
++ setattr(_MovedItems, move.name, move)
++
++
++def remove_move(name):
++ """Remove item from six.moves."""
++ try:
++ delattr(_MovedItems, name)
++ except AttributeError:
++ try:
++ del moves.__dict__[name]
++ except KeyError:
++ raise AttributeError("no such move, %r" % (name,))
++
++
++if PY3:
++ _meth_func = "__func__"
++ _meth_self = "__self__"
++
++ _func_closure = "__closure__"
++ _func_code = "__code__"
++ _func_defaults = "__defaults__"
++ _func_globals = "__globals__"
++else:
++ _meth_func = "im_func"
++ _meth_self = "im_self"
++
++ _func_closure = "func_closure"
++ _func_code = "func_code"
++ _func_defaults = "func_defaults"
++ _func_globals = "func_globals"
++
++
++try:
++ advance_iterator = next
++except NameError:
++ def advance_iterator(it):
++ return it.next()
++next = advance_iterator
++
++
++try:
++ callable = callable
++except NameError:
++ def callable(obj):
++ return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
++
++
++if PY3:
++ def get_unbound_function(unbound):
++ return unbound
++
++ create_bound_method = types.MethodType
++
++ def create_unbound_method(func, cls):
++ return func
++
++ Iterator = object
++else:
++ def get_unbound_function(unbound):
++ return unbound.im_func
++
++ def create_bound_method(func, obj):
++ return types.MethodType(func, obj, obj.__class__)
++
++ def create_unbound_method(func, cls):
++ return types.MethodType(func, None, cls)
++
++ class Iterator(object):
++
++ def next(self):
++ return type(self).__next__(self)
++
++ callable = callable
++_add_doc(get_unbound_function,
++ """Get the function out of a possibly unbound function""")
++
++
++get_method_function = operator.attrgetter(_meth_func)
++get_method_self = operator.attrgetter(_meth_self)
++get_function_closure = operator.attrgetter(_func_closure)
++get_function_code = operator.attrgetter(_func_code)
++get_function_defaults = operator.attrgetter(_func_defaults)
++get_function_globals = operator.attrgetter(_func_globals)
++
++
++if PY3:
++ def iterkeys(d, **kw):
++ return iter(d.keys(**kw))
++
++ def itervalues(d, **kw):
++ return iter(d.values(**kw))
++
++ def iteritems(d, **kw):
++ return iter(d.items(**kw))
++
++ def iterlists(d, **kw):
++ return iter(d.lists(**kw))
++
++ viewkeys = operator.methodcaller("keys")
++
++ viewvalues = operator.methodcaller("values")
++
++ viewitems = operator.methodcaller("items")
++else:
++ def iterkeys(d, **kw):
++ return d.iterkeys(**kw)
++
++ def itervalues(d, **kw):
++ return d.itervalues(**kw)
++
++ def iteritems(d, **kw):
++ return d.iteritems(**kw)
++
++ def iterlists(d, **kw):
++ return d.iterlists(**kw)
++
++ viewkeys = operator.methodcaller("viewkeys")
++
++ viewvalues = operator.methodcaller("viewvalues")
++
++ viewitems = operator.methodcaller("viewitems")
++
++_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.")
++_add_doc(itervalues, "Return an iterator over the values of a dictionary.")
++_add_doc(iteritems,
++ "Return an iterator over the (key, value) pairs of a dictionary.")
++_add_doc(iterlists,
++ "Return an iterator over the (key, [values]) pairs of a dictionary.")
++
++
++if PY3:
++ def b(s):
++ return s.encode("latin-1")
++
++ def u(s):
++ return s
++ unichr = chr
++ import struct
++ int2byte = struct.Struct(">B").pack
++ del struct
++ byte2int = operator.itemgetter(0)
++ indexbytes = operator.getitem
++ iterbytes = iter
++ import io
++ StringIO = io.StringIO
++ BytesIO = io.BytesIO
++ _assertCountEqual = "assertCountEqual"
++ if sys.version_info[1] <= 1:
++ _assertRaisesRegex = "assertRaisesRegexp"
++ _assertRegex = "assertRegexpMatches"
++ else:
++ _assertRaisesRegex = "assertRaisesRegex"
++ _assertRegex = "assertRegex"
++else:
++ def b(s):
++ return s
++ # Workaround for standalone backslash
++
++ def u(s):
++ return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
++ unichr = unichr
++ int2byte = chr
++
++ def byte2int(bs):
++ return ord(bs[0])
++
++ def indexbytes(buf, i):
++ return ord(buf[i])
++ iterbytes = functools.partial(itertools.imap, ord)
++ import StringIO
++ StringIO = BytesIO = StringIO.StringIO
++ _assertCountEqual = "assertItemsEqual"
++ _assertRaisesRegex = "assertRaisesRegexp"
++ _assertRegex = "assertRegexpMatches"
++_add_doc(b, """Byte literal""")
++_add_doc(u, """Text literal""")
++
++
++def assertCountEqual(self, *args, **kwargs):
++ return getattr(self, _assertCountEqual)(*args, **kwargs)
++
++
++def assertRaisesRegex(self, *args, **kwargs):
++ return getattr(self, _assertRaisesRegex)(*args, **kwargs)
++
++
++def assertRegex(self, *args, **kwargs):
++ return getattr(self, _assertRegex)(*args, **kwargs)
++
++
++if PY3:
++ exec_ = getattr(moves.builtins, "exec")
++
++ def reraise(tp, value, tb=None):
++ if value is None:
++ value = tp()
++ if value.__traceback__ is not tb:
++ raise value.with_traceback(tb)
++ raise value
++
++else:
++ def exec_(_code_, _globs_=None, _locs_=None):
++ """Execute code in a namespace."""
++ if _globs_ is None:
++ frame = sys._getframe(1)
++ _globs_ = frame.f_globals
++ if _locs_ is None:
++ _locs_ = frame.f_locals
++ del frame
++ elif _locs_ is None:
++ _locs_ = _globs_
++ exec("""exec _code_ in _globs_, _locs_""")
++
++ exec_("""def reraise(tp, value, tb=None):
++ raise tp, value, tb
++""")
++
++
++if sys.version_info[:2] == (3, 2):
++ exec_("""def raise_from(value, from_value):
++ if from_value is None:
++ raise value
++ raise value from from_value
++""")
++elif sys.version_info[:2] > (3, 2):
++ exec_("""def raise_from(value, from_value):
++ raise value from from_value
++""")
++else:
++ def raise_from(value, from_value):
++ raise value
++
++
++print_ = getattr(moves.builtins, "print", None)
++if print_ is None:
++ def print_(*args, **kwargs):
++ """The new-style print function for Python 2.4 and 2.5."""
++ fp = kwargs.pop("file", sys.stdout)
++ if fp is None:
++ return
++
++ def write(data):
++ if not isinstance(data, basestring):
++ data = str(data)
++ # If the file has an encoding, encode unicode with it.
++ if (isinstance(fp, file) and
++ isinstance(data, unicode) and
++ fp.encoding is not None):
++ errors = getattr(fp, "errors", None)
++ if errors is None:
++ errors = "strict"
++ data = data.encode(fp.encoding, errors)
++ fp.write(data)
++ want_unicode = False
++ sep = kwargs.pop("sep", None)
++ if sep is not None:
++ if isinstance(sep, unicode):
++ want_unicode = True
++ elif not isinstance(sep, str):
++ raise TypeError("sep must be None or a string")
++ end = kwargs.pop("end", None)
++ if end is not None:
++ if isinstance(end, unicode):
++ want_unicode = True
++ elif not isinstance(end, str):
++ raise TypeError("end must be None or a string")
++ if kwargs:
++ raise TypeError("invalid keyword arguments to print()")
++ if not want_unicode:
++ for arg in args:
++ if isinstance(arg, unicode):
++ want_unicode = True
++ break
++ if want_unicode:
++ newline = unicode("\n")
++ space = unicode(" ")
++ else:
++ newline = "\n"
++ space = " "
++ if sep is None:
++ sep = space
++ if end is None:
++ end = newline
++ for i, arg in enumerate(args):
++ if i:
++ write(sep)
++ write(arg)
++ write(end)
++if sys.version_info[:2] < (3, 3):
++ _print = print_
++
++ def print_(*args, **kwargs):
++ fp = kwargs.get("file", sys.stdout)
++ flush = kwargs.pop("flush", False)
++ _print(*args, **kwargs)
++ if flush and fp is not None:
++ fp.flush()
++
++_add_doc(reraise, """Reraise an exception.""")
++
++if sys.version_info[0:2] < (3, 4):
++ def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
++ updated=functools.WRAPPER_UPDATES):
++ def wrapper(f):
++ f = functools.wraps(wrapped, assigned, updated)(f)
++ f.__wrapped__ = wrapped
++ return f
++ return wrapper
++else:
++ wraps = functools.wraps
++
++
++def with_metaclass(meta, *bases):
++ """Create a base class with a metaclass."""
++ # This requires a bit of explanation: the basic idea is to make a dummy
++ # metaclass for one level of class instantiation that replaces itself with
++ # the actual metaclass.
++ class metaclass(meta):
++
++ def __new__(cls, name, this_bases, d):
++ return meta(name, bases, d)
++ return type.__new__(metaclass, 'temporary_class', (), {})
++
++
++def add_metaclass(metaclass):
++ """Class decorator for creating a class with a metaclass."""
++ def wrapper(cls):
++ orig_vars = cls.__dict__.copy()
++ slots = orig_vars.get('__slots__')
++ if slots is not None:
++ if isinstance(slots, str):
++ slots = [slots]
++ for slots_var in slots:
++ orig_vars.pop(slots_var)
++ orig_vars.pop('__dict__', None)
++ orig_vars.pop('__weakref__', None)
++ return metaclass(cls.__name__, cls.__bases__, orig_vars)
++ return wrapper
++
++
++def python_2_unicode_compatible(klass):
++ """
++ A decorator that defines __unicode__ and __str__ methods under Python 2.
++ Under Python 3 it does nothing.
++
++ To support Python 2 and 3 with a single code base, define a __str__ method
++ returning text and apply this decorator to the class.
++ """
++ if PY2:
++ if '__str__' not in klass.__dict__:
++ raise ValueError("@python_2_unicode_compatible cannot be applied "
++ "to %s because it doesn't define __str__()." %
++ klass.__name__)
++ klass.__unicode__ = klass.__str__
++ klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
++ return klass
++
++
++# Complete the moves implementation.
++# This code is at the end of this module to speed up module loading.
++# Turn this module into a package.
++__path__ = [] # required for PEP 302 and PEP 451
++__package__ = __name__ # see PEP 366 @ReservedAssignment
++if globals().get("__spec__") is not None:
++ __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable
++# Remove other six meta path importers, since they cause problems. This can
++# happen if six is removed from sys.modules and then reloaded. (Setuptools does
++# this for some reason.)
++if sys.meta_path:
++ for i, importer in enumerate(sys.meta_path):
++ # Here's some real nastiness: Another "instance" of the six module might
++ # be floating around. Therefore, we can't use isinstance() to check for
++ # the six meta path importer, since the other six instance will have
++ # inserted an importer with different class.
++ if (type(importer).__name__ == "_SixMetaPathImporter" and
++ importer.name == __name__):
++ del sys.meta_path[i]
++ break
++ del i, importer
++# Finally, add the importer to the meta path import hook.
++sys.meta_path.append(_importer)
diff --git a/third_party/libwebrtc/moz-patch-stack/0098.patch b/third_party/libwebrtc/moz-patch-stack/0098.patch
index 1faafdf8cf..dc3cc7ca1a 100644
--- a/third_party/libwebrtc/moz-patch-stack/0098.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0098.patch
@@ -1,35362 +1,20 @@
-From: Nico Grunbaum <na-g@nostrum.com>
-Date: Fri, 30 Apr 2021 21:51:00 +0000
-Subject: Bug 1654112 - Add grit dep for building webrtc on android; r=mjf
+From: Michael Froman <mfroman@mozilla.com>
+Date: Wed, 7 Dec 2022 17:09:00 +0000
+Subject: Bug 1744645 - pt1 - add a couple empty gni files to help with
+ BUILD.gn corrections. r=ng
-Differential Revision: https://phabricator.services.mozilla.com/D114027
-Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/3cce5e6938f0df87bd9ab12a5f556aceb93dfa1d
+Differential Revision: https://phabricator.services.mozilla.com/D163991
+Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/81d86382ee468f3b36deed00d0c9d59eb85524be
---
- tools/grit/.gitignore | 1 +
- tools/grit/BUILD.gn | 48 +
- tools/grit/MANIFEST.in | 3 +
- tools/grit/OWNERS | 8 +
- tools/grit/PRESUBMIT.py | 22 +
- tools/grit/README.md | 19 +
- tools/grit/grit.py | 31 +
- tools/grit/grit/__init__.py | 19 +
- tools/grit/grit/clique.py | 491 +++
- tools/grit/grit/clique_unittest.py | 265 ++
- tools/grit/grit/constants.py | 23 +
- tools/grit/grit/exception.py | 139 +
- tools/grit/grit/extern/BogoFP.py | 22 +
- tools/grit/grit/extern/FP.py | 72 +
- tools/grit/grit/extern/__init__.py | 0
- tools/grit/grit/extern/tclib.py | 503 +++
- tools/grit/grit/format/__init__.py | 8 +
- tools/grit/grit/format/android_xml.py | 212 ++
- .../grit/grit/format/android_xml_unittest.py | 149 +
- tools/grit/grit/format/c_format.py | 95 +
- tools/grit/grit/format/c_format_unittest.py | 81 +
- .../grit/grit/format/chrome_messages_json.py | 59 +
- .../format/chrome_messages_json_unittest.py | 190 +
- tools/grit/grit/format/data_pack.py | 321 ++
- tools/grit/grit/format/data_pack_unittest.py | 102 +
- .../grit/grit/format/gen_predetermined_ids.py | 144 +
- .../format/gen_predetermined_ids_unittest.py | 46 +
- tools/grit/grit/format/gzip_string.py | 46 +
- .../grit/grit/format/gzip_string_unittest.py | 65 +
- tools/grit/grit/format/html_inline.py | 602 ++++
- .../grit/grit/format/html_inline_unittest.py | 927 +++++
- tools/grit/grit/format/minifier.py | 45 +
- .../grit/grit/format/policy_templates_json.py | 26 +
- .../format/policy_templates_json_unittest.py | 207 ++
- tools/grit/grit/format/rc.py | 474 +++
- tools/grit/grit/format/rc_header.py | 48 +
- tools/grit/grit/format/rc_header_unittest.py | 138 +
- tools/grit/grit/format/rc_unittest.py | 415 +++
- tools/grit/grit/format/resource_map.py | 159 +
- .../grit/grit/format/resource_map_unittest.py | 345 ++
- tools/grit/grit/gather/__init__.py | 8 +
- tools/grit/grit/gather/admin_template.py | 62 +
- .../grit/gather/admin_template_unittest.py | 115 +
- tools/grit/grit/gather/chrome_html.py | 377 ++
- .../grit/grit/gather/chrome_html_unittest.py | 610 ++++
- tools/grit/grit/gather/chrome_scaled_image.py | 157 +
- .../gather/chrome_scaled_image_unittest.py | 209 ++
- tools/grit/grit/gather/interface.py | 172 +
- tools/grit/grit/gather/json_loader.py | 27 +
- tools/grit/grit/gather/policy_json.py | 325 ++
- .../grit/grit/gather/policy_json_unittest.py | 347 ++
- tools/grit/grit/gather/rc.py | 343 ++
- tools/grit/grit/gather/rc_unittest.py | 372 ++
- tools/grit/grit/gather/regexp.py | 82 +
- tools/grit/grit/gather/skeleton_gatherer.py | 149 +
- tools/grit/grit/gather/tr_html.py | 743 ++++
- tools/grit/grit/gather/tr_html_unittest.py | 524 +++
- tools/grit/grit/gather/txt.py | 38 +
- tools/grit/grit/gather/txt_unittest.py | 35 +
- tools/grit/grit/grd_reader.py | 238 ++
- tools/grit/grit/grd_reader_unittest.py | 346 ++
- tools/grit/grit/grit-todo.xml | 62 +
- tools/grit/grit/grit_runner.py | 334 ++
- tools/grit/grit/grit_runner_unittest.py | 42 +
- tools/grit/grit/lazy_re.py | 46 +
- tools/grit/grit/lazy_re_unittest.py | 40 +
- tools/grit/grit/node/__init__.py | 8 +
- tools/grit/grit/node/base.py | 670 ++++
- tools/grit/grit/node/base_unittest.py | 259 ++
- tools/grit/grit/node/brotli_util.py | 29 +
- tools/grit/grit/node/custom/__init__.py | 8 +
- tools/grit/grit/node/custom/filename.py | 29 +
- .../grit/node/custom/filename_unittest.py | 34 +
- tools/grit/grit/node/empty.py | 64 +
- tools/grit/grit/node/include.py | 170 +
- tools/grit/grit/node/include_unittest.py | 134 +
- tools/grit/grit/node/mapping.py | 60 +
- tools/grit/grit/node/message.py | 362 ++
- tools/grit/grit/node/message_unittest.py | 380 ++
- tools/grit/grit/node/misc.py | 707 ++++
- tools/grit/grit/node/misc_unittest.py | 590 ++++
- tools/grit/grit/node/mock_brotli.py | 10 +
- tools/grit/grit/node/node_io.py | 117 +
- tools/grit/grit/node/node_io_unittest.py | 182 +
- tools/grit/grit/node/structure.py | 375 ++
- tools/grit/grit/node/structure_unittest.py | 178 +
- tools/grit/grit/node/variant.py | 41 +
- tools/grit/grit/pseudo.py | 129 +
- tools/grit/grit/pseudo_rtl.py | 104 +
- tools/grit/grit/pseudo_unittest.py | 55 +
- tools/grit/grit/shortcuts.py | 93 +
- tools/grit/grit/shortcuts_unittest.py | 79 +
- tools/grit/grit/tclib.py | 246 ++
- tools/grit/grit/tclib_unittest.py | 180 +
- tools/grit/grit/test_suite_all.py | 34 +
- tools/grit/grit/testdata/GoogleDesktop.adm | 945 +++++
- tools/grit/grit/testdata/README.txt | 87 +
- tools/grit/grit/testdata/about.html | 45 +
- tools/grit/grit/testdata/android.xml | 24 +
- tools/grit/grit/testdata/bad_browser.html | 16 +
- tools/grit/grit/testdata/browser.html | 42 +
- tools/grit/grit/testdata/buildinfo.grd | 46 +
- tools/grit/grit/testdata/cache_prefix.html | 24 +
- .../grit/grit/testdata/cache_prefix_file.html | 25 +
- tools/grit/grit/testdata/chat_result.html | 24 +
- .../chrome/app/generated_resources.grd | 199 ++
- tools/grit/grit/testdata/chrome_html.html | 6 +
- .../grit/testdata/default_100_percent/a.png | Bin 0 -> 159 bytes
- .../grit/testdata/default_100_percent/b.png | 1 +
- tools/grit/grit/testdata/del_footer.html | 8 +
- tools/grit/grit/testdata/del_header.html | 60 +
- tools/grit/grit/testdata/deleted.html | 21 +
- tools/grit/grit/testdata/depfile.grd | 18 +
- tools/grit/grit/testdata/details.html | 10 +
- .../grit/testdata/duplicate-name-input.xml | 26 +
- tools/grit/grit/testdata/email_result.html | 34 +
- tools/grit/grit/testdata/email_thread.html | 10 +
- tools/grit/grit/testdata/error.html | 8 +
- tools/grit/grit/testdata/explicit_web.html | 11 +
- tools/grit/grit/testdata/footer.html | 14 +
- .../grit/testdata/generated_resources_fr.xtb | 3079 +++++++++++++++++
- .../grit/testdata/generated_resources_iw.xtb | 4 +
- .../grit/testdata/generated_resources_no.xtb | 4 +
- tools/grit/grit/testdata/grit_part.grdp | 5 +
- tools/grit/grit/testdata/header.html | 39 +
- tools/grit/grit/testdata/homepage.html | 37 +
- tools/grit/grit/testdata/hover.html | 177 +
- tools/grit/grit/testdata/include_test.html | 31 +
- tools/grit/grit/testdata/included_sample.html | 1 +
- tools/grit/grit/testdata/indexing_speed.html | 58 +
- tools/grit/grit/testdata/install_prefs.html | 92 +
- tools/grit/grit/testdata/install_prefs2.html | 52 +
- .../grit/testdata/klonk-alternate-skeleton.rc | Bin 0 -> 1088 bytes
- tools/grit/grit/testdata/klonk.ico | Bin 0 -> 766 bytes
- tools/grit/grit/testdata/klonk.rc | Bin 0 -> 9824 bytes
- .../grit/grit/testdata/ko_oem_enable_bug.html | 1 +
- .../grit/testdata/ko_oem_non_admin_bug.html | 1 +
- tools/grit/grit/testdata/mini.html | 36 +
- tools/grit/grit/testdata/oem_enable.html | 106 +
- tools/grit/grit/testdata/oem_non_admin.html | 39 +
- tools/grit/grit/testdata/onebox.html | 21 +
- tools/grit/grit/testdata/oneclick.html | 34 +
- tools/grit/grit/testdata/password.html | 37 +
- tools/grit/grit/testdata/preferences.html | 234 ++
- tools/grit/grit/testdata/preprocess_test.html | 7 +
- tools/grit/grit/testdata/privacy.html | 35 +
- tools/grit/grit/testdata/quit_apps.html | 49 +
- tools/grit/grit/testdata/recrawl.html | 30 +
- tools/grit/grit/testdata/resource_ids | 13 +
- tools/grit/grit/testdata/script.html | 38 +
- tools/grit/grit/testdata/searchbox.html | 22 +
- tools/grit/grit/testdata/sidebar_h.html | 82 +
- tools/grit/grit/testdata/sidebar_v.html | 267 ++
- tools/grit/grit/testdata/simple-input.xml | 52 +
- tools/grit/grit/testdata/simple.html | 3 +
- tools/grit/grit/testdata/source.rc | 57 +
- .../grit/testdata/special_100_percent/a.png | Bin 0 -> 159 bytes
- tools/grit/grit/testdata/status.html | 44 +
- .../grit/testdata/structure_variables.html | 4 +
- tools/grit/grit/testdata/substitute.grd | 31 +
- tools/grit/grit/testdata/substitute.xmb | 10 +
- .../grit/grit/testdata/substitute_no_ids.grd | 31 +
- tools/grit/grit/testdata/substitute_tmpl.grd | 31 +
- tools/grit/grit/testdata/test_css.css | 1 +
- tools/grit/grit/testdata/test_html.html | 1 +
- tools/grit/grit/testdata/test_js.js | 1 +
- tools/grit/grit/testdata/test_svg.svg | 1 +
- tools/grit/grit/testdata/test_text.txt | 1 +
- tools/grit/grit/testdata/time_related.html | 11 +
- tools/grit/grit/testdata/toolbar_about.html | 138 +
- .../grit/testdata/tools/grit/resource_ids | 176 +
- tools/grit/grit/testdata/transl.rc | 56 +
- tools/grit/grit/testdata/versions.html | 7 +
- tools/grit/grit/testdata/whitelist.txt | 4 +
- .../grit/testdata/whitelist_resources.grd | 54 +
- .../grit/grit/testdata/whitelist_strings.grd | 23 +
- tools/grit/grit/tool/__init__.py | 8 +
- tools/grit/grit/tool/android2grd.py | 484 +++
- tools/grit/grit/tool/android2grd_unittest.py | 181 +
- tools/grit/grit/tool/build.py | 556 +++
- tools/grit/grit/tool/build_unittest.py | 341 ++
- tools/grit/grit/tool/buildinfo.py | 78 +
- tools/grit/grit/tool/buildinfo_unittest.py | 90 +
- tools/grit/grit/tool/count.py | 52 +
- tools/grit/grit/tool/diff_structures.py | 119 +
- .../grit/tool/diff_structures_unittest.py | 46 +
- tools/grit/grit/tool/interface.py | 62 +
- tools/grit/grit/tool/menu_from_parts.py | 79 +
- tools/grit/grit/tool/newgrd.py | 85 +
- tools/grit/grit/tool/newgrd_unittest.py | 51 +
- tools/grit/grit/tool/postprocess_interface.py | 29 +
- tools/grit/grit/tool/postprocess_unittest.py | 64 +
- tools/grit/grit/tool/preprocess_interface.py | 25 +
- tools/grit/grit/tool/preprocess_unittest.py | 50 +
- tools/grit/grit/tool/rc2grd.py | 418 +++
- tools/grit/grit/tool/rc2grd_unittest.py | 163 +
- tools/grit/grit/tool/resize.py | 295 ++
- tools/grit/grit/tool/test.py | 24 +
- tools/grit/grit/tool/transl2tc.py | 251 ++
- tools/grit/grit/tool/transl2tc_unittest.py | 133 +
- tools/grit/grit/tool/unit.py | 43 +
- .../grit/tool/update_resource_ids/__init__.py | 305 ++
- .../grit/tool/update_resource_ids/assigner.py | 286 ++
- .../update_resource_ids/assigner_unittest.py | 154 +
- .../grit/tool/update_resource_ids/common.py | 101 +
- .../grit/tool/update_resource_ids/parser.py | 231 ++
- .../grit/tool/update_resource_ids/reader.py | 83 +
- tools/grit/grit/tool/xmb.py | 295 ++
- tools/grit/grit/tool/xmb_unittest.py | 132 +
- tools/grit/grit/util.py | 691 ++++
- tools/grit/grit/util_unittest.py | 118 +
- tools/grit/grit/xtb_reader.py | 140 +
- tools/grit/grit/xtb_reader_unittest.py | 110 +
- tools/grit/grit_info.py | 173 +
- tools/grit/grit_rule.gni | 485 +++
- tools/grit/minify_with_uglify.py | 44 +
- tools/grit/minimize_css.py | 105 +
- tools/grit/minimize_css_unittest.py | 58 +
- tools/grit/pak_util.py | 223 ++
- tools/grit/repack.gni | 189 +
- tools/grit/setup.py | 46 +
- tools/grit/stamp_grit_sources.py | 57 +
- tools/grit/third_party/six/LICENSE | 18 +
- tools/grit/third_party/six/README | 16 +
- tools/grit/third_party/six/README.chromium | 13 +
- tools/grit/third_party/six/__init__.py | 868 +++++
- 226 files changed, 33440 insertions(+)
- create mode 100644 tools/grit/.gitignore
- create mode 100644 tools/grit/BUILD.gn
- create mode 100644 tools/grit/MANIFEST.in
- create mode 100644 tools/grit/OWNERS
- create mode 100644 tools/grit/PRESUBMIT.py
- create mode 100644 tools/grit/README.md
- create mode 100644 tools/grit/grit.py
- create mode 100644 tools/grit/grit/__init__.py
- create mode 100644 tools/grit/grit/clique.py
- create mode 100644 tools/grit/grit/clique_unittest.py
- create mode 100644 tools/grit/grit/constants.py
- create mode 100644 tools/grit/grit/exception.py
- create mode 100644 tools/grit/grit/extern/BogoFP.py
- create mode 100644 tools/grit/grit/extern/FP.py
- create mode 100644 tools/grit/grit/extern/__init__.py
- create mode 100644 tools/grit/grit/extern/tclib.py
- create mode 100644 tools/grit/grit/format/__init__.py
- create mode 100644 tools/grit/grit/format/android_xml.py
- create mode 100644 tools/grit/grit/format/android_xml_unittest.py
- create mode 100644 tools/grit/grit/format/c_format.py
- create mode 100644 tools/grit/grit/format/c_format_unittest.py
- create mode 100644 tools/grit/grit/format/chrome_messages_json.py
- create mode 100644 tools/grit/grit/format/chrome_messages_json_unittest.py
- create mode 100644 tools/grit/grit/format/data_pack.py
- create mode 100644 tools/grit/grit/format/data_pack_unittest.py
- create mode 100644 tools/grit/grit/format/gen_predetermined_ids.py
- create mode 100644 tools/grit/grit/format/gen_predetermined_ids_unittest.py
- create mode 100644 tools/grit/grit/format/gzip_string.py
- create mode 100644 tools/grit/grit/format/gzip_string_unittest.py
- create mode 100644 tools/grit/grit/format/html_inline.py
- create mode 100644 tools/grit/grit/format/html_inline_unittest.py
- create mode 100644 tools/grit/grit/format/minifier.py
- create mode 100644 tools/grit/grit/format/policy_templates_json.py
- create mode 100644 tools/grit/grit/format/policy_templates_json_unittest.py
- create mode 100644 tools/grit/grit/format/rc.py
- create mode 100644 tools/grit/grit/format/rc_header.py
- create mode 100644 tools/grit/grit/format/rc_header_unittest.py
- create mode 100644 tools/grit/grit/format/rc_unittest.py
- create mode 100644 tools/grit/grit/format/resource_map.py
- create mode 100644 tools/grit/grit/format/resource_map_unittest.py
- create mode 100644 tools/grit/grit/gather/__init__.py
- create mode 100644 tools/grit/grit/gather/admin_template.py
- create mode 100644 tools/grit/grit/gather/admin_template_unittest.py
- create mode 100644 tools/grit/grit/gather/chrome_html.py
- create mode 100644 tools/grit/grit/gather/chrome_html_unittest.py
- create mode 100644 tools/grit/grit/gather/chrome_scaled_image.py
- create mode 100644 tools/grit/grit/gather/chrome_scaled_image_unittest.py
- create mode 100644 tools/grit/grit/gather/interface.py
- create mode 100644 tools/grit/grit/gather/json_loader.py
- create mode 100644 tools/grit/grit/gather/policy_json.py
- create mode 100644 tools/grit/grit/gather/policy_json_unittest.py
- create mode 100644 tools/grit/grit/gather/rc.py
- create mode 100644 tools/grit/grit/gather/rc_unittest.py
- create mode 100644 tools/grit/grit/gather/regexp.py
- create mode 100644 tools/grit/grit/gather/skeleton_gatherer.py
- create mode 100644 tools/grit/grit/gather/tr_html.py
- create mode 100644 tools/grit/grit/gather/tr_html_unittest.py
- create mode 100644 tools/grit/grit/gather/txt.py
- create mode 100644 tools/grit/grit/gather/txt_unittest.py
- create mode 100644 tools/grit/grit/grd_reader.py
- create mode 100644 tools/grit/grit/grd_reader_unittest.py
- create mode 100644 tools/grit/grit/grit-todo.xml
- create mode 100644 tools/grit/grit/grit_runner.py
- create mode 100644 tools/grit/grit/grit_runner_unittest.py
- create mode 100644 tools/grit/grit/lazy_re.py
- create mode 100644 tools/grit/grit/lazy_re_unittest.py
- create mode 100644 tools/grit/grit/node/__init__.py
- create mode 100644 tools/grit/grit/node/base.py
- create mode 100644 tools/grit/grit/node/base_unittest.py
- create mode 100644 tools/grit/grit/node/brotli_util.py
- create mode 100644 tools/grit/grit/node/custom/__init__.py
- create mode 100644 tools/grit/grit/node/custom/filename.py
- create mode 100644 tools/grit/grit/node/custom/filename_unittest.py
- create mode 100644 tools/grit/grit/node/empty.py
- create mode 100644 tools/grit/grit/node/include.py
- create mode 100644 tools/grit/grit/node/include_unittest.py
- create mode 100644 tools/grit/grit/node/mapping.py
- create mode 100644 tools/grit/grit/node/message.py
- create mode 100644 tools/grit/grit/node/message_unittest.py
- create mode 100644 tools/grit/grit/node/misc.py
- create mode 100644 tools/grit/grit/node/misc_unittest.py
- create mode 100644 tools/grit/grit/node/mock_brotli.py
- create mode 100644 tools/grit/grit/node/node_io.py
- create mode 100644 tools/grit/grit/node/node_io_unittest.py
- create mode 100644 tools/grit/grit/node/structure.py
- create mode 100644 tools/grit/grit/node/structure_unittest.py
- create mode 100644 tools/grit/grit/node/variant.py
- create mode 100644 tools/grit/grit/pseudo.py
- create mode 100644 tools/grit/grit/pseudo_rtl.py
- create mode 100644 tools/grit/grit/pseudo_unittest.py
- create mode 100644 tools/grit/grit/shortcuts.py
- create mode 100644 tools/grit/grit/shortcuts_unittest.py
- create mode 100644 tools/grit/grit/tclib.py
- create mode 100644 tools/grit/grit/tclib_unittest.py
- create mode 100644 tools/grit/grit/test_suite_all.py
- create mode 100644 tools/grit/grit/testdata/GoogleDesktop.adm
- create mode 100644 tools/grit/grit/testdata/README.txt
- create mode 100644 tools/grit/grit/testdata/about.html
- create mode 100644 tools/grit/grit/testdata/android.xml
- create mode 100644 tools/grit/grit/testdata/bad_browser.html
- create mode 100644 tools/grit/grit/testdata/browser.html
- create mode 100644 tools/grit/grit/testdata/buildinfo.grd
- create mode 100644 tools/grit/grit/testdata/cache_prefix.html
- create mode 100644 tools/grit/grit/testdata/cache_prefix_file.html
- create mode 100644 tools/grit/grit/testdata/chat_result.html
- create mode 100644 tools/grit/grit/testdata/chrome/app/generated_resources.grd
- create mode 100644 tools/grit/grit/testdata/chrome_html.html
- create mode 100644 tools/grit/grit/testdata/default_100_percent/a.png
- create mode 100644 tools/grit/grit/testdata/default_100_percent/b.png
- create mode 100644 tools/grit/grit/testdata/del_footer.html
- create mode 100644 tools/grit/grit/testdata/del_header.html
- create mode 100644 tools/grit/grit/testdata/deleted.html
- create mode 100644 tools/grit/grit/testdata/depfile.grd
- create mode 100644 tools/grit/grit/testdata/details.html
- create mode 100644 tools/grit/grit/testdata/duplicate-name-input.xml
- create mode 100644 tools/grit/grit/testdata/email_result.html
- create mode 100644 tools/grit/grit/testdata/email_thread.html
- create mode 100644 tools/grit/grit/testdata/error.html
- create mode 100644 tools/grit/grit/testdata/explicit_web.html
- create mode 100644 tools/grit/grit/testdata/footer.html
- create mode 100644 tools/grit/grit/testdata/generated_resources_fr.xtb
- create mode 100644 tools/grit/grit/testdata/generated_resources_iw.xtb
- create mode 100644 tools/grit/grit/testdata/generated_resources_no.xtb
- create mode 100644 tools/grit/grit/testdata/grit_part.grdp
- create mode 100644 tools/grit/grit/testdata/header.html
- create mode 100644 tools/grit/grit/testdata/homepage.html
- create mode 100644 tools/grit/grit/testdata/hover.html
- create mode 100644 tools/grit/grit/testdata/include_test.html
- create mode 100644 tools/grit/grit/testdata/included_sample.html
- create mode 100644 tools/grit/grit/testdata/indexing_speed.html
- create mode 100644 tools/grit/grit/testdata/install_prefs.html
- create mode 100644 tools/grit/grit/testdata/install_prefs2.html
- create mode 100644 tools/grit/grit/testdata/klonk-alternate-skeleton.rc
- create mode 100644 tools/grit/grit/testdata/klonk.ico
- create mode 100644 tools/grit/grit/testdata/klonk.rc
- create mode 100644 tools/grit/grit/testdata/ko_oem_enable_bug.html
- create mode 100644 tools/grit/grit/testdata/ko_oem_non_admin_bug.html
- create mode 100644 tools/grit/grit/testdata/mini.html
- create mode 100644 tools/grit/grit/testdata/oem_enable.html
- create mode 100644 tools/grit/grit/testdata/oem_non_admin.html
- create mode 100644 tools/grit/grit/testdata/onebox.html
- create mode 100644 tools/grit/grit/testdata/oneclick.html
- create mode 100644 tools/grit/grit/testdata/password.html
- create mode 100644 tools/grit/grit/testdata/preferences.html
- create mode 100644 tools/grit/grit/testdata/preprocess_test.html
- create mode 100644 tools/grit/grit/testdata/privacy.html
- create mode 100644 tools/grit/grit/testdata/quit_apps.html
- create mode 100644 tools/grit/grit/testdata/recrawl.html
- create mode 100644 tools/grit/grit/testdata/resource_ids
- create mode 100644 tools/grit/grit/testdata/script.html
- create mode 100644 tools/grit/grit/testdata/searchbox.html
- create mode 100644 tools/grit/grit/testdata/sidebar_h.html
- create mode 100644 tools/grit/grit/testdata/sidebar_v.html
- create mode 100644 tools/grit/grit/testdata/simple-input.xml
- create mode 100644 tools/grit/grit/testdata/simple.html
- create mode 100644 tools/grit/grit/testdata/source.rc
- create mode 100644 tools/grit/grit/testdata/special_100_percent/a.png
- create mode 100644 tools/grit/grit/testdata/status.html
- create mode 100644 tools/grit/grit/testdata/structure_variables.html
- create mode 100644 tools/grit/grit/testdata/substitute.grd
- create mode 100644 tools/grit/grit/testdata/substitute.xmb
- create mode 100644 tools/grit/grit/testdata/substitute_no_ids.grd
- create mode 100644 tools/grit/grit/testdata/substitute_tmpl.grd
- create mode 100644 tools/grit/grit/testdata/test_css.css
- create mode 100644 tools/grit/grit/testdata/test_html.html
- create mode 100644 tools/grit/grit/testdata/test_js.js
- create mode 100644 tools/grit/grit/testdata/test_svg.svg
- create mode 100644 tools/grit/grit/testdata/test_text.txt
- create mode 100644 tools/grit/grit/testdata/time_related.html
- create mode 100644 tools/grit/grit/testdata/toolbar_about.html
- create mode 100644 tools/grit/grit/testdata/tools/grit/resource_ids
- create mode 100644 tools/grit/grit/testdata/transl.rc
- create mode 100644 tools/grit/grit/testdata/versions.html
- create mode 100644 tools/grit/grit/testdata/whitelist.txt
- create mode 100644 tools/grit/grit/testdata/whitelist_resources.grd
- create mode 100644 tools/grit/grit/testdata/whitelist_strings.grd
- create mode 100644 tools/grit/grit/tool/__init__.py
- create mode 100644 tools/grit/grit/tool/android2grd.py
- create mode 100644 tools/grit/grit/tool/android2grd_unittest.py
- create mode 100644 tools/grit/grit/tool/build.py
- create mode 100644 tools/grit/grit/tool/build_unittest.py
- create mode 100644 tools/grit/grit/tool/buildinfo.py
- create mode 100644 tools/grit/grit/tool/buildinfo_unittest.py
- create mode 100644 tools/grit/grit/tool/count.py
- create mode 100644 tools/grit/grit/tool/diff_structures.py
- create mode 100644 tools/grit/grit/tool/diff_structures_unittest.py
- create mode 100644 tools/grit/grit/tool/interface.py
- create mode 100644 tools/grit/grit/tool/menu_from_parts.py
- create mode 100644 tools/grit/grit/tool/newgrd.py
- create mode 100644 tools/grit/grit/tool/newgrd_unittest.py
- create mode 100644 tools/grit/grit/tool/postprocess_interface.py
- create mode 100644 tools/grit/grit/tool/postprocess_unittest.py
- create mode 100644 tools/grit/grit/tool/preprocess_interface.py
- create mode 100644 tools/grit/grit/tool/preprocess_unittest.py
- create mode 100644 tools/grit/grit/tool/rc2grd.py
- create mode 100644 tools/grit/grit/tool/rc2grd_unittest.py
- create mode 100644 tools/grit/grit/tool/resize.py
- create mode 100644 tools/grit/grit/tool/test.py
- create mode 100644 tools/grit/grit/tool/transl2tc.py
- create mode 100644 tools/grit/grit/tool/transl2tc_unittest.py
- create mode 100644 tools/grit/grit/tool/unit.py
- create mode 100644 tools/grit/grit/tool/update_resource_ids/__init__.py
- create mode 100644 tools/grit/grit/tool/update_resource_ids/assigner.py
- create mode 100644 tools/grit/grit/tool/update_resource_ids/assigner_unittest.py
- create mode 100644 tools/grit/grit/tool/update_resource_ids/common.py
- create mode 100644 tools/grit/grit/tool/update_resource_ids/parser.py
- create mode 100644 tools/grit/grit/tool/update_resource_ids/reader.py
- create mode 100644 tools/grit/grit/tool/xmb.py
- create mode 100644 tools/grit/grit/tool/xmb_unittest.py
- create mode 100644 tools/grit/grit/util.py
- create mode 100644 tools/grit/grit/util_unittest.py
- create mode 100644 tools/grit/grit/xtb_reader.py
- create mode 100644 tools/grit/grit/xtb_reader_unittest.py
- create mode 100644 tools/grit/grit_info.py
- create mode 100644 tools/grit/grit_rule.gni
- create mode 100644 tools/grit/minify_with_uglify.py
- create mode 100644 tools/grit/minimize_css.py
- create mode 100644 tools/grit/minimize_css_unittest.py
- create mode 100644 tools/grit/pak_util.py
- create mode 100644 tools/grit/repack.gni
- create mode 100644 tools/grit/setup.py
- create mode 100644 tools/grit/stamp_grit_sources.py
- create mode 100644 tools/grit/third_party/six/LICENSE
- create mode 100644 tools/grit/third_party/six/README
- create mode 100644 tools/grit/third_party/six/README.chromium
- create mode 100644 tools/grit/third_party/six/__init__.py
+ tools/generate_stubs/rules.gni | 2 ++
+ 1 file changed, 2 insertions(+)
+ create mode 100644 tools/generate_stubs/rules.gni
-diff --git a/tools/grit/.gitignore b/tools/grit/.gitignore
+diff --git a/tools/generate_stubs/rules.gni b/tools/generate_stubs/rules.gni
new file mode 100644
-index 0000000000..0d20b6487c
+index 0000000000..1d9f36eb72
--- /dev/null
-+++ b/tools/grit/.gitignore
-@@ -0,0 +1 @@
-+*.pyc
-diff --git a/tools/grit/BUILD.gn b/tools/grit/BUILD.gn
-new file mode 100644
-index 0000000000..1cd3c75b55
---- /dev/null
-+++ b/tools/grit/BUILD.gn
-@@ -0,0 +1,48 @@
-+# Copyright 2014 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+# This target creates a stamp file that depends on all the sources in the grit
-+# directory. By depending on this, a target can force itself to be rebuilt if
-+# grit itself changes.
-+
-+import("//build/config/sanitizers/sanitizers.gni")
-+
-+action("grit_sources") {
-+ depfile = "$target_out_dir/grit_sources.d"
-+ script = "stamp_grit_sources.py"
-+
-+ inputs = [ "grit.py" ]
-+
-+ # Note that we can't call this "grit_sources.stamp" because that file is
-+ # implicitly created by GN for script actions.
-+ outputs = [ "$target_out_dir/grit_sources.script.stamp" ]
-+
-+ args = [
-+ rebase_path("//tools/grit", root_build_dir),
-+ rebase_path(outputs[0], root_build_dir),
-+ rebase_path(depfile, root_build_dir),
-+ ]
-+}
-+
-+group("grit_python_unittests") {
-+ testonly = true
-+
-+ data = [
-+ "//testing/scripts/common.py",
-+ "//testing/scripts/run_isolated_script_test.py",
-+ "//testing/xvfb.py",
-+ "//tools/grit/",
-+ "//third_party/catapult/third_party/typ/",
-+ ]
-+}
-+
-+# See https://crbug.com/983200
-+if (is_mac && is_asan) {
-+ create_bundle("brotli_mac_asan_workaround") {
-+ bundle_root_dir = "$target_out_dir/$target_name"
-+ bundle_executable_dir = bundle_root_dir
-+
-+ public_deps = [ "//third_party/brotli:brotli($host_toolchain)" ]
-+ }
-+}
-diff --git a/tools/grit/MANIFEST.in b/tools/grit/MANIFEST.in
-new file mode 100644
-index 0000000000..1cbff42400
---- /dev/null
-+++ b/tools/grit/MANIFEST.in
-@@ -0,0 +1,3 @@
-+exclude grit/test_suite_all.py
-+exclude grit/tool/test.py
-+global-exclude *_unittest.py
-diff --git a/tools/grit/OWNERS b/tools/grit/OWNERS
-new file mode 100644
-index 0000000000..6a8f447b82
---- /dev/null
-+++ b/tools/grit/OWNERS
-@@ -0,0 +1,8 @@
-+agrieve@chromium.org
-+flackr@chromium.org
-+thakis@chromium.org
-+thestig@chromium.org
-+
-+# Admin policy related grit tools.
-+per-file *policy*=file://components/policy/tools/OWNERS
-+per-file *admin_template*=file://components/policy/tools/OWNERS
-diff --git a/tools/grit/PRESUBMIT.py b/tools/grit/PRESUBMIT.py
-new file mode 100644
-index 0000000000..03b7188551
---- /dev/null
-+++ b/tools/grit/PRESUBMIT.py
-@@ -0,0 +1,22 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+"""grit unittests presubmit script.
-+
-+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts for
-+details on the presubmit API built into gcl.
-+"""
-+
-+
-+def RunUnittests(input_api, output_api):
-+ return input_api.canned_checks.RunUnitTests(input_api, output_api,
-+ [input_api.os_path.join('grit', 'test_suite_all.py')])
-+
-+
-+def CheckChangeOnUpload(input_api, output_api):
-+ return RunUnittests(input_api, output_api)
-+
-+
-+def CheckChangeOnCommit(input_api, output_api):
-+ return RunUnittests(input_api, output_api)
-diff --git a/tools/grit/README.md b/tools/grit/README.md
-new file mode 100644
-index 0000000000..b5c3f4b51b
---- /dev/null
-+++ b/tools/grit/README.md
-@@ -0,0 +1,19 @@
-+# GRIT (Google Resource and Internationalization Tool)
-+
-+This is a tool for projects to manage resources and simplify the localization
-+workflow.
-+
-+See the user guide for more details on using this project:
-+https://dev.chromium.org/developers/tools-we-use-in-chromium/grit/grit-users-guide
-+
-+## History
-+
-+This code previously used to live at
-+https://code.google.com/p/grit-i18n/source/checkout which still contains the
-+project's history. https://chromium.googlesource.com/external/grit-i18n/ is
-+a git mirror of the SVN repository that's identical except for the last two
-+commits. The project is now developed in the Chromium project directly.
-+
-+There is a read-only mirror of just this directory at
-+https://chromium.googlesource.com/chromium/src/tools/grit/ if you don't want to
-+check out all of Chromium.
-diff --git a/tools/grit/grit.py b/tools/grit/grit.py
-new file mode 100644
-index 0000000000..abd1ab6449
---- /dev/null
-+++ b/tools/grit/grit.py
-@@ -0,0 +1,31 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Bootstrapping for GRIT.
-+'''
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+
-+import grit.grit_runner
-+
-+sys.path.append(
-+ os.path.join(
-+ os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
-+ 'diagnosis'))
-+try:
-+ import crbug_1001171
-+except ImportError:
-+ crbug_1001171 = None
-+
-+
-+if __name__ == '__main__':
-+ if crbug_1001171:
-+ with crbug_1001171.DumpStateOnLookupError():
-+ sys.exit(grit.grit_runner.Main(sys.argv[1:]))
-+ else:
-+ sys.exit(grit.grit_runner.Main(sys.argv[1:]))
-diff --git a/tools/grit/grit/__init__.py b/tools/grit/grit/__init__.py
-new file mode 100644
-index 0000000000..91ac9ee896
---- /dev/null
-+++ b/tools/grit/grit/__init__.py
-@@ -0,0 +1,19 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Package 'grit'
-+'''
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+
-+
-+_CUR_DIR = os.path.abspath(os.path.dirname(__file__))
-+_GRIT_DIR = os.path.dirname(_CUR_DIR)
-+_THIRD_PARTY_DIR = os.path.join(_GRIT_DIR, 'third_party')
-+
-+if _THIRD_PARTY_DIR not in sys.path:
-+ sys.path.insert(0, _THIRD_PARTY_DIR)
-diff --git a/tools/grit/grit/clique.py b/tools/grit/grit/clique.py
-new file mode 100644
-index 0000000000..e7be3ec164
---- /dev/null
-+++ b/tools/grit/grit/clique.py
-@@ -0,0 +1,491 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Collections of messages and their translations, called cliques. Also
-+collections of cliques (uber-cliques).
-+'''
-+
-+from __future__ import print_function
-+
-+import re
-+
-+import six
-+
-+from grit import constants
-+from grit import exception
-+from grit import lazy_re
-+from grit import pseudo
-+from grit import pseudo_rtl
-+from grit import tclib
-+
-+
-+class UberClique(object):
-+ '''A factory (NOT a singleton factory) for making cliques. It has several
-+ methods for working with the cliques created using the factory.
-+ '''
-+
-+ def __init__(self):
-+ # A map from message ID to list of cliques whose source messages have
-+ # that ID. This will contain all cliques created using this factory.
-+ # Different messages can have the same ID because they have the
-+ # same translateable portion and placeholder names, but occur in different
-+ # places in the resource tree.
-+ #
-+ # Each list of cliques is kept sorted by description, to achieve
-+ # stable results from the BestClique method, see below.
-+ self.cliques_ = {}
-+
-+ # A map of clique IDs to list of languages to indicate translations where we
-+ # fell back to English.
-+ self.fallback_translations_ = {}
-+
-+ # A map of clique IDs to list of languages to indicate missing translations.
-+ self.missing_translations_ = {}
-+
-+ def _AddMissingTranslation(self, lang, clique, is_error):
-+ tl = self.fallback_translations_
-+ if is_error:
-+ tl = self.missing_translations_
-+ id = clique.GetId()
-+ if id not in tl:
-+ tl[id] = {}
-+ if lang not in tl[id]:
-+ tl[id][lang] = 1
-+
-+ def HasMissingTranslations(self):
-+ return len(self.missing_translations_) > 0
-+
-+ def MissingTranslationsReport(self):
-+ '''Returns a string suitable for printing to report missing
-+ and fallback translations to the user.
-+ '''
-+ def ReportTranslation(clique, langs):
-+ text = clique.GetMessage().GetPresentableContent()
-+ # The text 'error' (usually 'Error:' but we are conservative)
-+ # can trigger some build environments (Visual Studio, we're
-+ # looking at you) to consider invocation of grit to have failed,
-+ # so we make sure never to output that word.
-+ extract = re.sub(r'(?i)error', 'REDACTED', text[0:40])[0:40]
-+ ellipsis = ''
-+ if len(text) > 40:
-+ ellipsis = '...'
-+ langs_extract = langs[0:6]
-+ describe_langs = ','.join(langs_extract)
-+ if len(langs) > 6:
-+ describe_langs += " and %d more" % (len(langs) - 6)
-+ return " %s \"%s%s\" %s" % (clique.GetId(), extract, ellipsis,
-+ describe_langs)
-+ lines = []
-+ if len(self.fallback_translations_):
-+ lines.append(
-+ "WARNING: Fell back to English for the following translations:")
-+ for (id, langs) in self.fallback_translations_.items():
-+ lines.append(
-+ ReportTranslation(self.cliques_[id][0], list(langs.keys())))
-+ if len(self.missing_translations_):
-+ lines.append("ERROR: The following translations are MISSING:")
-+ for (id, langs) in self.missing_translations_.items():
-+ lines.append(
-+ ReportTranslation(self.cliques_[id][0], list(langs.keys())))
-+ return '\n'.join(lines)
-+
-+ def MakeClique(self, message, translateable=True):
-+ '''Create a new clique initialized with a message.
-+
-+ Args:
-+ message: tclib.Message()
-+ translateable: True | False
-+ '''
-+ clique = MessageClique(self, message, translateable)
-+
-+ # Enable others to find this clique by its message ID
-+ if message.GetId() in self.cliques_:
-+ presentable_text = clique.GetMessage().GetPresentableContent()
-+ if not message.HasAssignedId():
-+ for c in self.cliques_[message.GetId()]:
-+ assert c.GetMessage().GetPresentableContent() == presentable_text
-+ self.cliques_[message.GetId()].append(clique)
-+ # We need to keep each list of cliques sorted by description, to
-+ # achieve stable results from the BestClique method, see below.
-+ self.cliques_[message.GetId()].sort(
-+ key=lambda c:c.GetMessage().GetDescription())
-+ else:
-+ self.cliques_[message.GetId()] = [clique]
-+
-+ return clique
-+
-+ def FindCliqueAndAddTranslation(self, translation, language):
-+ '''Adds the specified translation to the clique with the source message
-+ it is a translation of.
-+
-+ Args:
-+ translation: tclib.Translation()
-+ language: 'en' | 'fr' ...
-+
-+ Return:
-+ True if the source message was found, otherwise false.
-+ '''
-+ if translation.GetId() in self.cliques_:
-+ for clique in self.cliques_[translation.GetId()]:
-+ clique.AddTranslation(translation, language)
-+ return True
-+ else:
-+ return False
-+
-+ def BestClique(self, id):
-+ '''Returns the "best" clique from a list of cliques. All the cliques
-+ must have the same ID. The "best" clique is chosen in the following
-+ order of preference:
-+ - The first clique that has a non-ID-based description.
-+ - If no such clique found, the first clique with an ID-based description.
-+ - Otherwise the first clique.
-+
-+ This method is stable in terms of always returning a clique with
-+ an identical description (on different runs of GRIT on the same
-+ data) because self.cliques_ is sorted by description.
-+ '''
-+ clique_list = self.cliques_[id]
-+ clique_with_id = None
-+ clique_default = None
-+ for clique in clique_list:
-+ if not clique_default:
-+ clique_default = clique
-+
-+ description = clique.GetMessage().GetDescription()
-+ if description and len(description) > 0:
-+ if not description.startswith('ID:'):
-+ # this is the preferred case so we exit right away
-+ return clique
-+ elif not clique_with_id:
-+ clique_with_id = clique
-+ if clique_with_id:
-+ return clique_with_id
-+ else:
-+ return clique_default
-+
-+ def BestCliquePerId(self):
-+ '''Iterates over the list of all cliques and returns the best clique for
-+ each ID. This will be the first clique with a source message that has a
-+ non-empty description, or an arbitrary clique if none of them has a
-+ description.
-+ '''
-+ for id in self.cliques_:
-+ yield self.BestClique(id)
-+
-+ def BestCliqueByOriginalText(self, text, meaning):
-+ '''Finds the "best" (as in BestClique()) clique that has original text
-+ 'text' and meaning 'meaning'. Returns None if there is no such clique.
-+ '''
-+ # If needed, this can be optimized by maintaining a map of
-+ # fingerprints of original text+meaning to cliques.
-+ for c in self.BestCliquePerId():
-+ msg = c.GetMessage()
-+ if msg.GetRealContent() == text and msg.GetMeaning() == meaning:
-+ return msg
-+ return None
-+
-+ def AllMessageIds(self):
-+ '''Returns a list of all defined message IDs.
-+ '''
-+ return list(self.cliques_.keys())
-+
-+ def AllCliques(self):
-+ '''Iterates over all cliques. Note that this can return multiple cliques
-+ with the same ID.
-+ '''
-+ for cliques in self.cliques_.values():
-+ for c in cliques:
-+ yield c
-+
-+ def GenerateXtbParserCallback(self, lang, debug=False):
-+ '''Creates a callback function as required by grit.xtb_reader.Parse().
-+ This callback will create Translation objects for each message from
-+ the XTB that exists in this uberclique, and add them as translations for
-+ the relevant cliques. The callback will add translations to the language
-+ specified by 'lang'
-+
-+ Args:
-+ lang: 'fr'
-+ debug: True | False
-+ '''
-+ def Callback(id, structure):
-+ if id not in self.cliques_:
-+ if debug:
-+ print("Ignoring translation #%s" % id)
-+ return
-+
-+ if debug:
-+ print("Adding translation #%s" % id)
-+
-+ # We fetch placeholder information from the original message (the XTB file
-+ # only contains placeholder names).
-+ original_msg = self.BestClique(id).GetMessage()
-+
-+ translation = tclib.Translation(id=id)
-+ for is_ph,text in structure:
-+ if not is_ph:
-+ translation.AppendText(text)
-+ else:
-+ found_placeholder = False
-+ for ph in original_msg.GetPlaceholders():
-+ if ph.GetPresentation() == text:
-+ translation.AppendPlaceholder(tclib.Placeholder(
-+ ph.GetPresentation(), ph.GetOriginal(), ph.GetExample()))
-+ found_placeholder = True
-+ break
-+ if not found_placeholder:
-+ raise exception.MismatchingPlaceholders(
-+ 'Translation for message ID %s had <ph name="%s"/>, no match\n'
-+ 'in original message' % (id, text))
-+ self.FindCliqueAndAddTranslation(translation, lang)
-+ return Callback
-+
-+
-+class CustomType(object):
-+ '''A base class you should implement if you wish to specify a custom type
-+ for a message clique (i.e. custom validation and optional modification of
-+ translations).'''
-+
-+ def Validate(self, message):
-+ '''Returns true if the message (a tclib.Message object) is valid,
-+ otherwise false.
-+ '''
-+ raise NotImplementedError()
-+
-+ def ValidateAndModify(self, lang, translation):
-+ '''Returns true if the translation (a tclib.Translation object) is valid,
-+ otherwise false. The language is also passed in. This method may modify
-+ the translation that is passed in, if it so wishes.
-+ '''
-+ raise NotImplementedError()
-+
-+ def ModifyTextPart(self, lang, text):
-+ '''If you call ModifyEachTextPart, it will turn around and call this method
-+ for each text part of the translation. You should return the modified
-+ version of the text, or just the original text to not change anything.
-+ '''
-+ raise NotImplementedError()
-+
-+ def ModifyEachTextPart(self, lang, translation):
-+ '''Call this to easily modify one or more of the textual parts of a
-+ translation. It will call ModifyTextPart for each part of the
-+ translation.
-+ '''
-+ contents = translation.GetContent()
-+ for ix in range(len(contents)):
-+ if (isinstance(contents[ix], six.string_types)):
-+ contents[ix] = self.ModifyTextPart(lang, contents[ix])
-+
-+
-+class OneOffCustomType(CustomType):
-+ '''A very simple custom type that performs the validation expressed by
-+ the input expression on all languages including the source language.
-+ The expression can access the variables 'lang', 'msg' and 'text()' where
-+ 'lang' is the language of 'msg', 'msg' is the message or translation being
-+ validated and 'text()' returns the real contents of 'msg' (for shorthand).
-+ '''
-+ def __init__(self, expression):
-+ self.expr = expression
-+ def Validate(self, message):
-+ return self.ValidateAndModify(MessageClique.source_language, message)
-+ def ValidateAndModify(self, lang, msg):
-+ def text():
-+ return msg.GetRealContent()
-+ return eval(self.expr, {},
-+ {'lang' : lang,
-+ 'text' : text,
-+ 'msg' : msg,
-+ })
-+
-+
-+class MessageClique(object):
-+ '''A message along with all of its translations. Also code to bring
-+ translations together with their original message.'''
-+
-+ # change this to the language code of Messages you add to cliques_.
-+ # TODO(joi) Actually change this based on the <grit> node's source language
-+ source_language = 'en'
-+
-+ # A constant translation we use when asked for a translation into the
-+ # special language constants.CONSTANT_LANGUAGE.
-+ CONSTANT_TRANSLATION = tclib.Translation(text='TTTTTT')
-+
-+ # A pattern to match messages that are empty or whitespace only.
-+ WHITESPACE_MESSAGE = lazy_re.compile(r'^\s*$')
-+
-+ def __init__(self, uber_clique, message, translateable=True,
-+ custom_type=None):
-+ '''Create a new clique initialized with just a message.
-+
-+ Note that messages with a body comprised only of whitespace will implicitly
-+ be marked non-translatable.
-+
-+ Args:
-+ uber_clique: Our uber-clique (collection of cliques)
-+ message: tclib.Message()
-+ translateable: True | False
-+ custom_type: instance of clique.CustomType interface
-+ '''
-+ # Our parent
-+ self.uber_clique = uber_clique
-+ # If not translateable, we only store the original message.
-+ self.translateable = translateable
-+
-+ # We implicitly mark messages that have a whitespace-only body as
-+ # non-translateable.
-+ if MessageClique.WHITESPACE_MESSAGE.match(message.GetRealContent()):
-+ self.translateable = False
-+
-+ # A mapping of language identifiers to tclib.BaseMessage and its
-+ # subclasses (i.e. tclib.Message and tclib.Translation).
-+ self.clique = { MessageClique.source_language : message }
-+ # A list of the "shortcut groups" this clique is
-+ # part of. Within any given shortcut group, no shortcut key (e.g. &J)
-+ # must appear more than once in each language for all cliques that
-+ # belong to the group.
-+ self.shortcut_groups = []
-+ # An instance of the CustomType interface, or None. If this is set, it will
-+ # be used to validate the original message and translations thereof, and
-+ # will also get a chance to modify translations of the message.
-+ self.SetCustomType(custom_type)
-+
-+ def GetMessage(self):
-+ '''Retrieves the tclib.Message that is the source for this clique.'''
-+ return self.clique[MessageClique.source_language]
-+
-+ def GetId(self):
-+ '''Retrieves the message ID of the messages in this clique.'''
-+ return self.GetMessage().GetId()
-+
-+ def IsTranslateable(self):
-+ return self.translateable
-+
-+ def AddToShortcutGroup(self, group):
-+ self.shortcut_groups.append(group)
-+
-+ def SetCustomType(self, custom_type):
-+ '''Makes this clique use custom_type for validating messages and
-+ translations, and optionally modifying translations.
-+ '''
-+ self.custom_type = custom_type
-+ if custom_type and not custom_type.Validate(self.GetMessage()):
-+ raise exception.InvalidMessage(self.GetMessage().GetRealContent())
-+
-+ def MessageForLanguage(self, lang, pseudo_if_no_match=True,
-+ fallback_to_english=False):
-+ '''Returns the message/translation for the specified language, providing
-+ a pseudotranslation if there is no available translation and a pseudo-
-+ translation is requested.
-+
-+ The translation of any message whatsoever in the special language
-+ 'x_constant' is the message "TTTTTT".
-+
-+ Args:
-+ lang: 'en'
-+ pseudo_if_no_match: True
-+ fallback_to_english: False
-+
-+ Return:
-+ tclib.BaseMessage
-+ '''
-+ if not self.translateable:
-+ return self.GetMessage()
-+
-+ if lang == constants.CONSTANT_LANGUAGE:
-+ return self.CONSTANT_TRANSLATION
-+
-+ for msglang in self.clique:
-+ if lang == msglang:
-+ return self.clique[msglang]
-+
-+ if lang == constants.FAKE_BIDI:
-+ return pseudo_rtl.PseudoRTLMessage(self.GetMessage())
-+
-+ if fallback_to_english:
-+ self.uber_clique._AddMissingTranslation(lang, self, is_error=False)
-+ return self.GetMessage()
-+
-+ # If we're not supposed to generate pseudotranslations, we add an error
-+ # report to a list of errors, then fail at a higher level, so that we
-+ # get a list of all messages that are missing translations.
-+ if not pseudo_if_no_match:
-+ self.uber_clique._AddMissingTranslation(lang, self, is_error=True)
-+
-+ return pseudo.PseudoMessage(self.GetMessage())
-+
-+ def AllMessagesThatMatch(self, lang_re, include_pseudo = True):
-+ '''Returns a map of all messages that match 'lang', including the pseudo
-+ translation if requested.
-+
-+ Args:
-+ lang_re: re.compile(r'fr|en')
-+ include_pseudo: True
-+
-+ Return:
-+ { 'en' : tclib.Message,
-+ 'fr' : tclib.Translation,
-+ pseudo.PSEUDO_LANG : tclib.Translation }
-+ '''
-+ if not self.translateable:
-+ return [self.GetMessage()]
-+
-+ matches = {}
-+ for msglang in self.clique:
-+ if lang_re.match(msglang):
-+ matches[msglang] = self.clique[msglang]
-+
-+ if include_pseudo:
-+ matches[pseudo.PSEUDO_LANG] = pseudo.PseudoMessage(self.GetMessage())
-+
-+ return matches
-+
-+ def AddTranslation(self, translation, language):
-+ '''Add a translation to this clique. The translation must have the same
-+ ID as the message that is the source for this clique.
-+
-+ If this clique is not translateable, the function just returns.
-+
-+ Args:
-+ translation: tclib.Translation()
-+ language: 'en'
-+
-+ Throws:
-+ grit.exception.InvalidTranslation if the translation you're trying to add
-+ doesn't have the same message ID as the source message of this clique.
-+ '''
-+ if not self.translateable:
-+ return
-+ if translation.GetId() != self.GetId():
-+ raise exception.InvalidTranslation(
-+ 'Msg ID %s, transl ID %s' % (self.GetId(), translation.GetId()))
-+
-+ assert not language in self.clique
-+
-+ # Because two messages can differ in the original content of their
-+ # placeholders yet share the same ID (because they are otherwise the
-+ # same), the translation we are getting may have different original
-+ # content for placeholders than our message, yet it is still the right
-+ # translation for our message (because it is for the same ID). We must
-+ # therefore fetch the original content of placeholders from our original
-+ # English message.
-+ #
-+ # See grit.clique_unittest.MessageCliqueUnittest.testSemiIdenticalCliques
-+ # for a concrete explanation of why this is necessary.
-+
-+ original = self.MessageForLanguage(self.source_language, False)
-+ if len(original.GetPlaceholders()) != len(translation.GetPlaceholders()):
-+ print("ERROR: '%s' translation of message id %s does not match" %
-+ (language, translation.GetId()))
-+ assert False
-+
-+ transl_msg = tclib.Translation(id=self.GetId(),
-+ text=translation.GetPresentableContent(),
-+ placeholders=original.GetPlaceholders())
-+
-+ if (self.custom_type and
-+ not self.custom_type.ValidateAndModify(language, transl_msg)):
-+ print("WARNING: %s translation failed validation: %s" %
-+ (language, transl_msg.GetId()))
-+
-+ self.clique[language] = transl_msg
-diff --git a/tools/grit/grit/clique_unittest.py b/tools/grit/grit/clique_unittest.py
-new file mode 100644
-index 0000000000..7d2d7318ba
---- /dev/null
-+++ b/tools/grit/grit/clique_unittest.py
-@@ -0,0 +1,265 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for grit.clique'''
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
-+
-+import re
-+import unittest
-+
-+from six import StringIO
-+
-+from grit import clique
-+from grit import exception
-+from grit import pseudo
-+from grit import tclib
-+from grit import grd_reader
-+from grit import util
-+
-+class MessageCliqueUnittest(unittest.TestCase):
-+ def testClique(self):
-+ factory = clique.UberClique()
-+ msg = tclib.Message(text='Hello USERNAME, how are you?',
-+ placeholders=[
-+ tclib.Placeholder('USERNAME', '%s', 'Joi')])
-+ c = factory.MakeClique(msg)
-+
-+ self.failUnless(c.GetMessage() == msg)
-+ self.failUnless(c.GetId() == msg.GetId())
-+
-+ msg_fr = tclib.Translation(text='Bonjour USERNAME, comment ca va?',
-+ id=msg.GetId(), placeholders=[
-+ tclib.Placeholder('USERNAME', '%s', 'Joi')])
-+ msg_de = tclib.Translation(text='Guten tag USERNAME, wie geht es dir?',
-+ id=msg.GetId(), placeholders=[
-+ tclib.Placeholder('USERNAME', '%s', 'Joi')])
-+
-+ c.AddTranslation(msg_fr, 'fr')
-+ factory.FindCliqueAndAddTranslation(msg_de, 'de')
-+
-+ # sort() sorts lists in-place and does not return them
-+ for lang in ('en', 'fr', 'de'):
-+ self.failUnless(lang in c.clique)
-+
-+ self.failUnless(c.MessageForLanguage('fr').GetRealContent() ==
-+ msg_fr.GetRealContent())
-+
-+ try:
-+ c.MessageForLanguage('zh-CN', False)
-+ self.fail('Should have gotten exception')
-+ except:
-+ pass
-+
-+ self.failUnless(c.MessageForLanguage('zh-CN', True) != None)
-+
-+ rex = re.compile('fr|de|bingo')
-+ self.failUnless(len(c.AllMessagesThatMatch(rex, False)) == 2)
-+ self.failUnless(
-+ c.AllMessagesThatMatch(rex, True)[pseudo.PSEUDO_LANG] is not None)
-+
-+ def testBestClique(self):
-+ factory = clique.UberClique()
-+ factory.MakeClique(tclib.Message(text='Alfur', description='alfaholl'))
-+ factory.MakeClique(tclib.Message(text='Alfur', description=''))
-+ factory.MakeClique(tclib.Message(text='Vaettur', description=''))
-+ factory.MakeClique(tclib.Message(text='Vaettur', description=''))
-+ factory.MakeClique(tclib.Message(text='Troll', description=''))
-+ factory.MakeClique(tclib.Message(text='Gryla', description='ID: IDS_GRYLA'))
-+ factory.MakeClique(tclib.Message(text='Gryla', description='vondakerling'))
-+ factory.MakeClique(tclib.Message(text='Leppaludi', description='ID: IDS_LL'))
-+ factory.MakeClique(tclib.Message(text='Leppaludi', description=''))
-+
-+ count_best_cliques = 0
-+ for c in factory.BestCliquePerId():
-+ count_best_cliques += 1
-+ msg = c.GetMessage()
-+ text = msg.GetRealContent()
-+ description = msg.GetDescription()
-+ if text == 'Alfur':
-+ self.failUnless(description == 'alfaholl')
-+ elif text == 'Gryla':
-+ self.failUnless(description == 'vondakerling')
-+ elif text == 'Leppaludi':
-+ self.failUnless(description == 'ID: IDS_LL')
-+ self.failUnless(count_best_cliques == 5)
-+
-+ def testAllInUberClique(self):
-+ resources = grd_reader.Parse(
-+ StringIO(u'''<?xml version="1.0" encoding="UTF-8"?>
-+<grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
-+ <release seq="3">
-+ <messages>
-+ <message name="IDS_GREETING" desc="Printed to greet the currently logged in user">
-+ Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, how are you doing today?
-+ </message>
-+ </messages>
-+ <structures>
-+ <structure type="dialog" name="IDD_ABOUTBOX" encoding="utf-16" file="grit/testdata/klonk.rc" />
-+ <structure type="tr_html" name="ID_HTML" file="grit/testdata/simple.html" />
-+ </structures>
-+ </release>
-+</grit>'''), util.PathFromRoot('.'))
-+ resources.SetOutputLanguage('en')
-+ resources.RunGatherers()
-+ content_list = []
-+ for clique_list in resources.UberClique().cliques_.values():
-+ for clique in clique_list:
-+ content_list.append(clique.GetMessage().GetRealContent())
-+ self.failUnless('Hello %s, how are you doing today?' in content_list)
-+ self.failUnless('Jack "Black" Daniels' in content_list)
-+ self.failUnless('Hello!' in content_list)
-+
-+ def testCorrectExceptionIfWrongEncodingOnResourceFile(self):
-+ '''This doesn't really belong in this unittest file, but what the heck.'''
-+ resources = grd_reader.Parse(
-+ StringIO(u'''<?xml version="1.0" encoding="UTF-8"?>
-+<grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
-+ <release seq="3">
-+ <structures>
-+ <structure type="dialog" name="IDD_ABOUTBOX" file="grit/testdata/klonk.rc" />
-+ </structures>
-+ </release>
-+</grit>'''), util.PathFromRoot('.'))
-+ self.assertRaises(exception.SectionNotFound, resources.RunGatherers)
-+
-+ def testSemiIdenticalCliques(self):
-+ messages = [
-+ tclib.Message(text='Hello USERNAME',
-+ placeholders=[tclib.Placeholder('USERNAME', '$1', 'Joi')]),
-+ tclib.Message(text='Hello USERNAME',
-+ placeholders=[tclib.Placeholder('USERNAME', '%s', 'Joi')]),
-+ ]
-+ self.failUnless(messages[0].GetId() == messages[1].GetId())
-+
-+ # Both of the above would share a translation.
-+ translation = tclib.Translation(id=messages[0].GetId(),
-+ text='Bonjour USERNAME',
-+ placeholders=[tclib.Placeholder(
-+ 'USERNAME', '$1', 'Joi')])
-+
-+ factory = clique.UberClique()
-+ cliques = [factory.MakeClique(msg) for msg in messages]
-+
-+ for clq in cliques:
-+ clq.AddTranslation(translation, 'fr')
-+
-+ self.failUnless(cliques[0].MessageForLanguage('fr').GetRealContent() ==
-+ 'Bonjour $1')
-+ self.failUnless(cliques[1].MessageForLanguage('fr').GetRealContent() ==
-+ 'Bonjour %s')
-+
-+ def testMissingTranslations(self):
-+ messages = [ tclib.Message(text='Hello'), tclib.Message(text='Goodbye') ]
-+ factory = clique.UberClique()
-+ cliques = [factory.MakeClique(msg) for msg in messages]
-+
-+ cliques[1].MessageForLanguage('fr', False, True)
-+
-+ self.failUnless(not factory.HasMissingTranslations())
-+
-+ cliques[0].MessageForLanguage('de', False, False)
-+
-+ self.failUnless(factory.HasMissingTranslations())
-+
-+ report = factory.MissingTranslationsReport()
-+ self.failUnless(report.count('WARNING') == 1)
-+ self.failUnless(report.count('8053599568341804890 "Goodbye" fr') == 1)
-+ self.failUnless(report.count('ERROR') == 1)
-+ self.failUnless(report.count('800120468867715734 "Hello" de') == 1)
-+
-+ def testCustomTypes(self):
-+ factory = clique.UberClique()
-+ message = tclib.Message(text='Bingo bongo')
-+ c = factory.MakeClique(message)
-+ try:
-+ c.SetCustomType(DummyCustomType())
-+ self.fail()
-+ except:
-+ pass # expected case - 'Bingo bongo' does not start with 'jjj'
-+
-+ message = tclib.Message(text='jjjBingo bongo')
-+ c = factory.MakeClique(message)
-+ c.SetCustomType(util.NewClassInstance(
-+ 'grit.clique_unittest.DummyCustomType', clique.CustomType))
-+ translation = tclib.Translation(id=message.GetId(), text='Bilingo bolongo')
-+ c.AddTranslation(translation, 'fr')
-+ self.failUnless(c.MessageForLanguage('fr').GetRealContent().startswith('jjj'))
-+
-+ def testWhitespaceMessagesAreNontranslateable(self):
-+ factory = clique.UberClique()
-+
-+ message = tclib.Message(text=' \t')
-+ c = factory.MakeClique(message, translateable=True)
-+ self.failIf(c.IsTranslateable())
-+
-+ message = tclib.Message(text='\n \n ')
-+ c = factory.MakeClique(message, translateable=True)
-+ self.failIf(c.IsTranslateable())
-+
-+ message = tclib.Message(text='\n hello')
-+ c = factory.MakeClique(message, translateable=True)
-+ self.failUnless(c.IsTranslateable())
-+
-+ def testEachCliqueKeptSorted(self):
-+ factory = clique.UberClique()
-+ msg_a = tclib.Message(text='hello', description='a')
-+ msg_b = tclib.Message(text='hello', description='b')
-+ msg_c = tclib.Message(text='hello', description='c')
-+ # Insert out of order
-+ clique_b = factory.MakeClique(msg_b, translateable=True)
-+ clique_a = factory.MakeClique(msg_a, translateable=True)
-+ clique_c = factory.MakeClique(msg_c, translateable=True)
-+ clique_list = factory.cliques_[clique_a.GetId()]
-+ self.failUnless(len(clique_list) == 3)
-+ self.failUnless(clique_list[0] == clique_a)
-+ self.failUnless(clique_list[1] == clique_b)
-+ self.failUnless(clique_list[2] == clique_c)
-+
-+ def testBestCliqueSortIsStable(self):
-+ factory = clique.UberClique()
-+ text = 'hello'
-+ msg_no_description = tclib.Message(text=text)
-+ msg_id_description_a = tclib.Message(text=text, description='ID: a')
-+ msg_id_description_b = tclib.Message(text=text, description='ID: b')
-+ msg_description_x = tclib.Message(text=text, description='x')
-+ msg_description_y = tclib.Message(text=text, description='y')
-+ clique_id = msg_no_description.GetId()
-+
-+ # Insert in an order that tests all outcomes.
-+ clique_no_description = factory.MakeClique(msg_no_description,
-+ translateable=True)
-+ self.failUnless(factory.BestClique(clique_id) == clique_no_description)
-+ clique_id_description_b = factory.MakeClique(msg_id_description_b,
-+ translateable=True)
-+ self.failUnless(factory.BestClique(clique_id) == clique_id_description_b)
-+ clique_id_description_a = factory.MakeClique(msg_id_description_a,
-+ translateable=True)
-+ self.failUnless(factory.BestClique(clique_id) == clique_id_description_a)
-+ clique_description_y = factory.MakeClique(msg_description_y,
-+ translateable=True)
-+ self.failUnless(factory.BestClique(clique_id) == clique_description_y)
-+ clique_description_x = factory.MakeClique(msg_description_x,
-+ translateable=True)
-+ self.failUnless(factory.BestClique(clique_id) == clique_description_x)
-+
-+
-+class DummyCustomType(clique.CustomType):
-+ def Validate(self, message):
-+ return message.GetRealContent().startswith('jjj')
-+ def ValidateAndModify(self, lang, translation):
-+ is_ok = self.Validate(translation)
-+ self.ModifyEachTextPart(lang, translation)
-+ def ModifyTextPart(self, lang, text):
-+ return 'jjj%s' % text
-+
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/constants.py b/tools/grit/grit/constants.py
-new file mode 100644
-index 0000000000..8229c94b09
---- /dev/null
-+++ b/tools/grit/grit/constants.py
-@@ -0,0 +1,23 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Constant definitions for GRIT.
-+'''
-+
-+from __future__ import print_function
-+
-+# This is the Icelandic noun meaning "grit" and is used to check that our
-+# input files are in the correct encoding. The middle character gets encoded
-+# as two bytes in UTF-8, so this is sufficient to detect incorrect encoding.
-+ENCODING_CHECK = u'm\u00f6l'
-+
-+# A special language, translations into which are always "TTTTTT".
-+CONSTANT_LANGUAGE = 'x_constant'
-+
-+FAKE_BIDI = 'fake-bidi'
-+
-+# Magic number added to the header of resources brotli compressed by grit. Used
-+# to easily identify resources as being brotli compressed. See
-+# ui/base/resource/resource_bundle.h for decompression usage.
-+BROTLI_CONST = b'\x1e\x9b'
-diff --git a/tools/grit/grit/exception.py b/tools/grit/grit/exception.py
-new file mode 100644
-index 0000000000..2a363fb077
---- /dev/null
-+++ b/tools/grit/grit/exception.py
-@@ -0,0 +1,139 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Exception types for GRIT.
-+'''
-+
-+from __future__ import print_function
-+
-+class Base(Exception):
-+ '''A base exception that uses the class's docstring in addition to any
-+ user-provided message as the body of the Base.
-+ '''
-+ def __init__(self, msg=''):
-+ if len(msg):
-+ if self.__doc__:
-+ msg = self.__doc__ + ': ' + msg
-+ else:
-+ msg = self.__doc__
-+ super(Base, self).__init__(msg)
-+
-+
-+class Parsing(Base):
-+ '''An error occurred parsing a GRD or XTB file.'''
-+ pass
-+
-+
-+class UnknownElement(Parsing):
-+ '''An unknown node type was encountered.'''
-+ pass
-+
-+
-+class MissingElement(Parsing):
-+ '''An expected element was missing.'''
-+ pass
-+
-+
-+class UnexpectedChild(Parsing):
-+ '''An unexpected child element was encountered (on a leaf node).'''
-+ pass
-+
-+
-+class UnexpectedAttribute(Parsing):
-+ '''The attribute was not expected'''
-+ pass
-+
-+
-+class UnexpectedContent(Parsing):
-+ '''This element should not have content'''
-+ pass
-+
-+class MissingMandatoryAttribute(Parsing):
-+ '''This element is missing a mandatory attribute'''
-+ pass
-+
-+
-+class MutuallyExclusiveMandatoryAttribute(Parsing):
-+ '''This element has 2 mutually exclusive mandatory attributes'''
-+ pass
-+
-+
-+class DuplicateKey(Parsing):
-+ '''A duplicate key attribute was found.'''
-+ pass
-+
-+
-+class TooManyExamples(Parsing):
-+ '''Only one <ex> element is allowed for each <ph> element.'''
-+ pass
-+
-+
-+class FileNotFound(Parsing):
-+ '''The resource file was not found.'''
-+ pass
-+
-+
-+class InvalidMessage(Base):
-+ '''The specified message failed validation.'''
-+ pass
-+
-+
-+class InvalidTranslation(Base):
-+ '''Attempt to add an invalid translation to a clique.'''
-+ pass
-+
-+
-+class NoSuchTranslation(Base):
-+ '''Requested translation not available'''
-+ pass
-+
-+
-+class NotReady(Base):
-+ '''Attempt to use an object before it is ready, or attempt to translate \
-+an empty document.'''
-+ pass
-+
-+
-+class MismatchingPlaceholders(Base):
-+ '''Placeholders do not match.'''
-+ pass
-+
-+
-+class InvalidPlaceholderName(Base):
-+ '''Placeholder name can only contain A-Z, a-z, 0-9 and underscore.'''
-+ pass
-+
-+
-+class BlockTagInTranslateableChunk(Base):
-+ '''A block tag was encountered where it wasn't expected.'''
-+ pass
-+
-+
-+class SectionNotFound(Base):
-+ '''The section you requested was not found in the RC file. Make \
-+sure the section ID is correct (matches the section's ID in the RC file). \
-+Also note that you may need to specify the RC file's encoding (using the \
-+encoding="" attribute) if it is not in the default Windows-1252 encoding. \
-+'''
-+ pass
-+
-+
-+class IdRangeOverlap(Base):
-+ '''ID range overlap.'''
-+ pass
-+
-+
-+class ReservedHeaderCollision(Base):
-+ '''Resource included with first 3 bytes matching reserved header.'''
-+ pass
-+
-+
-+class PlaceholderNotInsidePhNode(Base):
-+ '''Placeholder formatters should be inside <ph> element.'''
-+ pass
-+
-+
-+class InvalidCharactersInsidePhNode(Base):
-+ '''Invalid characters found inside <ph> element.'''
-+ pass
-diff --git a/tools/grit/grit/extern/BogoFP.py b/tools/grit/grit/extern/BogoFP.py
-new file mode 100644
-index 0000000000..fc90145833
---- /dev/null
-+++ b/tools/grit/grit/extern/BogoFP.py
-@@ -0,0 +1,22 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+"""Bogus fingerprint implementation, do not use for production,
-+provided only as an example.
-+
-+Usage:
-+ grit.py -h grit.extern.BogoFP xmb /tmp/foo
-+"""
-+
-+from __future__ import print_function
-+
-+import grit.extern.FP
-+
-+
-+def UnsignedFingerPrint(str, encoding='utf-8'):
-+ """Generate a fingerprint not intended for production from str (it
-+ reduces the precision of the production fingerprint by one bit).
-+ """
-+ return (0xFFFFF7FFFFFFFFFF &
-+ grit.extern.FP._UnsignedFingerPrintImpl(str, encoding))
-diff --git a/tools/grit/grit/extern/FP.py b/tools/grit/grit/extern/FP.py
-new file mode 100644
-index 0000000000..f4ec4d943f
---- /dev/null
-+++ b/tools/grit/grit/extern/FP.py
-@@ -0,0 +1,72 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+from __future__ import print_function
-+
-+try:
-+ import hashlib
-+ _new_md5 = hashlib.md5
-+except ImportError:
-+ import md5
-+ _new_md5 = md5.new
-+
-+
-+"""64-bit fingerprint support for strings.
-+
-+Usage:
-+ from extern import FP
-+ print('Fingerprint is %ld' % FP.FingerPrint('Hello world!'))
-+"""
-+
-+
-+def _UnsignedFingerPrintImpl(str, encoding='utf-8'):
-+ """Generate a 64-bit fingerprint by taking the first half of the md5
-+ of the string.
-+ """
-+ hex128 = _new_md5(str.encode(encoding)).hexdigest()
-+ int64 = int(hex128[:16], 16)
-+ return int64
-+
-+
-+def UnsignedFingerPrint(str, encoding='utf-8'):
-+ """Generate a 64-bit fingerprint.
-+
-+ The default implementation uses _UnsignedFingerPrintImpl, which
-+ takes the first half of the md5 of the string, but the
-+ implementation may be switched using SetUnsignedFingerPrintImpl.
-+ """
-+ return _UnsignedFingerPrintImpl(str, encoding)
-+
-+
-+def FingerPrint(str, encoding='utf-8'):
-+ fp = UnsignedFingerPrint(str, encoding=encoding)
-+ # interpret fingerprint as signed longs
-+ if fp & 0x8000000000000000:
-+ fp = -((~fp & 0xFFFFFFFFFFFFFFFF) + 1)
-+ return fp
-+
-+
-+def UseUnsignedFingerPrintFromModule(module_name):
-+ """Imports module_name and replaces UnsignedFingerPrint in the
-+ current module with the function of the same name from the imported
-+ module.
-+
-+ Returns the function object previously known as
-+ grit.extern.FP.UnsignedFingerPrint.
-+ """
-+ hash_module = __import__(module_name, fromlist=[module_name])
-+ return SetUnsignedFingerPrint(hash_module.UnsignedFingerPrint)
-+
-+
-+def SetUnsignedFingerPrint(function_object):
-+ """Sets grit.extern.FP.UnsignedFingerPrint to point to
-+ function_object.
-+
-+ Returns the function object previously known as
-+ grit.extern.FP.UnsignedFingerPrint.
-+ """
-+ global UnsignedFingerPrint
-+ original_function_object = UnsignedFingerPrint
-+ UnsignedFingerPrint = function_object
-+ return original_function_object
-diff --git a/tools/grit/grit/extern/__init__.py b/tools/grit/grit/extern/__init__.py
-new file mode 100644
-index 0000000000..e69de29bb2
-diff --git a/tools/grit/grit/extern/tclib.py b/tools/grit/grit/extern/tclib.py
-new file mode 100644
-index 0000000000..9952a87c11
---- /dev/null
-+++ b/tools/grit/grit/extern/tclib.py
-@@ -0,0 +1,503 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+# The tclib module contains tools for aggregating, verifying, and storing
-+# messages destined for the Translation Console, as well as for reading
-+# translations back and outputting them in some desired format.
-+#
-+# This has been stripped down to include only the functionality needed by grit
-+# for creating Windows .rc and .h files. These are the only parts needed by
-+# the Chrome build process.
-+
-+from __future__ import print_function
-+
-+from grit.extern import FP
-+
-+# This module assumes that within a bundle no two messages can have the
-+# same id unless they're identical.
-+
-+# The basic classes defined here for external use are Message and Translation,
-+# where the former is used for English messages and the latter for
-+# translations. These classes have a lot of common functionality, as expressed
-+# by the common parent class BaseMessage. Perhaps the most important
-+# distinction is that translated text is stored in UTF-8, whereas original text
-+# is stored in whatever encoding the client uses (presumably Latin-1).
-+
-+# --------------------
-+# The public interface
-+# --------------------
-+
-+# Generate message id from message text and meaning string (optional),
-+# both in utf-8 encoding
-+#
-+def GenerateMessageId(message, meaning=''):
-+ fp = FP.FingerPrint(message)
-+ if meaning:
-+ # combine the fingerprints of message and meaning
-+ fp2 = FP.FingerPrint(meaning)
-+ if fp < 0:
-+ fp = fp2 + (fp << 1) + 1
-+ else:
-+ fp = fp2 + (fp << 1)
-+ # To avoid negative ids we strip the high-order bit
-+ return str(fp & 0x7fffffffffffffff)
-+
-+# -------------------------------------------------------------------------
-+# The MessageTranslationError class is used to signal tclib-specific errors.
-+
-+
-+class MessageTranslationError(Exception):
-+
-+ def __init__(self, args = ''):
-+ self.args = args
-+
-+
-+# -----------------------------------------------------------
-+# The Placeholder class represents a placeholder in a message.
-+
-+class Placeholder(object):
-+ # String representation
-+ def __str__(self):
-+ return '%s, "%s", "%s"' % \
-+ (self.__presentation, self.__original, self.__example)
-+
-+ # Getters
-+ def GetOriginal(self):
-+ return self.__original
-+
-+ def GetPresentation(self):
-+ return self.__presentation
-+
-+ def GetExample(self):
-+ return self.__example
-+
-+ def __eq__(self, other):
-+ return self.EqualTo(other, strict=1, ignore_trailing_spaces=0)
-+
-+ # Equality test
-+ #
-+ # ignore_trailing_spaces: TC is using varchar to store the
-+ # phrwr fields, as a result of that, the trailing spaces
-+ # are removed by MySQL when the strings are stored into TC:-(
-+ # ignore_trailing_spaces parameter is used to ignore
-+ # trailing spaces during equivalence comparison.
-+ #
-+ def EqualTo(self, other, strict = 1, ignore_trailing_spaces = 1):
-+ if type(other) is not Placeholder:
-+ return 0
-+ if StringEquals(self.__presentation, other.__presentation,
-+ ignore_trailing_spaces):
-+ if not strict or (StringEquals(self.__original, other.__original,
-+ ignore_trailing_spaces) and
-+ StringEquals(self.__example, other.__example,
-+ ignore_trailing_spaces)):
-+ return 1
-+ return 0
-+
-+
-+# -----------------------------------------------------------------
-+# BaseMessage is the common parent class of Message and Translation.
-+# It is not meant for direct use.
-+
-+class BaseMessage(object):
-+ # Three types of message construction is supported. If the message text is a
-+ # simple string with no dynamic content, you can pass it to the constructor
-+ # as the "text" parameter. Otherwise, you can omit "text" and assemble the
-+ # message step by step using AppendText() and AppendPlaceholder(). Or, as an
-+ # alternative, you can give the constructor the "presentable" version of the
-+ # message and a list of placeholders; it will then parse the presentation and
-+ # build the message accordingly. For example:
-+ # Message(text = "There are NUM_BUGS bugs in your code",
-+ # placeholders = [Placeholder("NUM_BUGS", "%d", "33")],
-+ # description = "Bla bla bla")
-+ def __eq__(self, other):
-+ # "source encoding" is nonsense, so ignore it
-+ return _ObjectEquals(self, other, ['_BaseMessage__source_encoding'])
-+
-+ def GetName(self):
-+ return self.__name
-+
-+ def GetSourceEncoding(self):
-+ return self.__source_encoding
-+
-+ # Append a placeholder to the message
-+ def AppendPlaceholder(self, placeholder):
-+ if not isinstance(placeholder, Placeholder):
-+ raise MessageTranslationError("Invalid message placeholder %s in "
-+ "message %s" % (placeholder, self.GetId()))
-+ # Are there other placeholders with the same presentation?
-+ # If so, they need to be the same.
-+ for other in self.GetPlaceholders():
-+ if placeholder.GetPresentation() == other.GetPresentation():
-+ if not placeholder.EqualTo(other):
-+ raise MessageTranslationError(
-+ "Conflicting declarations of %s within message" %
-+ placeholder.GetPresentation())
-+ # update placeholder list
-+ dup = 0
-+ for item in self.__content:
-+ if isinstance(item, Placeholder) and placeholder.EqualTo(item):
-+ dup = 1
-+ break
-+ if not dup:
-+ self.__placeholders.append(placeholder)
-+
-+ # update content
-+ self.__content.append(placeholder)
-+
-+ # Strips leading and trailing whitespace, and returns a tuple
-+ # containing the leading and trailing space that was removed.
-+ def Strip(self):
-+ leading = trailing = ''
-+ if len(self.__content) > 0:
-+ s0 = self.__content[0]
-+ if not isinstance(s0, Placeholder):
-+ s = s0.lstrip()
-+ leading = s0[:-len(s)]
-+ self.__content[0] = s
-+
-+ s0 = self.__content[-1]
-+ if not isinstance(s0, Placeholder):
-+ s = s0.rstrip()
-+ trailing = s0[len(s):]
-+ self.__content[-1] = s
-+ return leading, trailing
-+
-+ # Return the id of this message
-+ def GetId(self):
-+ if self.__id is None:
-+ return self.GenerateId()
-+ return self.__id
-+
-+ # Set the id of this message
-+ def SetId(self, id):
-+ if id is None:
-+ self.__id = None
-+ else:
-+ self.__id = str(id) # Treat numerical ids as strings
-+
-+ # Return content of this message as a list (internal use only)
-+ def GetContent(self):
-+ return self.__content
-+
-+ # Return a human-readable version of this message
-+ def GetPresentableContent(self):
-+ presentable_content = ""
-+ for item in self.__content:
-+ if isinstance(item, Placeholder):
-+ presentable_content += item.GetPresentation()
-+ else:
-+ presentable_content += item
-+
-+ return presentable_content
-+
-+ # Return a fragment of a message in escaped format
-+ def EscapeFragment(self, fragment):
-+ return fragment.replace('%', '%%')
-+
-+ # Return the "original" version of this message, doing %-escaping
-+ # properly. If source_msg is specified, the placeholder original
-+ # information inside source_msg will be used instead.
-+ def GetOriginalContent(self, source_msg = None):
-+ original_content = ""
-+ for item in self.__content:
-+ if isinstance(item, Placeholder):
-+ if source_msg:
-+ ph = source_msg.GetPlaceholder(item.GetPresentation())
-+ if not ph:
-+ raise MessageTranslationError(
-+ "Placeholder %s doesn't exist in message: %s" %
-+ (item.GetPresentation(), source_msg))
-+ original_content += ph.GetOriginal()
-+ else:
-+ original_content += item.GetOriginal()
-+ else:
-+ original_content += self.EscapeFragment(item)
-+ return original_content
-+
-+ # Return the example of this message
-+ def GetExampleContent(self):
-+ example_content = ""
-+ for item in self.__content:
-+ if isinstance(item, Placeholder):
-+ example_content += item.GetExample()
-+ else:
-+ example_content += item
-+ return example_content
-+
-+ # Return a list of all unique placeholders in this message
-+ def GetPlaceholders(self):
-+ return self.__placeholders
-+
-+ # Return a placeholder in this message
-+ def GetPlaceholder(self, presentation):
-+ for item in self.__content:
-+ if (isinstance(item, Placeholder) and
-+ item.GetPresentation() == presentation):
-+ return item
-+ return None
-+
-+ # Return this message's description
-+ def GetDescription(self):
-+ return self.__description
-+
-+ # Add a message source
-+ def AddSource(self, source):
-+ self.__sources.append(source)
-+
-+ # Return this message's sources as a list
-+ def GetSources(self):
-+ return self.__sources
-+
-+ # Return this message's sources as a string
-+ def GetSourcesAsText(self, delimiter = "; "):
-+ return delimiter.join(self.__sources)
-+
-+ # Set the obsolete flag for a message (internal use only)
-+ def SetObsolete(self):
-+ self.__obsolete = 1
-+
-+ # Get the obsolete flag for a message (internal use only)
-+ def IsObsolete(self):
-+ return self.__obsolete
-+
-+ # Get the sequence number (0 by default)
-+ def GetSequenceNumber(self):
-+ return self.__sequence_number
-+
-+ # Set the sequence number
-+ def SetSequenceNumber(self, number):
-+ self.__sequence_number = number
-+
-+ # Increment instance counter
-+ def AddInstance(self):
-+ self.__num_instances += 1
-+
-+ # Return instance count
-+ def GetNumInstances(self):
-+ return self.__num_instances
-+
-+ def GetErrors(self, from_tc=0):
-+ """
-+ Returns a description of the problem if the message is not
-+ syntactically valid, or None if everything is fine.
-+
-+ Args:
-+ from_tc: indicates whether this message came from the TC. We let
-+ the TC get away with some things we normally wouldn't allow for
-+ historical reasons.
-+ """
-+ # check that placeholders are unambiguous
-+ pos = 0
-+ phs = {}
-+ for item in self.__content:
-+ if isinstance(item, Placeholder):
-+ phs[pos] = item
-+ pos += len(item.GetPresentation())
-+ else:
-+ pos += len(item)
-+ presentation = self.GetPresentableContent()
-+ for ph in self.GetPlaceholders():
-+ for pos in FindOverlapping(presentation, ph.GetPresentation()):
-+ # message contains the same text as a placeholder presentation
-+ other_ph = phs.get(pos)
-+ if ((not other_ph
-+ and not IsSubstringInPlaceholder(pos, len(ph.GetPresentation()), phs))
-+ or
-+ (other_ph and len(other_ph.GetPresentation()) < len(ph.GetPresentation()))):
-+ return "message contains placeholder name '%s':\n%s" % (
-+ ph.GetPresentation(), presentation)
-+ return None
-+
-+
-+ def __CopyTo(self, other):
-+ """
-+ Returns a copy of this BaseMessage.
-+ """
-+ assert isinstance(other, self.__class__) or isinstance(self, other.__class__)
-+ other.__source_encoding = self.__source_encoding
-+ other.__content = self.__content[:]
-+ other.__description = self.__description
-+ other.__id = self.__id
-+ other.__num_instances = self.__num_instances
-+ other.__obsolete = self.__obsolete
-+ other.__name = self.__name
-+ other.__placeholders = self.__placeholders[:]
-+ other.__sequence_number = self.__sequence_number
-+ other.__sources = self.__sources[:]
-+
-+ return other
-+
-+ def HasText(self):
-+ """Returns true iff this message has anything other than placeholders."""
-+ for item in self.__content:
-+ if not isinstance(item, Placeholder):
-+ return True
-+ return False
-+
-+# --------------------------------------------------------
-+# The Message class represents original (English) messages
-+
-+class Message(BaseMessage):
-+ # See BaseMessage constructor
-+ def __init__(self, source_encoding, text=None, id=None,
-+ description=None, meaning="", placeholders=None,
-+ source=None, sequence_number=0, clone_from=None,
-+ time_created=0, name=None, is_hidden = 0):
-+
-+ if clone_from is not None:
-+ BaseMessage.__init__(self, None, clone_from=clone_from)
-+ self.__meaning = clone_from.__meaning
-+ self.__time_created = clone_from.__time_created
-+ self.__is_hidden = clone_from.__is_hidden
-+ return
-+
-+ BaseMessage.__init__(self, source_encoding, text, id, description,
-+ placeholders, source, sequence_number,
-+ name=name)
-+ self.__meaning = meaning
-+ self.__time_created = time_created
-+ self.SetIsHidden(is_hidden)
-+
-+ # String representation
-+ def __str__(self):
-+ s = 'source: %s, id: %s, content: "%s", meaning: "%s", ' \
-+ 'description: "%s"' % \
-+ (self.GetSourcesAsText(), self.GetId(), self.GetPresentableContent(),
-+ self.__meaning, self.GetDescription())
-+ if self.GetName() is not None:
-+ s += ', name: "%s"' % self.GetName()
-+ placeholders = self.GetPlaceholders()
-+ for i in range(len(placeholders)):
-+ s += ", placeholder[%d]: %s" % (i, placeholders[i])
-+ return s
-+
-+ # Strips leading and trailing whitespace, and returns a tuple
-+ # containing the leading and trailing space that was removed.
-+ def Strip(self):
-+ leading = trailing = ''
-+ content = self.GetContent()
-+ if len(content) > 0:
-+ s0 = content[0]
-+ if not isinstance(s0, Placeholder):
-+ s = s0.lstrip()
-+ leading = s0[:-len(s)]
-+ content[0] = s
-+
-+ s0 = content[-1]
-+ if not isinstance(s0, Placeholder):
-+ s = s0.rstrip()
-+ trailing = s0[len(s):]
-+ content[-1] = s
-+ return leading, trailing
-+
-+ # Generate an id by hashing message content
-+ def GenerateId(self):
-+ self.SetId(GenerateMessageId(self.GetPresentableContent(),
-+ self.__meaning))
-+ return self.GetId()
-+
-+ def GetMeaning(self):
-+ return self.__meaning
-+
-+ def GetTimeCreated(self):
-+ return self.__time_created
-+
-+ # Equality operator
-+ def EqualTo(self, other, strict = 1):
-+ # Check id, meaning, content
-+ if self.GetId() != other.GetId():
-+ return 0
-+ if self.__meaning != other.__meaning:
-+ return 0
-+ if self.GetPresentableContent() != other.GetPresentableContent():
-+ return 0
-+ # Check descriptions if comparison is strict
-+ if (strict and
-+ self.GetDescription() is not None and
-+ other.GetDescription() is not None and
-+ self.GetDescription() != other.GetDescription()):
-+ return 0
-+ # Check placeholders
-+ ph1 = self.GetPlaceholders()
-+ ph2 = other.GetPlaceholders()
-+ if len(ph1) != len(ph2):
-+ return 0
-+ for i in range(len(ph1)):
-+ if not ph1[i].EqualTo(ph2[i], strict):
-+ return 0
-+
-+ return 1
-+
-+ def Copy(self):
-+ """
-+ Returns a copy of this Message.
-+ """
-+ assert isinstance(self, Message)
-+ return Message(None, clone_from=self)
-+
-+ def SetIsHidden(self, is_hidden):
-+ """Sets whether this message should be hidden.
-+
-+ Args:
-+ is_hidden : 0 or 1 - if the message should be hidden, 0 otherwise
-+ """
-+ if is_hidden not in [0, 1]:
-+ raise MessageTranslationError("is_hidden must be 0 or 1, got %s")
-+ self.__is_hidden = is_hidden
-+
-+ def IsHidden(self):
-+ """Returns 1 if this message is hidden, and 0 otherwise."""
-+ return self.__is_hidden
-+
-+# ----------------------------------------------------
-+# The Translation class represents translated messages
-+
-+class Translation(BaseMessage):
-+ # See BaseMessage constructor
-+ def __init__(self, source_encoding, text=None, id=None,
-+ description=None, placeholders=None, source=None,
-+ sequence_number=0, clone_from=None, ignore_ph_errors=0,
-+ name=None):
-+ if clone_from is not None:
-+ BaseMessage.__init__(self, None, clone_from=clone_from)
-+ return
-+
-+ BaseMessage.__init__(self, source_encoding, text, id, description,
-+ placeholders, source, sequence_number,
-+ ignore_ph_errors=ignore_ph_errors, name=name)
-+
-+ # String representation
-+ def __str__(self):
-+ s = 'source: %s, id: %s, content: "%s", description: "%s"' % \
-+ (self.GetSourcesAsText(), self.GetId(), self.GetPresentableContent(),
-+ self.GetDescription());
-+ placeholders = self.GetPlaceholders()
-+ for i in range(len(placeholders)):
-+ s += ", placeholder[%d]: %s" % (i, placeholders[i])
-+ return s
-+
-+ # Equality operator
-+ def EqualTo(self, other, strict=1):
-+ # Check id and content
-+ if self.GetId() != other.GetId():
-+ return 0
-+ if self.GetPresentableContent() != other.GetPresentableContent():
-+ return 0
-+ # Check placeholders
-+ ph1 = self.GetPlaceholders()
-+ ph2 = other.GetPlaceholders()
-+ if len(ph1) != len(ph2):
-+ return 0
-+ for i in range(len(ph1)):
-+ if not ph1[i].EqualTo(ph2[i], strict):
-+ return 0
-+
-+ return 1
-+
-+ def Copy(self):
-+ """
-+ Returns a copy of this Translation.
-+ """
-+ return Translation(None, clone_from=self)
-diff --git a/tools/grit/grit/format/__init__.py b/tools/grit/grit/format/__init__.py
-new file mode 100644
-index 0000000000..55d56b8cfd
---- /dev/null
-+++ b/tools/grit/grit/format/__init__.py
-@@ -0,0 +1,8 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Module grit.format
-+'''
-+
-+pass
-diff --git a/tools/grit/grit/format/android_xml.py b/tools/grit/grit/format/android_xml.py
-new file mode 100644
-index 0000000000..7eb288891f
---- /dev/null
-+++ b/tools/grit/grit/format/android_xml.py
-@@ -0,0 +1,212 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+"""Produces localized strings.xml files for Android.
-+
-+In cases where an "android" type output file is requested in a grd, the classes
-+in android_xml will process the messages and translations to produce a valid
-+strings.xml that is properly localized with the specified language.
-+
-+For example if the following output tag were to be included in a grd file
-+ <outputs>
-+ ...
-+ <output filename="values-es/strings.xml" type="android" lang="es" />
-+ ...
-+ </outputs>
-+
-+for a grd file with the following messages:
-+
-+ <message name="IDS_HELLO" desc="Simple greeting">Hello</message>
-+ <message name="IDS_WORLD" desc="The world">world</message>
-+
-+and there existed an appropriate xtb file containing the Spanish translations,
-+then the output would be:
-+
-+ <?xml version="1.0" encoding="utf-8"?>
-+ <resources xmlns:android="http://schemas.android.com/apk/res/android">
-+ <string name="hello">"Hola"</string>
-+ <string name="world">"mundo"</string>
-+ </resources>
-+
-+which would be written to values-es/strings.xml and usable by the Android
-+resource framework.
-+
-+Advanced usage
-+--------------
-+
-+To process only certain messages in a grd file, tag each desired message by
-+adding "android_java" to formatter_data. Then set the environmental variable
-+ANDROID_JAVA_TAGGED_ONLY to "true" when building the grd file. For example:
-+
-+ <message name="IDS_HELLO" formatter_data="android_java">Hello</message>
-+
-+To generate Android plurals (aka "quantity strings"), use the ICU plural syntax
-+in the grd file. This will automatically be transformed into a <purals> element
-+in the output xml file. For example:
-+
-+ <message name="IDS_CATS">
-+ {NUM_CATS, plural,
-+ =1 {1 cat}
-+ other {# cats}}
-+ </message>
-+
-+ will produce
-+
-+ <plurals name="cats">
-+ <item quantity="one">1 Katze</item>
-+ <item quantity="other">%d Katzen</item>
-+ </plurals>
-+"""
-+
-+from __future__ import print_function
-+
-+import os
-+import re
-+import xml.sax.saxutils
-+
-+from grit import lazy_re
-+from grit.node import message
-+
-+
-+# When this environmental variable has value "true", only tagged messages will
-+# be outputted.
-+_TAGGED_ONLY_ENV_VAR = 'ANDROID_JAVA_TAGGED_ONLY'
-+_TAGGED_ONLY_DEFAULT = False
-+
-+# In tagged-only mode, only messages with this tag will be ouputted.
-+_EMIT_TAG = 'android_java'
-+
-+_NAME_PATTERN = lazy_re.compile(r'IDS_(?P<name>[A-Z0-9_]+)\Z')
-+
-+# Most strings are output as a <string> element. Note the double quotes
-+# around the value to preserve whitespace.
-+_STRING_TEMPLATE = u'<string name="%s">"%s"</string>\n'
-+
-+# Some strings are output as a <plurals> element.
-+_PLURALS_TEMPLATE = '<plurals name="%s">\n%s</plurals>\n'
-+_PLURALS_ITEM_TEMPLATE = ' <item quantity="%s">%s</item>\n'
-+
-+# Matches e.g. "{HELLO, plural, HOW ARE YOU DOING}", while capturing
-+# "HOW ARE YOU DOING" in <items>.
-+_PLURALS_PATTERN = lazy_re.compile(r'\{[A-Z_]+,\s*plural,(?P<items>.*)\}$',
-+ flags=re.S)
-+
-+# Repeatedly matched against the <items> capture in _PLURALS_PATTERN,
-+# to match "<quantity>{<value>}".
-+_PLURALS_ITEM_PATTERN = lazy_re.compile(r'(?P<quantity>\S+?)\s*'
-+ r'\{(?P<value>.*?)\}')
-+_PLURALS_QUANTITY_MAP = {
-+ '=0': 'zero',
-+ 'zero': 'zero',
-+ '=1': 'one',
-+ 'one': 'one',
-+ '=2': 'two',
-+ 'two': 'two',
-+ 'few': 'few',
-+ 'many': 'many',
-+ 'other': 'other',
-+}
-+
-+
-+def Format(root, lang='en', output_dir='.'):
-+ yield ('<?xml version="1.0" encoding="utf-8"?>\n'
-+ '<resources '
-+ 'xmlns:android="http://schemas.android.com/apk/res/android">\n')
-+
-+ tagged_only = _TAGGED_ONLY_DEFAULT
-+ if _TAGGED_ONLY_ENV_VAR in os.environ:
-+ tagged_only = os.environ[_TAGGED_ONLY_ENV_VAR].lower()
-+ if tagged_only == 'true':
-+ tagged_only = True
-+ elif tagged_only == 'false':
-+ tagged_only = False
-+ else:
-+ raise Exception('env variable ANDROID_JAVA_TAGGED_ONLY must have value '
-+ 'true or false. Invalid value: %s' % tagged_only)
-+
-+ for item in root.ActiveDescendants():
-+ with item:
-+ if ShouldOutputNode(item, tagged_only):
-+ yield _FormatMessage(item, lang)
-+
-+ yield '</resources>\n'
-+
-+
-+def ShouldOutputNode(node, tagged_only):
-+ """Returns true if node should be outputted.
-+
-+ Args:
-+ node: a Node from the grd dom
-+ tagged_only: true, if only tagged messages should be outputted
-+ """
-+ return (isinstance(node, message.MessageNode) and
-+ (not tagged_only or _EMIT_TAG in node.formatter_data))
-+
-+
-+def _FormatPluralMessage(message):
-+ """Compiles ICU plural syntax to the body of an Android <plurals> element.
-+
-+ 1. In a .grd file, we can write a plural string like this:
-+
-+ <message name="IDS_THINGS">
-+ {NUM_THINGS, plural,
-+ =1 {1 thing}
-+ other {# things}}
-+ </message>
-+
-+ 2. The Android equivalent looks like this:
-+
-+ <plurals name="things">
-+ <item quantity="one">1 thing</item>
-+ <item quantity="other">%d things</item>
-+ </plurals>
-+
-+ This method takes the body of (1) and converts it to the body of (2).
-+
-+ If the message is *not* a plural string, this function returns `None`.
-+ If the message includes quantities without an equivalent format in Android,
-+ it raises an exception.
-+ """
-+ ret = {}
-+ plural_match = _PLURALS_PATTERN.match(message)
-+ if not plural_match:
-+ return None
-+ body_in = plural_match.group('items').strip()
-+ lines = []
-+ quantities_so_far = set()
-+ for item_match in _PLURALS_ITEM_PATTERN.finditer(body_in):
-+ quantity_in = item_match.group('quantity')
-+ quantity_out = _PLURALS_QUANTITY_MAP.get(quantity_in)
-+ value_in = item_match.group('value')
-+ value_out = '"' + value_in.replace('#', '%d') + '"'
-+ if quantity_out:
-+ # only one line per quantity out (https://crbug.com/787488)
-+ if quantity_out not in quantities_so_far:
-+ quantities_so_far.add(quantity_out)
-+ lines.append(_PLURALS_ITEM_TEMPLATE % (quantity_out, value_out))
-+ else:
-+ raise Exception('Unsupported plural quantity for android '
-+ 'strings.xml: %s' % quantity_in)
-+ return ''.join(lines)
-+
-+
-+def _FormatMessage(item, lang):
-+ """Writes out a single string as a <resource/> element."""
-+
-+ mangled_name = item.GetTextualIds()[0]
-+ match = _NAME_PATTERN.match(mangled_name)
-+ if not match:
-+ raise Exception('Unexpected resource name: %s' % mangled_name)
-+ name = match.group('name').lower()
-+
-+ value = item.ws_at_start + item.Translate(lang) + item.ws_at_end
-+ # Replace < > & with &lt; &gt; &amp; to ensure we generate valid XML and
-+ # replace ' " with \' \" to conform to Android's string formatting rules.
-+ value = xml.sax.saxutils.escape(value, {"'": "\\'", '"': '\\"'})
-+
-+ plurals = _FormatPluralMessage(value)
-+ if plurals:
-+ return _PLURALS_TEMPLATE % (name, plurals)
-+ else:
-+ return _STRING_TEMPLATE % (name, value)
-diff --git a/tools/grit/grit/format/android_xml_unittest.py b/tools/grit/grit/format/android_xml_unittest.py
-new file mode 100644
-index 0000000000..d9f476fddf
---- /dev/null
-+++ b/tools/grit/grit/format/android_xml_unittest.py
-@@ -0,0 +1,149 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+"""Unittest for android_xml.py."""
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+import unittest
-+
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+from six import StringIO
-+
-+from grit import util
-+from grit.format import android_xml
-+from grit.node import message
-+from grit.tool import build
-+
-+
-+class AndroidXmlUnittest(unittest.TestCase):
-+
-+ def testMessages(self):
-+ root = util.ParseGrdForUnittest(r"""
-+ <messages>
-+ <message name="IDS_SIMPLE" desc="A vanilla string">
-+ Martha
-+ </message>
-+ <message name="IDS_ONE_LINE" desc="On one line">sat and wondered</message>
-+ <message name="IDS_QUOTES" desc="A string with quotation marks">
-+ out loud, "Why don't I build a flying car?"
-+ </message>
-+ <message name="IDS_MULTILINE" desc="A string split over several lines">
-+ She gathered
-+wood, charcoal, and
-+a sledge hammer.
-+ </message>
-+ <message name="IDS_WHITESPACE" desc="A string with extra whitespace.">
-+ ''' How old fashioned -- she thought. '''
-+ </message>
-+ <message name="IDS_PLACEHOLDERS" desc="A string with placeholders">
-+ I'll buy a <ph name="WAVELENGTH">%d<ex>200</ex></ph> nm laser at <ph name="STORE_NAME">%s<ex>the grocery store</ex></ph>.
-+ </message>
-+ <message name="IDS_PLURALS" desc="A string using the ICU plural format">
-+ {NUM_THINGS, plural,
-+ =1 {Maybe I'll get one laser.}
-+ other {Maybe I'll get # lasers.}}
-+ </message>
-+ <message name="IDS_PLURALS_NO_SPACE" desc="A string using the ICU plural format with no space">
-+ {NUM_MISSISSIPPIS, plural,
-+ =1{OneMississippi}other{ManyMississippis}}
-+ </message>
-+ </messages>
-+ """)
-+
-+ buf = StringIO()
-+ build.RcBuilder.ProcessNode(root, DummyOutput('android', 'en'), buf)
-+ output = buf.getvalue()
-+ expected = r"""
-+<?xml version="1.0" encoding="utf-8"?>
-+<resources xmlns:android="http://schemas.android.com/apk/res/android">
-+<string name="simple">"Martha"</string>
-+<string name="one_line">"sat and wondered"</string>
-+<string name="quotes">"out loud, \"Why don\'t I build a flying car?\""</string>
-+<string name="multiline">"She gathered
-+wood, charcoal, and
-+a sledge hammer."</string>
-+<string name="whitespace">" How old fashioned -- she thought. "</string>
-+<string name="placeholders">"I\'ll buy a %d nm laser at %s."</string>
-+<plurals name="plurals">
-+ <item quantity="one">"Maybe I\'ll get one laser."</item>
-+ <item quantity="other">"Maybe I\'ll get %d lasers."</item>
-+</plurals>
-+<plurals name="plurals_no_space">
-+ <item quantity="one">"OneMississippi"</item>
-+ <item quantity="other">"ManyMississippis"</item>
-+</plurals>
-+</resources>
-+"""
-+ self.assertEqual(output.strip(), expected.strip())
-+
-+
-+ def testConflictingPlurals(self):
-+ root = util.ParseGrdForUnittest(r"""
-+ <messages>
-+ <message name="IDS_PLURALS" desc="A string using the ICU plural format">
-+ {NUM_THINGS, plural,
-+ =1 {Maybe I'll get one laser.}
-+ one {Maybe I'll get one laser.}
-+ other {Maybe I'll get # lasers.}}
-+ </message>
-+ </messages>
-+ """)
-+
-+ buf = StringIO()
-+ build.RcBuilder.ProcessNode(root, DummyOutput('android', 'en'), buf)
-+ output = buf.getvalue()
-+ expected = r"""
-+<?xml version="1.0" encoding="utf-8"?>
-+<resources xmlns:android="http://schemas.android.com/apk/res/android">
-+<plurals name="plurals">
-+ <item quantity="one">"Maybe I\'ll get one laser."</item>
-+ <item quantity="other">"Maybe I\'ll get %d lasers."</item>
-+</plurals>
-+</resources>
-+"""
-+ self.assertEqual(output.strip(), expected.strip())
-+
-+
-+ def testTaggedOnly(self):
-+ root = util.ParseGrdForUnittest(r"""
-+ <messages>
-+ <message name="IDS_HELLO" desc="" formatter_data="android_java">
-+ Hello
-+ </message>
-+ <message name="IDS_WORLD" desc="">
-+ world
-+ </message>
-+ </messages>
-+ """)
-+
-+ msg_hello, msg_world = root.GetChildrenOfType(message.MessageNode)
-+ self.assertTrue(android_xml.ShouldOutputNode(msg_hello, tagged_only=True))
-+ self.assertFalse(android_xml.ShouldOutputNode(msg_world, tagged_only=True))
-+ self.assertTrue(android_xml.ShouldOutputNode(msg_hello, tagged_only=False))
-+ self.assertTrue(android_xml.ShouldOutputNode(msg_world, tagged_only=False))
-+
-+
-+class DummyOutput(object):
-+
-+ def __init__(self, type, language):
-+ self.type = type
-+ self.language = language
-+
-+ def GetType(self):
-+ return self.type
-+
-+ def GetLanguage(self):
-+ return self.language
-+
-+ def GetOutputFilename(self):
-+ return 'hello.gif'
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/format/c_format.py b/tools/grit/grit/format/c_format.py
-new file mode 100644
-index 0000000000..16809a9f70
---- /dev/null
-+++ b/tools/grit/grit/format/c_format.py
-@@ -0,0 +1,95 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+"""Formats as a .C file for compilation.
-+"""
-+
-+from __future__ import print_function
-+
-+import codecs
-+import os
-+import re
-+
-+import six
-+
-+from grit import util
-+
-+
-+def _FormatHeader(root, output_dir):
-+ """Returns the required preamble for C files."""
-+ # Find the location of the resource header file, so that we can include
-+ # it.
-+ resource_header = 'resource.h' # fall back to this
-+ for output in root.GetOutputFiles():
-+ if output.attrs['type'] == 'rc_header':
-+ resource_header = os.path.abspath(output.GetOutputFilename())
-+ resource_header = util.MakeRelativePath(output_dir, resource_header)
-+ return """// This file is automatically generated by GRIT. Do not edit.
-+
-+#include "%s"
-+
-+// All strings are UTF-8
-+""" % (resource_header)
-+# end _FormatHeader() function
-+
-+
-+def Format(root, lang='en', output_dir='.'):
-+ """Outputs a C switch statement representing the string table."""
-+ from grit.node import message
-+ assert isinstance(lang, six.string_types)
-+
-+ yield _FormatHeader(root, output_dir)
-+
-+ yield 'const char* GetString(int id) {\n switch (id) {'
-+
-+ for item in root.ActiveDescendants():
-+ with item:
-+ if isinstance(item, message.MessageNode):
-+ yield _FormatMessage(item, lang)
-+
-+ yield '\n default:\n return 0;\n }\n}\n'
-+
-+
-+def _HexToOct(match):
-+ "Return the octal form of the hex numbers"
-+ hex = match.group("hex")
-+ result = ""
-+ while len(hex):
-+ next_num = int(hex[2:4], 16)
-+ result += "\\" + '%03o' % next_num
-+ hex = hex[4:]
-+ return match.group("escaped_backslashes") + result
-+
-+
-+def _FormatMessage(item, lang):
-+ """Format a single <message> element."""
-+
-+ message = item.ws_at_start + item.Translate(lang) + item.ws_at_end
-+ # Output message with non-ascii chars escaped as octal numbers C's grammar
-+ # allows escaped hexadecimal numbers to be infinite, but octal is always of
-+ # the form \OOO. Python 3 doesn't support string-escape, so we have to jump
-+ # through some hoops here via codecs.escape_encode.
-+ # This basically does:
-+ # - message - the starting string
-+ # - message.encode(...) - convert to bytes
-+ # - codecs.escape_encode(...) - convert non-ASCII bytes to \x## escapes
-+ # - (...).decode() - convert bytes back to a string
-+ message = codecs.escape_encode(message.encode('utf-8'))[0].decode('utf-8')
-+ # an escaped char is (\xHH)+ but only if the initial
-+ # backslash is not escaped.
-+ not_a_backslash = r"(^|[^\\])" # beginning of line or a non-backslash char
-+ escaped_backslashes = not_a_backslash + r"(\\\\)*"
-+ hex_digits = r"((\\x)[0-9a-f]{2})+"
-+ two_digit_hex_num = re.compile(
-+ r"(?P<escaped_backslashes>%s)(?P<hex>%s)"
-+ % (escaped_backslashes, hex_digits))
-+ message = two_digit_hex_num.sub(_HexToOct, message)
-+ # unescape \ (convert \\ back to \)
-+ message = message.replace('\\\\', '\\')
-+ message = message.replace('"', '\\"')
-+ message = util.LINEBREAKS.sub(r'\\n', message)
-+
-+ name_attr = item.GetTextualIds()[0]
-+
-+ return '\n case %s:\n return "%s";' % (name_attr, message)
-diff --git a/tools/grit/grit/format/c_format_unittest.py b/tools/grit/grit/format/c_format_unittest.py
-new file mode 100644
-index 0000000000..380120c42f
---- /dev/null
-+++ b/tools/grit/grit/format/c_format_unittest.py
-@@ -0,0 +1,81 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+"""Unittest for c_format.py.
-+"""
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+import unittest
-+
-+from six import StringIO
-+
-+from grit import util
-+from grit.tool import build
-+
-+
-+class CFormatUnittest(unittest.TestCase):
-+
-+ def testMessages(self):
-+ root = util.ParseGrdForUnittest(u"""
-+ <messages>
-+ <message name="IDS_QUESTIONS">Do you want to play questions?</message>
-+ <message name="IDS_QUOTES">
-+ "What's in a name, <ph name="NAME">%s<ex>Brandon</ex></ph>?"
-+ </message>
-+ <message name="IDS_LINE_BREAKS">
-+ Was that rhetoric?
-+No.
-+Statement. Two all. Game point.
-+</message>
-+ <message name="IDS_NON_ASCII">
-+ \u00f5\\xc2\\xa4\\\u00a4\\\\xc3\\xb5\u4924
-+ </message>
-+ </messages>
-+ """)
-+
-+ buf = StringIO()
-+ build.RcBuilder.ProcessNode(root, DummyOutput('c_format', 'en'), buf)
-+ output = util.StripBlankLinesAndComments(buf.getvalue())
-+ self.assertEqual(u"""\
-+#include "resource.h"
-+const char* GetString(int id) {
-+ switch (id) {
-+ case IDS_QUESTIONS:
-+ return "Do you want to play questions?";
-+ case IDS_QUOTES:
-+ return "\\"What\\'s in a name, %s?\\"";
-+ case IDS_LINE_BREAKS:
-+ return "Was that rhetoric?\\nNo.\\nStatement. Two all. Game point.";
-+ case IDS_NON_ASCII:
-+ return "\\303\\265\\xc2\\xa4\\\\302\\244\\\\xc3\\xb5\\344\\244\\244";
-+ default:
-+ return 0;
-+ }
-+}""", output)
-+
-+
-+class DummyOutput(object):
-+
-+ def __init__(self, type, language):
-+ self.type = type
-+ self.language = language
-+
-+ def GetType(self):
-+ return self.type
-+
-+ def GetLanguage(self):
-+ return self.language
-+
-+ def GetOutputFilename(self):
-+ return 'hello.gif'
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/format/chrome_messages_json.py b/tools/grit/grit/format/chrome_messages_json.py
-new file mode 100644
-index 0000000000..88ec1d914b
---- /dev/null
-+++ b/tools/grit/grit/format/chrome_messages_json.py
-@@ -0,0 +1,59 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+"""Formats as a .json file that can be used to localize Google Chrome
-+extensions."""
-+
-+from __future__ import print_function
-+
-+from json import JSONEncoder
-+
-+from grit import constants
-+from grit.node import message
-+
-+def Format(root, lang='en', output_dir='.'):
-+ """Format the messages as JSON."""
-+ yield '{'
-+
-+ encoder = JSONEncoder(ensure_ascii=False)
-+ format = '"%s":{"message":%s%s}'
-+ placeholder_format = '"%i":{"content":"$%i"}'
-+ first = True
-+ for child in root.ActiveDescendants():
-+ if isinstance(child, message.MessageNode):
-+ id = child.attrs['name']
-+ if id.startswith('IDR_') or id.startswith('IDS_'):
-+ id = id[4:]
-+
-+ translation_missing = child.GetCliques()[0].clique.get(lang) is None;
-+ if (child.ShouldFallbackToEnglish() and translation_missing and
-+ lang != constants.FAKE_BIDI):
-+ # Skip the string if it's not translated. Chrome will fallback
-+ # to English automatically.
-+ continue
-+
-+ loc_message = encoder.encode(child.ws_at_start + child.Translate(lang) +
-+ child.ws_at_end)
-+
-+ # Replace $n place-holders with $n$ and add an appropriate "placeholders"
-+ # entry. Note that chrome.i18n.getMessage only supports 9 placeholders:
-+ # https://developer.chrome.com/extensions/i18n#method-getMessage
-+ placeholders = ''
-+ for i in range(1, 10):
-+ if loc_message.find('$%d' % i) == -1:
-+ break
-+ loc_message = loc_message.replace('$%d' % i, '$%d$' % i)
-+ if placeholders:
-+ placeholders += ','
-+ placeholders += placeholder_format % (i, i)
-+
-+ if not first:
-+ yield ','
-+ first = False
-+
-+ if placeholders:
-+ placeholders = ',"placeholders":{%s}' % placeholders
-+ yield format % (id, loc_message, placeholders)
-+
-+ yield '}'
-diff --git a/tools/grit/grit/format/chrome_messages_json_unittest.py b/tools/grit/grit/format/chrome_messages_json_unittest.py
-new file mode 100644
-index 0000000000..a54e6bdc1c
---- /dev/null
-+++ b/tools/grit/grit/format/chrome_messages_json_unittest.py
-@@ -0,0 +1,190 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+"""Unittest for chrome_messages_json.py.
-+"""
-+
-+from __future__ import print_function
-+
-+import json
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+import unittest
-+
-+from six import StringIO
-+
-+from grit import grd_reader
-+from grit import util
-+from grit.tool import build
-+
-+class ChromeMessagesJsonFormatUnittest(unittest.TestCase):
-+
-+ # The default unittest diff limit is too low for our unittests.
-+ # Allow the framework to show the full diff output all the time.
-+ maxDiff = None
-+
-+ def testMessages(self):
-+ root = util.ParseGrdForUnittest(u"""
-+ <messages>
-+ <message name="IDS_SIMPLE_MESSAGE">
-+ Simple message.
-+ </message>
-+ <message name="IDS_QUOTES">
-+ element\u2019s \u201c<ph name="NAME">%s<ex>name</ex></ph>\u201d attribute
-+ </message>
-+ <message name="IDS_PLACEHOLDERS">
-+ <ph name="ERROR_COUNT">%1$d<ex>1</ex></ph> error, <ph name="WARNING_COUNT">%2$d<ex>1</ex></ph> warning
-+ </message>
-+ <message name="IDS_PLACEHOLDERS_SUBSTITUTED_BY_GETMESSAGE">
-+ <ph name="BEGIN">$1<ex>a</ex></ph>test<ph name="END">$2<ex>b</ex></ph>
-+ </message>
-+ <message name="IDS_STARTS_WITH_SPACE">
-+ ''' (<ph name="COUNT">%d<ex>2</ex></ph>)
-+ </message>
-+ <message name="IDS_ENDS_WITH_SPACE">
-+ (<ph name="COUNT">%d<ex>2</ex></ph>) '''
-+ </message>
-+ <message name="IDS_SPACE_AT_BOTH_ENDS">
-+ ''' (<ph name="COUNT">%d<ex>2</ex></ph>) '''
-+ </message>
-+ <message name="IDS_DOUBLE_QUOTES">
-+ A "double quoted" message.
-+ </message>
-+ <message name="IDS_BACKSLASH">
-+ \\
-+ </message>
-+ </messages>
-+ """)
-+
-+ buf = StringIO()
-+ build.RcBuilder.ProcessNode(root, DummyOutput('chrome_messages_json', 'en'),
-+ buf)
-+ output = buf.getvalue()
-+ test = u"""
-+{
-+ "SIMPLE_MESSAGE": {
-+ "message": "Simple message."
-+ },
-+ "QUOTES": {
-+ "message": "element\u2019s \u201c%s\u201d attribute"
-+ },
-+ "PLACEHOLDERS": {
-+ "message": "%1$d error, %2$d warning"
-+ },
-+ "PLACEHOLDERS_SUBSTITUTED_BY_GETMESSAGE": {
-+ "message": "$1$test$2$",
-+ "placeholders": {
-+ "1": {
-+ "content": "$1"
-+ },
-+ "2": {
-+ "content": "$2"
-+ }
-+ }
-+ },
-+ "STARTS_WITH_SPACE": {
-+ "message": " (%d)"
-+ },
-+ "ENDS_WITH_SPACE": {
-+ "message": "(%d) "
-+ },
-+ "SPACE_AT_BOTH_ENDS": {
-+ "message": " (%d) "
-+ },
-+ "DOUBLE_QUOTES": {
-+ "message": "A \\"double quoted\\" message."
-+ },
-+ "BACKSLASH": {
-+ "message": "\\\\"
-+ }
-+}
-+"""
-+ self.assertEqual(json.loads(test), json.loads(output))
-+
-+ def testTranslations(self):
-+ root = util.ParseGrdForUnittest("""
-+ <messages>
-+ <message name="ID_HELLO">Hello!</message>
-+ <message name="ID_HELLO_USER">Hello <ph name="USERNAME">%s<ex>
-+ Joi</ex></ph></message>
-+ </messages>
-+ """)
-+
-+ buf = StringIO()
-+ build.RcBuilder.ProcessNode(root, DummyOutput('chrome_messages_json', 'fr'),
-+ buf)
-+ output = buf.getvalue()
-+ test = u"""
-+{
-+ "ID_HELLO": {
-+ "message": "H\u00e9P\u00e9ll\u00f4P\u00f4!"
-+ },
-+ "ID_HELLO_USER": {
-+ "message": "H\u00e9P\u00e9ll\u00f4P\u00f4 %s"
-+ }
-+}
-+"""
-+ self.assertEqual(json.loads(test), json.loads(output))
-+
-+ def testSkipMissingTranslations(self):
-+ grd = """<?xml version="1.0" encoding="UTF-8"?>
-+<grit latest_public_release="2" current_release="3" source_lang_id="en"
-+ base_dir="%s">
-+ <outputs>
-+ </outputs>
-+ <release seq="3" allow_pseudo="False">
-+ <messages fallback_to_english="true">
-+ <message name="ID_HELLO_NO_TRANSLATION">Hello not translated</message>
-+ </messages>
-+ </release>
-+</grit>"""
-+ root = grd_reader.Parse(StringIO(grd), dir=".")
-+
-+ buf = StringIO()
-+ build.RcBuilder.ProcessNode(root, DummyOutput('chrome_messages_json', 'fr'),
-+ buf)
-+ output = buf.getvalue()
-+ test = u'{}'
-+ self.assertEqual(test, output)
-+
-+ def testVerifyMinification(self):
-+ root = util.ParseGrdForUnittest(u"""
-+ <messages>
-+ <message name="IDS">
-+ <ph name="BEGIN">$1<ex>a</ex></ph>test<ph name="END">$2<ex>b</ex></ph>
-+ </message>
-+ </messages>
-+ """)
-+
-+ buf = StringIO()
-+ build.RcBuilder.ProcessNode(root, DummyOutput('chrome_messages_json', 'en'),
-+ buf)
-+ output = buf.getvalue()
-+ test = (u'{"IDS":{"message":"$1$test$2$","placeholders":'
-+ u'{"1":{"content":"$1"},"2":{"content":"$2"}}}}')
-+ self.assertEqual(test, output)
-+
-+
-+class DummyOutput(object):
-+
-+ def __init__(self, type, language):
-+ self.type = type
-+ self.language = language
-+
-+ def GetType(self):
-+ return self.type
-+
-+ def GetLanguage(self):
-+ return self.language
-+
-+ def GetOutputFilename(self):
-+ return 'hello.gif'
-+
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/format/data_pack.py b/tools/grit/grit/format/data_pack.py
-new file mode 100644
-index 0000000000..f7128a4725
---- /dev/null
-+++ b/tools/grit/grit/format/data_pack.py
-@@ -0,0 +1,321 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+"""Support for formatting a data pack file used for platform agnostic resource
-+files.
-+"""
-+
-+from __future__ import print_function
-+
-+import collections
-+import os
-+import struct
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+import six
-+
-+from grit import util
-+from grit.node import include
-+from grit.node import message
-+from grit.node import structure
-+
-+
-+PACK_FILE_VERSION = 5
-+BINARY, UTF8, UTF16 = range(3)
-+
-+
-+GrdInfoItem = collections.namedtuple('GrdInfoItem',
-+ ['textual_id', 'id', 'path'])
-+
-+
-+class WrongFileVersion(Exception):
-+ pass
-+
-+
-+class CorruptDataPack(Exception):
-+ pass
-+
-+
-+class DataPackSizes(object):
-+ def __init__(self, header, id_table, alias_table, data):
-+ self.header = header
-+ self.id_table = id_table
-+ self.alias_table = alias_table
-+ self.data = data
-+
-+ @property
-+ def total(self):
-+ return sum(v for v in self.__dict__.values())
-+
-+ def __iter__(self):
-+ yield ('header', self.header)
-+ yield ('id_table', self.id_table)
-+ yield ('alias_table', self.alias_table)
-+ yield ('data', self.data)
-+
-+ def __eq__(self, other):
-+ return self.__dict__ == other.__dict__
-+
-+ def __repr__(self):
-+ return self.__class__.__name__ + repr(self.__dict__)
-+
-+
-+class DataPackContents(object):
-+ def __init__(self, resources, encoding, version, aliases, sizes):
-+ # Map of resource_id -> str.
-+ self.resources = resources
-+ # Encoding (int).
-+ self.encoding = encoding
-+ # Version (int).
-+ self.version = version
-+ # Map of resource_id->canonical_resource_id
-+ self.aliases = aliases
-+ # DataPackSizes instance.
-+ self.sizes = sizes
-+
-+
-+def Format(root, lang='en', output_dir='.'):
-+ """Writes out the data pack file format (platform agnostic resource file)."""
-+ id_map = root.GetIdMap()
-+ data = {}
-+ root.info = []
-+ for node in root.ActiveDescendants():
-+ with node:
-+ if isinstance(node, (include.IncludeNode, message.MessageNode,
-+ structure.StructureNode)):
-+ value = node.GetDataPackValue(lang, util.BINARY)
-+ if value is not None:
-+ resource_id = id_map[node.GetTextualIds()[0]]
-+ data[resource_id] = value
-+ root.info.append('{},{},{}'.format(
-+ node.attrs.get('name'), resource_id, node.source))
-+ return WriteDataPackToString(data, UTF8)
-+
-+
-+def ReadDataPack(input_file):
-+ return ReadDataPackFromString(util.ReadFile(input_file, util.BINARY))
-+
-+
-+def ReadDataPackFromString(data):
-+ """Reads a data pack file and returns a dictionary."""
-+ # Read the header.
-+ version = struct.unpack('<I', data[:4])[0]
-+ if version == 4:
-+ resource_count, encoding = struct.unpack('<IB', data[4:9])
-+ alias_count = 0
-+ header_size = 9
-+ elif version == 5:
-+ encoding, resource_count, alias_count = struct.unpack('<BxxxHH', data[4:12])
-+ header_size = 12
-+ else:
-+ raise WrongFileVersion('Found version: ' + str(version))
-+
-+ resources = {}
-+ kIndexEntrySize = 2 + 4 # Each entry is a uint16 and a uint32.
-+ def entry_at_index(idx):
-+ offset = header_size + idx * kIndexEntrySize
-+ return struct.unpack('<HI', data[offset:offset + kIndexEntrySize])
-+
-+ prev_resource_id, prev_offset = entry_at_index(0)
-+ for i in range(1, resource_count + 1):
-+ resource_id, offset = entry_at_index(i)
-+ resources[prev_resource_id] = data[prev_offset:offset]
-+ prev_resource_id, prev_offset = resource_id, offset
-+
-+ id_table_size = (resource_count + 1) * kIndexEntrySize
-+ # Read the alias table.
-+ kAliasEntrySize = 2 + 2 # uint16, uint16
-+ def alias_at_index(idx):
-+ offset = header_size + id_table_size + idx * kAliasEntrySize
-+ return struct.unpack('<HH', data[offset:offset + kAliasEntrySize])
-+
-+ aliases = {}
-+ for i in range(alias_count):
-+ resource_id, index = alias_at_index(i)
-+ aliased_id = entry_at_index(index)[0]
-+ aliases[resource_id] = aliased_id
-+ resources[resource_id] = resources[aliased_id]
-+
-+ alias_table_size = kAliasEntrySize * alias_count
-+ sizes = DataPackSizes(
-+ header_size, id_table_size, alias_table_size,
-+ len(data) - header_size - id_table_size - alias_table_size)
-+ assert sizes.total == len(data), 'original={} computed={}'.format(
-+ len(data), sizes.total)
-+ return DataPackContents(resources, encoding, version, aliases, sizes)
-+
-+
-+def WriteDataPackToString(resources, encoding):
-+ """Returns bytes with a map of id=>data in the data pack format."""
-+ ret = []
-+
-+ # Compute alias map.
-+ resource_ids = sorted(resources)
-+ # Use reversed() so that for duplicates lower IDs clobber higher ones.
-+ id_by_data = {resources[k]: k for k in reversed(resource_ids)}
-+ # Map of resource_id -> resource_id, where value < key.
-+ alias_map = {k: id_by_data[v] for k, v in resources.items()
-+ if id_by_data[v] != k}
-+
-+ # Write file header.
-+ resource_count = len(resources) - len(alias_map)
-+ # Padding bytes added for alignment.
-+ ret.append(struct.pack('<IBxxxHH', PACK_FILE_VERSION, encoding,
-+ resource_count, len(alias_map)))
-+ HEADER_LENGTH = 4 + 4 + 2 + 2
-+
-+ # Each main table entry is: uint16 + uint32 (and an extra entry at the end).
-+ # Each alias table entry is: uint16 + uint16.
-+ data_offset = HEADER_LENGTH + (resource_count + 1) * 6 + len(alias_map) * 4
-+
-+ # Write main table.
-+ index_by_id = {}
-+ deduped_data = []
-+ index = 0
-+ for resource_id in resource_ids:
-+ if resource_id in alias_map:
-+ continue
-+ data = resources[resource_id]
-+ if isinstance(data, six.text_type):
-+ data = data.encode('utf-8')
-+ index_by_id[resource_id] = index
-+ ret.append(struct.pack('<HI', resource_id, data_offset))
-+ data_offset += len(data)
-+ deduped_data.append(data)
-+ index += 1
-+
-+ assert index == resource_count
-+ # Add an extra entry at the end.
-+ ret.append(struct.pack('<HI', 0, data_offset))
-+
-+ # Write alias table.
-+ for resource_id in sorted(alias_map):
-+ index = index_by_id[alias_map[resource_id]]
-+ ret.append(struct.pack('<HH', resource_id, index))
-+
-+ # Write data.
-+ ret.extend(deduped_data)
-+ return b''.join(ret)
-+
-+
-+def WriteDataPack(resources, output_file, encoding):
-+ """Writes a map of id=>data into output_file as a data pack."""
-+ content = WriteDataPackToString(resources, encoding)
-+ with open(output_file, 'wb') as file:
-+ file.write(content)
-+
-+
-+def ReadGrdInfo(grd_file):
-+ info_dict = {}
-+ with open(grd_file + '.info', 'rt') as f:
-+ for line in f:
-+ item = GrdInfoItem._make(line.strip().split(','))
-+ info_dict[int(item.id)] = item
-+ return info_dict
-+
-+
-+def RePack(output_file, input_files, whitelist_file=None,
-+ suppress_removed_key_output=False,
-+ output_info_filepath=None):
-+ """Write a new data pack file by combining input pack files.
-+
-+ Args:
-+ output_file: path to the new data pack file.
-+ input_files: a list of paths to the data pack files to combine.
-+ whitelist_file: path to the file that contains the list of resource IDs
-+ that should be kept in the output file or None to include
-+ all resources.
-+ suppress_removed_key_output: allows the caller to suppress the output from
-+ RePackFromDataPackStrings.
-+ output_info_file: If not None, specify the output .info filepath.
-+
-+ Raises:
-+ KeyError: if there are duplicate keys or resource encoding is
-+ inconsistent.
-+ """
-+ input_data_packs = [ReadDataPack(filename) for filename in input_files]
-+ input_info_files = [filename + '.info' for filename in input_files]
-+ whitelist = None
-+ if whitelist_file:
-+ lines = util.ReadFile(whitelist_file, 'utf-8').strip().splitlines()
-+ if not lines:
-+ raise Exception('Whitelist file should not be empty')
-+ whitelist = set(int(x) for x in lines)
-+ inputs = [(p.resources, p.encoding) for p in input_data_packs]
-+ resources, encoding = RePackFromDataPackStrings(
-+ inputs, whitelist, suppress_removed_key_output)
-+ WriteDataPack(resources, output_file, encoding)
-+ if output_info_filepath is None:
-+ output_info_filepath = output_file + '.info'
-+ with open(output_info_filepath, 'w') as output_info_file:
-+ for filename in input_info_files:
-+ with open(filename, 'r') as info_file:
-+ output_info_file.writelines(info_file.readlines())
-+
-+
-+def RePackFromDataPackStrings(inputs, whitelist,
-+ suppress_removed_key_output=False):
-+ """Combines all inputs into one.
-+
-+ Args:
-+ inputs: a list of (resources_by_id, encoding) tuples to be combined.
-+ whitelist: a list of resource IDs that should be kept in the output string
-+ or None to include all resources.
-+ suppress_removed_key_output: Do not print removed keys.
-+
-+ Returns:
-+ Returns (resources_by_id, encoding).
-+
-+ Raises:
-+ KeyError: if there are duplicate keys or resource encoding is
-+ inconsistent.
-+ """
-+ resources = {}
-+ encoding = None
-+ for input_resources, input_encoding in inputs:
-+ # Make sure we have no dups.
-+ duplicate_keys = set(input_resources.keys()) & set(resources.keys())
-+ if duplicate_keys:
-+ raise KeyError('Duplicate keys: ' + str(list(duplicate_keys)))
-+
-+ # Make sure encoding is consistent.
-+ if encoding in (None, BINARY):
-+ encoding = input_encoding
-+ elif input_encoding not in (BINARY, encoding):
-+ raise KeyError('Inconsistent encodings: ' + str(encoding) +
-+ ' vs ' + str(input_encoding))
-+
-+ if whitelist:
-+ whitelisted_resources = dict([(key, input_resources[key])
-+ for key in input_resources.keys()
-+ if key in whitelist])
-+ resources.update(whitelisted_resources)
-+ removed_keys = [key for key in input_resources.keys()
-+ if key not in whitelist]
-+ if not suppress_removed_key_output:
-+ for key in removed_keys:
-+ print('RePackFromDataPackStrings Removed Key:', key)
-+ else:
-+ resources.update(input_resources)
-+
-+ # Encoding is 0 for BINARY, 1 for UTF8 and 2 for UTF16
-+ if encoding is None:
-+ encoding = BINARY
-+ return resources, encoding
-+
-+
-+def main():
-+ # Write a simple file.
-+ data = {1: '', 4: 'this is id 4', 6: 'this is id 6', 10: ''}
-+ WriteDataPack(data, 'datapack1.pak', UTF8)
-+ data2 = {1000: 'test', 5: 'five'}
-+ WriteDataPack(data2, 'datapack2.pak', UTF8)
-+ print('wrote datapack1 and datapack2 to current directory.')
-+
-+
-+if __name__ == '__main__':
-+ main()
-diff --git a/tools/grit/grit/format/data_pack_unittest.py b/tools/grit/grit/format/data_pack_unittest.py
-new file mode 100644
-index 0000000000..fcd7035473
---- /dev/null
-+++ b/tools/grit/grit/format/data_pack_unittest.py
-@@ -0,0 +1,102 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for grit.format.data_pack'''
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+import unittest
-+
-+from grit.format import data_pack
-+
-+
-+class FormatDataPackUnittest(unittest.TestCase):
-+ def testReadDataPackV4(self):
-+ expected_data = (
-+ b'\x04\x00\x00\x00' # header(version
-+ b'\x04\x00\x00\x00' # no. entries,
-+ b'\x01' # encoding)
-+ b'\x01\x00\x27\x00\x00\x00' # index entry 1
-+ b'\x04\x00\x27\x00\x00\x00' # index entry 4
-+ b'\x06\x00\x33\x00\x00\x00' # index entry 6
-+ b'\x0a\x00\x3f\x00\x00\x00' # index entry 10
-+ b'\x00\x00\x3f\x00\x00\x00' # extra entry for the size of last
-+ b'this is id 4this is id 6') # data
-+ expected_data_pack = data_pack.DataPackContents(
-+ {
-+ 1: b'',
-+ 4: b'this is id 4',
-+ 6: b'this is id 6',
-+ 10: b'',
-+ }, data_pack.UTF8, 4, {}, data_pack.DataPackSizes(9, 30, 0, 24))
-+ loaded = data_pack.ReadDataPackFromString(expected_data)
-+ self.assertDictEqual(expected_data_pack.__dict__, loaded.__dict__)
-+
-+ def testReadWriteDataPackV5(self):
-+ expected_data = (
-+ b'\x05\x00\x00\x00' # version
-+ b'\x01\x00\x00\x00' # encoding & padding
-+ b'\x03\x00' # resource_count
-+ b'\x01\x00' # alias_count
-+ b'\x01\x00\x28\x00\x00\x00' # index entry 1
-+ b'\x04\x00\x28\x00\x00\x00' # index entry 4
-+ b'\x06\x00\x34\x00\x00\x00' # index entry 6
-+ b'\x00\x00\x40\x00\x00\x00' # extra entry for the size of last
-+ b'\x0a\x00\x01\x00' # alias table
-+ b'this is id 4this is id 6') # data
-+ input_resources = {
-+ 1: b'',
-+ 4: b'this is id 4',
-+ 6: b'this is id 6',
-+ 10: b'this is id 4',
-+ }
-+ data = data_pack.WriteDataPackToString(input_resources, data_pack.UTF8)
-+ self.assertEquals(data, expected_data)
-+
-+ expected_data_pack = data_pack.DataPackContents({
-+ 1: b'',
-+ 4: input_resources[4],
-+ 6: input_resources[6],
-+ 10: input_resources[4],
-+ }, data_pack.UTF8, 5, {10: 4}, data_pack.DataPackSizes(12, 24, 4, 24))
-+ loaded = data_pack.ReadDataPackFromString(expected_data)
-+ self.assertDictEqual(expected_data_pack.__dict__, loaded.__dict__)
-+
-+ def testRePackUnittest(self):
-+ expected_with_whitelist = {
-+ 1: 'Never gonna', 10: 'give you up', 20: 'Never gonna let',
-+ 30: 'you down', 40: 'Never', 50: 'gonna run around and',
-+ 60: 'desert you'}
-+ expected_without_whitelist = {
-+ 1: 'Never gonna', 10: 'give you up', 20: 'Never gonna let', 65: 'Close',
-+ 30: 'you down', 40: 'Never', 50: 'gonna run around and', 4: 'click',
-+ 60: 'desert you', 6: 'chirr', 32: 'oops, try again', 70: 'Awww, snap!'}
-+ inputs = [{1: 'Never gonna', 4: 'click', 6: 'chirr', 10: 'give you up'},
-+ {20: 'Never gonna let', 30: 'you down', 32: 'oops, try again'},
-+ {40: 'Never', 50: 'gonna run around and', 60: 'desert you'},
-+ {65: 'Close', 70: 'Awww, snap!'}]
-+ whitelist = [1, 10, 20, 30, 40, 50, 60]
-+ inputs = [(i, data_pack.UTF8) for i in inputs]
-+
-+ # RePack using whitelist
-+ output, _ = data_pack.RePackFromDataPackStrings(
-+ inputs, whitelist, suppress_removed_key_output=True)
-+ self.assertDictEqual(expected_with_whitelist, output,
-+ 'Incorrect resource output')
-+
-+ # RePack a None whitelist
-+ output, _ = data_pack.RePackFromDataPackStrings(
-+ inputs, None, suppress_removed_key_output=True)
-+ self.assertDictEqual(expected_without_whitelist, output,
-+ 'Incorrect resource output')
-+
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/format/gen_predetermined_ids.py b/tools/grit/grit/format/gen_predetermined_ids.py
-new file mode 100644
-index 0000000000..9b2aa7b1a5
---- /dev/null
-+++ b/tools/grit/grit/format/gen_predetermined_ids.py
-@@ -0,0 +1,144 @@
-+#!/usr/bin/env python
-+# Copyright 2017 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+"""
-+A tool to generate a predetermined resource ids file that can be used as an
-+input to grit via the -p option. This is meant to be run manually every once in
-+a while and its output checked in. See tools/gritsettings/README.md for details.
-+"""
-+
-+from __future__ import print_function
-+
-+import os
-+import re
-+import sys
-+
-+# Regular expression for parsing the #define macro format. Matches both the
-+# version of the macro with whitelist support and the one without. For example,
-+# Without generate whitelist flag:
-+# #define IDS_FOO_MESSAGE 1234
-+# With generate whitelist flag:
-+# #define IDS_FOO_MESSAGE (::ui::WhitelistedResource<1234>(), 1234)
-+RESOURCE_EXTRACT_REGEX = re.compile(r'^#define (\S*).* (\d+)\)?$', re.MULTILINE)
-+
-+ORDERED_RESOURCE_IDS_REGEX = re.compile(r'^Resource=(\d*)$', re.MULTILINE)
-+
-+
-+def _GetResourceNameIdPairsIter(string_to_scan):
-+ """Gets an iterator of the resource name and id pairs of the given string.
-+
-+ Scans the input string for lines of the form "#define NAME ID" and returns
-+ an iterator over all matching (NAME, ID) pairs.
-+
-+ Args:
-+ string_to_scan: The input string to scan.
-+
-+ Yields:
-+ A tuple of name and id.
-+ """
-+ for match in RESOURCE_EXTRACT_REGEX.finditer(string_to_scan):
-+ yield match.group(1, 2)
-+
-+
-+def _ReadOrderedResourceIds(path):
-+ """Reads ordered resource ids from the given file.
-+
-+ The resources are expected to be of the format produced by running Chrome
-+ with --print-resource-ids command line.
-+
-+ Args:
-+ path: File path to read resource ids from.
-+
-+ Returns:
-+ An array of ordered resource ids.
-+ """
-+ ordered_resource_ids = []
-+ with open(path, "r") as f:
-+ for match in ORDERED_RESOURCE_IDS_REGEX.finditer(f.read()):
-+ ordered_resource_ids.append(int(match.group(1)))
-+ return ordered_resource_ids
-+
-+
-+def GenerateResourceMapping(original_resources, ordered_resource_ids):
-+ """Generates a resource mapping from the ordered ids and the original mapping.
-+
-+ The returned dict will assign new ids to ordered_resource_ids numerically
-+ increasing from 101.
-+
-+ Args:
-+ original_resources: A dict of original resource ids to resource names.
-+ ordered_resource_ids: An array of ordered resource ids.
-+
-+ Returns:
-+ A dict of resource ids to resource names.
-+ """
-+ output_resource_map = {}
-+ # 101 is used as the starting value since other parts of GRIT require it to be
-+ # the minimum (e.g. rc_header.py) based on Windows resource numbering.
-+ next_id = 101
-+ for original_id in ordered_resource_ids:
-+ resource_name = original_resources[original_id]
-+ output_resource_map[next_id] = resource_name
-+ next_id += 1
-+ return output_resource_map
-+
-+
-+def ReadResourceIdsFromFile(file, original_resources):
-+ """Reads resource ids from a GRIT-produced header file.
-+
-+ Args:
-+ file: File to a GRIT-produced header file to read from.
-+ original_resources: Dict of resource ids to resource names to add to.
-+ """
-+ for resource_name, resource_id in _GetResourceNameIdPairsIter(file.read()):
-+ original_resources[int(resource_id)] = resource_name
-+
-+
-+def _ReadOriginalResourceIds(out_dir):
-+ """Reads resource ids from GRIT header files in the specified directory.
-+
-+ Args:
-+ out_dir: A Chrome build output directory (e.g. out/gn) to scan.
-+
-+ Returns:
-+ A dict of resource ids to resource names.
-+ """
-+ original_resources = {}
-+ for root, dirnames, filenames in os.walk(out_dir + '/gen'):
-+ for filename in filenames:
-+ if filename.endswith(('_resources.h', '_settings.h', '_strings.h')):
-+ with open(os.path.join(root, filename), "r") as f:
-+ ReadResourceIdsFromFile(f, original_resources)
-+ return original_resources
-+
-+
-+def _GeneratePredeterminedIdsFile(ordered_resources_file, out_dir):
-+ """Generates a predetermined ids file.
-+
-+ Args:
-+ ordered_resources_file: File path to read ordered resource ids from.
-+ out_dir: A Chrome build output directory (e.g. out/gn) to scan.
-+
-+ Returns:
-+ A dict of resource ids to resource names.
-+ """
-+ original_resources = _ReadOriginalResourceIds(out_dir)
-+ ordered_resource_ids = _ReadOrderedResourceIds(ordered_resources_file)
-+ output_resource_map = GenerateResourceMapping(original_resources,
-+ ordered_resource_ids)
-+ for res_id in sorted(output_resource_map.keys()):
-+ print(output_resource_map[res_id], res_id)
-+
-+
-+def main(argv):
-+ if len(argv) != 2:
-+ print("usage: gen_predetermined_ids.py <ordered_resources_file> <out_dir>")
-+ sys.exit(1)
-+ ordered_resources_file, out_dir = argv[0], argv[1]
-+ _GeneratePredeterminedIdsFile(ordered_resources_file, out_dir)
-+
-+
-+if '__main__' == __name__:
-+ main(sys.argv[1:])
-diff --git a/tools/grit/grit/format/gen_predetermined_ids_unittest.py b/tools/grit/grit/format/gen_predetermined_ids_unittest.py
-new file mode 100644
-index 0000000000..bd0331adb4
---- /dev/null
-+++ b/tools/grit/grit/format/gen_predetermined_ids_unittest.py
-@@ -0,0 +1,46 @@
-+#!/usr/bin/env python
-+# Copyright 2017 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for the gen_predetermined_ids module.'''
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+import unittest
-+
-+from six import StringIO
-+
-+from grit.format import gen_predetermined_ids
-+
-+class GenPredeterminedIdsUnittest(unittest.TestCase):
-+ def testGenerateResourceMapping(self):
-+ original_resources = {200: 'A', 201: 'B', 300: 'C', 350: 'D', 370: 'E'}
-+ ordered_resource_ids = [300, 201, 370]
-+ mapping = gen_predetermined_ids.GenerateResourceMapping(
-+ original_resources, ordered_resource_ids)
-+ self.assertEqual({101: 'C', 102: 'B', 103: 'E'}, mapping)
-+
-+ def testReadResourceIdsFromFile(self):
-+ f = StringIO('''
-+// This file is automatically generated by GRIT. Do not edit.
-+
-+#pragma once
-+
-+#define IDS_BOOKMARKS_NO_ITEMS 12500
-+#define IDS_BOOKMARK_BAR_IMPORT_LINK (::ui::WhitelistedResource<12501>(), 12501)
-+#define IDS_BOOKMARK_X (::ui::WhitelistedResource<12502>(), 12502)
-+''')
-+ resources = {}
-+ gen_predetermined_ids.ReadResourceIdsFromFile(f, resources)
-+ self.assertEqual({12500: 'IDS_BOOKMARKS_OPEN_ALL',
-+ 12501: 'IDS_BOOKMARKS_OPEN_ALL_INCOGNITO',
-+ 12502: 'IDS_BOOKMARK_X'}, resources)
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/format/gzip_string.py b/tools/grit/grit/format/gzip_string.py
-new file mode 100644
-index 0000000000..3cd17185c9
---- /dev/null
-+++ b/tools/grit/grit/format/gzip_string.py
-@@ -0,0 +1,46 @@
-+# Copyright (c) 2016 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+"""Provides gzip utilities for strings.
-+"""
-+
-+from __future__ import print_function
-+
-+import gzip
-+import io
-+import subprocess
-+
-+
-+def GzipStringRsyncable(data):
-+ # Make call to host system's gzip to get access to --rsyncable option. This
-+ # option makes updates much smaller - if one line is changed in the resource,
-+ # it won't have to push the entire compressed resource with the update.
-+ # Instead, --rsyncable breaks the file into small chunks, so that one doesn't
-+ # affect the other in compression, and then only that chunk will have to be
-+ # updated.
-+ gzip_proc = subprocess.Popen(['gzip', '--stdout', '--rsyncable',
-+ '--best', '--no-name'],
-+ stdin=subprocess.PIPE,
-+ stdout=subprocess.PIPE,
-+ stderr=subprocess.PIPE)
-+ data, stderr = gzip_proc.communicate(data)
-+ if gzip_proc.returncode != 0:
-+ raise subprocess.CalledProcessError(gzip_proc.returncode, 'gzip',
-+ stderr)
-+ return data
-+
-+
-+def GzipString(data):
-+ # Gzipping using Python's built in gzip: Windows doesn't ship with gzip, and
-+ # OSX's gzip does not have an --rsyncable option built in. Although this is
-+ # not preferable to --rsyncable, it is an option for the systems that do
-+ # not have --rsyncable. If used over GzipStringRsyncable, the primary
-+ # difference of this function's compression will be larger updates every time
-+ # a compressed resource is changed.
-+ gzip_output = io.BytesIO()
-+ with gzip.GzipFile(mode='wb', compresslevel=9, fileobj=gzip_output,
-+ mtime=0) as gzip_file:
-+ gzip_file.write(data)
-+ data = gzip_output.getvalue()
-+ gzip_output.close()
-+ return data
-diff --git a/tools/grit/grit/format/gzip_string_unittest.py b/tools/grit/grit/format/gzip_string_unittest.py
-new file mode 100644
-index 0000000000..c0cfbe1837
---- /dev/null
-+++ b/tools/grit/grit/format/gzip_string_unittest.py
-@@ -0,0 +1,65 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2016 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for grit.format.gzip_string'''
-+
-+from __future__ import print_function
-+
-+import gzip
-+import io
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+import unittest
-+
-+from grit.format import gzip_string
-+
-+
-+class FormatGzipStringUnittest(unittest.TestCase):
-+
-+ def testGzipStringRsyncable(self):
-+ # Can only test the rsyncable version on platforms which support rsyncable,
-+ # which at the moment is Linux.
-+ if sys.platform == 'linux2':
-+ header_begin = (b'\x1f\x8b') # gzip first two bytes
-+ input = (b'TEST STRING STARTING NOW'
-+ b'continuing'
-+ b'<even more>'
-+ b'<finished NOW>')
-+
-+ compressed = gzip_string.GzipStringRsyncable(input)
-+ self.failUnless(header_begin == compressed[:2])
-+
-+ compressed_file = io.BytesIO()
-+ compressed_file.write(compressed)
-+ compressed_file.seek(0)
-+
-+ with gzip.GzipFile(mode='rb', fileobj=compressed_file) as f:
-+ output = f.read()
-+ self.failUnless(output == input)
-+
-+ def testGzipString(self):
-+ header_begin = b'\x1f\x8b' # gzip first two bytes
-+ input = (b'TEST STRING STARTING NOW'
-+ b'continuing'
-+ b'<even more>'
-+ b'<finished NOW>')
-+
-+ compressed = gzip_string.GzipString(input)
-+ self.failUnless(header_begin == compressed[:2])
-+
-+ compressed_file = io.BytesIO()
-+ compressed_file.write(compressed)
-+ compressed_file.seek(0)
-+
-+ with gzip.GzipFile(mode='rb', fileobj=compressed_file) as f:
-+ output = f.read()
-+ self.failUnless(output == input)
-+
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/format/html_inline.py b/tools/grit/grit/format/html_inline.py
-new file mode 100644
-index 0000000000..da55216ea4
---- /dev/null
-+++ b/tools/grit/grit/format/html_inline.py
-@@ -0,0 +1,602 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+"""Flattens a HTML file by inlining its external resources.
-+
-+This is a small script that takes a HTML file, looks for src attributes
-+and inlines the specified file, producing one HTML file with no external
-+dependencies. It recursively inlines the included files.
-+"""
-+
-+from __future__ import print_function
-+
-+import os
-+import re
-+import sys
-+import base64
-+import mimetypes
-+
-+from grit import lazy_re
-+from grit import util
-+from grit.format import minifier
-+
-+# There is a python bug that makes mimetypes crash if the Windows
-+# registry contains non-Latin keys ( http://bugs.python.org/issue9291
-+# ). Initing manually and blocking external mime-type databases will
-+# prevent that bug and if we add svg manually, it will still give us
-+# the data we need.
-+mimetypes.init([])
-+mimetypes.add_type('image/svg+xml', '.svg')
-+
-+# webm video type is not always available if mimetype package is outdated.
-+mimetypes.add_type('video/webm', '.webm')
-+
-+DIST_DEFAULT = 'chromium'
-+DIST_ENV_VAR = 'CHROMIUM_BUILD'
-+DIST_SUBSTR = '%DISTRIBUTION%'
-+
-+# Matches beginning of an "if" block.
-+_BEGIN_IF_BLOCK = lazy_re.compile(
-+ r'<if [^>]*?expr=("(?P<expr1>[^">]*)"|\'(?P<expr2>[^\'>]*)\')[^>]*?>')
-+
-+# Matches ending of an "if" block.
-+_END_IF_BLOCK = lazy_re.compile(r'</if>')
-+
-+# Used by DoInline to replace various links with inline content.
-+_STYLESHEET_RE = lazy_re.compile(
-+ r'<link rel="stylesheet"[^>]+?href="(?P<filename>[^"]*)".*?>(\s*</link>)?',
-+ re.DOTALL)
-+_INCLUDE_RE = lazy_re.compile(
-+ r'(?P<comment>\/\/ )?<include[^>]+?'
-+ r'src=("(?P<file1>[^">]*)"|\'(?P<file2>[^\'>]*)\').*?>(\s*</include>)?',
-+ re.DOTALL)
-+_SRC_RE = lazy_re.compile(
-+ r'<(?!script)(?:[^>]+?\s)src="(?!\[\[|{{)(?P<filename>[^"\']*)"',
-+ re.MULTILINE)
-+# This re matches '<img srcset="..."' or '<source srcset="..."'
-+_SRCSET_RE = lazy_re.compile(
-+ r'<(img|source)\b(?:[^>]*?\s)srcset="(?!\[\[|{{|\$i18n{)'
-+ r'(?P<srcset>[^"\']*)"',
-+ re.MULTILINE)
-+# This re is for splitting srcset value string into "image candidate strings".
-+# Notes:
-+# - HTML 5.2 states that URL cannot start or end with comma.
-+# - the "descriptor" is either "width descriptor" or "pixel density descriptor".
-+# The first one consists of "valid non-negative integer + letter 'x'",
-+# the second one is formed of "positive valid floating-point number +
-+# letter 'w'". As a reasonable compromise, we match a list of characters
-+# that form both of them.
-+# Matches for example "img2.png 2x" or "img9.png 11E-2w".
-+_SRCSET_ENTRY_RE = lazy_re.compile(
-+ r'\s*(?P<url>[^,\s]\S+[^,\s])'
-+ r'(?:\s+(?P<descriptor>[\deE.-]+[wx]))?\s*'
-+ r'(?P<separator>,|$)',
-+ re.MULTILINE)
-+_ICON_RE = lazy_re.compile(
-+ r'<link rel="icon"\s(?:[^>]+?\s)?'
-+ r'href=(?P<quote>")(?P<filename>[^"\']*)\1',
-+ re.MULTILINE)
-+
-+
-+def GetDistribution():
-+ """Helper function that gets the distribution we are building.
-+
-+ Returns:
-+ string
-+ """
-+ distribution = DIST_DEFAULT
-+ if DIST_ENV_VAR in os.environ:
-+ distribution = os.environ[DIST_ENV_VAR]
-+ if len(distribution) > 1 and distribution[0] == '_':
-+ distribution = distribution[1:].lower()
-+ return distribution
-+
-+def ConvertFileToDataURL(filename, base_path, distribution, inlined_files,
-+ names_only):
-+ """Convert filename to inlined data URI.
-+
-+ Takes a filename from ether "src" or "srcset", and attempts to read the file
-+ at 'filename'. Returns data URI as string with given file inlined.
-+ If it finds DIST_SUBSTR string in file name, replaces it with distribution.
-+ If filename contains ':', it is considered URL and not translated.
-+
-+ Args:
-+ filename: filename string from ether src or srcset attributes.
-+ base_path: path that to look for files in
-+ distribution: string that should replace DIST_SUBSTR
-+ inlined_files: The name of the opened file is appended to this list.
-+ names_only: If true, the function will not read the file but just return "".
-+ It will still add the filename to |inlined_files|.
-+
-+ Returns:
-+ string
-+ """
-+ if filename.find(':') != -1:
-+ # filename is probably a URL, which we don't want to bother inlining
-+ return filename
-+
-+ filename = filename.replace(DIST_SUBSTR , distribution)
-+ filepath = os.path.normpath(os.path.join(base_path, filename))
-+ inlined_files.add(filepath)
-+
-+ if names_only:
-+ return ""
-+
-+ mimetype = mimetypes.guess_type(filename)[0]
-+ if mimetype is None:
-+ raise Exception('%s is of an an unknown type and '
-+ 'cannot be stored in a data url.' % filename)
-+ inline_data = base64.standard_b64encode(util.ReadFile(filepath, util.BINARY))
-+ return 'data:%s;base64,%s' % (mimetype, inline_data.decode('utf-8'))
-+
-+
-+def SrcInlineAsDataURL(
-+ src_match, base_path, distribution, inlined_files, names_only=False,
-+ filename_expansion_function=None):
-+ """regex replace function.
-+
-+ Takes a regex match for src="filename", attempts to read the file
-+ at 'filename' and returns the src attribute with the file inlined
-+ as a data URI. If it finds DIST_SUBSTR string in file name, replaces
-+ it with distribution.
-+
-+ Args:
-+ src_match: regex match object with 'filename' named capturing group
-+ base_path: path that to look for files in
-+ distribution: string that should replace DIST_SUBSTR
-+ inlined_files: The name of the opened file is appended to this list.
-+ names_only: If true, the function will not read the file but just return "".
-+ It will still add the filename to |inlined_files|.
-+
-+ Returns:
-+ string
-+ """
-+ filename = src_match.group('filename')
-+ if filename_expansion_function:
-+ filename = filename_expansion_function(filename)
-+
-+ data_url = ConvertFileToDataURL(filename, base_path, distribution,
-+ inlined_files, names_only)
-+
-+ if not data_url:
-+ return data_url
-+
-+ prefix = src_match.string[src_match.start():src_match.start('filename')]
-+ suffix = src_match.string[src_match.end('filename'):src_match.end()]
-+ return prefix + data_url + suffix
-+
-+def SrcsetInlineAsDataURL(
-+ srcset_match, base_path, distribution, inlined_files, names_only=False,
-+ filename_expansion_function=None):
-+ """regex replace function to inline files in srcset="..." attributes
-+
-+ Takes a regex match for srcset="filename 1x, filename 2x, ...", attempts to
-+ read the files referenced by filenames and returns the srcset attribute with
-+ the files inlined as a data URI. If it finds DIST_SUBSTR string in file name,
-+ replaces it with distribution.
-+
-+ Args:
-+ srcset_match: regex match object with 'srcset' named capturing group
-+ base_path: path that to look for files in
-+ distribution: string that should replace DIST_SUBSTR
-+ inlined_files: The name of the opened file is appended to this list.
-+ names_only: If true, the function will not read the file but just return "".
-+ It will still add the filename to |inlined_files|.
-+
-+ Returns:
-+ string
-+ """
-+ srcset = srcset_match.group('srcset')
-+
-+ if not srcset:
-+ return srcset_match.group(0)
-+
-+ # HTML 5.2 defines srcset as a list of "image candidate strings".
-+ # Each of them consists of URL and descriptor.
-+ # _SRCSET_ENTRY_RE splits srcset into a list of URLs, descriptors and
-+ # commas.
-+ # The descriptor part will be None if that optional regex didn't match
-+ parts = _SRCSET_ENTRY_RE.split(srcset)
-+
-+ if not parts:
-+ return srcset_match.group(0)
-+
-+ # List of image candidate strings that will form new srcset="..."
-+ new_candidates = []
-+
-+ # When iterating over split srcset we fill this parts of a single image
-+ # candidate string: [url, descriptor]
-+ candidate = [];
-+
-+ # Each entry should consist of some text before the entry, the url,
-+ # the descriptor or None if the entry has no descriptor, a comma separator or
-+ # the end of the line, and finally some text after the entry (which is the
-+ # same as the text before the next entry).
-+ for i in range(0, len(parts) - 1, 4):
-+ before, url, descriptor, separator, after = parts[i:i+5]
-+
-+ # There must be a comma-separated next entry or this must be the last entry.
-+ assert separator == "," or (separator == "" and i == len(parts) - 5), (
-+ "Bad srcset format in {}".format(srcset_match.group(0)))
-+ # Both before and after the entry must be empty
-+ assert before == after == "", (
-+ "Bad srcset format in {}".format(srcset_match.group(0)))
-+
-+ if filename_expansion_function:
-+ filename = filename_expansion_function(url)
-+ else:
-+ filename = url
-+
-+ data_url = ConvertFileToDataURL(filename, base_path, distribution,
-+ inlined_files, names_only)
-+
-+ # This is not "names_only" mode
-+ if data_url:
-+ candidate = [data_url]
-+ if descriptor:
-+ candidate.append(descriptor)
-+
-+ new_candidates.append(" ".join(candidate))
-+
-+ prefix = srcset_match.string[srcset_match.start():
-+ srcset_match.start('srcset')]
-+ suffix = srcset_match.string[srcset_match.end('srcset'):srcset_match.end()]
-+ return prefix + ','.join(new_candidates) + suffix
-+
-+class InlinedData:
-+ """Helper class holding the results from DoInline().
-+
-+ Holds the inlined data and the set of filenames of all the inlined
-+ files.
-+ """
-+ def __init__(self, inlined_data, inlined_files):
-+ self.inlined_data = inlined_data
-+ self.inlined_files = inlined_files
-+
-+def DoInline(
-+ input_filename, grd_node, allow_external_script=False,
-+ preprocess_only=False, names_only=False, strip_whitespace=False,
-+ rewrite_function=None, filename_expansion_function=None):
-+ """Helper function that inlines the resources in a specified file.
-+
-+ Reads input_filename, finds all the src attributes and attempts to
-+ inline the files they are referring to, then returns the result and
-+ the set of inlined files.
-+
-+ Args:
-+ input_filename: name of file to read in
-+ grd_node: html node from the grd file for this include tag
-+ preprocess_only: Skip all HTML processing, only handle <if> and <include>.
-+ names_only: |nil| will be returned for the inlined contents (faster).
-+ strip_whitespace: remove whitespace and comments in the input files.
-+ rewrite_function: function(filepath, text, distribution) which will be
-+ called to rewrite html content before inlining images.
-+ filename_expansion_function: function(filename) which will be called to
-+ rewrite filenames before attempting to read them.
-+ Returns:
-+ a tuple of the inlined data as a string and the set of filenames
-+ of all the inlined files
-+ """
-+ if filename_expansion_function:
-+ input_filename = filename_expansion_function(input_filename)
-+ input_filepath = os.path.dirname(input_filename)
-+ distribution = GetDistribution()
-+
-+ # Keep track of all the files we inline.
-+ inlined_files = set()
-+
-+ def SrcReplace(src_match, filepath=input_filepath,
-+ inlined_files=inlined_files):
-+ """Helper function to provide SrcInlineAsDataURL with the base file path"""
-+ return SrcInlineAsDataURL(
-+ src_match, filepath, distribution, inlined_files, names_only=names_only,
-+ filename_expansion_function=filename_expansion_function)
-+
-+ def SrcsetReplace(srcset_match, filepath=input_filepath,
-+ inlined_files=inlined_files):
-+ """Helper function to provide SrcsetInlineAsDataURL with the base file
-+ path.
-+ """
-+ return SrcsetInlineAsDataURL(
-+ srcset_match, filepath, distribution, inlined_files,
-+ names_only=names_only,
-+ filename_expansion_function=filename_expansion_function)
-+
-+ def GetFilepath(src_match, base_path = input_filepath):
-+ filename = [v for k, v in src_match.groupdict().items()
-+ if k.startswith('file') and v][0]
-+
-+ if filename.find(':') != -1:
-+ # filename is probably a URL, which we don't want to bother inlining
-+ return None
-+
-+ filename = filename.replace('%DISTRIBUTION%', distribution)
-+ if filename_expansion_function:
-+ filename = filename_expansion_function(filename)
-+ return os.path.normpath(os.path.join(base_path, filename))
-+
-+ def IsConditionSatisfied(src_match):
-+ expr1 = src_match.group('expr1') or ''
-+ expr2 = src_match.group('expr2') or ''
-+ return grd_node is None or grd_node.EvaluateCondition(expr1 + expr2)
-+
-+ def CheckConditionalElements(str):
-+ """Helper function to conditionally inline inner elements"""
-+ while True:
-+ begin_if = _BEGIN_IF_BLOCK.search(str)
-+ if begin_if is None:
-+ if _END_IF_BLOCK.search(str) is not None:
-+ raise Exception('Unmatched </if>')
-+ return str
-+
-+ condition_satisfied = IsConditionSatisfied(begin_if)
-+ leading = str[0:begin_if.start()]
-+ content_start = begin_if.end()
-+
-+ # Find matching "if" block end.
-+ count = 1
-+ pos = begin_if.end()
-+ while True:
-+ end_if = _END_IF_BLOCK.search(str, pos)
-+ if end_if is None:
-+ raise Exception('Unmatched <if>')
-+
-+ next_if = _BEGIN_IF_BLOCK.search(str, pos)
-+ if next_if is None or next_if.start() >= end_if.end():
-+ count = count - 1
-+ if count == 0:
-+ break
-+ pos = end_if.end()
-+ else:
-+ count = count + 1
-+ pos = next_if.end()
-+
-+ content = str[content_start:end_if.start()]
-+ trailing = str[end_if.end():]
-+
-+ if condition_satisfied:
-+ str = leading + CheckConditionalElements(content) + trailing
-+ else:
-+ str = leading + trailing
-+
-+ def InlineFileContents(src_match,
-+ pattern,
-+ inlined_files=inlined_files,
-+ strip_whitespace=False):
-+ """Helper function to inline external files of various types"""
-+ filepath = GetFilepath(src_match)
-+ if filepath is None:
-+ return src_match.group(0)
-+ inlined_files.add(filepath)
-+
-+ if names_only:
-+ inlined_files.update(GetResourceFilenames(
-+ filepath,
-+ grd_node,
-+ allow_external_script,
-+ rewrite_function,
-+ filename_expansion_function=filename_expansion_function))
-+ return ""
-+ # To recursively save inlined files, we need InlinedData instance returned
-+ # by DoInline.
-+ inlined_data_inst=DoInline(filepath, grd_node,
-+ allow_external_script=allow_external_script,
-+ preprocess_only=preprocess_only,
-+ strip_whitespace=strip_whitespace,
-+ filename_expansion_function=filename_expansion_function)
-+
-+ inlined_files.update(inlined_data_inst.inlined_files)
-+
-+ return pattern % inlined_data_inst.inlined_data;
-+
-+
-+ def InlineIncludeFiles(src_match):
-+ """Helper function to directly inline generic external files (without
-+ wrapping them with any kind of tags).
-+ """
-+ return InlineFileContents(src_match, '%s')
-+
-+ def InlineScript(match):
-+ """Helper function to inline external script files"""
-+ attrs = (match.group('attrs1') + match.group('attrs2')).strip()
-+ if attrs:
-+ attrs = ' ' + attrs
-+ return InlineFileContents(match, '<script' + attrs + '>%s</script>',
-+ strip_whitespace=True)
-+
-+ def InlineCSSText(text, css_filepath):
-+ """Helper function that inlines external resources in CSS text"""
-+ filepath = os.path.dirname(css_filepath)
-+ # Allow custom modifications before inlining images.
-+ if rewrite_function:
-+ text = rewrite_function(filepath, text, distribution)
-+ text = InlineCSSImages(text, filepath)
-+ return InlineCSSImports(text, filepath)
-+
-+ def InlineCSSFile(src_match, pattern, base_path=input_filepath):
-+ """Helper function to inline external CSS files.
-+
-+ Args:
-+ src_match: A regular expression match with a named group named "filename".
-+ pattern: The pattern to replace with the contents of the CSS file.
-+ base_path: The base path to use for resolving the CSS file.
-+
-+ Returns:
-+ The text that should replace the reference to the CSS file.
-+ """
-+ filepath = GetFilepath(src_match, base_path)
-+ if filepath is None:
-+ return src_match.group(0)
-+
-+ # Even if names_only is set, the CSS file needs to be opened, because it
-+ # can link to images that need to be added to the file set.
-+ inlined_files.add(filepath)
-+
-+ # Inline stylesheets included in this css file.
-+ text = _INCLUDE_RE.sub(InlineIncludeFiles, util.ReadFile(filepath, 'utf-8'))
-+ # When resolving CSS files we need to pass in the path so that relative URLs
-+ # can be resolved.
-+
-+ return pattern % InlineCSSText(text, filepath)
-+
-+ def GetUrlRegexString(postfix=''):
-+ """Helper function that returns a string for a regex that matches url('')
-+ but not url([[ ]]) or url({{ }}). Appends |postfix| to group names.
-+ """
-+ url_re = (r'url\((?!\[\[|{{)(?P<q%s>"|\'|)(?P<filename%s>[^"\'()]*)'
-+ r'(?P=q%s)\)')
-+ return url_re % (postfix, postfix, postfix)
-+
-+ def InlineCSSImages(text, filepath=input_filepath):
-+ """Helper function that inlines external images in CSS backgrounds."""
-+ # Replace contents of url() for css attributes: content, background,
-+ # or *-image.
-+ property_re = r'(content|background|[\w-]*-image):[^;]*'
-+ # Replace group names to prevent duplicates when forming value_re.
-+ image_set_value_re = (r'image-set\(([ ]*' + GetUrlRegexString('2') +
-+ r'[ ]*[0-9.]*x[ ]*(,[ ]*)?)+\)')
-+ value_re = '(%s|%s)' % (GetUrlRegexString(), image_set_value_re)
-+ css_re = property_re + value_re
-+ return re.sub(css_re, lambda m: InlineCSSUrls(m, filepath), text)
-+
-+ def InlineCSSUrls(src_match, filepath=input_filepath):
-+ """Helper function that inlines each url on a CSS image rule match."""
-+ # Replace contents of url() references in matches.
-+ return re.sub(GetUrlRegexString(),
-+ lambda m: SrcReplace(m, filepath),
-+ src_match.group(0))
-+
-+ def InlineCSSImports(text, filepath=input_filepath):
-+ """Helper function that inlines CSS files included via the @import
-+ directive.
-+ """
-+ return re.sub(r'@import\s+' + GetUrlRegexString() + r';',
-+ lambda m: InlineCSSFile(m, '%s', filepath),
-+ text)
-+
-+
-+ flat_text = util.ReadFile(input_filename, 'utf-8')
-+
-+ # Check conditional elements, remove unsatisfied ones from the file. We do
-+ # this twice. The first pass is so that we don't even bother calling
-+ # InlineScript, InlineCSSFile and InlineIncludeFiles on text we're eventually
-+ # going to throw out anyway.
-+ flat_text = CheckConditionalElements(flat_text)
-+
-+ flat_text = _INCLUDE_RE.sub(InlineIncludeFiles, flat_text)
-+
-+ if not preprocess_only:
-+ if strip_whitespace:
-+ flat_text = minifier.Minify(flat_text.encode('utf-8'),
-+ input_filename).decode('utf-8')
-+
-+ if not allow_external_script:
-+ # We need to inline css and js before we inline images so that image
-+ # references gets inlined in the css and js
-+ flat_text = re.sub(r'<script (?P<attrs1>.*?)src="(?P<filename>[^"\']*)"'
-+ r'(?P<attrs2>.*?)></script>',
-+ InlineScript,
-+ flat_text)
-+
-+ flat_text = _STYLESHEET_RE.sub(
-+ lambda m: InlineCSSFile(m, '<style>%s</style>'),
-+ flat_text)
-+
-+ # Check conditional elements, second pass. This catches conditionals in any
-+ # of the text we just inlined.
-+ flat_text = CheckConditionalElements(flat_text)
-+
-+ # Allow custom modifications before inlining images.
-+ if rewrite_function:
-+ flat_text = rewrite_function(input_filepath, flat_text, distribution)
-+
-+ if not preprocess_only:
-+ flat_text = _SRC_RE.sub(SrcReplace, flat_text)
-+ flat_text = _SRCSET_RE.sub(SrcsetReplace, flat_text)
-+
-+ # TODO(arv): Only do this inside <style> tags.
-+ flat_text = InlineCSSImages(flat_text)
-+
-+ flat_text = _ICON_RE.sub(SrcReplace, flat_text)
-+
-+ if names_only:
-+ flat_text = None # Will contains garbage if the flag is set anyway.
-+ return InlinedData(flat_text, inlined_files)
-+
-+
-+def InlineToString(input_filename, grd_node, preprocess_only = False,
-+ allow_external_script=False, strip_whitespace=False,
-+ rewrite_function=None, filename_expansion_function=None):
-+ """Inlines the resources in a specified file and returns it as a string.
-+
-+ Args:
-+ input_filename: name of file to read in
-+ grd_node: html node from the grd file for this include tag
-+ Returns:
-+ the inlined data as a string
-+ """
-+ try:
-+ return DoInline(
-+ input_filename,
-+ grd_node,
-+ preprocess_only=preprocess_only,
-+ allow_external_script=allow_external_script,
-+ strip_whitespace=strip_whitespace,
-+ rewrite_function=rewrite_function,
-+ filename_expansion_function=filename_expansion_function).inlined_data
-+ except IOError as e:
-+ raise Exception("Failed to open %s while trying to flatten %s. (%s)" %
-+ (e.filename, input_filename, e.strerror))
-+
-+
-+def InlineToFile(input_filename, output_filename, grd_node):
-+ """Inlines the resources in a specified file and writes it.
-+
-+ Reads input_filename, finds all the src attributes and attempts to
-+ inline the files they are referring to, then writes the result
-+ to output_filename.
-+
-+ Args:
-+ input_filename: name of file to read in
-+ output_filename: name of file to be written to
-+ grd_node: html node from the grd file for this include tag
-+ Returns:
-+ a set of filenames of all the inlined files
-+ """
-+ inlined_data = InlineToString(input_filename, grd_node)
-+ with open(output_filename, 'wb') as out_file:
-+ out_file.write(inlined_data)
-+
-+
-+def GetResourceFilenames(filename,
-+ grd_node,
-+ allow_external_script=False,
-+ rewrite_function=None,
-+ filename_expansion_function=None):
-+ """For a grd file, returns a set of all the files that would be inline."""
-+ try:
-+ return DoInline(
-+ filename,
-+ grd_node,
-+ names_only=True,
-+ preprocess_only=False,
-+ allow_external_script=allow_external_script,
-+ strip_whitespace=False,
-+ rewrite_function=rewrite_function,
-+ filename_expansion_function=filename_expansion_function).inlined_files
-+ except IOError as e:
-+ raise Exception("Failed to open %s while trying to flatten %s. (%s)" %
-+ (e.filename, filename, e.strerror))
-+
-+
-+def main():
-+ if len(sys.argv) <= 2:
-+ print("Flattens a HTML file by inlining its external resources.\n")
-+ print("html_inline.py inputfile outputfile")
-+ else:
-+ InlineToFile(sys.argv[1], sys.argv[2], None)
-+
-+if __name__ == '__main__':
-+ main()
-diff --git a/tools/grit/grit/format/html_inline_unittest.py b/tools/grit/grit/format/html_inline_unittest.py
-new file mode 100644
-index 0000000000..1b11e9e476
---- /dev/null
-+++ b/tools/grit/grit/format/html_inline_unittest.py
-@@ -0,0 +1,927 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for grit.format.html_inline'''
-+
-+from __future__ import print_function
-+
-+import os
-+import re
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+import unittest
-+
-+from grit import util
-+from grit.format import html_inline
-+
-+
-+class HtmlInlineUnittest(unittest.TestCase):
-+ '''Unit tests for HtmlInline.'''
-+
-+ def testGetResourceFilenames(self):
-+ '''Tests that all included files are returned by GetResourceFilenames.'''
-+
-+ files = {
-+ 'index.html': '''
-+ <!DOCTYPE HTML>
-+ <html>
-+ <head>
-+ <link rel="stylesheet" href="test.css">
-+ <link rel="stylesheet"
-+ href="really-long-long-long-long-long-test.css">
-+ </head>
-+ <body>
-+ <include src='test.html'>
-+ <include
-+ src="really-long-long-long-long-long-test-file-omg-so-long.html">
-+ </body>
-+ </html>
-+ ''',
-+
-+ 'test.html': '''
-+ <include src="test2.html">
-+ ''',
-+
-+ 'really-long-long-long-long-long-test-file-omg-so-long.html': '''
-+ <!-- This really long named resource should be included. -->
-+ ''',
-+
-+ 'test2.html': '''
-+ <!-- This second level resource should also be included. -->
-+ ''',
-+
-+ 'test.css': '''
-+ .image {
-+ background: url('test.png');
-+ }
-+ ''',
-+
-+ 'really-long-long-long-long-long-test.css': '''
-+ a:hover {
-+ font-weight: bold; /* Awesome effect is awesome! */
-+ }
-+ ''',
-+
-+ 'test.png': 'PNG DATA',
-+ }
-+
-+ source_resources = set()
-+ tmp_dir = util.TempDir(files)
-+ for filename in files:
-+ source_resources.add(tmp_dir.GetPath(filename))
-+
-+ resources = html_inline.GetResourceFilenames(tmp_dir.GetPath('index.html'),
-+ None)
-+ resources.add(tmp_dir.GetPath('index.html'))
-+ self.failUnlessEqual(resources, source_resources)
-+ tmp_dir.CleanUp()
-+
-+ def testUnmatchedEndIfBlock(self):
-+ '''Tests that an unmatched </if> raises an exception.'''
-+
-+ files = {
-+ 'index.html': '''
-+ <!DOCTYPE HTML>
-+ <html>
-+ <if expr="lang == 'fr'">
-+ bonjour
-+ </if>
-+ <if expr='lang == "de"'>
-+ hallo
-+ </if>
-+ </if>
-+ </html>
-+ ''',
-+ }
-+
-+ tmp_dir = util.TempDir(files)
-+
-+ with self.assertRaises(Exception) as cm:
-+ html_inline.GetResourceFilenames(tmp_dir.GetPath('index.html'), None)
-+ self.failUnlessEqual(str(cm.exception), 'Unmatched </if>')
-+ tmp_dir.CleanUp()
-+
-+ def testCompressedJavaScript(self):
-+ '''Tests that ".src=" doesn't treat as a tag.'''
-+
-+ files = {
-+ 'index.js': '''
-+ if(i<j)a.src="hoge.png";
-+ ''',
-+ }
-+
-+ source_resources = set()
-+ tmp_dir = util.TempDir(files)
-+ for filename in files:
-+ source_resources.add(tmp_dir.GetPath(filename))
-+
-+ resources = html_inline.GetResourceFilenames(tmp_dir.GetPath('index.js'),
-+ None)
-+ resources.add(tmp_dir.GetPath('index.js'))
-+ self.failUnlessEqual(resources, source_resources)
-+ tmp_dir.CleanUp()
-+
-+ def testInlineCSSImports(self):
-+ '''Tests that @import directives in inlined CSS files are inlined too.
-+ '''
-+
-+ files = {
-+ 'index.html': '''
-+ <html>
-+ <head>
-+ <link rel="stylesheet" href="css/test.css">
-+ </head>
-+ </html>
-+ ''',
-+
-+ 'css/test.css': '''
-+ @import url('test2.css');
-+ blink {
-+ display: none;
-+ }
-+ ''',
-+
-+ 'css/test2.css': '''
-+ .image {
-+ background: url('../images/test.png');
-+ }
-+ '''.strip(),
-+
-+ 'images/test.png': 'PNG DATA'
-+ }
-+
-+ expected_inlined = '''
-+ <html>
-+ <head>
-+ <style>
-+ .image {
-+ background: url('data:image/png;base64,UE5HIERBVEE=');
-+ }
-+ blink {
-+ display: none;
-+ }
-+ </style>
-+ </head>
-+ </html>
-+ '''
-+
-+ source_resources = set()
-+ tmp_dir = util.TempDir(files)
-+ for filename in files:
-+ source_resources.add(tmp_dir.GetPath(util.normpath(filename)))
-+
-+ result = html_inline.DoInline(tmp_dir.GetPath('index.html'), None)
-+ resources = result.inlined_files
-+ resources.add(tmp_dir.GetPath('index.html'))
-+ self.failUnlessEqual(resources, source_resources)
-+ self.failUnlessEqual(expected_inlined,
-+ util.FixLineEnd(result.inlined_data, '\n'))
-+
-+ tmp_dir.CleanUp()
-+
-+ def testInlineIgnoresPolymerBindings(self):
-+ '''Tests that polymer bindings are ignored when inlining.
-+ '''
-+
-+ files = {
-+ 'index.html': '''
-+ <html>
-+ <head>
-+ <link rel="stylesheet" href="test.css">
-+ </head>
-+ <body>
-+ <iron-icon src="[[icon]]"></iron-icon><!-- Should be ignored. -->
-+ <iron-icon src="{{src}}"></iron-icon><!-- Also ignored. -->
-+ <!-- [[image]] should be ignored. -->
-+ <div style="background: url([[image]]),
-+ url('test.png');">
-+ </div>
-+ <div style="background: url('test.png'),
-+ url([[image]]);">
-+ </div>
-+ </body>
-+ </html>
-+ ''',
-+
-+ 'test.css': '''
-+ .image {
-+ background: url('test.png');
-+ background-image: url([[ignoreMe]]);
-+ background-image: image-set(url({{alsoMe}}), 1x);
-+ background-image: image-set(
-+ url({{ignore}}) 1x,
-+ url('test.png') 2x);
-+ }
-+ ''',
-+
-+ 'test.png': 'PNG DATA'
-+ }
-+
-+ expected_inlined = '''
-+ <html>
-+ <head>
-+ <style>
-+ .image {
-+ background: url('data:image/png;base64,UE5HIERBVEE=');
-+ background-image: url([[ignoreMe]]);
-+ background-image: image-set(url({{alsoMe}}), 1x);
-+ background-image: image-set(
-+ url({{ignore}}) 1x,
-+ url('data:image/png;base64,UE5HIERBVEE=') 2x);
-+ }
-+ </style>
-+ </head>
-+ <body>
-+ <iron-icon src="[[icon]]"></iron-icon><!-- Should be ignored. -->
-+ <iron-icon src="{{src}}"></iron-icon><!-- Also ignored. -->
-+ <!-- [[image]] should be ignored. -->
-+ <div style="background: url([[image]]),
-+ url('data:image/png;base64,UE5HIERBVEE=');">
-+ </div>
-+ <div style="background: url('data:image/png;base64,UE5HIERBVEE='),
-+ url([[image]]);">
-+ </div>
-+ </body>
-+ </html>
-+ '''
-+
-+ source_resources = set()
-+ tmp_dir = util.TempDir(files)
-+ for filename in files:
-+ source_resources.add(tmp_dir.GetPath(util.normpath(filename)))
-+
-+ result = html_inline.DoInline(tmp_dir.GetPath('index.html'), None)
-+ resources = result.inlined_files
-+ resources.add(tmp_dir.GetPath('index.html'))
-+ self.failUnlessEqual(resources, source_resources)
-+ self.failUnlessEqual(expected_inlined,
-+ util.FixLineEnd(result.inlined_data, '\n'))
-+
-+ tmp_dir.CleanUp()
-+
-+ def testInlineCSSWithIncludeDirective(self):
-+ '''Tests that include directive in external css files also inlined'''
-+
-+ files = {
-+ 'index.html': '''
-+ <html>
-+ <head>
-+ <link rel="stylesheet" href="foo.css">
-+ </head>
-+ </html>
-+ ''',
-+
-+ 'foo.css': '''<include src="style.css">''',
-+
-+ 'style.css': '''
-+ <include src="style2.css">
-+ blink {
-+ display: none;
-+ }
-+ ''',
-+ 'style2.css': '''h1 {}''',
-+ }
-+
-+ expected_inlined = '''
-+ <html>
-+ <head>
-+ <style>
-+ h1 {}
-+ blink {
-+ display: none;
-+ }
-+ </style>
-+ </head>
-+ </html>
-+ '''
-+
-+ source_resources = set()
-+ tmp_dir = util.TempDir(files)
-+ for filename in files:
-+ source_resources.add(tmp_dir.GetPath(filename))
-+
-+ result = html_inline.DoInline(tmp_dir.GetPath('index.html'), None)
-+ resources = result.inlined_files
-+ resources.add(tmp_dir.GetPath('index.html'))
-+ self.failUnlessEqual(resources, source_resources)
-+ self.failUnlessEqual(expected_inlined,
-+ util.FixLineEnd(result.inlined_data, '\n'))
-+ tmp_dir.CleanUp()
-+
-+ def testCssIncludedFileNames(self):
-+ '''Tests that all included files from css are returned'''
-+
-+ files = {
-+ 'index.html': '''
-+ <!DOCTYPE HTML>
-+ <html>
-+ <head>
-+ <link rel="stylesheet" href="test.css">
-+ </head>
-+ <body>
-+ </body>
-+ </html>
-+ ''',
-+
-+ 'test.css': '''
-+ <include src="test2.css">
-+ ''',
-+
-+ 'test2.css': '''
-+ <include src="test3.css">
-+ .image {
-+ background: url('test.png');
-+ }
-+ ''',
-+
-+ 'test3.css': '''h1 {}''',
-+
-+ 'test.png': 'PNG DATA'
-+ }
-+
-+ source_resources = set()
-+ tmp_dir = util.TempDir(files)
-+ for filename in files:
-+ source_resources.add(tmp_dir.GetPath(filename))
-+
-+ resources = html_inline.GetResourceFilenames(tmp_dir.GetPath('index.html'),
-+ None)
-+ resources.add(tmp_dir.GetPath('index.html'))
-+ self.failUnlessEqual(resources, source_resources)
-+ tmp_dir.CleanUp()
-+
-+ def testInlineCSSLinks(self):
-+ '''Tests that only CSS files referenced via relative URLs are inlined.'''
-+
-+ files = {
-+ 'index.html': '''
-+ <html>
-+ <head>
-+ <link rel="stylesheet" href="foo.css">
-+ <link rel="stylesheet" href="chrome://resources/bar.css">
-+ </head>
-+ </html>
-+ ''',
-+
-+ 'foo.css': '''
-+ @import url(chrome://resources/blurp.css);
-+ blink {
-+ display: none;
-+ }
-+ ''',
-+ }
-+
-+ expected_inlined = '''
-+ <html>
-+ <head>
-+ <style>
-+ @import url(chrome://resources/blurp.css);
-+ blink {
-+ display: none;
-+ }
-+ </style>
-+ <link rel="stylesheet" href="chrome://resources/bar.css">
-+ </head>
-+ </html>
-+ '''
-+
-+ source_resources = set()
-+ tmp_dir = util.TempDir(files)
-+ for filename in files:
-+ source_resources.add(tmp_dir.GetPath(filename))
-+
-+ result = html_inline.DoInline(tmp_dir.GetPath('index.html'), None)
-+ resources = result.inlined_files
-+ resources.add(tmp_dir.GetPath('index.html'))
-+ self.failUnlessEqual(resources, source_resources)
-+ self.failUnlessEqual(expected_inlined,
-+ util.FixLineEnd(result.inlined_data, '\n'))
-+ tmp_dir.CleanUp()
-+
-+ def testFilenameVariableExpansion(self):
-+ '''Tests that variables are expanded in filenames before inlining.'''
-+
-+ files = {
-+ 'index.html': '''
-+ <html>
-+ <head>
-+ <link rel="stylesheet" href="style[WHICH].css">
-+ <script src="script[WHICH].js"></script>
-+ </head>
-+ <include src="tmpl[WHICH].html">
-+ <img src="img[WHICH].png">
-+ </html>
-+ ''',
-+ 'style1.css': '''h1 {}''',
-+ 'tmpl1.html': '''<h1></h1>''',
-+ 'script1.js': '''console.log('hello');''',
-+ 'img1.png': '''abc''',
-+ }
-+
-+ expected_inlined = '''
-+ <html>
-+ <head>
-+ <style>h1 {}</style>
-+ <script>console.log('hello');</script>
-+ </head>
-+ <h1></h1>
-+ <img src="data:image/png;base64,YWJj">
-+ </html>
-+ '''
-+
-+ source_resources = set()
-+ tmp_dir = util.TempDir(files)
-+ for filename in files:
-+ source_resources.add(tmp_dir.GetPath(filename))
-+
-+ def replacer(var, repl):
-+ return lambda filename: filename.replace('[%s]' % var, repl)
-+
-+ # Test normal inlining.
-+ result = html_inline.DoInline(
-+ tmp_dir.GetPath('index.html'),
-+ None,
-+ filename_expansion_function=replacer('WHICH', '1'))
-+ resources = result.inlined_files
-+ resources.add(tmp_dir.GetPath('index.html'))
-+ self.failUnlessEqual(resources, source_resources)
-+ self.failUnlessEqual(expected_inlined,
-+ util.FixLineEnd(result.inlined_data, '\n'))
-+
-+ # Test names-only inlining.
-+ result = html_inline.DoInline(
-+ tmp_dir.GetPath('index.html'),
-+ None,
-+ names_only=True,
-+ filename_expansion_function=replacer('WHICH', '1'))
-+ resources = result.inlined_files
-+ resources.add(tmp_dir.GetPath('index.html'))
-+ self.failUnlessEqual(resources, source_resources)
-+ tmp_dir.CleanUp()
-+
-+ def testWithCloseTags(self):
-+ '''Tests that close tags are removed.'''
-+
-+ files = {
-+ 'index.html': '''
-+ <html>
-+ <head>
-+ <link rel="stylesheet" href="style1.css"></link>
-+ <link rel="stylesheet" href="style2.css">
-+ </link>
-+ <link rel="stylesheet" href="style2.css"
-+ >
-+ </link>
-+ <script src="script1.js"></script>
-+ </head>
-+ <include src="tmpl1.html"></include>
-+ <include src="tmpl2.html">
-+ </include>
-+ <include src="tmpl2.html"
-+ >
-+ </include>
-+ <img src="img1.png">
-+ <include src='single-double-quotes.html"></include>
-+ <include src="double-single-quotes.html'></include>
-+ </html>
-+ ''',
-+ 'style1.css': '''h1 {}''',
-+ 'style2.css': '''h2 {}''',
-+ 'tmpl1.html': '''<h1></h1>''',
-+ 'tmpl2.html': '''<h2></h2>''',
-+ 'script1.js': '''console.log('hello');''',
-+ 'img1.png': '''abc''',
-+ }
-+
-+ expected_inlined = '''
-+ <html>
-+ <head>
-+ <style>h1 {}</style>
-+ <style>h2 {}</style>
-+ <style>h2 {}</style>
-+ <script>console.log('hello');</script>
-+ </head>
-+ <h1></h1>
-+ <h2></h2>
-+ <h2></h2>
-+ <img src="data:image/png;base64,YWJj">
-+ <include src='single-double-quotes.html"></include>
-+ <include src="double-single-quotes.html'></include>
-+ </html>
-+ '''
-+
-+ source_resources = set()
-+ tmp_dir = util.TempDir(files)
-+ for filename in files:
-+ source_resources.add(tmp_dir.GetPath(filename))
-+
-+ # Test normal inlining.
-+ result = html_inline.DoInline(
-+ tmp_dir.GetPath('index.html'),
-+ None)
-+ resources = result.inlined_files
-+ resources.add(tmp_dir.GetPath('index.html'))
-+ self.failUnlessEqual(resources, source_resources)
-+ self.failUnlessEqual(expected_inlined,
-+ util.FixLineEnd(result.inlined_data, '\n'))
-+ tmp_dir.CleanUp()
-+
-+ def testCommentedJsInclude(self):
-+ '''Tests that <include> works inside a comment.'''
-+
-+ files = {
-+ 'include.js': '// <include src="other.js">',
-+ 'other.js': '// Copyright somebody\nalert(1);',
-+ }
-+
-+ expected_inlined = '// Copyright somebody\nalert(1);'
-+
-+ source_resources = set()
-+ tmp_dir = util.TempDir(files)
-+ for filename in files:
-+ source_resources.add(tmp_dir.GetPath(filename))
-+
-+ result = html_inline.DoInline(tmp_dir.GetPath('include.js'), None)
-+ resources = result.inlined_files
-+ resources.add(tmp_dir.GetPath('include.js'))
-+ self.failUnlessEqual(resources, source_resources)
-+ self.failUnlessEqual(expected_inlined,
-+ util.FixLineEnd(result.inlined_data, '\n'))
-+ tmp_dir.CleanUp()
-+
-+ def testCommentedJsIf(self):
-+ '''Tests that <if> works inside a comment.'''
-+
-+ files = {
-+ 'if.js': '''
-+ // <if expr="True">
-+ yep();
-+ // </if>
-+
-+ // <if expr="False">
-+ nope();
-+ // </if>
-+ ''',
-+ }
-+
-+ expected_inlined = '''
-+ //
-+ yep();
-+ //
-+
-+ //
-+ '''
-+
-+ source_resources = set()
-+ tmp_dir = util.TempDir(files)
-+ for filename in files:
-+ source_resources.add(tmp_dir.GetPath(filename))
-+
-+ class FakeGrdNode(object):
-+ def EvaluateCondition(self, cond):
-+ return eval(cond)
-+
-+ result = html_inline.DoInline(tmp_dir.GetPath('if.js'), FakeGrdNode())
-+ resources = result.inlined_files
-+
-+ resources.add(tmp_dir.GetPath('if.js'))
-+ self.failUnlessEqual(resources, source_resources)
-+ self.failUnlessEqual(expected_inlined,
-+ util.FixLineEnd(result.inlined_data, '\n'))
-+ tmp_dir.CleanUp()
-+
-+ def testImgSrcset(self):
-+ '''Tests that img srcset="" attributes are converted.'''
-+
-+ # Note that there is no space before "img10.png" and that
-+ # "img11.png" has no descriptor.
-+ files = {
-+ 'index.html': '''
-+ <html>
-+ <img src="img1.png" srcset="img2.png 1x, img3.png 2x">
-+ <img src="img4.png" srcset=" img5.png 1x , img6.png 2x ">
-+ <img src="chrome://theme/img11.png" srcset="img7.png 1x, '''\
-+ '''chrome://theme/img13.png 2x">
-+ <img srcset="img8.png 300w, img9.png 11E-2w,img10.png -1e2w">
-+ <img srcset="img11.png">
-+ <img srcset="img11.png, img2.png 1x">
-+ <img srcset="img2.png 1x, img11.png">
-+ </html>
-+ ''',
-+ 'img1.png': '''a1''',
-+ 'img2.png': '''a2''',
-+ 'img3.png': '''a3''',
-+ 'img4.png': '''a4''',
-+ 'img5.png': '''a5''',
-+ 'img6.png': '''a6''',
-+ 'img7.png': '''a7''',
-+ 'img8.png': '''a8''',
-+ 'img9.png': '''a9''',
-+ 'img10.png': '''a10''',
-+ 'img11.png': '''a11''',
-+ }
-+
-+ expected_inlined = '''
-+ <html>
-+ <img src="data:image/png;base64,YTE=" srcset="data:image/png;base64,'''\
-+ '''YTI= 1x,data:image/png;base64,YTM= 2x">
-+ <img src="data:image/png;base64,YTQ=" srcset="data:image/png;base64,'''\
-+ '''YTU= 1x,data:image/png;base64,YTY= 2x">
-+ <img src="chrome://theme/img11.png" srcset="data:image/png;base64,'''\
-+ '''YTc= 1x,chrome://theme/img13.png 2x">
-+ <img srcset="data:image/png;base64,YTg= 300w,data:image/png;base64,'''\
-+ '''YTk= 11E-2w,data:image/png;base64,YTEw -1e2w">
-+ <img srcset="data:image/png;base64,YTEx">
-+ <img srcset="data:image/png;base64,YTEx,data:image/png;base64,YTI= 1x">
-+ <img srcset="data:image/png;base64,YTI= 1x,data:image/png;base64,YTEx">
-+ </html>
-+ '''
-+
-+ source_resources = set()
-+ tmp_dir = util.TempDir(files)
-+ for filename in files:
-+ source_resources.add(tmp_dir.GetPath(filename))
-+
-+ # Test normal inlining.
-+ result = html_inline.DoInline(
-+ tmp_dir.GetPath('index.html'),
-+ None)
-+ resources = result.inlined_files
-+ resources.add(tmp_dir.GetPath('index.html'))
-+ self.failUnlessEqual(resources, source_resources)
-+ self.failUnlessEqual(expected_inlined,
-+ util.FixLineEnd(result.inlined_data, '\n'))
-+ tmp_dir.CleanUp()
-+
-+ def testImgSrcsetIgnoresI18n(self):
-+ '''Tests that $i18n{...} strings are ignored when inlining.
-+ '''
-+
-+ src_html = '''
-+ <html>
-+ <head></head>
-+ <body>
-+ <img srcset="$i18n{foo}">
-+ </body>
-+ </html>
-+ '''
-+
-+ files = {
-+ 'index.html': src_html,
-+ }
-+
-+ expected_inlined = src_html
-+
-+ source_resources = set()
-+ tmp_dir = util.TempDir(files)
-+ for filename in files:
-+ source_resources.add(tmp_dir.GetPath(util.normpath(filename)))
-+
-+ result = html_inline.DoInline(tmp_dir.GetPath('index.html'), None)
-+ resources = result.inlined_files
-+ resources.add(tmp_dir.GetPath('index.html'))
-+ self.failUnlessEqual(resources, source_resources)
-+ self.failUnlessEqual(expected_inlined,
-+ util.FixLineEnd(result.inlined_data, '\n'))
-+ tmp_dir.CleanUp()
-+
-+ def testSourceSrcset(self):
-+ '''Tests that source srcset="" attributes are converted.'''
-+
-+ # Note that there is no space before "img10.png" and that
-+ # "img11.png" has no descriptor.
-+ files = {
-+ 'index.html': '''
-+ <html>
-+ <source src="img1.png" srcset="img2.png 1x, img3.png 2x">
-+ <source src="img4.png" srcset=" img5.png 1x , img6.png 2x ">
-+ <source src="chrome://theme/img11.png" srcset="img7.png 1x, '''\
-+ '''chrome://theme/img13.png 2x">
-+ <source srcset="img8.png 300w, img9.png 11E-2w,img10.png -1e2w">
-+ <source srcset="img11.png">
-+ </html>
-+ ''',
-+ 'img1.png': '''a1''',
-+ 'img2.png': '''a2''',
-+ 'img3.png': '''a3''',
-+ 'img4.png': '''a4''',
-+ 'img5.png': '''a5''',
-+ 'img6.png': '''a6''',
-+ 'img7.png': '''a7''',
-+ 'img8.png': '''a8''',
-+ 'img9.png': '''a9''',
-+ 'img10.png': '''a10''',
-+ 'img11.png': '''a11''',
-+ }
-+
-+ expected_inlined = '''
-+ <html>
-+ <source src="data:image/png;base64,YTE=" srcset="data:image/png;'''\
-+ '''base64,YTI= 1x,data:image/png;base64,YTM= 2x">
-+ <source src="data:image/png;base64,YTQ=" srcset="data:image/png;'''\
-+ '''base64,YTU= 1x,data:image/png;base64,YTY= 2x">
-+ <source src="chrome://theme/img11.png" srcset="data:image/png;'''\
-+ '''base64,YTc= 1x,chrome://theme/img13.png 2x">
-+ <source srcset="data:image/png;base64,YTg= 300w,data:image/png;'''\
-+ '''base64,YTk= 11E-2w,data:image/png;base64,YTEw -1e2w">
-+ <source srcset="data:image/png;base64,YTEx">
-+ </html>
-+ '''
-+
-+ source_resources = set()
-+ tmp_dir = util.TempDir(files)
-+ for filename in files:
-+ source_resources.add(tmp_dir.GetPath(filename))
-+
-+ # Test normal inlining.
-+ result = html_inline.DoInline(tmp_dir.GetPath('index.html'), None)
-+ resources = result.inlined_files
-+ resources.add(tmp_dir.GetPath('index.html'))
-+ self.failUnlessEqual(resources, source_resources)
-+ self.failUnlessEqual(expected_inlined,
-+ util.FixLineEnd(result.inlined_data, '\n'))
-+ tmp_dir.CleanUp()
-+
-+ def testConditionalInclude(self):
-+ '''Tests that output and dependency generation includes only files not'''\
-+ ''' blocked by <if> macros.'''
-+
-+ files = {
-+ 'index.html': '''
-+ <html>
-+ <if expr="True">
-+ <img src="img1.png" srcset="img2.png 1x, img3.png 2x">
-+ </if>
-+ <if expr="False">
-+ <img src="img4.png" srcset=" img5.png 1x, img6.png 2x ">
-+ </if>
-+ <if expr="True">
-+ <img src="chrome://theme/img11.png" srcset="img7.png 1x, '''\
-+ '''chrome://theme/img13.png 2x">
-+ </if>
-+ <img srcset="img8.png 300w, img9.png 11E-2w,img10.png -1e2w">
-+ </html>
-+ ''',
-+ 'img1.png': '''a1''',
-+ 'img2.png': '''a2''',
-+ 'img3.png': '''a3''',
-+ 'img4.png': '''a4''',
-+ 'img5.png': '''a5''',
-+ 'img6.png': '''a6''',
-+ 'img7.png': '''a7''',
-+ 'img8.png': '''a8''',
-+ 'img9.png': '''a9''',
-+ 'img10.png': '''a10''',
-+ }
-+
-+ expected_inlined = '''
-+ <html>
-+ <img src="data:image/png;base64,YTE=" srcset="data:image/png;base64,'''\
-+ '''YTI= 1x,data:image/png;base64,YTM= 2x">
-+ <img src="chrome://theme/img11.png" srcset="data:image/png;base64,'''\
-+ '''YTc= 1x,chrome://theme/img13.png 2x">
-+ <img srcset="data:image/png;base64,YTg= 300w,data:image/png;base64,'''\
-+ '''YTk= 11E-2w,data:image/png;base64,YTEw -1e2w">
-+ </html>
-+ '''
-+
-+ expected_files = [
-+ 'index.html',
-+ 'img1.png',
-+ 'img2.png',
-+ 'img3.png',
-+ 'img7.png',
-+ 'img8.png',
-+ 'img9.png',
-+ 'img10.png'
-+ ]
-+
-+ source_resources = set()
-+ tmp_dir = util.TempDir(files)
-+ for filename in expected_files:
-+ source_resources.add(tmp_dir.GetPath(filename))
-+
-+ class FakeGrdNode(object):
-+ def EvaluateCondition(self, cond):
-+ return eval(cond)
-+
-+ # Test normal inlining.
-+ result = html_inline.DoInline(
-+ tmp_dir.GetPath('index.html'),
-+ FakeGrdNode())
-+ resources = result.inlined_files
-+ resources.add(tmp_dir.GetPath('index.html'))
-+ self.failUnlessEqual(resources, source_resources)
-+
-+ # ignore whitespace
-+ expected_inlined = re.sub(r'\s+', ' ', expected_inlined)
-+ actually_inlined = re.sub(r'\s+', ' ',
-+ util.FixLineEnd(result.inlined_data, '\n'))
-+ self.failUnlessEqual(expected_inlined, actually_inlined);
-+ tmp_dir.CleanUp()
-+
-+ def testPreprocessOnlyEvaluatesIncludeAndIf(self):
-+ '''Tests that preprocess_only=true evaluates <include> and <if> only. '''
-+
-+ files = {
-+ 'index.html': '''
-+ <html>
-+ <head>
-+ <link rel="stylesheet" href="not_inlined.css">
-+ <script src="also_not_inlined.js">
-+ </head>
-+ <body>
-+ <include src="inline_this.html">
-+ <if expr="True">
-+ <p>'if' should be evaluated.</p>
-+ </if>
-+ </body>
-+ </html>
-+ ''',
-+ 'not_inlined.css': ''' /* <link> should not be inlined. */ ''',
-+ 'also_not_inlined.js': ''' // <script> should not be inlined. ''',
-+ 'inline_this.html': ''' <p>'include' should be inlined.</p> '''
-+ }
-+
-+ expected_inlined = '''
-+ <html>
-+ <head>
-+ <link rel="stylesheet" href="not_inlined.css">
-+ <script src="also_not_inlined.js">
-+ </head>
-+ <body>
-+ <p>'include' should be inlined.</p>
-+ <p>'if' should be evaluated.</p>
-+ </body>
-+ </html>
-+ '''
-+
-+ source_resources = set()
-+ tmp_dir = util.TempDir(files)
-+ source_resources.add(tmp_dir.GetPath('index.html'))
-+ source_resources.add(tmp_dir.GetPath('inline_this.html'))
-+
-+ result = html_inline.DoInline(tmp_dir.GetPath('index.html'), None,
-+ preprocess_only=True)
-+ resources = result.inlined_files
-+ resources.add(tmp_dir.GetPath('index.html'))
-+ self.failUnlessEqual(resources, source_resources)
-+
-+ # Ignore whitespace
-+ expected_inlined = re.sub(r'\s+', ' ', expected_inlined)
-+ actually_inlined = re.sub(r'\s+', ' ',
-+ util.FixLineEnd(result.inlined_data, '\n'))
-+ self.failUnlessEqual(expected_inlined, actually_inlined)
-+
-+ tmp_dir.CleanUp()
-+
-+ def testPreprocessOnlyAppliesRecursively(self):
-+ '''Tests that preprocess_only=true propagates to included files. '''
-+
-+ files = {
-+ 'index.html': '''
-+ <html>
-+ <include src="outer_include.html">
-+ </html>
-+ ''',
-+ 'outer_include.html': '''
-+ <include src="inner_include.html">
-+ <link rel="stylesheet" href="not_inlined.css">
-+ ''',
-+ 'inner_include.html': ''' <p>This should be inlined in index.html</p> ''',
-+ 'not_inlined.css': ''' /* This should not be inlined. */ '''
-+ }
-+
-+ expected_inlined = '''
-+ <html>
-+ <p>This should be inlined in index.html</p>
-+ <link rel="stylesheet" href="not_inlined.css">
-+ </html>
-+ '''
-+
-+ source_resources = set()
-+ tmp_dir = util.TempDir(files)
-+ source_resources.add(tmp_dir.GetPath('index.html'))
-+ source_resources.add(tmp_dir.GetPath('outer_include.html'))
-+ source_resources.add(tmp_dir.GetPath('inner_include.html'))
-+
-+ result = html_inline.DoInline(tmp_dir.GetPath('index.html'), None,
-+ preprocess_only=True)
-+ resources = result.inlined_files
-+ resources.add(tmp_dir.GetPath('index.html'))
-+ self.failUnlessEqual(resources, source_resources)
-+
-+ # Ignore whitespace
-+ expected_inlined = re.sub(r'\s+', ' ', expected_inlined)
-+ actually_inlined = re.sub(r'\s+', ' ',
-+ util.FixLineEnd(result.inlined_data, '\n'))
-+ self.failUnlessEqual(expected_inlined, actually_inlined)
-+
-+ tmp_dir.CleanUp()
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/format/minifier.py b/tools/grit/grit/format/minifier.py
-new file mode 100644
-index 0000000000..1a0ea34e49
---- /dev/null
-+++ b/tools/grit/grit/format/minifier.py
-@@ -0,0 +1,45 @@
-+# Copyright 2016 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+"""Framework for stripping whitespace and comments from resource files"""
-+
-+from __future__ import print_function
-+
-+from os import path
-+import subprocess
-+import sys
-+
-+import six
-+
-+__js_minifier = None
-+__css_minifier = None
-+
-+def SetJsMinifier(minifier):
-+ global __js_minifier
-+ __js_minifier = minifier.split()
-+
-+def SetCssMinifier(minifier):
-+ global __css_minifier
-+ __css_minifier = minifier.split()
-+
-+def Minify(source, filename):
-+ """Minify |source| (bytes) from |filename| and return bytes."""
-+ file_type = path.splitext(filename)[1]
-+ minifier = None
-+ if file_type == '.js':
-+ minifier = __js_minifier
-+ elif file_type == '.css':
-+ minifier = __css_minifier
-+ if not minifier:
-+ return source
-+ p = subprocess.Popen(
-+ minifier,
-+ stdin=subprocess.PIPE,
-+ stdout=subprocess.PIPE,
-+ stderr=subprocess.PIPE)
-+ (stdout, stderr) = p.communicate(source)
-+ if p.returncode != 0:
-+ print('Minification failed for %s' % filename)
-+ print(stderr)
-+ sys.exit(p.returncode)
-+ return stdout
-diff --git a/tools/grit/grit/format/policy_templates_json.py b/tools/grit/grit/format/policy_templates_json.py
-new file mode 100644
-index 0000000000..2f9330bb9a
---- /dev/null
-+++ b/tools/grit/grit/format/policy_templates_json.py
-@@ -0,0 +1,26 @@
-+# Copyright 2017 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+"""Translates policy_templates.json files.
-+"""
-+
-+from __future__ import print_function
-+
-+from grit.node import structure
-+
-+
-+def Format(root, lang='en', output_dir='.'):
-+ policy_json = None
-+ for item in root.ActiveDescendants():
-+ with item:
-+ if (isinstance(item, structure.StructureNode) and
-+ item.attrs['type'] == 'policy_template_metafile'):
-+ json_text = item.gatherer.Translate(
-+ lang,
-+ pseudo_if_not_available=item.PseudoIsAllowed(),
-+ fallback_to_english=item.ShouldFallbackToEnglish())
-+ # We're only expecting one node of this kind.
-+ assert not policy_json
-+ policy_json = json_text
-+ return policy_json
-diff --git a/tools/grit/grit/format/policy_templates_json_unittest.py b/tools/grit/grit/format/policy_templates_json_unittest.py
-new file mode 100644
-index 0000000000..e252c94e2c
---- /dev/null
-+++ b/tools/grit/grit/format/policy_templates_json_unittest.py
-@@ -0,0 +1,207 @@
-+#!/usr/bin/env python
-+# coding: utf-8
-+# Copyright 2017 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+"""Unittest for policy_templates_json.py.
-+"""
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+import grit.extern.tclib
-+import tempfile
-+import unittest
-+
-+from six import StringIO
-+
-+from grit import grd_reader
-+from grit.tool import build
-+
-+
-+class PolicyTemplatesJsonUnittest(unittest.TestCase):
-+
-+ def testPolicyTranslation(self):
-+ # Create test policy_templates.json data.
-+ caption = "The main policy"
-+ caption_translation = "Die Hauptrichtlinie"
-+
-+ message = \
-+ "Red cabbage stays red cabbage and wedding dress stays wedding dress"
-+ message_translation = \
-+ "Blaukraut bleibt Blaukraut und Brautkleid bleibt Brautkleid"
-+
-+ schema_key_description = "Number of users"
-+ schema_key_description_translation = "Anzahl der Nutzer"
-+
-+ policy_json = """
-+ {
-+ "policy_definitions": [
-+ {
-+ 'name': 'MainPolicy',
-+ 'type': 'main',
-+ 'owners': ['foo@bar.com'],
-+ 'schema': {
-+ 'properties': {
-+ 'default_launch_container': {
-+ 'enum': [
-+ 'tab',
-+ 'window',
-+ ],
-+ 'type': 'string',
-+ },
-+ 'users_number': {
-+ 'description': '''%s''',
-+ 'type': 'integer',
-+ },
-+ },
-+ 'type': 'object',
-+ },
-+ 'supported_on': ['chrome_os:29-'],
-+ 'features': {
-+ 'can_be_recommended': True,
-+ 'dynamic_refresh': True,
-+ },
-+ 'example_value': True,
-+ 'caption': '''%s''',
-+ 'tags': [],
-+ 'desc': '''This policy does stuff.'''
-+ },
-+ ],
-+ "policy_atomic_group_definitions": [],
-+ "placeholders": [],
-+ "messages": {
-+ 'message_string_id': {
-+ 'desc': '''The description is removed from the grit output''',
-+ 'text': '''%s'''
-+ }
-+ }
-+ }""" % (schema_key_description, caption, message)
-+
-+ # Create translations. The translation IDs are hashed from the English text.
-+ caption_id = grit.extern.tclib.GenerateMessageId(caption);
-+ message_id = grit.extern.tclib.GenerateMessageId(message);
-+ schema_key_description_id = grit.extern.tclib.GenerateMessageId(
-+ schema_key_description)
-+ policy_xtb = """
-+<?xml version="1.0" ?>
-+<!DOCTYPE translationbundle>
-+<translationbundle lang="de">
-+<translation id="%s">%s</translation>
-+<translation id="%s">%s</translation>
-+<translation id="%s">%s</translation>
-+</translationbundle>""" % (caption_id, caption_translation,
-+ message_id, message_translation,
-+ schema_key_description_id,
-+ schema_key_description_translation)
-+
-+ # Write both to a temp file.
-+ tmp_dir_name = tempfile.gettempdir()
-+
-+ json_file_path = os.path.join(tmp_dir_name, 'test.json')
-+ with open(json_file_path, 'w') as f:
-+ f.write(policy_json.strip())
-+
-+ xtb_file_path = os.path.join(tmp_dir_name, 'test.xtb')
-+ with open(xtb_file_path, 'w') as f:
-+ f.write(policy_xtb.strip())
-+
-+ # Assemble a test grit tree, similar to policy_templates.grd.
-+ grd_text = '''
-+ <grit base_dir="." latest_public_release="0" current_release="1" source_lang_id="en">
-+ <translations>
-+ <file path="%s" lang="de" />
-+ </translations>
-+ <release seq="1">
-+ <structures>
-+ <structure name="IDD_POLICY_SOURCE_FILE" file="%s" type="policy_template_metafile" />
-+ </structures>
-+ </release>
-+ </grit>''' % (xtb_file_path, json_file_path)
-+ grd_string_io = StringIO(grd_text)
-+
-+ # Parse the grit tree and load the policies' JSON with a gatherer.
-+ grd = grd_reader.Parse(grd_string_io, dir=tmp_dir_name, defines={'_google_chrome': True})
-+ grd.SetOutputLanguage('en')
-+ grd.RunGatherers()
-+
-+ # Remove the temp files.
-+ os.unlink(xtb_file_path)
-+ os.unlink(json_file_path)
-+
-+ # Run grit with en->de translation.
-+ env_lang = 'en'
-+ out_lang = 'de'
-+ env_defs = {'_google_chrome': '1'}
-+
-+ grd.SetOutputLanguage(env_lang)
-+ grd.SetDefines(env_defs)
-+ buf = StringIO()
-+ build.RcBuilder.ProcessNode(grd, DummyOutput('policy_templates', out_lang), buf)
-+ output = buf.getvalue()
-+
-+ # Caption and message texts get taken from xtb.
-+ # desc is 'translated' to some pseudo-English
-+ # 'ThïPïs pôPôlïPïcýPý dôéPôés stüPüff'.
-+ expected = u"""{
-+ "policy_definitions": [
-+ {
-+ "caption": "%s",
-+ "desc": "Th\xefP\xefs p\xf4P\xf4l\xefP\xefc\xfdP\xfd d\xf4\xe9P\xf4\xe9s st\xfcP\xfcff.",
-+ "example_value": true,
-+ "features": {"can_be_recommended": true, "dynamic_refresh": true},
-+ "name": "MainPolicy",
-+ "owners": ["foo@bar.com"],
-+ "schema": {
-+ "properties": {
-+ "default_launch_container": {
-+ "enum": [
-+ "tab",
-+ "window"
-+ ],
-+ "type": "string"
-+ },
-+ "users_number": {
-+ "description": "%s",
-+ "type": "integer"
-+ }
-+ },
-+ "type": "object"
-+ },
-+ "supported_on": ["chrome_os:29-"],
-+ "tags": [],
-+ "type": "main"
-+ }
-+ ],
-+ "policy_atomic_group_definitions": [
-+ ],
-+ "messages": {
-+ "message_string_id": {
-+ "text": "%s"
-+ }
-+ }
-+
-+}""" % (caption_translation, schema_key_description_translation,
-+ message_translation)
-+ self.assertEqual(expected, output)
-+
-+
-+class DummyOutput(object):
-+
-+ def __init__(self, type, language):
-+ self.type = type
-+ self.language = language
-+
-+ def GetType(self):
-+ return self.type
-+
-+ def GetLanguage(self):
-+ return self.language
-+
-+ def GetOutputFilename(self):
-+ return 'hello.gif'
-diff --git a/tools/grit/grit/format/rc.py b/tools/grit/grit/format/rc.py
-new file mode 100644
-index 0000000000..ed32bb809e
---- /dev/null
-+++ b/tools/grit/grit/format/rc.py
-@@ -0,0 +1,474 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Support for formatting an RC file for compilation.
-+'''
-+
-+from __future__ import print_function
-+
-+import os
-+import re
-+from functools import partial
-+
-+import six
-+
-+from grit import util
-+from grit.node import misc
-+
-+
-+def Format(root, lang='en', output_dir='.'):
-+ from grit.node import empty, include, message, structure
-+
-+ yield _FormatHeader(root, lang, output_dir)
-+
-+ for item in root.ActiveDescendants():
-+ if isinstance(item, empty.MessagesNode):
-+ # Write one STRINGTABLE per <messages> container.
-+ # This is hacky: it iterates over the children twice.
-+ yield 'STRINGTABLE\nBEGIN\n'
-+ for subitem in item.ActiveDescendants():
-+ if isinstance(subitem, message.MessageNode):
-+ with subitem:
-+ yield FormatMessage(subitem, lang)
-+ yield 'END\n\n'
-+ elif isinstance(item, include.IncludeNode):
-+ with item:
-+ yield FormatInclude(item, lang, output_dir)
-+ elif isinstance(item, structure.StructureNode):
-+ with item:
-+ yield FormatStructure(item, lang, output_dir)
-+
-+
-+'''
-+This dictionary defines the language charset pair lookup table, which is used
-+for replacing the GRIT expand variables for language info in Product Version
-+resource. The key is the language ISO country code, and the value
-+is the language and character-set pair, which is a hexadecimal string
-+consisting of the concatenation of the language and character-set identifiers.
-+The first 4 digit of the value is the hex value of LCID, the remaining
-+4 digits is the hex value of character-set id(code page)of the language.
-+
-+LCID resource: http://msdn.microsoft.com/en-us/library/ms776294.aspx
-+Codepage resource: http://www.science.co.il/language/locale-codes.asp
-+
-+We have defined three GRIT expand_variables to be used in the version resource
-+file to set the language info. Here is an example how they should be used in
-+the VS_VERSION_INFO section of the resource file to allow GRIT to localize
-+the language info correctly according to product locale.
-+
-+VS_VERSION_INFO VERSIONINFO
-+...
-+BEGIN
-+ BLOCK "StringFileInfo"
-+ BEGIN
-+ BLOCK "[GRITVERLANGCHARSETHEX]"
-+ BEGIN
-+ ...
-+ END
-+ END
-+ BLOCK "VarFileInfo"
-+ BEGIN
-+ VALUE "Translation", [GRITVERLANGID], [GRITVERCHARSETID]
-+ END
-+END
-+
-+'''
-+
-+_LANGUAGE_CHARSET_PAIR = {
-+ # Language neutral LCID, unicode(1200) code page.
-+ 'neutral' : '000004b0',
-+ # LANG_USER_DEFAULT LCID, unicode(1200) code page.
-+ 'userdefault' : '040004b0',
-+ 'ar' : '040104e8',
-+ 'fi' : '040b04e4',
-+ 'ko' : '041203b5',
-+ 'es' : '0c0a04e4',
-+ 'bg' : '040204e3',
-+ # No codepage for filipino, use unicode(1200).
-+ 'fil' : '046404e4',
-+ 'fr' : '040c04e4',
-+ 'lv' : '042604e9',
-+ 'sv' : '041d04e4',
-+ 'ca' : '040304e4',
-+ 'de' : '040704e4',
-+ 'lt' : '042704e9',
-+ # Do not use! This is only around for backwards
-+ # compatibility and will be removed - use fil instead
-+ 'tl' : '0c0004b0',
-+ 'zh-CN' : '080403a8',
-+ 'zh-TW' : '040403b6',
-+ 'zh-HK' : '0c0403b6',
-+ 'el' : '040804e5',
-+ 'no' : '001404e4',
-+ 'nb' : '041404e4',
-+ 'nn' : '081404e4',
-+ 'th' : '041e036a',
-+ 'he' : '040d04e7',
-+ 'iw' : '040d04e7',
-+ 'pl' : '041504e2',
-+ 'tr' : '041f04e6',
-+ 'hr' : '041a04e4',
-+ # No codepage for Hindi, use unicode(1200).
-+ 'hi' : '043904b0',
-+ 'pt-PT' : '081604e4',
-+ 'pt-BR' : '041604e4',
-+ 'uk' : '042204e3',
-+ 'cs' : '040504e2',
-+ 'hu' : '040e04e2',
-+ 'ro' : '041804e2',
-+ # No codepage for Urdu, use unicode(1200).
-+ 'ur' : '042004b0',
-+ 'da' : '040604e4',
-+ 'is' : '040f04e4',
-+ 'ru' : '041904e3',
-+ 'vi' : '042a04ea',
-+ 'nl' : '041304e4',
-+ 'id' : '042104e4',
-+ 'sr' : '081a04e2',
-+ 'en-GB' : '0809040e',
-+ 'it' : '041004e4',
-+ 'sk' : '041b04e2',
-+ 'et' : '042504e9',
-+ 'ja' : '041103a4',
-+ 'sl' : '042404e2',
-+ 'en' : '040904b0',
-+ # LCID for Mexico; Windows does not support L.A. LCID.
-+ 'es-419' : '080a04e4',
-+ # No codepage for Bengali, use unicode(1200).
-+ 'bn' : '044504b0',
-+ 'fa' : '042904e8',
-+ # No codepage for Gujarati, use unicode(1200).
-+ 'gu' : '044704b0',
-+ # No codepage for Kannada, use unicode(1200).
-+ 'kn' : '044b04b0',
-+ # Malay (Malaysia) [ms-MY]
-+ 'ms' : '043e04e4',
-+ # No codepage for Malayalam, use unicode(1200).
-+ 'ml' : '044c04b0',
-+ # No codepage for Marathi, use unicode(1200).
-+ 'mr' : '044e04b0',
-+ # No codepage for Oriya , use unicode(1200).
-+ 'or' : '044804b0',
-+ # No codepage for Tamil, use unicode(1200).
-+ 'ta' : '044904b0',
-+ # No codepage for Telugu, use unicode(1200).
-+ 'te' : '044a04b0',
-+ # No codepage for Amharic, use unicode(1200). >= Vista.
-+ 'am' : '045e04b0',
-+ 'sw' : '044104e4',
-+ 'af' : '043604e4',
-+ 'eu' : '042d04e4',
-+ 'fr-CA' : '0c0c04e4',
-+ 'gl' : '045604e4',
-+ # No codepage for Zulu, use unicode(1200).
-+ 'zu' : '043504b0',
-+ 'fake-bidi' : '040d04e7',
-+}
-+
-+# Language ID resource: http://msdn.microsoft.com/en-us/library/ms776294.aspx
-+#
-+# There is no appropriate sublang for Spanish (Latin America) [es-419], so we
-+# use Mexico. SUBLANG_DEFAULT would incorrectly map to Spain. Unlike other
-+# Latin American countries, Mexican Spanish is supported by VERSIONINFO:
-+# http://msdn.microsoft.com/en-us/library/aa381058.aspx
-+
-+_LANGUAGE_DIRECTIVE_PAIR = {
-+ 'neutral' : 'LANG_NEUTRAL, SUBLANG_NEUTRAL',
-+ 'userdefault' : 'LANG_NEUTRAL, SUBLANG_DEFAULT',
-+ 'ar' : 'LANG_ARABIC, SUBLANG_DEFAULT',
-+ 'fi' : 'LANG_FINNISH, SUBLANG_DEFAULT',
-+ 'ko' : 'LANG_KOREAN, SUBLANG_KOREAN',
-+ 'es' : 'LANG_SPANISH, SUBLANG_SPANISH_MODERN',
-+ 'bg' : 'LANG_BULGARIAN, SUBLANG_DEFAULT',
-+ # LANG_FILIPINO (100) not in VC 7 winnt.h.
-+ 'fil' : '100, SUBLANG_DEFAULT',
-+ 'fr' : 'LANG_FRENCH, SUBLANG_FRENCH',
-+ 'lv' : 'LANG_LATVIAN, SUBLANG_DEFAULT',
-+ 'sv' : 'LANG_SWEDISH, SUBLANG_SWEDISH',
-+ 'ca' : 'LANG_CATALAN, SUBLANG_DEFAULT',
-+ 'de' : 'LANG_GERMAN, SUBLANG_GERMAN',
-+ 'lt' : 'LANG_LITHUANIAN, SUBLANG_LITHUANIAN',
-+ # Do not use! See above.
-+ 'tl' : 'LANG_NEUTRAL, SUBLANG_DEFAULT',
-+ 'zh-CN' : 'LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED',
-+ 'zh-TW' : 'LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL',
-+ 'zh-HK' : 'LANG_CHINESE, SUBLANG_CHINESE_HONGKONG',
-+ 'el' : 'LANG_GREEK, SUBLANG_DEFAULT',
-+ 'no' : 'LANG_NORWEGIAN, SUBLANG_DEFAULT',
-+ 'nb' : 'LANG_NORWEGIAN, SUBLANG_NORWEGIAN_BOKMAL',
-+ 'nn' : 'LANG_NORWEGIAN, SUBLANG_NORWEGIAN_NYNORSK',
-+ 'th' : 'LANG_THAI, SUBLANG_DEFAULT',
-+ 'he' : 'LANG_HEBREW, SUBLANG_DEFAULT',
-+ 'iw' : 'LANG_HEBREW, SUBLANG_DEFAULT',
-+ 'pl' : 'LANG_POLISH, SUBLANG_DEFAULT',
-+ 'tr' : 'LANG_TURKISH, SUBLANG_DEFAULT',
-+ 'hr' : 'LANG_CROATIAN, SUBLANG_DEFAULT',
-+ 'hi' : 'LANG_HINDI, SUBLANG_DEFAULT',
-+ 'pt-PT' : 'LANG_PORTUGUESE, SUBLANG_PORTUGUESE',
-+ 'pt-BR' : 'LANG_PORTUGUESE, SUBLANG_DEFAULT',
-+ 'uk' : 'LANG_UKRAINIAN, SUBLANG_DEFAULT',
-+ 'cs' : 'LANG_CZECH, SUBLANG_DEFAULT',
-+ 'hu' : 'LANG_HUNGARIAN, SUBLANG_DEFAULT',
-+ 'ro' : 'LANG_ROMANIAN, SUBLANG_DEFAULT',
-+ 'ur' : 'LANG_URDU, SUBLANG_DEFAULT',
-+ 'da' : 'LANG_DANISH, SUBLANG_DEFAULT',
-+ 'is' : 'LANG_ICELANDIC, SUBLANG_DEFAULT',
-+ 'ru' : 'LANG_RUSSIAN, SUBLANG_DEFAULT',
-+ 'vi' : 'LANG_VIETNAMESE, SUBLANG_DEFAULT',
-+ 'nl' : 'LANG_DUTCH, SUBLANG_DEFAULT',
-+ 'id' : 'LANG_INDONESIAN, SUBLANG_DEFAULT',
-+ 'sr' : 'LANG_SERBIAN, SUBLANG_SERBIAN_LATIN',
-+ 'en-GB' : 'LANG_ENGLISH, SUBLANG_ENGLISH_UK',
-+ 'it' : 'LANG_ITALIAN, SUBLANG_DEFAULT',
-+ 'sk' : 'LANG_SLOVAK, SUBLANG_DEFAULT',
-+ 'et' : 'LANG_ESTONIAN, SUBLANG_DEFAULT',
-+ 'ja' : 'LANG_JAPANESE, SUBLANG_DEFAULT',
-+ 'sl' : 'LANG_SLOVENIAN, SUBLANG_DEFAULT',
-+ 'en' : 'LANG_ENGLISH, SUBLANG_ENGLISH_US',
-+ # No L.A. sublang exists.
-+ 'es-419' : 'LANG_SPANISH, SUBLANG_SPANISH_MEXICAN',
-+ 'bn' : 'LANG_BENGALI, SUBLANG_DEFAULT',
-+ 'fa' : 'LANG_PERSIAN, SUBLANG_DEFAULT',
-+ 'gu' : 'LANG_GUJARATI, SUBLANG_DEFAULT',
-+ 'kn' : 'LANG_KANNADA, SUBLANG_DEFAULT',
-+ 'ms' : 'LANG_MALAY, SUBLANG_DEFAULT',
-+ 'ml' : 'LANG_MALAYALAM, SUBLANG_DEFAULT',
-+ 'mr' : 'LANG_MARATHI, SUBLANG_DEFAULT',
-+ 'or' : 'LANG_ORIYA, SUBLANG_DEFAULT',
-+ 'ta' : 'LANG_TAMIL, SUBLANG_DEFAULT',
-+ 'te' : 'LANG_TELUGU, SUBLANG_DEFAULT',
-+ 'am' : 'LANG_AMHARIC, SUBLANG_DEFAULT',
-+ 'sw' : 'LANG_SWAHILI, SUBLANG_DEFAULT',
-+ 'af' : 'LANG_AFRIKAANS, SUBLANG_DEFAULT',
-+ 'eu' : 'LANG_BASQUE, SUBLANG_DEFAULT',
-+ 'fr-CA' : 'LANG_FRENCH, SUBLANG_FRENCH_CANADIAN',
-+ 'gl' : 'LANG_GALICIAN, SUBLANG_DEFAULT',
-+ 'zu' : 'LANG_ZULU, SUBLANG_DEFAULT',
-+ 'pa' : 'LANG_PUNJABI, SUBLANG_PUNJABI_INDIA',
-+ 'sa' : 'LANG_SANSKRIT, SUBLANG_SANSKRIT_INDIA',
-+ 'si' : 'LANG_SINHALESE, SUBLANG_SINHALESE_SRI_LANKA',
-+ 'ne' : 'LANG_NEPALI, SUBLANG_NEPALI_NEPAL',
-+ 'ti' : 'LANG_TIGRIGNA, SUBLANG_TIGRIGNA_ERITREA',
-+ 'fake-bidi' : 'LANG_HEBREW, SUBLANG_DEFAULT',
-+}
-+
-+# A note on 'no-specific-language' in the following few functions:
-+# Some build systems may wish to call GRIT to scan for dependencies in
-+# a language-agnostic way, and can then specify this fake language as
-+# the output context. It should never be used when output is actually
-+# being generated.
-+
-+def GetLangCharsetPair(language):
-+ if language in _LANGUAGE_CHARSET_PAIR:
-+ return _LANGUAGE_CHARSET_PAIR[language]
-+ if language != 'no-specific-language':
-+ print('Warning:GetLangCharsetPair() found undefined language %s' % language)
-+ return ''
-+
-+def GetLangDirectivePair(language):
-+ if language in _LANGUAGE_DIRECTIVE_PAIR:
-+ return _LANGUAGE_DIRECTIVE_PAIR[language]
-+
-+ # We don't check for 'no-specific-language' here because this
-+ # function should only get called when output is being formatted,
-+ # and at that point we would not want to get
-+ # 'no-specific-language' passed as the language.
-+ print('Warning:GetLangDirectivePair() found undefined language %s' % language)
-+ return 'unknown language: see tools/grit/format/rc.py'
-+
-+def GetLangIdHex(language):
-+ if language in _LANGUAGE_CHARSET_PAIR:
-+ langcharset = _LANGUAGE_CHARSET_PAIR[language]
-+ lang_id = '0x' + langcharset[0:4]
-+ return lang_id
-+ if language != 'no-specific-language':
-+ print('Warning:GetLangIdHex() found undefined language %s' % language)
-+ return ''
-+
-+
-+def GetCharsetIdDecimal(language):
-+ if language in _LANGUAGE_CHARSET_PAIR:
-+ langcharset = _LANGUAGE_CHARSET_PAIR[language]
-+ charset_decimal = int(langcharset[4:], 16)
-+ return str(charset_decimal)
-+ if language != 'no-specific-language':
-+ print('Warning:GetCharsetIdDecimal() found undefined language %s' % language)
-+ return ''
-+
-+
-+def GetUnifiedLangCode(language) :
-+ r = re.compile('([a-z]{1,2})_([a-z]{1,2})')
-+ if r.match(language) :
-+ underscore = language.find('_')
-+ return language[0:underscore] + '-' + language[underscore + 1:].upper()
-+ return language
-+
-+
-+def RcSubstitutions(substituter, lang):
-+ '''Add language-based substitutions for Rc files to the substitutor.'''
-+ unified_lang_code = GetUnifiedLangCode(lang)
-+ substituter.AddSubstitutions({
-+ 'GRITVERLANGCHARSETHEX': GetLangCharsetPair(unified_lang_code),
-+ 'GRITVERLANGID': GetLangIdHex(unified_lang_code),
-+ 'GRITVERCHARSETID': GetCharsetIdDecimal(unified_lang_code)})
-+
-+
-+def _FormatHeader(root, lang, output_dir):
-+ '''Returns the required preamble for RC files.'''
-+ assert isinstance(lang, six.string_types)
-+ assert isinstance(root, misc.GritNode)
-+ # Find the location of the resource header file, so that we can include
-+ # it.
-+ resource_header = 'resource.h' # fall back to this
-+ language_directive = ''
-+ for output in root.GetOutputFiles():
-+ if output.attrs['type'] == 'rc_header':
-+ resource_header = os.path.abspath(output.GetOutputFilename())
-+ resource_header = util.MakeRelativePath(output_dir, resource_header)
-+ if output.attrs['lang'] != lang:
-+ continue
-+ if output.attrs['language_section'] == '':
-+ # If no language_section is requested, no directive is added
-+ # (Used when the generated rc will be included from another rc
-+ # file that will have the appropriate language directive)
-+ language_directive = ''
-+ elif output.attrs['language_section'] == 'neutral':
-+ # If a neutral language section is requested (default), add a
-+ # neutral language directive
-+ language_directive = 'LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL'
-+ elif output.attrs['language_section'] == 'lang':
-+ language_directive = 'LANGUAGE %s' % GetLangDirectivePair(lang)
-+ resource_header = resource_header.replace('\\', '\\\\')
-+ return '''// This file is automatically generated by GRIT. Do not edit.
-+
-+#include "%s"
-+#include <winresrc.h>
-+#ifdef IDC_STATIC
-+#undef IDC_STATIC
-+#endif
-+#define IDC_STATIC (-1)
-+
-+%s
-+
-+
-+''' % (resource_header, language_directive)
-+# end _FormatHeader() function
-+
-+
-+def FormatMessage(item, lang):
-+ '''Returns a single message of a string table.'''
-+ message = item.ws_at_start + item.Translate(lang) + item.ws_at_end
-+ # Escape quotation marks (RC format uses doubling-up
-+ message = message.replace('"', '""')
-+ # Replace linebreaks with a \n escape
-+ message = util.LINEBREAKS.sub(r'\\n', message)
-+ if hasattr(item.GetRoot(), 'GetSubstituter'):
-+ substituter = item.GetRoot().GetSubstituter()
-+ message = substituter.Substitute(message)
-+
-+ name_attr = item.GetTextualIds()[0]
-+
-+ return ' %-15s "%s"\n' % (name_attr, message)
-+
-+
-+def _FormatSection(item, lang, output_dir):
-+ '''Writes out an .rc file section.'''
-+ assert isinstance(lang, six.string_types)
-+ from grit.node import structure
-+ assert isinstance(item, structure.StructureNode)
-+
-+ if item.IsExcludedFromRc():
-+ return ''
-+
-+ text = item.gatherer.Translate(
-+ lang, skeleton_gatherer=item.GetSkeletonGatherer(),
-+ pseudo_if_not_available=item.PseudoIsAllowed(),
-+ fallback_to_english=item.ShouldFallbackToEnglish()) + '\n\n'
-+
-+ # Replace the language expand_variables in version rc info.
-+ if item.ExpandVariables() and hasattr(item.GetRoot(), 'GetSubstituter'):
-+ substituter = item.GetRoot().GetSubstituter()
-+ text = substituter.Substitute(text)
-+ return text
-+
-+
-+def FormatInclude(item, lang, output_dir, type=None, process_html=False):
-+ '''Formats an item that is included in an .rc file (e.g. an ICON).
-+
-+ Args:
-+ item: an IncludeNode or StructureNode
-+ lang, output_dir: standard formatter parameters
-+ type: .rc file resource type, e.g. 'ICON' (ignored unless item is a
-+ StructureNode)
-+ process_html: False/True (ignored unless item is a StructureNode)
-+ '''
-+ assert isinstance(lang, six.string_types)
-+ from grit.node import structure
-+ from grit.node import include
-+ assert isinstance(item, (structure.StructureNode, include.IncludeNode))
-+
-+ if isinstance(item, include.IncludeNode):
-+ type = item.attrs['type'].upper()
-+ process_html = item.attrs['flattenhtml'] == 'true'
-+ filename_only = item.attrs['filenameonly'] == 'true'
-+ relative_path = item.attrs['relativepath'] == 'true'
-+ else:
-+ assert (isinstance(item, structure.StructureNode) and item.attrs['type'] in
-+ ['admin_template', 'chrome_html', 'chrome_scaled_image',
-+ 'tr_html', 'txt'])
-+ filename_only = False
-+ relative_path = False
-+
-+ # By default, we use relative pathnames to included resources so that
-+ # sharing the resulting .rc files is possible.
-+ #
-+ # The FileForLanguage() Function has the side effect of generating the file
-+ # if needed (e.g. if it is an HTML file include).
-+ file_for_lang = item.FileForLanguage(lang, output_dir)
-+ if file_for_lang is None:
-+ return ''
-+
-+ filename = os.path.abspath(file_for_lang)
-+ if process_html:
-+ filename = item.Process(output_dir)
-+ elif filename_only:
-+ filename = os.path.basename(filename)
-+ elif relative_path:
-+ filename = util.MakeRelativePath(output_dir, filename)
-+
-+ filename = filename.replace('\\', '\\\\') # escape for the RC format
-+
-+ if isinstance(item, structure.StructureNode) and item.IsExcludedFromRc():
-+ return ''
-+
-+ name = item.attrs['name']
-+ item_id = item.GetRoot().GetIdMap()[name]
-+ return '// ID: %d\n%-18s %-18s "%s"\n' % (item_id, name, type, filename)
-+
-+
-+def _DoNotFormat(item, lang, output_dir):
-+ return ''
-+
-+
-+# Formatter instance to use for each type attribute
-+# when formatting Structure nodes.
-+_STRUCTURE_FORMATTERS = {
-+ 'accelerators' : _FormatSection,
-+ 'dialog' : _FormatSection,
-+ 'menu' : _FormatSection,
-+ 'rcdata' : _FormatSection,
-+ 'version' : _FormatSection,
-+ 'admin_template' : partial(FormatInclude, type='ADM'),
-+ 'chrome_html' : partial(FormatInclude, type='BINDATA',
-+ process_html=True),
-+ 'chrome_scaled_image' : partial(FormatInclude, type='BINDATA'),
-+ 'tr_html' : partial(FormatInclude, type='HTML'),
-+ 'txt' : partial(FormatInclude, type='TXT'),
-+ 'policy_template_metafile': _DoNotFormat,
-+}
-+
-+
-+def FormatStructure(item, lang, output_dir):
-+ formatter = _STRUCTURE_FORMATTERS[item.attrs['type']]
-+ return formatter(item, lang, output_dir)
-diff --git a/tools/grit/grit/format/rc_header.py b/tools/grit/grit/format/rc_header.py
-new file mode 100644
-index 0000000000..ea2c217f53
---- /dev/null
-+++ b/tools/grit/grit/format/rc_header.py
-@@ -0,0 +1,48 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Item formatters for RC headers.
-+'''
-+
-+from __future__ import print_function
-+
-+
-+def Format(root, lang='en', output_dir='.'):
-+ yield '''\
-+// This file is automatically generated by GRIT. Do not edit.
-+
-+#pragma once
-+'''
-+ # Check for emit nodes under the rc_header. If any emit node
-+ # is present, we assume it means the GRD file wants to override
-+ # the default header, with no includes.
-+ default_includes = ['#include <atlres.h>', '']
-+ emit_lines = []
-+ for output_node in root.GetOutputFiles():
-+ if output_node.GetType() == 'rc_header':
-+ for child in output_node.children:
-+ if child.name == 'emit' and child.attrs['emit_type'] == 'prepend':
-+ emit_lines.append(child.GetCdata())
-+ for line in emit_lines or default_includes:
-+ yield line + '\n'
-+ if root.IsWhitelistSupportEnabled():
-+ yield '#include "ui/base/resource/whitelist.h"\n'
-+ for line in FormatDefines(root):
-+ yield line
-+
-+
-+def FormatDefines(root):
-+ '''Yields #define SYMBOL 1234 lines.
-+
-+ Args:
-+ root: A GritNode.
-+ '''
-+ tids = root.GetIdMap()
-+ rc_header_format = '#define {0} {1}\n'
-+ if root.IsWhitelistSupportEnabled():
-+ rc_header_format = '#define {0} (::ui::WhitelistedResource<{1}>(), {1})\n'
-+ for item in root.ActiveDescendants():
-+ with item:
-+ for tid in item.GetTextualIds():
-+ yield rc_header_format.format(tid, tids[tid])
-diff --git a/tools/grit/grit/format/rc_header_unittest.py b/tools/grit/grit/format/rc_header_unittest.py
-new file mode 100644
-index 0000000000..eed4d70a99
---- /dev/null
-+++ b/tools/grit/grit/format/rc_header_unittest.py
-@@ -0,0 +1,138 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for the rc_header formatter'''
-+
-+# GRD samples exceed the 80 character limit.
-+# pylint: disable-msg=C6310
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+import unittest
-+
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+from grit import util
-+from grit.format import rc_header
-+
-+
-+class RcHeaderFormatterUnittest(unittest.TestCase):
-+ def FormatAll(self, grd):
-+ output = rc_header.FormatDefines(grd)
-+ return ''.join(output).replace(' ', '')
-+
-+ def testFormatter(self):
-+ grd = util.ParseGrdForUnittest('''
-+ <includes first_id="300" comment="bingo">
-+ <include type="gif" name="ID_LOGO" file="images/logo.gif" />
-+ </includes>
-+ <messages first_id="10000">
-+ <message name="IDS_GREETING" desc="Printed to greet the currently logged in user">
-+ Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, how are you doing today?
-+ </message>
-+ <message name="IDS_BONGO">
-+ Bongo!
-+ </message>
-+ </messages>
-+ <structures>
-+ <structure type="dialog" name="IDD_NARROW_DIALOG" file="rc_files/dialogs.rc" />
-+ <structure type="version" name="VS_VERSION_INFO" file="rc_files/version.rc" />
-+ </structures>''')
-+ output = self.FormatAll(grd)
-+ self.failUnless(output.count('IDS_GREETING10000'))
-+ self.failUnless(output.count('ID_LOGO300'))
-+
-+ def testOnlyDefineResourcesThatSatisfyOutputCondition(self):
-+ grd = util.ParseGrdForUnittest('''
-+ <includes first_id="300" comment="bingo">
-+ <include type="gif" name="ID_LOGO" file="images/logo.gif" />
-+ </includes>
-+ <messages first_id="10000">
-+ <message name="IDS_FIRSTPRESENTSTRING" desc="Present in .rc file.">
-+ I will appear in the .rc file.
-+ </message>
-+ <if expr="False"> <!--Do not include in the .rc files until used.-->
-+ <message name="IDS_MISSINGSTRING" desc="Not present in .rc file.">
-+ I will not appear in the .rc file.
-+ </message>
-+ </if>
-+ <if expr="lang != 'es'">
-+ <message name="IDS_LANGUAGESPECIFICSTRING" desc="Present in .rc file.">
-+ Hello.
-+ </message>
-+ </if>
-+ <if expr="lang == 'es'">
-+ <message name="IDS_LANGUAGESPECIFICSTRING" desc="Present in .rc file.">
-+ Hola.
-+ </message>
-+ </if>
-+ <message name="IDS_THIRDPRESENTSTRING" desc="Present in .rc file.">
-+ I will also appear in the .rc file.
-+ </message>
-+ </messages>''')
-+ output = self.FormatAll(grd)
-+ self.failUnless(output.count('IDS_FIRSTPRESENTSTRING10000'))
-+ self.failIf(output.count('IDS_MISSINGSTRING'))
-+ self.failUnless(output.count('IDS_LANGUAGESPECIFICSTRING10002'))
-+ self.failUnless(output.count('IDS_THIRDPRESENTSTRING10003'))
-+
-+ def testEmit(self):
-+ grd = util.ParseGrdForUnittest('''
-+ <outputs>
-+ <output type="rc_all" filename="dummy">
-+ <emit emit_type="prepend">Wrong</emit>
-+ </output>
-+ <if expr="False">
-+ <output type="rc_header" filename="dummy">
-+ <emit emit_type="prepend">No</emit>
-+ </output>
-+ </if>
-+ <output type="rc_header" filename="dummy">
-+ <emit emit_type="append">Error</emit>
-+ </output>
-+ <output type="rc_header" filename="dummy">
-+ <emit emit_type="prepend">Bingo</emit>
-+ </output>
-+ </outputs>''')
-+ output = ''.join(rc_header.Format(grd, 'en', '.'))
-+ output = util.StripBlankLinesAndComments(output)
-+ self.assertEqual('#pragma once\nBingo', output)
-+
-+ def testRcHeaderFormat(self):
-+ grd = util.ParseGrdForUnittest('''
-+ <includes first_id="300" comment="bingo">
-+ <include type="gif" name="IDR_LOGO" file="images/logo.gif" />
-+ </includes>
-+ <messages first_id="10000">
-+ <message name="IDS_GREETING" desc="Printed to greet the currently logged in user">
-+ Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, how are you doing today?
-+ </message>
-+ <message name="IDS_BONGO">
-+ Bongo!
-+ </message>
-+ </messages>''')
-+
-+ # Using the default settings.
-+ output = rc_header.FormatDefines(grd)
-+ self.assertEqual(('#define IDR_LOGO 300\n'
-+ '#define IDS_GREETING 10000\n'
-+ '#define IDS_BONGO 10001\n'), ''.join(output))
-+
-+ # Using resource whitelist support.
-+ grd.SetWhitelistSupportEnabled(True)
-+ output = rc_header.FormatDefines(grd)
-+ self.assertEqual(('#define IDR_LOGO '
-+ '(::ui::WhitelistedResource<300>(), 300)\n'
-+ '#define IDS_GREETING '
-+ '(::ui::WhitelistedResource<10000>(), 10000)\n'
-+ '#define IDS_BONGO '
-+ '(::ui::WhitelistedResource<10001>(), 10001)\n'),
-+ ''.join(output))
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/format/rc_unittest.py b/tools/grit/grit/format/rc_unittest.py
-new file mode 100644
-index 0000000000..d23f063596
---- /dev/null
-+++ b/tools/grit/grit/format/rc_unittest.py
-@@ -0,0 +1,415 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2011 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for grit.format.rc'''
-+
-+from __future__ import print_function
-+
-+import os
-+import re
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+import tempfile
-+import unittest
-+
-+from six import StringIO
-+
-+from grit import grd_reader
-+from grit import util
-+from grit.node import structure
-+from grit.tool import build
-+
-+
-+_PREAMBLE = '''\
-+#include "resource.h"
-+#include <winresrc.h>
-+#ifdef IDC_STATIC
-+#undef IDC_STATIC
-+#endif
-+#define IDC_STATIC (-1)
-+'''
-+
-+
-+class DummyOutput(object):
-+ def __init__(self, type, language, file = 'hello.gif'):
-+ self.type = type
-+ self.language = language
-+ self.file = file
-+
-+ def GetType(self):
-+ return self.type
-+
-+ def GetLanguage(self):
-+ return self.language
-+
-+ def GetOutputFilename(self):
-+ return self.file
-+
-+
-+class FormatRcUnittest(unittest.TestCase):
-+ def testMessages(self):
-+ root = util.ParseGrdForUnittest("""
-+ <messages>
-+ <message name="IDS_BTN_GO" desc="Button text" meaning="verb">Go!</message>
-+ <message name="IDS_GREETING" desc="Printed to greet the currently logged in user">
-+ Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, how are you doing today?
-+ </message>
-+ <message name="BONGO" desc="Flippo nippo">
-+ Howdie "Mr. Elephant", how are you doing? '''
-+ </message>
-+ <message name="IDS_WITH_LINEBREAKS">
-+Good day sir,
-+I am a bee
-+Sting sting
-+ </message>
-+ </messages>
-+ """)
-+
-+ buf = StringIO()
-+ build.RcBuilder.ProcessNode(root, DummyOutput('rc_all', 'en'), buf)
-+ output = util.StripBlankLinesAndComments(buf.getvalue())
-+ self.assertEqual(_PREAMBLE + u'''\
-+STRINGTABLE
-+BEGIN
-+ IDS_BTN_GO "Go!"
-+ IDS_GREETING "Hello %s, how are you doing today?"
-+ BONGO "Howdie ""Mr. Elephant"", how are you doing? "
-+ IDS_WITH_LINEBREAKS "Good day sir,\\nI am a bee\\nSting sting"
-+END''', output)
-+
-+ def testRcSection(self):
-+ root = util.ParseGrdForUnittest(r'''
-+ <structures>
-+ <structure type="menu" name="IDC_KLONKMENU" file="grit\testdata\klonk.rc" encoding="utf-16" />
-+ <structure type="dialog" name="IDD_ABOUTBOX" file="grit\testdata\klonk.rc" encoding="utf-16" />
-+ <structure type="version" name="VS_VERSION_INFO" file="grit\testdata\klonk.rc" encoding="utf-16" />
-+ </structures>''')
-+ root.SetOutputLanguage('en')
-+ root.RunGatherers()
-+
-+ buf = StringIO()
-+ build.RcBuilder.ProcessNode(root, DummyOutput('rc_all', 'en'), buf)
-+ output = util.StripBlankLinesAndComments(buf.getvalue())
-+ expected = _PREAMBLE + u'''\
-+IDC_KLONKMENU MENU
-+BEGIN
-+ POPUP "&File"
-+ BEGIN
-+ MENUITEM "E&xit", IDM_EXIT
-+ MENUITEM "This be ""Klonk"" me like", ID_FILE_THISBE
-+ POPUP "gonk"
-+ BEGIN
-+ MENUITEM "Klonk && is [good]", ID_GONK_KLONKIS
-+ END
-+ END
-+ POPUP "&Help"
-+ BEGIN
-+ MENUITEM "&About ...", IDM_ABOUT
-+ END
-+END
-+
-+IDD_ABOUTBOX DIALOGEX 22, 17, 230, 75
-+STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
-+CAPTION "About"
-+FONT 8, "System", 0, 0, 0x0
-+BEGIN
-+ ICON IDI_KLONK,IDC_MYICON,14,9,20,20
-+ LTEXT "klonk Version ""yibbee"" 1.0",IDC_STATIC,49,10,119,8,
-+ SS_NOPREFIX
-+ LTEXT "Copyright (C) 2005",IDC_STATIC,49,20,119,8
-+ DEFPUSHBUTTON "OK",IDOK,195,6,30,11,WS_GROUP
-+ CONTROL "Jack ""Black"" Daniels",IDC_RADIO1,"Button",
-+ BS_AUTORADIOBUTTON,46,51,84,10
-+END
-+
-+VS_VERSION_INFO VERSIONINFO
-+ FILEVERSION 1,0,0,1
-+ PRODUCTVERSION 1,0,0,1
-+ FILEFLAGSMASK 0x17L
-+#ifdef _DEBUG
-+ FILEFLAGS 0x1L
-+#else
-+ FILEFLAGS 0x0L
-+#endif
-+ FILEOS 0x4L
-+ FILETYPE 0x1L
-+ FILESUBTYPE 0x0L
-+BEGIN
-+ BLOCK "StringFileInfo"
-+ BEGIN
-+ BLOCK "040904b0"
-+ BEGIN
-+ VALUE "FileDescription", "klonk Application"
-+ VALUE "FileVersion", "1, 0, 0, 1"
-+ VALUE "InternalName", "klonk"
-+ VALUE "LegalCopyright", "Copyright (C) 2005"
-+ VALUE "OriginalFilename", "klonk.exe"
-+ VALUE "ProductName", " klonk Application"
-+ VALUE "ProductVersion", "1, 0, 0, 1"
-+ END
-+ END
-+ BLOCK "VarFileInfo"
-+ BEGIN
-+ VALUE "Translation", 0x409, 1200
-+ END
-+END'''.strip()
-+ for expected_line, output_line in zip(expected.split(), output.split()):
-+ self.assertEqual(expected_line, output_line)
-+
-+ def testRcIncludeStructure(self):
-+ root = util.ParseGrdForUnittest('''
-+ <structures>
-+ <structure type="tr_html" name="IDR_HTML" file="bingo.html"/>
-+ <structure type="tr_html" name="IDR_HTML2" file="bingo2.html"/>
-+ </structures>''', base_dir = '/temp')
-+ # We do not run gatherers as it is not needed and wouldn't find the file
-+
-+ buf = StringIO()
-+ build.RcBuilder.ProcessNode(root, DummyOutput('rc_all', 'en'), buf)
-+ output = util.StripBlankLinesAndComments(buf.getvalue())
-+ expected = (_PREAMBLE +
-+ u'IDR_HTML HTML "%s"\n'
-+ u'IDR_HTML2 HTML "%s"'
-+ % (util.normpath('/temp/bingo.html').replace('\\', '\\\\'),
-+ util.normpath('/temp/bingo2.html').replace('\\', '\\\\')))
-+ # hackety hack to work on win32&lin
-+ output = re.sub(r'"[c-zC-Z]:', '"', output)
-+ self.assertEqual(expected, output)
-+
-+ def testRcIncludeFile(self):
-+ root = util.ParseGrdForUnittest('''
-+ <includes>
-+ <include type="TXT" name="TEXT_ONE" file="bingo.txt"/>
-+ <include type="TXT" name="TEXT_TWO" file="bingo2.txt" filenameonly="true" />
-+ </includes>''', base_dir = '/temp')
-+
-+ buf = StringIO()
-+ build.RcBuilder.ProcessNode(root, DummyOutput('rc_all', 'en'), buf)
-+ output = util.StripBlankLinesAndComments(buf.getvalue())
-+ expected = (_PREAMBLE +
-+ u'TEXT_ONE TXT "%s"\n'
-+ u'TEXT_TWO TXT "%s"'
-+ % (util.normpath('/temp/bingo.txt').replace('\\', '\\\\'),
-+ 'bingo2.txt'))
-+ # hackety hack to work on win32&lin
-+ output = re.sub(r'"[c-zC-Z]:', '"', output)
-+ self.assertEqual(expected, output)
-+
-+ def testRcIncludeFlattenedHtmlFile(self):
-+ input_file = util.PathFromRoot('grit/testdata/include_test.html')
-+ output_file = '%s/HTML_FILE1_include_test.html' % tempfile.gettempdir()
-+ root = util.ParseGrdForUnittest('''
-+ <includes>
-+ <include name="HTML_FILE1" flattenhtml="true" file="%s" type="BINDATA" />
-+ </includes>''' % input_file)
-+
-+ buf = StringIO()
-+ build.RcBuilder.ProcessNode(root, DummyOutput('rc_all', 'en', output_file),
-+ buf)
-+ output = util.StripBlankLinesAndComments(buf.getvalue())
-+
-+ expected = (_PREAMBLE +
-+ u'HTML_FILE1 BINDATA "HTML_FILE1_include_test.html"')
-+ # hackety hack to work on win32&lin
-+ output = re.sub(r'"[c-zC-Z]:', '"', output)
-+ self.assertEqual(expected, output)
-+
-+ file_contents = util.ReadFile(output_file, 'utf-8')
-+
-+ # Check for the content added by the <include> tag.
-+ self.failUnless(file_contents.find('Hello Include!') != -1)
-+ # Check for the content that was removed by if tag.
-+ self.failUnless(file_contents.find('should be removed') == -1)
-+ # Check for the content that was kept in place by if.
-+ self.failUnless(file_contents.find('should be kept') != -1)
-+ self.failUnless(file_contents.find('in the middle...') != -1)
-+ self.failUnless(file_contents.find('at the end...') != -1)
-+ # Check for nested content that was kept
-+ self.failUnless(file_contents.find('nested true should be kept') != -1)
-+ self.failUnless(file_contents.find('silbing true should be kept') != -1)
-+ # Check for removed "<if>" and "</if>" tags.
-+ self.failUnless(file_contents.find('<if expr=') == -1)
-+ self.failUnless(file_contents.find('</if>') == -1)
-+ os.remove(output_file)
-+
-+ def testStructureNodeOutputfile(self):
-+ input_file = util.PathFromRoot('grit/testdata/simple.html')
-+ root = util.ParseGrdForUnittest('''
-+ <structures>
-+ <structure type="tr_html" name="IDR_HTML" file="%s" />
-+ </structures>''' % input_file)
-+ struct, = root.GetChildrenOfType(structure.StructureNode)
-+ # We must run the gatherer since we'll be wanting the translation of the
-+ # file. The file exists in the location pointed to.
-+ root.SetOutputLanguage('en')
-+ root.RunGatherers()
-+
-+ output_dir = tempfile.gettempdir()
-+ en_file = struct.FileForLanguage('en', output_dir)
-+ self.failUnless(en_file == input_file)
-+ fr_file = struct.FileForLanguage('fr', output_dir)
-+ self.failUnless(fr_file == os.path.join(output_dir, 'fr_simple.html'))
-+
-+ contents = util.ReadFile(fr_file, 'utf-8')
-+
-+ self.failUnless(contents.find('<p>') != -1) # should contain the markup
-+ self.failUnless(contents.find('Hello!') == -1) # should be translated
-+ os.remove(fr_file)
-+
-+ def testChromeHtmlNodeOutputfile(self):
-+ input_file = util.PathFromRoot('grit/testdata/chrome_html.html')
-+ output_file = '%s/HTML_FILE1_chrome_html.html' % tempfile.gettempdir()
-+ root = util.ParseGrdForUnittest('''
-+ <structures>
-+ <structure type="chrome_html" name="HTML_FILE1" file="%s" flattenhtml="true" />
-+ </structures>''' % input_file)
-+ struct, = root.GetChildrenOfType(structure.StructureNode)
-+ struct.gatherer.SetDefines({'scale_factors': '2x'})
-+ # We must run the gatherers since we'll be wanting the chrome_html output.
-+ # The file exists in the location pointed to.
-+ root.SetOutputLanguage('en')
-+ root.RunGatherers()
-+
-+ buf = StringIO()
-+ build.RcBuilder.ProcessNode(root, DummyOutput('rc_all', 'en', output_file),
-+ buf)
-+ output = util.StripBlankLinesAndComments(buf.getvalue())
-+ expected = (_PREAMBLE +
-+ u'HTML_FILE1 BINDATA "HTML_FILE1_chrome_html.html"')
-+ # hackety hack to work on win32&lin
-+ output = re.sub(r'"[c-zC-Z]:', '"', output)
-+ self.assertEqual(expected, output)
-+
-+ file_contents = util.ReadFile(output_file, 'utf-8')
-+
-+ # Check for the content added by the <include> tag.
-+ self.failUnless(file_contents.find('Hello Include!') != -1)
-+ # Check for inserted -webkit-image-set.
-+ self.failUnless(file_contents.find('content: -webkit-image-set') != -1)
-+ os.remove(output_file)
-+
-+ def testSubstitutionHtml(self):
-+ input_file = util.PathFromRoot('grit/testdata/toolbar_about.html')
-+ root = grd_reader.Parse(StringIO('''<?xml version="1.0" encoding="UTF-8"?>
-+ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
-+ <release seq="1" allow_pseudo="False">
-+ <structures fallback_to_english="True">
-+ <structure type="tr_html" name="IDR_HTML" file="%s" expand_variables="true"/>
-+ </structures>
-+ </release>
-+ </grit>
-+ ''' % input_file), util.PathFromRoot('.'))
-+ root.SetOutputLanguage('ar')
-+ # We must run the gatherers since we'll be wanting the translation of the
-+ # file. The file exists in the location pointed to.
-+ root.RunGatherers()
-+
-+ output_dir = tempfile.gettempdir()
-+ struct, = root.GetChildrenOfType(structure.StructureNode)
-+ ar_file = struct.FileForLanguage('ar', output_dir)
-+ self.failUnless(ar_file == os.path.join(output_dir,
-+ 'ar_toolbar_about.html'))
-+
-+ contents = util.ReadFile(ar_file, 'utf-8')
-+
-+ self.failUnless(contents.find('dir="RTL"') != -1)
-+ os.remove(ar_file)
-+
-+ def testFallbackToEnglish(self):
-+ root = util.ParseGrdForUnittest(r'''
-+ <structures fallback_to_english="True">
-+ <structure type="dialog" name="IDD_ABOUTBOX" file="grit\testdata\klonk.rc" encoding="utf-16" />
-+ </structures>''', base_dir=util.PathFromRoot('.'))
-+ root.SetOutputLanguage('en')
-+ root.RunGatherers()
-+
-+ buf = StringIO()
-+ formatter = build.RcBuilder.ProcessNode(
-+ root, DummyOutput('rc_all', 'bingobongo'), buf)
-+ output = util.StripBlankLinesAndComments(buf.getvalue())
-+ self.assertEqual(_PREAMBLE + '''\
-+IDD_ABOUTBOX DIALOGEX 22, 17, 230, 75
-+STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
-+CAPTION "About"
-+FONT 8, "System", 0, 0, 0x0
-+BEGIN
-+ ICON IDI_KLONK,IDC_MYICON,14,9,20,20
-+ LTEXT "klonk Version ""yibbee"" 1.0",IDC_STATIC,49,10,119,8,
-+ SS_NOPREFIX
-+ LTEXT "Copyright (C) 2005",IDC_STATIC,49,20,119,8
-+ DEFPUSHBUTTON "OK",IDOK,195,6,30,11,WS_GROUP
-+ CONTROL "Jack ""Black"" Daniels",IDC_RADIO1,"Button",
-+ BS_AUTORADIOBUTTON,46,51,84,10
-+END''', output)
-+
-+
-+ def testSubstitutionRc(self):
-+ root = grd_reader.Parse(StringIO(r'''<?xml version="1.0" encoding="UTF-8"?>
-+ <grit latest_public_release="2" source_lang_id="en-US" current_release="3"
-+ base_dir=".">
-+ <outputs>
-+ <output lang="en" type="rc_all" filename="grit\testdata\klonk_resources.rc"/>
-+ </outputs>
-+ <release seq="1" allow_pseudo="False">
-+ <structures>
-+ <structure type="menu" name="IDC_KLONKMENU"
-+ file="grit\testdata\klonk.rc" encoding="utf-16"
-+ expand_variables="true" />
-+ </structures>
-+ <messages>
-+ <message name="good" sub_variable="true">
-+ excellent
-+ </message>
-+ </messages>
-+ </release>
-+ </grit>
-+ '''), util.PathFromRoot('.'))
-+ root.SetOutputLanguage('en')
-+ root.RunGatherers()
-+
-+ buf = StringIO()
-+ build.RcBuilder.ProcessNode(root, DummyOutput('rc_all', 'en'), buf)
-+ output = buf.getvalue()
-+ self.assertEqual('''
-+// This file is automatically generated by GRIT. Do not edit.
-+
-+#include "resource.h"
-+#include <winresrc.h>
-+#ifdef IDC_STATIC
-+#undef IDC_STATIC
-+#endif
-+#define IDC_STATIC (-1)
-+
-+LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
-+
-+
-+IDC_KLONKMENU MENU
-+BEGIN
-+ POPUP "&File"
-+ BEGIN
-+ MENUITEM "E&xit", IDM_EXIT
-+ MENUITEM "This be ""Klonk"" me like", ID_FILE_THISBE
-+ POPUP "gonk"
-+ BEGIN
-+ MENUITEM "Klonk && is excellent", ID_GONK_KLONKIS
-+ END
-+ END
-+ POPUP "&Help"
-+ BEGIN
-+ MENUITEM "&About ...", IDM_ABOUT
-+ END
-+END
-+
-+STRINGTABLE
-+BEGIN
-+ good "excellent"
-+END
-+'''.strip(), output.strip())
-+
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/format/resource_map.py b/tools/grit/grit/format/resource_map.py
-new file mode 100644
-index 0000000000..95a8b83160
---- /dev/null
-+++ b/tools/grit/grit/format/resource_map.py
-@@ -0,0 +1,159 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''This file contains item formatters for resource_map_header and
-+resource_map_source files. A resource map is a mapping between resource names
-+(string) and the internal resource ID.'''
-+
-+from __future__ import print_function
-+
-+import os
-+from functools import partial
-+
-+from grit import util
-+
-+
-+def GetFormatter(type):
-+ if type == 'resource_map_header':
-+ return _FormatHeader
-+ if type == 'resource_file_map_source':
-+ return partial(_FormatSource, _GetItemPath)
-+ if type == 'resource_map_source':
-+ return partial(_FormatSource, _GetItemName)
-+
-+
-+def GetMapName(root):
-+ '''Get the name of the resource map based on the header file name. E.g.,
-+ if our header filename is theme_resources.h, we name our resource map
-+ kThemeResourcesMap.
-+
-+ |root| is the grd file root.'''
-+ outputs = root.GetOutputFiles()
-+ rc_header_file = None
-+ for output in outputs:
-+ if 'rc_header' == output.GetType():
-+ rc_header_file = output.GetFilename()
-+ if not rc_header_file:
-+ raise Exception('unable to find resource header filename')
-+ filename = os.path.splitext(os.path.split(rc_header_file)[1])[0]
-+ filename = filename[0].upper() + filename[1:]
-+ while True:
-+ pos = filename.find('_')
-+ if pos == -1 or pos >= len(filename):
-+ break
-+ filename = filename[:pos] + filename[pos + 1].upper() + filename[pos + 2:]
-+ return 'k' + filename
-+
-+
-+def _FormatHeader(root, lang='en', output_dir='.'):
-+ '''Create the header file for the resource mapping. This file just declares
-+ an array of name/value pairs.'''
-+ return '''\
-+// This file is automatically generated by GRIT. Do not edit.
-+
-+#include <stddef.h>
-+
-+#ifndef GRIT_RESOURCE_MAP_STRUCT_
-+#define GRIT_RESOURCE_MAP_STRUCT_
-+struct GritResourceMap {
-+ const char* const name;
-+ int value;
-+};
-+#endif // GRIT_RESOURCE_MAP_STRUCT_
-+
-+extern const GritResourceMap %(map_name)s[];
-+extern const size_t %(map_name)sSize;
-+''' % { 'map_name': GetMapName(root) }
-+
-+
-+def _FormatSourceHeader(root, output_dir):
-+ '''Create the header of the C++ source file for the resource mapping.'''
-+ rc_header_file = None
-+ map_header_file = None
-+ for output in root.GetOutputFiles():
-+ type = output.GetType()
-+ if 'rc_header' == type:
-+ rc_header_file = util.MakeRelativePath(output_dir,
-+ output.GetOutputFilename())
-+ elif 'resource_map_header' == type:
-+ map_header_file = util.MakeRelativePath(output_dir,
-+ output.GetOutputFilename())
-+ if not rc_header_file or not map_header_file:
-+ raise Exception('resource_map_source output type requires '
-+ 'a resource_map_header and rc_header outputs')
-+ return '''\
-+// This file is automatically generated by GRIT. Do not edit.
-+
-+#include "%(map_header_file)s"
-+
-+#include <stddef.h>
-+
-+#include "base/stl_util.h"
-+
-+#include "%(rc_header_file)s"
-+
-+const GritResourceMap %(map_name)s[] = {
-+''' % { 'map_header_file': map_header_file,
-+ 'rc_header_file': rc_header_file,
-+ 'map_name': GetMapName(root),
-+ }
-+
-+
-+def _FormatSourceFooter(root):
-+ # Return the footer text.
-+ return '''\
-+};
-+
-+const size_t %(map_name)sSize = base::size(%(map_name)s);
-+''' % { 'map_name': GetMapName(root) }
-+
-+
-+def _FormatSource(get_key, root, lang, output_dir):
-+ from grit.node import include, structure, message
-+ id_map = root.GetIdMap()
-+ yield _FormatSourceHeader(root, output_dir)
-+ seen = set()
-+ for item in root.ActiveDescendants():
-+ if not item.IsResourceMapSource():
-+ continue
-+ key = get_key(item)
-+ tid = item.attrs['name']
-+ if tid not in id_map or key in seen:
-+ continue
-+ seen.add(key)
-+ yield ' {"%s", %s},\n' % (key, tid)
-+ yield _FormatSourceFooter(root)
-+
-+
-+def _GetItemName(item):
-+ return item.attrs['name']
-+
-+# Check if |path2| is a subpath of |path1|.
-+def _IsSubpath(path1, path2):
-+ path1_abs = os.path.abspath(path1)
-+ path2_abs = os.path.abspath(path2)
-+ common = os.path.commonprefix([path1_abs, path2_abs])
-+ return path1_abs == common
-+
-+def _GetItemPath(item):
-+ path = item.GetInputPath().replace("\\", "/")
-+
-+ # Handle the case where the file resides within the output folder,
-+ # by expanding any variables as well as replacing the output folder name with
-+ # a fixed string such that the key added to the map does not depend on a given
-+ # developer's setup.
-+ #
-+ # For example this will convert the following path:
-+ # ../../out/gchrome/${root_gen_dir}/ui/webui/resources/js/foo.js
-+ # to:
-+ # @out_folder@/gen/ui/webui/resources/js/foo.js
-+
-+ real_path = item.ToRealPath(item.GetInputPath())
-+ if (item.attrs.get('use_base_dir', 'true') != 'true' and
-+ _IsSubpath(os.path.curdir, real_path)):
-+ path = os.path.join(
-+ '@out_folder@', os.path.relpath(real_path)).replace("\\", "/")
-+
-+ assert '$' not in path, 'all variables should have been expanded'
-+ return path
-diff --git a/tools/grit/grit/format/resource_map_unittest.py b/tools/grit/grit/format/resource_map_unittest.py
-new file mode 100644
-index 0000000000..3499b321ef
---- /dev/null
-+++ b/tools/grit/grit/format/resource_map_unittest.py
-@@ -0,0 +1,345 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for grit.format.resource_map'''
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+import unittest
-+
-+from grit import util
-+from grit.format import resource_map
-+
-+
-+class FormatResourceMapUnittest(unittest.TestCase):
-+ def testFormatResourceMap(self):
-+ grd = util.ParseGrdForUnittest('''
-+ <outputs>
-+ <output type="rc_header" filename="the_rc_header.h" />
-+ <output type="resource_map_header"
-+ filename="the_resource_map_header.h" />
-+ </outputs>
-+ <release seq="3">
-+ <structures first_id="300">
-+ <structure type="menu" name="IDC_KLONKMENU"
-+ file="grit\\testdata\\klonk.rc" encoding="utf-16" />
-+ </structures>
-+ <includes first_id="10000">
-+ <include type="foo" file="abc" name="IDS_FIRSTPRESENT" />
-+ <if expr="False">
-+ <include type="foo" file="def" name="IDS_MISSING" />
-+ </if>
-+ <if expr="lang != 'es'">
-+ <include type="foo" file="ghi" name="IDS_LANGUAGESPECIFIC" />
-+ </if>
-+ <if expr="lang == 'es'">
-+ <include type="foo" file="jkl" name="IDS_LANGUAGESPECIFIC" />
-+ </if>
-+ <include type="foo" file="mno" name="IDS_THIRDPRESENT" />
-+ <include type="foo" file="opq" name="IDS_FOURTHPRESENT"
-+ skip_in_resource_map="true" />
-+ </includes>
-+ </release>''', run_gatherers=True)
-+ output = util.StripBlankLinesAndComments(''.join(
-+ resource_map.GetFormatter('resource_map_header')(grd, 'en', '.')))
-+ self.assertEqual('''\
-+#include <stddef.h>
-+#ifndef GRIT_RESOURCE_MAP_STRUCT_
-+#define GRIT_RESOURCE_MAP_STRUCT_
-+struct GritResourceMap {
-+ const char* const name;
-+ int value;
-+};
-+#endif // GRIT_RESOURCE_MAP_STRUCT_
-+extern const GritResourceMap kTheRcHeader[];
-+extern const size_t kTheRcHeaderSize;''', output)
-+ output = util.StripBlankLinesAndComments(''.join(
-+ resource_map.GetFormatter('resource_map_source')(grd, 'en', '.')))
-+ self.assertEqual('''\
-+#include "the_resource_map_header.h"
-+#include <stddef.h>
-+#include "base/stl_util.h"
-+#include "the_rc_header.h"
-+const GritResourceMap kTheRcHeader[] = {
-+ {"IDC_KLONKMENU", IDC_KLONKMENU},
-+ {"IDS_FIRSTPRESENT", IDS_FIRSTPRESENT},
-+ {"IDS_LANGUAGESPECIFIC", IDS_LANGUAGESPECIFIC},
-+ {"IDS_THIRDPRESENT", IDS_THIRDPRESENT},
-+};
-+const size_t kTheRcHeaderSize = base::size(kTheRcHeader);''', output)
-+ output = util.StripBlankLinesAndComments(''.join(
-+ resource_map.GetFormatter('resource_file_map_source')(grd, 'en', '.')))
-+ self.assertEqual('''\
-+#include "the_resource_map_header.h"
-+#include <stddef.h>
-+#include "base/stl_util.h"
-+#include "the_rc_header.h"
-+const GritResourceMap kTheRcHeader[] = {
-+ {"grit/testdata/klonk.rc", IDC_KLONKMENU},
-+ {"abc", IDS_FIRSTPRESENT},
-+ {"ghi", IDS_LANGUAGESPECIFIC},
-+ {"mno", IDS_THIRDPRESENT},
-+};
-+const size_t kTheRcHeaderSize = base::size(kTheRcHeader);''', output)
-+
-+ def testFormatResourceMapWithGeneratedFile(self):
-+ os.environ["root_gen_dir"] = "gen"
-+
-+ grd = util.ParseGrdForUnittest('''\
-+ <outputs>
-+ <output type="rc_header" filename="the_rc_header.h" />
-+ <output type="resource_map_header"
-+ filename="resource_map_header.h" />
-+ </outputs>
-+ <release seq="3">
-+ <includes first_id="10000">
-+ <include type="BINDATA"
-+ file="${root_gen_dir}/foo/bar/baz.js"
-+ name="IDR_FOO_BAR_BAZ_JS"
-+ use_base_dir="false"
-+ compress="gzip" />
-+ </includes>
-+ </release>''', run_gatherers=True)
-+
-+ formatter = resource_map.GetFormatter('resource_file_map_source')
-+ output = util.StripBlankLinesAndComments(''.join(formatter(grd, 'en', '.')))
-+ expected = '''\
-+#include "resource_map_header.h"
-+#include <stddef.h>
-+#include "base/stl_util.h"
-+#include "the_rc_header.h"
-+const GritResourceMap kTheRcHeader[] = {
-+ {"@out_folder@/gen/foo/bar/baz.js", IDR_FOO_BAR_BAZ_JS},
-+};
-+const size_t kTheRcHeaderSize = base::size(kTheRcHeader);'''
-+ self.assertEqual(expected, output)
-+
-+ def testFormatResourceMapWithOutputAllEqualsFalseForStructures(self):
-+ grd = util.ParseGrdForUnittest('''
-+ <outputs>
-+ <output type="rc_header" filename="the_rc_header.h" />
-+ <output type="resource_map_header"
-+ filename="the_resource_map_header.h" />
-+ <output type="resource_map_source"
-+ filename="the_resource_map_header.cc" />
-+ </outputs>
-+ <release seq="3">
-+ <structures first_id="300">
-+ <structure type="chrome_scaled_image" name="IDR_KLONKMENU"
-+ file="foo.png" />
-+ <if expr="False">
-+ <structure type="chrome_scaled_image" name="IDR_MISSING"
-+ file="bar.png" />
-+ </if>
-+ <if expr="True">
-+ <structure type="chrome_scaled_image" name="IDR_BLOB"
-+ file="blob.png" />
-+ </if>
-+ <if expr="True">
-+ <then>
-+ <structure type="chrome_scaled_image" name="IDR_METEOR"
-+ file="meteor.png" />
-+ </then>
-+ <else>
-+ <structure type="chrome_scaled_image" name="IDR_METEOR"
-+ file="roetem.png" />
-+ </else>
-+ </if>
-+ <if expr="False">
-+ <structure type="chrome_scaled_image" name="IDR_LAST"
-+ file="zyx.png" />
-+ </if>
-+ <if expr="True">
-+ <structure type="chrome_scaled_image" name="IDR_LAST"
-+ file="xyz.png" />
-+ </if>
-+ </structures>
-+ </release>''', run_gatherers=True)
-+ output = util.StripBlankLinesAndComments(''.join(
-+ resource_map.GetFormatter('resource_map_header')(grd, 'en', '.')))
-+ self.assertEqual('''\
-+#include <stddef.h>
-+#ifndef GRIT_RESOURCE_MAP_STRUCT_
-+#define GRIT_RESOURCE_MAP_STRUCT_
-+struct GritResourceMap {
-+ const char* const name;
-+ int value;
-+};
-+#endif // GRIT_RESOURCE_MAP_STRUCT_
-+extern const GritResourceMap kTheRcHeader[];
-+extern const size_t kTheRcHeaderSize;''', output)
-+ output = util.StripBlankLinesAndComments(''.join(
-+ resource_map.GetFormatter('resource_map_source')(grd, 'en', '.')))
-+ self.assertEqual('''\
-+#include "the_resource_map_header.h"
-+#include <stddef.h>
-+#include "base/stl_util.h"
-+#include "the_rc_header.h"
-+const GritResourceMap kTheRcHeader[] = {
-+ {"IDR_KLONKMENU", IDR_KLONKMENU},
-+ {"IDR_BLOB", IDR_BLOB},
-+ {"IDR_METEOR", IDR_METEOR},
-+ {"IDR_LAST", IDR_LAST},
-+};
-+const size_t kTheRcHeaderSize = base::size(kTheRcHeader);''', output)
-+ output = util.StripBlankLinesAndComments(''.join(
-+ resource_map.GetFormatter('resource_map_source')(grd, 'en', '.')))
-+ self.assertEqual('''\
-+#include "the_resource_map_header.h"
-+#include <stddef.h>
-+#include "base/stl_util.h"
-+#include "the_rc_header.h"
-+const GritResourceMap kTheRcHeader[] = {
-+ {"IDR_KLONKMENU", IDR_KLONKMENU},
-+ {"IDR_BLOB", IDR_BLOB},
-+ {"IDR_METEOR", IDR_METEOR},
-+ {"IDR_LAST", IDR_LAST},
-+};
-+const size_t kTheRcHeaderSize = base::size(kTheRcHeader);''', output)
-+
-+ def testFormatResourceMapWithOutputAllEqualsFalseForIncludes(self):
-+ grd = util.ParseGrdForUnittest('''
-+ <outputs>
-+ <output type="rc_header" filename="the_rc_header.h" />
-+ <output type="resource_map_header"
-+ filename="the_resource_map_header.h" />
-+ </outputs>
-+ <release seq="3">
-+ <structures first_id="300">
-+ <structure type="menu" name="IDC_KLONKMENU"
-+ file="grit\\testdata\\klonk.rc" encoding="utf-16" />
-+ </structures>
-+ <includes first_id="10000">
-+ <include type="foo" file="abc" name="IDS_FIRSTPRESENT" />
-+ <if expr="False">
-+ <include type="foo" file="def" name="IDS_MISSING" />
-+ </if>
-+ <include type="foo" file="mno" name="IDS_THIRDPRESENT" />
-+ <if expr="True">
-+ <include type="foo" file="blob" name="IDS_BLOB" />
-+ </if>
-+ <if expr="True">
-+ <then>
-+ <include type="foo" file="meteor" name="IDS_METEOR" />
-+ </then>
-+ <else>
-+ <include type="foo" file="roetem" name="IDS_METEOR" />
-+ </else>
-+ </if>
-+ <if expr="False">
-+ <include type="foo" file="zyx" name="IDS_LAST" />
-+ </if>
-+ <if expr="True">
-+ <include type="foo" file="xyz" name="IDS_LAST" />
-+ </if>
-+ </includes>
-+ </release>''', run_gatherers=True)
-+ output = util.StripBlankLinesAndComments(''.join(
-+ resource_map.GetFormatter('resource_map_header')(grd, 'en', '.')))
-+ self.assertEqual('''\
-+#include <stddef.h>
-+#ifndef GRIT_RESOURCE_MAP_STRUCT_
-+#define GRIT_RESOURCE_MAP_STRUCT_
-+struct GritResourceMap {
-+ const char* const name;
-+ int value;
-+};
-+#endif // GRIT_RESOURCE_MAP_STRUCT_
-+extern const GritResourceMap kTheRcHeader[];
-+extern const size_t kTheRcHeaderSize;''', output)
-+ output = util.StripBlankLinesAndComments(''.join(
-+ resource_map.GetFormatter('resource_map_source')(grd, 'en', '.')))
-+ self.assertEqual('''\
-+#include "the_resource_map_header.h"
-+#include <stddef.h>
-+#include "base/stl_util.h"
-+#include "the_rc_header.h"
-+const GritResourceMap kTheRcHeader[] = {
-+ {"IDC_KLONKMENU", IDC_KLONKMENU},
-+ {"IDS_FIRSTPRESENT", IDS_FIRSTPRESENT},
-+ {"IDS_THIRDPRESENT", IDS_THIRDPRESENT},
-+ {"IDS_BLOB", IDS_BLOB},
-+ {"IDS_METEOR", IDS_METEOR},
-+ {"IDS_LAST", IDS_LAST},
-+};
-+const size_t kTheRcHeaderSize = base::size(kTheRcHeader);''', output)
-+ output = util.StripBlankLinesAndComments(''.join(
-+ resource_map.GetFormatter('resource_file_map_source')(grd, 'en', '.')))
-+ self.assertEqual('''\
-+#include "the_resource_map_header.h"
-+#include <stddef.h>
-+#include "base/stl_util.h"
-+#include "the_rc_header.h"
-+const GritResourceMap kTheRcHeader[] = {
-+ {"grit/testdata/klonk.rc", IDC_KLONKMENU},
-+ {"abc", IDS_FIRSTPRESENT},
-+ {"mno", IDS_THIRDPRESENT},
-+ {"blob", IDS_BLOB},
-+ {"meteor", IDS_METEOR},
-+ {"xyz", IDS_LAST},
-+};
-+const size_t kTheRcHeaderSize = base::size(kTheRcHeader);''', output)
-+
-+ def testFormatStringResourceMap(self):
-+ grd = util.ParseGrdForUnittest('''
-+ <outputs>
-+ <output type="rc_header" filename="the_rc_header.h" />
-+ <output type="resource_map_header" filename="the_rc_map_header.h" />
-+ <output type="resource_map_source" filename="the_rc_map_source.cc" />
-+ </outputs>
-+ <release seq="1" allow_pseudo="false">
-+ <messages fallback_to_english="true">
-+ <message name="IDS_PRODUCT_NAME" desc="The application name">
-+ Application
-+ </message>
-+ <if expr="True">
-+ <message name="IDS_DEFAULT_TAB_TITLE_TITLE_CASE"
-+ desc="In Title Case: The default title in a tab.">
-+ New Tab
-+ </message>
-+ </if>
-+ <if expr="False">
-+ <message name="IDS_DEFAULT_TAB_TITLE"
-+ desc="The default title in a tab.">
-+ New tab
-+ </message>
-+ </if>
-+ </messages>
-+ </release>''', run_gatherers=True)
-+ grd.InitializeIds()
-+ output = util.StripBlankLinesAndComments(''.join(
-+ resource_map.GetFormatter('resource_map_header')(grd, 'en', '.')))
-+ self.assertEqual('''\
-+#include <stddef.h>
-+#ifndef GRIT_RESOURCE_MAP_STRUCT_
-+#define GRIT_RESOURCE_MAP_STRUCT_
-+struct GritResourceMap {
-+ const char* const name;
-+ int value;
-+};
-+#endif // GRIT_RESOURCE_MAP_STRUCT_
-+extern const GritResourceMap kTheRcHeader[];
-+extern const size_t kTheRcHeaderSize;''', output)
-+ output = util.StripBlankLinesAndComments(''.join(
-+ resource_map.GetFormatter('resource_map_source')(grd, 'en', '.')))
-+ self.assertEqual('''\
-+#include "the_rc_map_header.h"
-+#include <stddef.h>
-+#include "base/stl_util.h"
-+#include "the_rc_header.h"
-+const GritResourceMap kTheRcHeader[] = {
-+ {"IDS_PRODUCT_NAME", IDS_PRODUCT_NAME},
-+ {"IDS_DEFAULT_TAB_TITLE_TITLE_CASE", IDS_DEFAULT_TAB_TITLE_TITLE_CASE},
-+};
-+const size_t kTheRcHeaderSize = base::size(kTheRcHeader);''', output)
-+
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/gather/__init__.py b/tools/grit/grit/gather/__init__.py
-new file mode 100644
-index 0000000000..2d578f5643
---- /dev/null
-+++ b/tools/grit/grit/gather/__init__.py
-@@ -0,0 +1,8 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Module grit.gather
-+'''
-+
-+pass
-diff --git a/tools/grit/grit/gather/admin_template.py b/tools/grit/grit/gather/admin_template.py
-new file mode 100644
-index 0000000000..c26b6a88d7
---- /dev/null
-+++ b/tools/grit/grit/gather/admin_template.py
-@@ -0,0 +1,62 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Gatherer for administrative template files.
-+'''
-+
-+from __future__ import print_function
-+
-+import re
-+
-+from grit.gather import regexp
-+from grit import exception
-+from grit import lazy_re
-+
-+
-+class MalformedAdminTemplateException(exception.Base):
-+ '''This file doesn't look like a .adm file to me.'''
-+ pass
-+
-+
-+class AdmGatherer(regexp.RegexpGatherer):
-+ '''Gatherer for the translateable portions of an admin template.
-+
-+ This gatherer currently makes the following assumptions:
-+ - there is only one [strings] section and it is always the last section
-+ of the file
-+ - translateable strings do not need to be escaped.
-+ '''
-+
-+ # Finds the strings section as the group named 'strings'
-+ _STRINGS_SECTION = lazy_re.compile(
-+ r'(?P<first_part>.+^\[strings\])(?P<strings>.+)\Z',
-+ re.MULTILINE | re.DOTALL)
-+
-+ # Finds the translateable sections from within the [strings] section.
-+ _TRANSLATEABLES = lazy_re.compile(
-+ r'^\s*[A-Za-z0-9_]+\s*=\s*"(?P<text>.+)"\s*$',
-+ re.MULTILINE)
-+
-+ def Escape(self, text):
-+ return text.replace('\n', '\\n')
-+
-+ def UnEscape(self, text):
-+ return text.replace('\\n', '\n')
-+
-+ def Parse(self):
-+ if self.have_parsed_:
-+ return
-+ self.have_parsed_ = True
-+
-+ self.text_ = self._LoadInputFile().strip()
-+ m = self._STRINGS_SECTION.match(self.text_)
-+ if not m:
-+ raise MalformedAdminTemplateException()
-+ # Add the first part, which is all nontranslateable, to the skeleton
-+ self._AddNontranslateableChunk(m.group('first_part'))
-+ # Then parse the rest using the _TRANSLATEABLES regexp.
-+ self._RegExpParse(self._TRANSLATEABLES, m.group('strings'))
-+
-+ def GetTextualIds(self):
-+ return [self.extkey]
-diff --git a/tools/grit/grit/gather/admin_template_unittest.py b/tools/grit/grit/gather/admin_template_unittest.py
-new file mode 100644
-index 0000000000..c637af3a75
---- /dev/null
-+++ b/tools/grit/grit/gather/admin_template_unittest.py
-@@ -0,0 +1,115 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for the admin template gatherer.'''
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+import unittest
-+
-+from six import StringIO
-+
-+from grit.gather import admin_template
-+from grit import util
-+from grit import grd_reader
-+from grit import grit_runner
-+from grit.tool import build
-+
-+
-+class AdmGathererUnittest(unittest.TestCase):
-+ def testParsingAndTranslating(self):
-+ pseudofile = StringIO(
-+ 'bingo bongo\n'
-+ 'ding dong\n'
-+ '[strings] \n'
-+ 'whatcha="bingo bongo"\n'
-+ 'gotcha = "bingolabongola "the wise" fingulafongula" \n')
-+ gatherer = admin_template.AdmGatherer(pseudofile)
-+ gatherer.Parse()
-+ self.failUnless(len(gatherer.GetCliques()) == 2)
-+ self.failUnless(gatherer.GetCliques()[1].GetMessage().GetRealContent() ==
-+ 'bingolabongola "the wise" fingulafongula')
-+
-+ translation = gatherer.Translate('en')
-+ self.failUnless(translation == gatherer.GetText().strip())
-+
-+ def testErrorHandling(self):
-+ pseudofile = StringIO(
-+ 'bingo bongo\n'
-+ 'ding dong\n'
-+ 'whatcha="bingo bongo"\n'
-+ 'gotcha = "bingolabongola "the wise" fingulafongula" \n')
-+ gatherer = admin_template.AdmGatherer(pseudofile)
-+ self.assertRaises(admin_template.MalformedAdminTemplateException,
-+ gatherer.Parse)
-+
-+ _TRANSLATABLES_FROM_FILE = (
-+ 'Google', 'Google Desktop', 'Preferences',
-+ 'Controls Google Desktop preferences',
-+ 'Indexing and Capture Control',
-+ 'Controls what files, web pages, and other content will be indexed by Google Desktop.',
-+ 'Prevent indexing of email',
-+ # there are lots more but we don't check any further
-+ )
-+
-+ def VerifyCliquesFromAdmFile(self, cliques):
-+ self.failUnless(len(cliques) > 20)
-+ for clique, expected in zip(cliques, self._TRANSLATABLES_FROM_FILE):
-+ text = clique.GetMessage().GetRealContent()
-+ self.failUnless(text == expected)
-+
-+ def testFromFile(self):
-+ fname = util.PathFromRoot('grit/testdata/GoogleDesktop.adm')
-+ gatherer = admin_template.AdmGatherer(fname)
-+ gatherer.Parse()
-+ cliques = gatherer.GetCliques()
-+ self.VerifyCliquesFromAdmFile(cliques)
-+
-+ def MakeGrd(self):
-+ grd = grd_reader.Parse(StringIO('''<?xml version="1.0" encoding="UTF-8"?>
-+ <grit latest_public_release="2" source_lang_id="en-US" current_release="3">
-+ <release seq="3">
-+ <structures>
-+ <structure type="admin_template" name="IDAT_GOOGLE_DESKTOP_SEARCH"
-+ file="GoogleDesktop.adm" exclude_from_rc="true" />
-+ <structure type="txt" name="BINGOBONGO"
-+ file="README.txt" exclude_from_rc="true" />
-+ </structures>
-+ </release>
-+ <outputs>
-+ <output filename="de_res.rc" type="rc_all" lang="de" />
-+ </outputs>
-+ </grit>'''), util.PathFromRoot('grit/testdata'))
-+ grd.SetOutputLanguage('en')
-+ grd.RunGatherers()
-+ return grd
-+
-+ def testInGrd(self):
-+ grd = self.MakeGrd()
-+ cliques = grd.children[0].children[0].children[0].GetCliques()
-+ self.VerifyCliquesFromAdmFile(cliques)
-+
-+ def testFileIsOutput(self):
-+ grd = self.MakeGrd()
-+ dirname = util.TempDir({})
-+ try:
-+ tool = build.RcBuilder()
-+ tool.o = grit_runner.Options()
-+ tool.output_directory = dirname.GetPath()
-+ tool.res = grd
-+ tool.Process()
-+
-+ self.failUnless(os.path.isfile(dirname.GetPath('de_GoogleDesktop.adm')))
-+ self.failUnless(os.path.isfile(dirname.GetPath('de_README.txt')))
-+ finally:
-+ dirname.CleanUp()
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/gather/chrome_html.py b/tools/grit/grit/gather/chrome_html.py
-new file mode 100644
-index 0000000000..71c1332d66
---- /dev/null
-+++ b/tools/grit/grit/gather/chrome_html.py
-@@ -0,0 +1,377 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+"""Prepares a Chrome HTML file by inlining resources and adding references to
-+high DPI resources and removing references to unsupported scale factors.
-+
-+This is a small gatherer that takes a HTML file, looks for src attributes
-+and inlines the specified file, producing one HTML file with no external
-+dependencies. It recursively inlines the included files. When inlining CSS
-+image files this script also checks for the existence of high DPI versions
-+of the inlined file including those on relevant platforms. Unsupported scale
-+factors are also removed from existing image sets to support explicitly
-+referencing all available images.
-+"""
-+
-+from __future__ import print_function
-+
-+import os
-+import re
-+
-+from grit import lazy_re
-+from grit import util
-+from grit.format import html_inline
-+from grit.gather import interface
-+
-+
-+# Distribution string to replace with distribution.
-+DIST_SUBSTR = '%DISTRIBUTION%'
-+
-+
-+# Matches a chrome theme source URL.
-+_THEME_SOURCE = lazy_re.compile(
-+ r'(?P<baseurl>chrome://theme/IDR_[A-Z0-9_]*)(?P<query>\?.*)?')
-+# Pattern for matching CSS url() function.
-+_CSS_URL_PATTERN = r'url\((?P<quote>"|\'|)(?P<filename>[^"\'()]*)(?P=quote)\)'
-+# Matches CSS url() functions with the capture group 'filename'.
-+_CSS_URL = lazy_re.compile(_CSS_URL_PATTERN)
-+# Matches one or more CSS image urls used in given properties.
-+_CSS_IMAGE_URLS = lazy_re.compile(
-+ r'(?P<attribute>content|background|[\w-]*-image):\s*'
-+ r'(?P<urls>(' + _CSS_URL_PATTERN + r'\s*,?\s*)+)')
-+# Matches CSS image sets.
-+_CSS_IMAGE_SETS = lazy_re.compile(
-+ r'(?P<attribute>content|background|[\w-]*-image):[ ]*'
-+ r'-webkit-image-set\((?P<images>'
-+ r'(\s*,?\s*url\((?P<quote>"|\'|)[^"\'()]*(?P=quote)\)[ ]*[0-9.]*x)*)\)',
-+ re.MULTILINE)
-+# Matches a single image in a CSS image set with the capture group scale.
-+_CSS_IMAGE_SET_IMAGE = lazy_re.compile(r'\s*,?\s*'
-+ r'url\((?P<quote>"|\'|)[^"\'()]*(?P=quote)\)[ ]*(?P<scale>[0-9.]*x)',
-+ re.MULTILINE)
-+_HTML_IMAGE_SRC = lazy_re.compile(
-+ r'<img[^>]+src=\"(?P<filename>[^">]*)\"[^>]*>')
-+
-+def GetImageList(
-+ base_path, filename, scale_factors, distribution,
-+ filename_expansion_function=None):
-+ """Generate the list of images which match the provided scale factors.
-+
-+ Takes an image filename and checks for files of the same name in folders
-+ corresponding to the supported scale factors. If the file is from a
-+ chrome://theme/ source, inserts supported @Nx scale factors as high DPI
-+ versions.
-+
-+ Args:
-+ base_path: path to look for relative file paths in
-+ filename: name of the base image file
-+ scale_factors: a list of the supported scale factors (i.e. ['2x'])
-+ distribution: string that should replace %DISTRIBUTION%
-+
-+ Returns:
-+ array of tuples containing scale factor and image (i.e.
-+ [('1x', 'image.png'), ('2x', '2x/image.png')]).
-+ """
-+ # Any matches for which a chrome URL handler will serve all scale factors
-+ # can simply request all scale factors.
-+ theme_match = _THEME_SOURCE.match(filename)
-+ if theme_match:
-+ images = [('1x', filename)]
-+ for scale_factor in scale_factors:
-+ scale_filename = "%s@%s" % (theme_match.group('baseurl'), scale_factor)
-+ if theme_match.group('query'):
-+ scale_filename += theme_match.group('query')
-+ images.append((scale_factor, scale_filename))
-+ return images
-+
-+ if filename.find(':') != -1:
-+ # filename is probably a URL, only return filename itself.
-+ return [('1x', filename)]
-+
-+ filename = filename.replace(DIST_SUBSTR, distribution)
-+ if filename_expansion_function:
-+ filename = filename_expansion_function(filename)
-+ filepath = os.path.join(base_path, filename)
-+ images = [('1x', filename)]
-+
-+ for scale_factor in scale_factors:
-+ # Check for existence of file and add to image set.
-+ scale_path = os.path.split(os.path.join(base_path, filename))
-+ scale_image_path = os.path.join(scale_path[0], scale_factor, scale_path[1])
-+ if os.path.isfile(scale_image_path):
-+ # HTML/CSS always uses forward slashed paths.
-+ parts = filename.rsplit('/', 1)
-+ if len(parts) == 1:
-+ path = ''
-+ else:
-+ path = parts[0] + '/'
-+ scale_image_name = path + scale_factor + '/' + parts[-1]
-+ images.append((scale_factor, scale_image_name))
-+ return images
-+
-+
-+def GenerateImageSet(images, quote):
-+ """Generates a -webkit-image-set for the provided list of images.
-+
-+ Args:
-+ images: an array of tuples giving scale factor and file path
-+ (i.e. [('1x', 'image.png'), ('2x', '2x/image.png')]).
-+ quote: a string giving the quotation character to use (i.e. "'")
-+
-+ Returns:
-+ string giving a -webkit-image-set rule referencing the provided images.
-+ (i.e. '-webkit-image-set(url('image.png') 1x, url('2x/image.png') 2x)')
-+ """
-+ imageset = []
-+ for (scale_factor, filename) in images:
-+ imageset.append("url(%s%s%s) %s" % (quote, filename, quote, scale_factor))
-+ return "-webkit-image-set(%s)" % (', '.join(imageset))
-+
-+
-+def UrlToImageSet(
-+ src_match, base_path, scale_factors, distribution,
-+ filename_expansion_function=None):
-+ """Regex replace function which replaces url() with -webkit-image-set.
-+
-+ Takes a regex match for url('path'). If the file is local, checks for
-+ files of the same name in folders corresponding to the supported scale
-+ factors. If the file is from a chrome://theme/ source, inserts the
-+ supported @Nx scale factor request. In either case inserts a
-+ -webkit-image-set rule to fetch the appropriate image for the current
-+ scale factor.
-+
-+ Args:
-+ src_match: regex match object from _CSS_URLS
-+ base_path: path to look for relative file paths in
-+ scale_factors: a list of the supported scale factors (i.e. ['2x'])
-+ distribution: string that should replace %DISTRIBUTION%.
-+
-+ Returns:
-+ string
-+ """
-+ quote = src_match.group('quote')
-+ filename = src_match.group('filename')
-+ image_list = GetImageList(
-+ base_path, filename, scale_factors, distribution,
-+ filename_expansion_function=filename_expansion_function)
-+
-+ # Don't modify the source if there is only one image.
-+ if len(image_list) == 1:
-+ return src_match.group(0)
-+
-+ return GenerateImageSet(image_list, quote)
-+
-+
-+def InsertImageSet(
-+ src_match, base_path, scale_factors, distribution,
-+ filename_expansion_function=None):
-+ """Regex replace function which inserts -webkit-image-set rules.
-+
-+ Takes a regex match for `property: url('path')[, url('path')]+`.
-+ Replaces one or more occurances of the match with image set rules.
-+
-+ Args:
-+ src_match: regex match object from _CSS_IMAGE_URLS
-+ base_path: path to look for relative file paths in
-+ scale_factors: a list of the supported scale factors (i.e. ['2x'])
-+ distribution: string that should replace %DISTRIBUTION%.
-+
-+ Returns:
-+ string
-+ """
-+ attr = src_match.group('attribute')
-+ urls = _CSS_URL.sub(
-+ lambda m: UrlToImageSet(m, base_path, scale_factors, distribution,
-+ filename_expansion_function),
-+ src_match.group('urls'))
-+
-+ return "%s: %s" % (attr, urls)
-+
-+
-+def InsertImageStyle(
-+ src_match, base_path, scale_factors, distribution,
-+ filename_expansion_function=None):
-+ """Regex replace function which adds a content style to an <img>.
-+
-+ Takes a regex match from _HTML_IMAGE_SRC and replaces the attribute with a CSS
-+ style which defines the image set.
-+ """
-+ filename = src_match.group('filename')
-+ image_list = GetImageList(
-+ base_path, filename, scale_factors, distribution,
-+ filename_expansion_function=filename_expansion_function)
-+
-+ # Don't modify the source if there is only one image or image already defines
-+ # a style.
-+ if src_match.group(0).find(" style=\"") != -1 or len(image_list) == 1:
-+ return src_match.group(0)
-+
-+ return "%s style=\"content: %s;\">" % (src_match.group(0)[:-1],
-+ GenerateImageSet(image_list, "'"))
-+
-+
-+def InsertImageSets(
-+ filepath, text, scale_factors, distribution,
-+ filename_expansion_function=None):
-+ """Helper function that adds references to external images available in any of
-+ scale_factors in CSS backgrounds.
-+ """
-+ # Add high DPI urls for css attributes: content, background,
-+ # or *-image or <img src="foo">.
-+ return _CSS_IMAGE_URLS.sub(
-+ lambda m: InsertImageSet(
-+ m, filepath, scale_factors, distribution,
-+ filename_expansion_function=filename_expansion_function),
-+ _HTML_IMAGE_SRC.sub(
-+ lambda m: InsertImageStyle(
-+ m, filepath, scale_factors, distribution,
-+ filename_expansion_function=filename_expansion_function),
-+ text))
-+
-+
-+def RemoveImagesNotIn(scale_factors, src_match):
-+ """Regex replace function which removes images for scale factors not in
-+ scale_factors.
-+
-+ Takes a regex match for _CSS_IMAGE_SETS. For each image in the group images,
-+ checks if this scale factor is in scale_factors and if not, removes it.
-+
-+ Args:
-+ scale_factors: a list of the supported scale factors (i.e. ['1x', '2x'])
-+ src_match: regex match object from _CSS_IMAGE_SETS
-+
-+ Returns:
-+ string
-+ """
-+ attr = src_match.group('attribute')
-+ images = _CSS_IMAGE_SET_IMAGE.sub(
-+ lambda m: m.group(0) if m.group('scale') in scale_factors else '',
-+ src_match.group('images'))
-+ return "%s: -webkit-image-set(%s)" % (attr, images)
-+
-+
-+def RemoveImageSetImages(text, scale_factors):
-+ """Helper function which removes images in image sets not in the list of
-+ supported scale_factors.
-+ """
-+ return _CSS_IMAGE_SETS.sub(
-+ lambda m: RemoveImagesNotIn(scale_factors, m), text)
-+
-+
-+def ProcessImageSets(
-+ filepath, text, scale_factors, distribution,
-+ filename_expansion_function=None):
-+ """Helper function that adds references to external images available in other
-+ scale_factors and removes images from image-sets in unsupported scale_factors.
-+ """
-+ # Explicitly add 1x to supported scale factors so that it is not removed.
-+ supported_scale_factors = ['1x']
-+ supported_scale_factors.extend(scale_factors)
-+ return InsertImageSets(
-+ filepath,
-+ RemoveImageSetImages(text, supported_scale_factors),
-+ scale_factors,
-+ distribution,
-+ filename_expansion_function=filename_expansion_function)
-+
-+
-+class ChromeHtml(interface.GathererBase):
-+ """Represents an HTML document processed for Chrome WebUI.
-+
-+ HTML documents used in Chrome WebUI have local resources inlined and
-+ automatically insert references to high DPI assets used in CSS properties
-+ with the use of the -webkit-image-set value. References to unsupported scale
-+ factors in image sets are also removed. This does not generate any
-+ translateable messages and instead generates a single DataPack resource.
-+ """
-+
-+ def __init__(self, *args, **kwargs):
-+ super(ChromeHtml, self).__init__(*args, **kwargs)
-+ self.allow_external_script_ = False
-+ self.flatten_html_ = False
-+ self.preprocess_only_ = False
-+ # 1x resources are implicitly already in the source and do not need to be
-+ # added.
-+ self.scale_factors_ = []
-+ self.filename_expansion_function = None
-+
-+ def SetAttributes(self, attrs):
-+ self.allow_external_script_ = ('allowexternalscript' in attrs and
-+ attrs['allowexternalscript'] == 'true')
-+ self.preprocess_only_ = ('preprocess' in attrs and
-+ attrs['preprocess'] == 'true')
-+ self.flatten_html_ = (self.preprocess_only_ or ('flattenhtml' in attrs and
-+ attrs['flattenhtml'] == 'true'))
-+
-+ def SetDefines(self, defines):
-+ if 'scale_factors' in defines:
-+ self.scale_factors_ = defines['scale_factors'].split(',')
-+
-+ def GetText(self):
-+ """Returns inlined text of the HTML document."""
-+ return self.inlined_text_
-+
-+ def GetTextualIds(self):
-+ return [self.extkey]
-+
-+ def GetData(self, lang, encoding):
-+ """Returns inlined text of the HTML document."""
-+ ret = self.inlined_text_
-+ if encoding == util.BINARY:
-+ ret = ret.encode('utf-8')
-+ return ret
-+
-+ def GetHtmlResourceFilenames(self):
-+ """Returns a set of all filenames inlined by this file."""
-+ if self.flatten_html_:
-+ return html_inline.GetResourceFilenames(
-+ self.grd_node.ToRealPath(self.GetInputPath()),
-+ self.grd_node,
-+ allow_external_script=self.allow_external_script_,
-+ rewrite_function=lambda fp, t, d: ProcessImageSets(
-+ fp, t, self.scale_factors_, d,
-+ filename_expansion_function=self.filename_expansion_function),
-+ filename_expansion_function=self.filename_expansion_function)
-+ return []
-+
-+ def Translate(self, lang, pseudo_if_not_available=True,
-+ skeleton_gatherer=None, fallback_to_english=False):
-+ """Returns this document translated."""
-+ return self.inlined_text_
-+
-+ def SetFilenameExpansionFunction(self, fn):
-+ self.filename_expansion_function = fn
-+
-+ def Parse(self):
-+ """Parses and inlines the represented file."""
-+
-+ filename = self.GetInputPath()
-+ # If there is a grd_node, prefer its GetInputPath(), as that may do more
-+ # processing to make the call to ToRealPath() below work correctly.
-+ if self.grd_node:
-+ filename = self.grd_node.GetInputPath()
-+ if self.filename_expansion_function:
-+ filename = self.filename_expansion_function(filename)
-+ # Hack: some unit tests supply an absolute path and no root node.
-+ if not os.path.isabs(filename):
-+ filename = self.grd_node.ToRealPath(filename)
-+ if self.flatten_html_:
-+ self.inlined_text_ = html_inline.InlineToString(
-+ filename,
-+ self.grd_node,
-+ allow_external_script = self.allow_external_script_,
-+ strip_whitespace=True,
-+ preprocess_only = self.preprocess_only_,
-+ rewrite_function=lambda fp, t, d: ProcessImageSets(
-+ fp, t, self.scale_factors_, d,
-+ filename_expansion_function=self.filename_expansion_function),
-+ filename_expansion_function=self.filename_expansion_function)
-+ else:
-+ distribution = html_inline.GetDistribution()
-+ self.inlined_text_ = ProcessImageSets(
-+ os.path.dirname(filename),
-+ util.ReadFile(filename, 'utf-8'),
-+ self.scale_factors_,
-+ distribution,
-+ filename_expansion_function=self.filename_expansion_function)
-diff --git a/tools/grit/grit/gather/chrome_html_unittest.py b/tools/grit/grit/gather/chrome_html_unittest.py
-new file mode 100644
-index 0000000000..8c75ee5bf4
---- /dev/null
-+++ b/tools/grit/grit/gather/chrome_html_unittest.py
-@@ -0,0 +1,610 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for grit.gather.chrome_html'''
-+
-+from __future__ import print_function
-+
-+import os
-+import re
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+import unittest
-+
-+from grit import lazy_re
-+from grit import util
-+from grit.gather import chrome_html
-+
-+
-+_NEW_LINE = lazy_re.compile('(\r\n|\r|\n)', re.MULTILINE)
-+
-+
-+def StandardizeHtml(text):
-+ '''Standardizes the newline format and png mime type in Html text.'''
-+ return _NEW_LINE.sub('\n', text).replace('data:image/x-png;',
-+ 'data:image/png;')
-+
-+
-+class ChromeHtmlUnittest(unittest.TestCase):
-+ '''Unit tests for ChromeHtml.'''
-+
-+ def testFileResources(self):
-+ '''Tests inlined image file resources with available high DPI assets.'''
-+
-+ tmp_dir = util.TempDir({
-+ 'index.html': '''
-+ <!DOCTYPE HTML>
-+ <html>
-+ <head>
-+ <link rel="stylesheet" href="test.css">
-+ </head>
-+ <body>
-+ <!-- Don't need a body. -->
-+ </body>
-+ </html>
-+ ''',
-+
-+ 'test.css': '''
-+ .image {
-+ background: url('test.png');
-+ }
-+ ''',
-+
-+ 'test.png': 'PNG DATA',
-+
-+ '1.4x/test.png': '1.4x PNG DATA',
-+
-+ '1.8x/test.png': '1.8x PNG DATA',
-+ })
-+
-+ html = chrome_html.ChromeHtml(tmp_dir.GetPath('index.html'))
-+ html.SetDefines({'scale_factors': '1.4x,1.8x'})
-+ html.SetAttributes({'flattenhtml': 'true'})
-+ html.Parse()
-+ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
-+ StandardizeHtml('''
-+ <!DOCTYPE HTML>
-+ <html>
-+ <head>
-+ <style>
-+ .image {
-+ background: -webkit-image-set(url('data:image/png;base64,UE5HIERBVEE=') 1x, url('data:image/png;base64,MS40eCBQTkcgREFUQQ==') 1.4x, url('data:image/png;base64,MS44eCBQTkcgREFUQQ==') 1.8x);
-+ }
-+ </style>
-+ </head>
-+ <body>
-+ <!-- Don't need a body. -->
-+ </body>
-+ </html>
-+ '''))
-+ tmp_dir.CleanUp()
-+
-+ def testFileResourcesImageTag(self):
-+ '''Tests inlined image file resources with available high DPI assets on
-+ an image tag.'''
-+
-+ tmp_dir = util.TempDir({
-+ 'index.html': '''
-+ <!DOCTYPE HTML>
-+ <html>
-+ <body>
-+ <img id="foo" src="test.png">
-+ </body>
-+ </html>
-+ ''',
-+
-+ 'test.png': 'PNG DATA',
-+
-+ '2x/test.png': '2x PNG DATA',
-+ })
-+
-+ html = chrome_html.ChromeHtml(tmp_dir.GetPath('index.html'))
-+ html.SetDefines({'scale_factors': '2x'})
-+ html.SetAttributes({'flattenhtml': 'true'})
-+ html.Parse()
-+ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
-+ StandardizeHtml('''
-+ <!DOCTYPE HTML>
-+ <html>
-+ <body>
-+ <img id="foo" src="data:image/png;base64,UE5HIERBVEE=" style="content: -webkit-image-set(url('data:image/png;base64,UE5HIERBVEE=') 1x, url('data:image/png;base64,MnggUE5HIERBVEE=') 2x);">
-+ </body>
-+ </html>
-+ '''))
-+ tmp_dir.CleanUp()
-+
-+ def testFileResourcesNoFlatten(self):
-+ '''Tests non-inlined image file resources with available high DPI assets.'''
-+
-+ tmp_dir = util.TempDir({
-+ 'test.css': '''
-+ .image {
-+ background: url('test.png');
-+ }
-+ ''',
-+
-+ 'test.png': 'PNG DATA',
-+
-+ '1.4x/test.png': '1.4x PNG DATA',
-+
-+ '1.8x/test.png': '1.8x PNG DATA',
-+ })
-+
-+ html = chrome_html.ChromeHtml(tmp_dir.GetPath('test.css'))
-+ html.SetDefines({'scale_factors': '1.4x,1.8x'})
-+ html.SetAttributes({'flattenhtml': 'false'})
-+ html.Parse()
-+ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
-+ StandardizeHtml('''
-+ .image {
-+ background: -webkit-image-set(url('test.png') 1x, url('1.4x/test.png') 1.4x, url('1.8x/test.png') 1.8x);
-+ }
-+ '''))
-+ tmp_dir.CleanUp()
-+
-+ def testFileResourcesNoFlattenSubdir(self):
-+ '''Tests non-inlined image file resources w/high DPI assets in subdirs.'''
-+
-+ tmp_dir = util.TempDir({
-+ 'test.css': '''
-+ .image {
-+ background: url('sub/test.png');
-+ }
-+ ''',
-+
-+ 'sub/test.png': 'PNG DATA',
-+
-+ 'sub/1.4x/test.png': '1.4x PNG DATA',
-+
-+ 'sub/1.8x/test.png': '1.8x PNG DATA',
-+ })
-+
-+ html = chrome_html.ChromeHtml(tmp_dir.GetPath('test.css'))
-+ html.SetDefines({'scale_factors': '1.4x,1.8x'})
-+ html.SetAttributes({'flattenhtml': 'false'})
-+ html.Parse()
-+ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
-+ StandardizeHtml('''
-+ .image {
-+ background: -webkit-image-set(url('sub/test.png') 1x, url('sub/1.4x/test.png') 1.4x, url('sub/1.8x/test.png') 1.8x);
-+ }
-+ '''))
-+ tmp_dir.CleanUp()
-+
-+ def testFileResourcesPreprocess(self):
-+ '''Tests preprocessed image file resources with available high DPI
-+ assets.'''
-+
-+ tmp_dir = util.TempDir({
-+ 'test.css': '''
-+ .image {
-+ background: url('test.png');
-+ }
-+ ''',
-+
-+ 'test.png': 'PNG DATA',
-+
-+ '1.4x/test.png': '1.4x PNG DATA',
-+
-+ '1.8x/test.png': '1.8x PNG DATA',
-+ })
-+
-+ html = chrome_html.ChromeHtml(tmp_dir.GetPath('test.css'))
-+ html.SetDefines({'scale_factors': '1.4x,1.8x'})
-+ html.SetAttributes({'flattenhtml': 'false', 'preprocess': 'true'})
-+ html.Parse()
-+ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
-+ StandardizeHtml('''
-+ .image {
-+ background: -webkit-image-set(url('test.png') 1x, url('1.4x/test.png') 1.4x, url('1.8x/test.png') 1.8x);
-+ }
-+ '''))
-+ tmp_dir.CleanUp()
-+
-+ def testFileResourcesDoubleQuotes(self):
-+ '''Tests inlined image file resources if url() filename is double quoted.'''
-+
-+ tmp_dir = util.TempDir({
-+ 'test.css': '''
-+ .image {
-+ background: url("test.png");
-+ }
-+ ''',
-+
-+ 'test.png': 'PNG DATA',
-+
-+ '2x/test.png': '2x PNG DATA',
-+ })
-+
-+ html = chrome_html.ChromeHtml(tmp_dir.GetPath('test.css'))
-+ html.SetDefines({'scale_factors': '2x'})
-+ html.SetAttributes({'flattenhtml': 'true'})
-+ html.Parse()
-+ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
-+ StandardizeHtml('''
-+ .image {
-+ background: -webkit-image-set(url("data:image/png;base64,UE5HIERBVEE=") 1x, url("data:image/png;base64,MnggUE5HIERBVEE=") 2x);
-+ }
-+ '''))
-+ tmp_dir.CleanUp()
-+
-+ def testFileResourcesNoQuotes(self):
-+ '''Tests inlined image file resources when url() filename is unquoted.'''
-+
-+ tmp_dir = util.TempDir({
-+ 'test.css': '''
-+ .image {
-+ background: url(test.png);
-+ }
-+ ''',
-+
-+ 'test.png': 'PNG DATA',
-+
-+ '2x/test.png': '2x PNG DATA',
-+ })
-+
-+ html = chrome_html.ChromeHtml(tmp_dir.GetPath('test.css'))
-+ html.SetDefines({'scale_factors': '2x'})
-+ html.SetAttributes({'flattenhtml': 'true'})
-+ html.Parse()
-+ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
-+ StandardizeHtml('''
-+ .image {
-+ background: -webkit-image-set(url(data:image/png;base64,UE5HIERBVEE=) 1x, url(data:image/png;base64,MnggUE5HIERBVEE=) 2x);
-+ }
-+ '''))
-+ tmp_dir.CleanUp()
-+
-+ def testFileResourcesSubdirs(self):
-+ '''Tests inlined image file resources if url() filename is in a subdir.'''
-+
-+ tmp_dir = util.TempDir({
-+ 'test.css': '''
-+ .image {
-+ background: url('some/sub/path/test.png');
-+ }
-+ ''',
-+
-+ 'some/sub/path/test.png': 'PNG DATA',
-+
-+ 'some/sub/path/2x/test.png': '2x PNG DATA',
-+ })
-+
-+ html = chrome_html.ChromeHtml(tmp_dir.GetPath('test.css'))
-+ html.SetDefines({'scale_factors': '2x'})
-+ html.SetAttributes({'flattenhtml': 'true'})
-+ html.Parse()
-+ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
-+ StandardizeHtml('''
-+ .image {
-+ background: -webkit-image-set(url('data:image/png;base64,UE5HIERBVEE=') 1x, url('data:image/png;base64,MnggUE5HIERBVEE=') 2x);
-+ }
-+ '''))
-+ tmp_dir.CleanUp()
-+
-+ def testFileResourcesNoFile(self):
-+ '''Tests inlined image file resources without available high DPI assets.'''
-+
-+ tmp_dir = util.TempDir({
-+ 'index.html': '''
-+ <!DOCTYPE HTML>
-+ <html>
-+ <head>
-+ <link rel="stylesheet" href="test.css">
-+ </head>
-+ <body>
-+ <!-- Don't need a body. -->
-+ </body>
-+ </html>
-+ ''',
-+
-+ 'test.css': '''
-+ .image {
-+ background: url('test.png');
-+ }
-+ ''',
-+
-+ 'test.png': 'PNG DATA',
-+ })
-+
-+ html = chrome_html.ChromeHtml(tmp_dir.GetPath('index.html'))
-+ html.SetDefines({'scale_factors': '2x'})
-+ html.SetAttributes({'flattenhtml': 'true'})
-+ html.Parse()
-+ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
-+ StandardizeHtml('''
-+ <!DOCTYPE HTML>
-+ <html>
-+ <head>
-+ <style>
-+ .image {
-+ background: url('data:image/png;base64,UE5HIERBVEE=');
-+ }
-+ </style>
-+ </head>
-+ <body>
-+ <!-- Don't need a body. -->
-+ </body>
-+ </html>
-+ '''))
-+ tmp_dir.CleanUp()
-+
-+ def testFileResourcesMultipleBackgrounds(self):
-+ '''Tests inlined image file resources with two url()s.'''
-+
-+ tmp_dir = util.TempDir({
-+ 'test.css': '''
-+ .image {
-+ background: url(test.png), url(test.png);
-+ }
-+ ''',
-+
-+ 'test.png': 'PNG DATA',
-+
-+ '2x/test.png': '2x PNG DATA',
-+ })
-+
-+ html = chrome_html.ChromeHtml(tmp_dir.GetPath('test.css'))
-+ html.SetDefines({'scale_factors': '2x'})
-+ html.SetAttributes({'flattenhtml': 'true'})
-+ html.Parse()
-+ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
-+ StandardizeHtml('''
-+ .image {
-+ background: -webkit-image-set(url(data:image/png;base64,UE5HIERBVEE=) 1x, url(data:image/png;base64,MnggUE5HIERBVEE=) 2x), -webkit-image-set(url(data:image/png;base64,UE5HIERBVEE=) 1x, url(data:image/png;base64,MnggUE5HIERBVEE=) 2x);
-+ }
-+ '''))
-+ tmp_dir.CleanUp()
-+
-+ def testFileResourcesMultipleBackgroundsWithNewline1(self):
-+ '''Tests inlined image file resources with line break after first url().'''
-+
-+ tmp_dir = util.TempDir({
-+ 'test.css': '''
-+ .image {
-+ background: url(test.png),
-+ url(test.png);
-+ }
-+ ''',
-+
-+ 'test.png': 'PNG DATA',
-+
-+ '2x/test.png': '2x PNG DATA',
-+ })
-+
-+ html = chrome_html.ChromeHtml(tmp_dir.GetPath('test.css'))
-+ html.SetDefines({'scale_factors': '2x'})
-+ html.SetAttributes({'flattenhtml': 'true'})
-+ html.Parse()
-+ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
-+ StandardizeHtml('''
-+ .image {
-+ background: -webkit-image-set(url(data:image/png;base64,UE5HIERBVEE=) 1x, url(data:image/png;base64,MnggUE5HIERBVEE=) 2x),
-+ -webkit-image-set(url(data:image/png;base64,UE5HIERBVEE=) 1x, url(data:image/png;base64,MnggUE5HIERBVEE=) 2x);
-+ }
-+ '''))
-+ tmp_dir.CleanUp()
-+
-+ def testFileResourcesMultipleBackgroundsWithNewline2(self):
-+ '''Tests inlined image file resources with line break before first url()
-+ and before second url().'''
-+
-+ tmp_dir = util.TempDir({
-+ 'test.css': '''
-+ .image {
-+ background:
-+ url(test.png),
-+ url(test.png);
-+ }
-+ ''',
-+
-+ 'test.png': 'PNG DATA',
-+
-+ '2x/test.png': '2x PNG DATA',
-+ })
-+
-+ html = chrome_html.ChromeHtml(tmp_dir.GetPath('test.css'))
-+ html.SetDefines({'scale_factors': '2x'})
-+ html.SetAttributes({'flattenhtml': 'true'})
-+ html.Parse()
-+ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
-+ StandardizeHtml('''
-+ .image {
-+ background: -webkit-image-set(url(data:image/png;base64,UE5HIERBVEE=) 1x, url(data:image/png;base64,MnggUE5HIERBVEE=) 2x),
-+ -webkit-image-set(url(data:image/png;base64,UE5HIERBVEE=) 1x, url(data:image/png;base64,MnggUE5HIERBVEE=) 2x);
-+ }
-+ '''))
-+ tmp_dir.CleanUp()
-+
-+ def testFileResourcesCRLF(self):
-+ '''Tests inlined image file resource when url() is preceded by a Windows
-+ style line break.'''
-+
-+ tmp_dir = util.TempDir({
-+ 'test.css': '''
-+ .image {
-+ background:\r\nurl(test.png);
-+ }
-+ ''',
-+
-+ 'test.png': 'PNG DATA',
-+
-+ '2x/test.png': '2x PNG DATA',
-+ })
-+
-+ html = chrome_html.ChromeHtml(tmp_dir.GetPath('test.css'))
-+ html.SetDefines({'scale_factors': '2x'})
-+ html.SetAttributes({'flattenhtml': 'true'})
-+ html.Parse()
-+ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
-+ StandardizeHtml('''
-+ .image {
-+ background: -webkit-image-set(url(data:image/png;base64,UE5HIERBVEE=) 1x, url(data:image/png;base64,MnggUE5HIERBVEE=) 2x);
-+ }
-+ '''))
-+ tmp_dir.CleanUp()
-+
-+ def testThemeResources(self):
-+ '''Tests inserting high DPI chrome://theme references.'''
-+
-+ tmp_dir = util.TempDir({
-+ 'index.html': '''
-+ <!DOCTYPE HTML>
-+ <html>
-+ <head>
-+ <link rel="stylesheet" href="test.css">
-+ </head>
-+ <body>
-+ <!-- Don't need a body. -->
-+ </body>
-+ </html>
-+ ''',
-+
-+ 'test.css': '''
-+ .image {
-+ background: url('chrome://theme/IDR_RESOURCE_NAME');
-+ content: url('chrome://theme/IDR_RESOURCE_NAME_WITH_Q?$1');
-+ }
-+ ''',
-+ })
-+
-+ html = chrome_html.ChromeHtml(tmp_dir.GetPath('index.html'))
-+ html.SetDefines({'scale_factors': '2x'})
-+ html.SetAttributes({'flattenhtml': 'true'})
-+ html.Parse()
-+ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
-+ StandardizeHtml('''
-+ <!DOCTYPE HTML>
-+ <html>
-+ <head>
-+ <style>
-+ .image {
-+ background: -webkit-image-set(url('chrome://theme/IDR_RESOURCE_NAME') 1x, url('chrome://theme/IDR_RESOURCE_NAME@2x') 2x);
-+ content: -webkit-image-set(url('chrome://theme/IDR_RESOURCE_NAME_WITH_Q?$1') 1x, url('chrome://theme/IDR_RESOURCE_NAME_WITH_Q@2x?$1') 2x);
-+ }
-+ </style>
-+ </head>
-+ <body>
-+ <!-- Don't need a body. -->
-+ </body>
-+ </html>
-+ '''))
-+ tmp_dir.CleanUp()
-+
-+ def testRemoveUnsupportedScale(self):
-+ '''Tests removing an unsupported scale factor from an explicit image-set.'''
-+
-+ tmp_dir = util.TempDir({
-+ 'index.html': '''
-+ <!DOCTYPE HTML>
-+ <html>
-+ <head>
-+ <link rel="stylesheet" href="test.css">
-+ </head>
-+ <body>
-+ <!-- Don't need a body. -->
-+ </body>
-+ </html>
-+ ''',
-+
-+ 'test.css': '''
-+ .image {
-+ background: -webkit-image-set(url('test.png') 1x,
-+ url('test1.4.png') 1.4x,
-+ url('test1.8.png') 1.8x);
-+ }
-+ ''',
-+
-+ 'test.png': 'PNG DATA',
-+
-+ 'test1.4.png': '1.4x PNG DATA',
-+
-+ 'test1.8.png': '1.8x PNG DATA',
-+ })
-+
-+ html = chrome_html.ChromeHtml(tmp_dir.GetPath('index.html'))
-+ html.SetDefines({'scale_factors': '1.8x'})
-+ html.SetAttributes({'flattenhtml': 'true'})
-+ html.Parse()
-+ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
-+ StandardizeHtml('''
-+ <!DOCTYPE HTML>
-+ <html>
-+ <head>
-+ <style>
-+ .image {
-+ background: -webkit-image-set(url('data:image/png;base64,UE5HIERBVEE=') 1x,
-+ url('data:image/png;base64,MS44eCBQTkcgREFUQQ==') 1.8x);
-+ }
-+ </style>
-+ </head>
-+ <body>
-+ <!-- Don't need a body. -->
-+ </body>
-+ </html>
-+ '''))
-+ tmp_dir.CleanUp()
-+
-+ def testExpandVariablesInFilename(self):
-+ '''
-+ Tests variable substitution in filenames while flattening images
-+ with multiple scale factors.
-+ '''
-+
-+ tmp_dir = util.TempDir({
-+ 'index.html': '''
-+ <!DOCTYPE HTML>
-+ <html>
-+ <head>
-+ <link rel="stylesheet" href="test.css">
-+ </head>
-+ <body>
-+ <!-- Don't need a body. -->
-+ </body>
-+ </html>
-+ ''',
-+
-+ 'test.css': '''
-+ .image {
-+ background: url('test[WHICH].png');
-+ }
-+ ''',
-+
-+ 'test1.png': 'PNG DATA',
-+ '1.4x/test1.png': '1.4x PNG DATA',
-+ '1.8x/test1.png': '1.8x PNG DATA',
-+ })
-+
-+ def replacer(var, repl):
-+ return lambda filename: filename.replace('[%s]' % var, repl)
-+
-+ html = chrome_html.ChromeHtml(tmp_dir.GetPath('index.html'))
-+ html.SetDefines({'scale_factors': '1.4x,1.8x'})
-+ html.SetAttributes({'flattenhtml': 'true'})
-+ html.SetFilenameExpansionFunction(replacer('WHICH', '1'));
-+ html.Parse()
-+ self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
-+ StandardizeHtml('''
-+ <!DOCTYPE HTML>
-+ <html>
-+ <head>
-+ <style>
-+ .image {
-+ background: -webkit-image-set(url('data:image/png;base64,UE5HIERBVEE=') 1x, url('data:image/png;base64,MS40eCBQTkcgREFUQQ==') 1.4x, url('data:image/png;base64,MS44eCBQTkcgREFUQQ==') 1.8x);
-+ }
-+ </style>
-+ </head>
-+ <body>
-+ <!-- Don't need a body. -->
-+ </body>
-+ </html>
-+ '''))
-+ tmp_dir.CleanUp()
-+
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/gather/chrome_scaled_image.py b/tools/grit/grit/gather/chrome_scaled_image.py
-new file mode 100644
-index 0000000000..44f98cbcf0
---- /dev/null
-+++ b/tools/grit/grit/gather/chrome_scaled_image.py
-@@ -0,0 +1,157 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Gatherer for <structure type="chrome_scaled_image">.
-+'''
-+
-+from __future__ import print_function
-+
-+import os
-+import struct
-+
-+from grit import exception
-+from grit import lazy_re
-+from grit import util
-+from grit.gather import interface
-+
-+
-+_PNG_SCALE_CHUNK = b'\0\0\0\0csCl\xc1\x30\x60\x4d'
-+
-+
-+def _RescaleImage(data, from_scale, to_scale):
-+ if from_scale != to_scale:
-+ assert from_scale == 100
-+ # Rather than rescaling the image we add a custom chunk directing Chrome to
-+ # rescale it on load. Just append it to the PNG data since
-+ # _MoveSpecialChunksToFront will move it later anyway.
-+ data += _PNG_SCALE_CHUNK
-+ return data
-+
-+
-+_PNG_MAGIC = b'\x89PNG\r\n\x1a\n'
-+
-+'''Mandatory first chunk in order for the png to be valid.'''
-+_FIRST_CHUNK = b'IHDR'
-+
-+'''Special chunks to move immediately after the IHDR chunk. (so that the PNG
-+remains valid.)
-+'''
-+_SPECIAL_CHUNKS = frozenset(b'csCl npTc'.split())
-+
-+'''Any ancillary chunk not in this list is deleted from the PNG.'''
-+_ANCILLARY_CHUNKS_TO_LEAVE = frozenset(
-+ b'bKGD cHRM gAMA iCCP pHYs sBIT sRGB tRNS acTL fcTL fdAT'.split())
-+
-+
-+def _MoveSpecialChunksToFront(data):
-+ '''Move special chunks immediately after the IHDR chunk (so that the PNG
-+ remains valid). Also delete ancillary chunks that are not on our whitelist.
-+ '''
-+ first = [_PNG_MAGIC]
-+ special_chunks = []
-+ rest = []
-+ for chunk in _ChunkifyPNG(data):
-+ type = chunk[4:8]
-+ critical = type < b'a'
-+ if type == _FIRST_CHUNK:
-+ first.append(chunk)
-+ elif type in _SPECIAL_CHUNKS:
-+ special_chunks.append(chunk)
-+ elif critical or type in _ANCILLARY_CHUNKS_TO_LEAVE:
-+ rest.append(chunk)
-+ return b''.join(first + special_chunks + rest)
-+
-+
-+def _ChunkifyPNG(data):
-+ '''Given a PNG image, yield its chunks in order.'''
-+ assert data.startswith(_PNG_MAGIC)
-+ pos = 8
-+ while pos != len(data):
-+ length = 12 + struct.unpack_from('>I', data, pos)[0]
-+ assert 12 <= length <= len(data) - pos
-+ yield data[pos:pos+length]
-+ pos += length
-+
-+
-+def _MakeBraceGlob(strings):
-+ '''Given ['foo', 'bar'], return '{foo,bar}', for error reporting.
-+ '''
-+ if len(strings) == 1:
-+ return strings[0]
-+ else:
-+ return '{' + ','.join(strings) + '}'
-+
-+
-+class ChromeScaledImage(interface.GathererBase):
-+ '''Represents an image that exists in multiple layout variants
-+ (e.g. "default", "touch") and multiple scale variants
-+ (e.g. "100_percent", "200_percent").
-+ '''
-+
-+ split_context_re_ = lazy_re.compile(r'(.+)_(\d+)_percent\Z')
-+
-+ def _FindInputFile(self):
-+ output_context = self.grd_node.GetRoot().output_context
-+ match = self.split_context_re_.match(output_context)
-+ if not match:
-+ raise exception.MissingMandatoryAttribute(
-+ 'All <output> nodes must have an appropriate context attribute'
-+ ' (e.g. context="touch_200_percent")')
-+ req_layout, req_scale = match.group(1), int(match.group(2))
-+
-+ layouts = [req_layout]
-+ try_default_layout = self.grd_node.GetRoot().fallback_to_default_layout
-+ if try_default_layout and 'default' not in layouts:
-+ layouts.append('default')
-+
-+ scales = [req_scale]
-+ try_low_res = self.grd_node.FindBooleanAttribute(
-+ 'fallback_to_low_resolution', default=False, skip_self=False)
-+ if try_low_res and 100 not in scales:
-+ scales.append(100)
-+
-+ for layout in layouts:
-+ for scale in scales:
-+ dir = '%s_%s_percent' % (layout, scale)
-+ path = os.path.join(dir, self.rc_file)
-+ if os.path.exists(self.grd_node.ToRealPath(path)):
-+ return path, scale, req_scale
-+
-+ if not try_default_layout:
-+ # The file was not found in the specified output context and it was
-+ # explicitly indicated that the default context should not be searched
-+ # as a fallback, so return an empty path.
-+ return None, 100, req_scale
-+
-+ # The file was found in neither the specified context nor the default
-+ # context, so raise an exception.
-+ dir = "%s_%s_percent" % (_MakeBraceGlob(layouts),
-+ _MakeBraceGlob([str(x) for x in scales]))
-+ raise exception.FileNotFound(
-+ 'Tried ' + self.grd_node.ToRealPath(os.path.join(dir, self.rc_file)))
-+
-+ def GetInputPath(self):
-+ path, scale, req_scale = self._FindInputFile()
-+ return path
-+
-+ def Parse(self):
-+ pass
-+
-+ def GetTextualIds(self):
-+ return [self.extkey]
-+
-+ def GetData(self, lang, encoding):
-+ assert encoding == util.BINARY
-+
-+ path, scale, req_scale = self._FindInputFile()
-+ if path is None:
-+ return None
-+
-+ data = util.ReadFile(self.grd_node.ToRealPath(path), util.BINARY)
-+ data = _RescaleImage(data, scale, req_scale)
-+ data = _MoveSpecialChunksToFront(data)
-+ return data
-+
-+ def Translate(self, *args, **kwargs):
-+ return self.GetData()
-diff --git a/tools/grit/grit/gather/chrome_scaled_image_unittest.py b/tools/grit/grit/gather/chrome_scaled_image_unittest.py
-new file mode 100644
-index 0000000000..1cebfc6de2
---- /dev/null
-+++ b/tools/grit/grit/gather/chrome_scaled_image_unittest.py
-@@ -0,0 +1,209 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for ChromeScaledImage.'''
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__),
-+ '../..')))
-+
-+import re
-+import struct
-+import unittest
-+import zlib
-+
-+from grit import exception
-+from grit import util
-+from grit.format import data_pack
-+from grit.tool import build
-+
-+
-+_OUTFILETYPES = [
-+ ('.h', 'rc_header'),
-+ ('_map.cc', 'resource_map_source'),
-+ ('_map.h', 'resource_map_header'),
-+ ('.pak', 'data_package'),
-+ ('.rc', 'rc_all'),
-+]
-+
-+
-+_PNG_HEADER = (
-+ b'\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52'
-+ b'\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90\x77\x53'
-+ b'\xde')
-+_PNG_FOOTER = (
-+ b'\x00\x00\x00\x0c\x49\x44\x41\x54\x18\x57\x63\xf8\xff\xff\x3f\x00'
-+ b'\x05\xfe\x02\xfe\xa7\x35\x81\x84\x00\x00\x00\x00\x49\x45\x4e\x44'
-+ b'\xae\x42\x60\x82')
-+
-+
-+def _MakePNG(chunks):
-+ # Python 3 changed the return value of zlib.crc32 to an unsigned int.
-+ format = 'i' if sys.version_info.major < 3 else 'I'
-+ pack_int32 = struct.Struct('>' + format).pack
-+ chunks = [pack_int32(len(payload)) + type + payload +
-+ pack_int32(zlib.crc32(type + payload))
-+ for type, payload in chunks]
-+ return _PNG_HEADER + b''.join(chunks) + _PNG_FOOTER
-+
-+
-+def _GetFilesInPak(pakname):
-+ '''Get a set of the files that were actually included in the .pak output.
-+ '''
-+ return set(data_pack.ReadDataPack(pakname).resources.values())
-+
-+
-+def _GetFilesInRc(rcname, tmp_dir, contents):
-+ '''Get a set of the files that were actually included in the .rc output.
-+ '''
-+ data = util.ReadFile(rcname, util.BINARY).decode('utf-16')
-+ contents = dict((tmp_dir.GetPath(k), v) for k, v in contents.items())
-+ return set(contents[os.path.normpath(m.group(1))]
-+ for m in re.finditer(r'(?m)^\w+\s+BINDATA\s+"([^"]+)"$', data))
-+
-+
-+def _MakeFallbackAttr(fallback):
-+ if fallback is None:
-+ return ''
-+ else:
-+ return ' fallback_to_low_resolution="%s"' % ('false', 'true')[fallback]
-+
-+
-+def _Structures(fallback, *body):
-+ return '<structures%s>\n%s\n</structures>' % (
-+ _MakeFallbackAttr(fallback), '\n'.join(body))
-+
-+
-+def _Structure(name, file, fallback=None):
-+ return '<structure name="%s" file="%s" type="chrome_scaled_image"%s />' % (
-+ name, file, _MakeFallbackAttr(fallback))
-+
-+
-+def _If(expr, *body):
-+ return '<if expr="%s">\n%s\n</if>' % (expr, '\n'.join(body))
-+
-+
-+def _RunBuildTest(self, structures, inputs, expected_outputs, skip_rc=False,
-+ layout_fallback=''):
-+ outputs = '\n'.join('<output filename="out/%s%s" type="%s" context="%s"%s />'
-+ % (context, ext, type, context, layout_fallback)
-+ for ext, type in _OUTFILETYPES
-+ for context in expected_outputs)
-+
-+ infiles = {
-+ 'in/in.grd': ('''<?xml version="1.0" encoding="UTF-8"?>
-+ <grit latest_public_release="0" current_release="1">
-+ <outputs>
-+ %s
-+ </outputs>
-+ <release seq="1">
-+ %s
-+ </release>
-+ </grit>
-+ ''' % (outputs, structures)).encode('utf-8'),
-+ }
-+ for pngpath, pngdata in inputs.items():
-+ normpath = os.path.normpath('in/' + pngpath)
-+ infiles[normpath] = pngdata
-+ class Options(object):
-+ pass
-+
-+ with util.TempDir(infiles, mode='wb') as tmp_dir:
-+ with tmp_dir.AsCurrentDir():
-+ options = Options()
-+ options.input = tmp_dir.GetPath('in/in.grd')
-+ options.verbose = False
-+ options.extra_verbose = False
-+ build.RcBuilder().Run(options, [])
-+ for context, expected_data in expected_outputs.items():
-+ self.assertEquals(expected_data,
-+ _GetFilesInPak(tmp_dir.GetPath('out/%s.pak' % context)))
-+ if not skip_rc:
-+ self.assertEquals(expected_data,
-+ _GetFilesInRc(tmp_dir.GetPath('out/%s.rc' % context),
-+ tmp_dir, infiles))
-+
-+
-+class ChromeScaledImageUnittest(unittest.TestCase):
-+ def testNormalFallback(self):
-+ d123a = _MakePNG([(b'AbCd', b'')])
-+ t123a = _MakePNG([(b'EfGh', b'')])
-+ d123b = _MakePNG([(b'IjKl', b'')])
-+ _RunBuildTest(self,
-+ _Structures(None,
-+ _Structure('IDR_A', 'a.png'),
-+ _Structure('IDR_B', 'b.png'),
-+ ),
-+ {'default_123_percent/a.png': d123a,
-+ 'tactile_123_percent/a.png': t123a,
-+ 'default_123_percent/b.png': d123b,
-+ },
-+ {'default_123_percent': set([d123a, d123b]),
-+ 'tactile_123_percent': set([t123a, d123b]),
-+ })
-+
-+ def testNormalFallbackFailure(self):
-+ self.assertRaises(
-+ exception.FileNotFound, _RunBuildTest, self,
-+ _Structures(
-+ None,
-+ _Structure('IDR_A', 'a.png'),
-+ ), {
-+ 'default_100_percent/a.png': _MakePNG([(b'AbCd', b'')]),
-+ 'tactile_100_percent/a.png': _MakePNG([(b'EfGh', b'')]),
-+ }, {'tactile_123_percent': 'should fail before using this'})
-+
-+ def testLowresFallback(self):
-+ png = _MakePNG([(b'Abcd', b'')])
-+ png_with_csCl = _MakePNG([(b'csCl', b''), (b'Abcd', b'')])
-+ for outer in (None, False, True):
-+ for inner in (None, False, True):
-+ args = (
-+ self,
-+ _Structures(outer,
-+ _Structure('IDR_A', 'a.png', inner),
-+ ),
-+ {'default_100_percent/a.png': png},
-+ {'tactile_200_percent': set([png_with_csCl])})
-+ if inner or (inner is None and outer):
-+ # should fall back to 100%
-+ _RunBuildTest(*args, skip_rc=True)
-+ else:
-+ # shouldn't fall back
-+ self.assertRaises(exception.FileNotFound, _RunBuildTest, *args)
-+
-+ # Test fallback failure with fallback_to_low_resolution=True
-+ self.assertRaises(exception.FileNotFound,
-+ _RunBuildTest, self,
-+ _Structures(True,
-+ _Structure('IDR_A', 'a.png'),
-+ ),
-+ {}, # no files
-+ {'tactile_123_percent': 'should fail before using this'})
-+
-+ def testNoFallbackToDefaultLayout(self):
-+ d123a = _MakePNG([(b'AbCd', b'')])
-+ t123a = _MakePNG([(b'EfGh', b'')])
-+ d123b = _MakePNG([(b'IjKl', b'')])
-+ _RunBuildTest(self,
-+ _Structures(None,
-+ _Structure('IDR_A', 'a.png'),
-+ _Structure('IDR_B', 'b.png'),
-+ ),
-+ {'default_123_percent/a.png': d123a,
-+ 'tactile_123_percent/a.png': t123a,
-+ 'default_123_percent/b.png': d123b,
-+ },
-+ {'default_123_percent': set([d123a, d123b]),
-+ 'tactile_123_percent': set([t123a]),
-+ },
-+ layout_fallback=' fallback_to_default_layout="false"')
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/gather/interface.py b/tools/grit/grit/gather/interface.py
-new file mode 100644
-index 0000000000..15d64f9326
---- /dev/null
-+++ b/tools/grit/grit/gather/interface.py
-@@ -0,0 +1,172 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Interface for all gatherers.
-+'''
-+
-+from __future__ import print_function
-+
-+import os.path
-+
-+import six
-+
-+from grit import clique
-+from grit import util
-+
-+
-+class GathererBase(object):
-+ '''Interface for all gatherer implementations. Subclasses must implement
-+ all methods that raise NotImplemented.'''
-+
-+ def __init__(self, rc_file, extkey=None, encoding='cp1252', is_skeleton=False):
-+ '''Initializes the gatherer object's attributes, but does not attempt to
-+ read the input file.
-+
-+ Args:
-+ rc_file: The 'file' attribute of the <structure> node (usually the
-+ relative path to the source file).
-+ extkey: e.g. 'ID_MY_DIALOG'
-+ encoding: e.g. 'utf-8'
-+ is_skeleton: Indicates whether this gatherer is a skeleton gatherer, in
-+ which case we should not do some types of processing on the
-+ translateable bits.
-+ '''
-+ self.rc_file = rc_file
-+ self.extkey = extkey
-+ self.encoding = encoding
-+ # A default uberclique that is local to this object. Users can override
-+ # this with the uberclique they are using.
-+ self.uberclique = clique.UberClique()
-+ # Indicates whether this gatherer is a skeleton gatherer, in which case
-+ # we should not do some types of processing on the translateable bits.
-+ self.is_skeleton = is_skeleton
-+ # Stores the grd node on which this gatherer is running. This allows
-+ # evaluating expressions.
-+ self.grd_node = None
-+
-+ def SetAttributes(self, attrs):
-+ '''Sets node attributes used by the gatherer.
-+
-+ By default, this does nothing. If special handling is desired, it should be
-+ overridden by the child gatherer.
-+
-+ Args:
-+ attrs: The mapping of node attributes.
-+ '''
-+ pass
-+
-+ def SetDefines(self, defines):
-+ '''Sets global defines used by the gatherer.
-+
-+ By default, this does nothing. If special handling is desired, it should be
-+ overridden by the child gatherer.
-+
-+ Args:
-+ defines: The mapping of define values.
-+ '''
-+ pass
-+
-+ def SetGrdNode(self, node):
-+ '''Sets the grd node on which this gatherer is running.
-+ '''
-+ self.grd_node = node
-+
-+ def SetUberClique(self, uberclique):
-+ '''Overrides the default uberclique so that cliques created by this object
-+ become part of the uberclique supplied by the user.
-+ '''
-+ self.uberclique = uberclique
-+
-+ def Parse(self):
-+ '''Reads and parses the contents of what is being gathered.'''
-+ raise NotImplementedError()
-+
-+ def GetData(self, lang, encoding):
-+ '''Returns the data to be added to the DataPack for this node or None if
-+ this node does not add a DataPack entry.
-+ '''
-+ return None
-+
-+ def GetText(self):
-+ '''Returns the text of what is being gathered.'''
-+ raise NotImplementedError()
-+
-+ def GetTextualIds(self):
-+ '''Returns the mnemonic IDs that need to be defined for the resource
-+ being gathered to compile correctly.'''
-+ return []
-+
-+ def GetCliques(self):
-+ '''Returns the MessageClique objects for all translateable portions.'''
-+ return []
-+
-+ def GetInputPath(self):
-+ return self.rc_file
-+
-+ def GetHtmlResourceFilenames(self):
-+ """Returns a set of all filenames inlined by this gatherer."""
-+ return []
-+
-+ def Translate(self, lang, pseudo_if_not_available=True,
-+ skeleton_gatherer=None, fallback_to_english=False):
-+ '''Returns the resource being gathered, with translateable portions filled
-+ with the translation for language 'lang'.
-+
-+ If pseudo_if_not_available is true, a pseudotranslation will be used for any
-+ message that doesn't have a real translation available.
-+
-+ If no translation is available and pseudo_if_not_available is false,
-+ fallback_to_english controls the behavior. If it is false, throw an error.
-+ If it is true, use the English version of the message as its own
-+ "translation".
-+
-+ If skeleton_gatherer is specified, the translation will use the nontranslateable
-+ parts from the gatherer 'skeleton_gatherer', which must be of the same type
-+ as 'self'.
-+
-+ If fallback_to_english
-+
-+ Args:
-+ lang: 'en'
-+ pseudo_if_not_available: True | False
-+ skeleton_gatherer: other_gatherer
-+ fallback_to_english: True | False
-+
-+ Return:
-+ e.g. 'ID_THIS_SECTION TYPE\n...BEGIN\n "Translated message"\n......\nEND'
-+
-+ Raises:
-+ grit.exception.NotReady() if used before Parse() has been successfully
-+ called.
-+ grit.exception.NoSuchTranslation() if 'pseudo_if_not_available' and
-+ fallback_to_english are both false and there is no translation for the
-+ requested language.
-+ '''
-+ raise NotImplementedError()
-+
-+ def SubstituteMessages(self, substituter):
-+ '''Applies substitutions to all messages in the gatherer.
-+
-+ Args:
-+ substituter: a grit.util.Substituter object.
-+ '''
-+ pass
-+
-+ def SetFilenameExpansionFunction(self, fn):
-+ '''Sets a function for rewriting filenames before gathering.'''
-+ pass
-+
-+ # TODO(benrg): Move this elsewhere, since it isn't part of the interface.
-+ def _LoadInputFile(self):
-+ '''A convenience function for subclasses that loads the contents of the
-+ input file.
-+ '''
-+ if isinstance(self.rc_file, six.string_types):
-+ path = self.GetInputPath()
-+ # Hack: some unit tests supply an absolute path and no root node.
-+ if not os.path.isabs(path):
-+ path = self.grd_node.ToRealPath(path)
-+ return util.ReadFile(path, self.encoding)
-+ else:
-+ return self.rc_file.read()
-diff --git a/tools/grit/grit/gather/json_loader.py b/tools/grit/grit/gather/json_loader.py
-new file mode 100644
-index 0000000000..058e5f17ae
---- /dev/null
-+++ b/tools/grit/grit/gather/json_loader.py
-@@ -0,0 +1,27 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+from __future__ import print_function
-+
-+from grit.gather import interface
-+
-+
-+class JsonLoader(interface.GathererBase):
-+ '''A simple gatherer that loads and parses a JSON file.'''
-+
-+ def Parse(self):
-+ '''Reads and parses the text of self._json_text into the data structure in
-+ self._data.
-+ '''
-+ self._json_text = self._LoadInputFile()
-+ self._data = None
-+
-+ globs = {}
-+ exec('data = ' + self._json_text, globs)
-+ self._data = globs['data']
-+
-+ def GetData(self, lang, encoding):
-+ '''Returns the parsed JSON data.'''
-+ assert encoding == 'utf-8'
-+ return self._data
-diff --git a/tools/grit/grit/gather/policy_json.py b/tools/grit/grit/gather/policy_json.py
-new file mode 100644
-index 0000000000..6621c5f3c4
---- /dev/null
-+++ b/tools/grit/grit/gather/policy_json.py
-@@ -0,0 +1,325 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Support for "policy_templates.json" format used by the policy template
-+generator as a source for generating ADM,ADMX,etc files.'''
-+
-+from __future__ import print_function
-+
-+import json
-+import sys
-+
-+import six
-+
-+from grit.gather import skeleton_gatherer
-+from grit import util
-+from grit import tclib
-+from xml.dom import minidom
-+from xml.parsers.expat import ExpatError
-+
-+
-+class PolicyJson(skeleton_gatherer.SkeletonGatherer):
-+ '''Collects and translates the following strings from policy_templates.json:
-+ - captions, descriptions, labels and Android app support details of policies
-+ - captions of enumeration items
-+ - misc strings from the 'messages' section
-+ Translatable strings may have untranslateable placeholders with the same
-+ format that is used in .grd files.
-+ '''
-+
-+ def _AddEndline(self, add_comma):
-+ '''Adds an endline to the skeleton tree. If add_comma is true, adds a
-+ comma before the endline.
-+
-+ Args:
-+ add_comma: A boolean to add a comma or not.
-+ '''
-+ self._AddNontranslateableChunk(',\n' if add_comma else '\n')
-+
-+ def _ParsePlaceholder(self, placeholder, msg):
-+ '''Extracts a placeholder from a DOM node and adds it to a tclib Message.
-+
-+ Args:
-+ placeholder: A DOM node of the form:
-+ <ph name="PLACEHOLDER_NAME">Placeholder text<ex>Example value</ex></ph>
-+ msg: The placeholder is added to this message.
-+ '''
-+ text = []
-+ example_text = []
-+ for node1 in placeholder.childNodes:
-+ if (node1.nodeType == minidom.Node.TEXT_NODE):
-+ text.append(node1.data)
-+ elif (node1.nodeType == minidom.Node.ELEMENT_NODE and
-+ node1.tagName == 'ex'):
-+ for node2 in node1.childNodes:
-+ example_text.append(node2.toxml())
-+ else:
-+ raise Exception('Unexpected element inside a placeholder: ' +
-+ node2.toxml())
-+ if example_text == []:
-+ # In such cases the original text is okay for an example.
-+ example_text = text
-+
-+ replaced_text = self.Escape(''.join(text).strip())
-+ replaced_text = replaced_text.replace('$1', self._config['app_name'])
-+ replaced_text = replaced_text.replace('$2', self._config['os_name'])
-+ replaced_text = replaced_text.replace('$3', self._config['frame_name'])
-+
-+ msg.AppendPlaceholder(tclib.Placeholder(
-+ placeholder.attributes['name'].value,
-+ replaced_text,
-+ ''.join(example_text).strip()))
-+
-+ def _ParseMessage(self, string, desc):
-+ '''Parses a given string and adds it to the output as a translatable chunk
-+ with a given description.
-+
-+ Args:
-+ string: The message string to parse.
-+ desc: The description of the message (for the translators).
-+ '''
-+ msg = tclib.Message(description=desc)
-+ xml = '<msg>' + string + '</msg>'
-+ try:
-+ node = minidom.parseString(xml).childNodes[0]
-+ except ExpatError:
-+ reason = '''Input isn't valid XML (has < & > been escaped?): ''' + string
-+ six.reraise(Exception, reason, sys.exc_info()[2])
-+
-+ for child in node.childNodes:
-+ if child.nodeType == minidom.Node.TEXT_NODE:
-+ msg.AppendText(child.data)
-+ elif child.nodeType == minidom.Node.ELEMENT_NODE:
-+ if child.tagName == 'ph':
-+ self._ParsePlaceholder(child, msg)
-+ else:
-+ raise Exception("Not implemented.")
-+ else:
-+ raise Exception("Not implemented.")
-+ self.skeleton_.append(self.uberclique.MakeClique(msg))
-+
-+ def _ParseNode(self, node):
-+ '''Traverses the subtree of a DOM node, and register a tclib message for
-+ all the <message> nodes.
-+ '''
-+ att_text = []
-+ if node.attributes:
-+ for key, value in sorted(node.attributes.items()):
-+ att_text.append(' %s=\"%s\"' % (key, value))
-+ self._AddNontranslateableChunk("<%s%s>" %
-+ (node.tagName, ''.join(att_text)))
-+ if node.tagName == 'message':
-+ msg = tclib.Message(description=node.attributes['desc'])
-+ for child in node.childNodes:
-+ if child.nodeType == minidom.Node.TEXT_NODE:
-+ if msg == None:
-+ self._AddNontranslateableChunk(child.data)
-+ else:
-+ msg.AppendText(child.data)
-+ elif child.nodeType == minidom.Node.ELEMENT_NODE:
-+ if child.tagName == 'ph':
-+ self._ParsePlaceholder(child, msg)
-+ else:
-+ assert False
-+ self.skeleton_.append(self.uberclique.MakeClique(msg))
-+ else:
-+ for child in node.childNodes:
-+ if child.nodeType == minidom.Node.TEXT_NODE:
-+ self._AddNontranslateableChunk(child.data)
-+ elif node.nodeType == minidom.Node.ELEMENT_NODE:
-+ self._ParseNode(child)
-+
-+ self._AddNontranslateableChunk("</%s>" % node.tagName)
-+
-+ def _AddIndentedNontranslateableChunk(self, depth, string):
-+ '''Adds a nontranslateable chunk of text to the internally stored output.
-+
-+ Args:
-+ depth: The number of double spaces to prepend to the next argument string.
-+ string: The chunk of text to add.
-+ '''
-+ result = []
-+ while depth > 0:
-+ result.append(' ')
-+ depth = depth - 1
-+ result.append(string)
-+ self._AddNontranslateableChunk(''.join(result))
-+
-+ def _GetDescription(self, item, item_type, parent_item, key):
-+ '''Creates a description for a translatable message. The description gives
-+ some context for the person who will translate this message.
-+
-+ Args:
-+ item: A policy or an enumeration item.
-+ item_type: 'enum_item' | 'policy'
-+ parent_item: The owner of item. (A policy of type group or enum.)
-+ key: The name of the key to parse.
-+ depth: The level of indentation.
-+ '''
-+ key_map = {
-+ 'desc': 'Description',
-+ 'caption': 'Caption',
-+ 'label': 'Label',
-+ 'arc_support': 'Information about the effect on Android apps'
-+ }
-+ if item_type == 'policy':
-+ return ('%s of the policy named %s [owner(s): %s]' %
-+ (key_map[key], item['name'],
-+ ','.join(item['owners'] if 'owners' in item else 'unknown')))
-+ if item_type == 'enum_item':
-+ return ('%s of the option named %s in policy %s [owner(s): %s]' %
-+ (key_map[key], item['name'], parent_item['name'],
-+ ','.join(parent_item['owners'] if 'owners' in parent_item else 'unknown')))
-+ raise Exception('Unexpected type %s' % item_type)
-+
-+ def _AddSchemaKeys(self, obj, depth):
-+ obj_type = type(obj)
-+ if obj_type == dict:
-+ self._AddNontranslateableChunk('{\n')
-+ keys = sorted(obj.keys())
-+ for count, (key) in enumerate(keys, 1):
-+ json_key = "%s: " % json.dumps(key)
-+ self._AddIndentedNontranslateableChunk(depth + 1, json_key)
-+ if key == 'description' and type(obj[key]) == str:
-+ self._AddNontranslateableChunk("\"")
-+ self._ParseMessage(obj[key], 'Description of schema property')
-+ self._AddNontranslateableChunk("\"")
-+ elif type(obj[key]) in (bool, int, str):
-+ self._AddSchemaKeys(obj[key], 0)
-+ else:
-+ self._AddSchemaKeys(obj[key], depth + 1)
-+ self._AddEndline(count < len(keys))
-+ self._AddIndentedNontranslateableChunk(depth, '}')
-+ elif obj_type == list:
-+ self._AddNontranslateableChunk('[\n')
-+ for count, (item) in enumerate(obj, 1):
-+ self._AddSchemaKeys(item, depth + 1)
-+ self._AddEndline(count < len(obj))
-+ self._AddIndentedNontranslateableChunk(depth, ']')
-+ elif obj_type in (bool, int, str):
-+ self._AddIndentedNontranslateableChunk(depth, json.dumps(obj))
-+ else:
-+ raise Exception('Invalid schema object: %s' % obj)
-+
-+ def _AddPolicyKey(self, item, item_type, parent_item, key, depth):
-+ '''Given a policy/enumeration item and a key, adds that key and its value
-+ into the output.
-+ E.g.:
-+ 'example_value': 123
-+ If key indicates that the value is a translatable string, then it is parsed
-+ as a translatable string.
-+
-+ Args:
-+ item: A policy or an enumeration item.
-+ item_type: 'enum_item' | 'policy'
-+ parent_item: The owner of item. (A policy of type group or enum.)
-+ key: The name of the key to parse.
-+ depth: The level of indentation.
-+ '''
-+ self._AddIndentedNontranslateableChunk(depth, "%s: " % json.dumps(key))
-+ if key in ('desc', 'caption', 'label', 'arc_support'):
-+ self._AddNontranslateableChunk("\"")
-+ self._ParseMessage(
-+ item[key],
-+ self._GetDescription(item, item_type, parent_item, key))
-+ self._AddNontranslateableChunk("\"")
-+ elif key in ('schema', 'validation_schema', 'description_schema'):
-+ self._AddSchemaKeys(item[key], depth)
-+ else:
-+ self._AddNontranslateableChunk(json.dumps(item[key], ensure_ascii=False))
-+
-+ def _AddItems(self, items, item_type, parent_item, depth):
-+ '''Parses and adds a list of items from the JSON file. Items can be policies
-+ or parts of an enum policy.
-+
-+ Args:
-+ items: Either a list of policies or a list of dictionaries.
-+ item_type: 'enum_item' | 'policy'
-+ parent_item: If items contains a list of policies, then this is the policy
-+ group that owns them. If items contains a list of enumeration items,
-+ then this is the enum policy that holds them.
-+ depth: Indicates the depth of our position in the JSON hierarchy. Used to
-+ add nice line-indent to the output.
-+ '''
-+ for item_count, (item1) in enumerate(items, 1):
-+ self._AddIndentedNontranslateableChunk(depth, "{\n")
-+ keys = sorted(item1.keys())
-+ for keys_count, (key) in enumerate(keys, 1):
-+ if key == 'items':
-+ self._AddIndentedNontranslateableChunk(depth + 1, "\"items\": [\n")
-+ self._AddItems(item1['items'], 'enum_item', item1, depth + 2)
-+ self._AddIndentedNontranslateableChunk(depth + 1, "]")
-+ elif key == 'policies' and all(not isinstance(x, str)
-+ for x in item1['policies']):
-+ self._AddIndentedNontranslateableChunk(depth + 1, "\"policies\": [\n")
-+ self._AddItems(item1['policies'], 'policy', item1, depth + 2)
-+ self._AddIndentedNontranslateableChunk(depth + 1, "]")
-+ else:
-+ self._AddPolicyKey(item1, item_type, parent_item, key, depth + 1)
-+ self._AddEndline(keys_count < len(keys))
-+ self._AddIndentedNontranslateableChunk(depth, "}")
-+ self._AddEndline(item_count < len(items))
-+
-+ def _AddMessages(self):
-+ '''Processed and adds the 'messages' section to the output.'''
-+ self._AddNontranslateableChunk(" \"messages\": {\n")
-+ messages = self.data['messages'].items()
-+ for count, (name, message) in enumerate(messages, 1):
-+ self._AddNontranslateableChunk(" %s: {\n" % json.dumps(name))
-+ self._AddNontranslateableChunk(" \"text\": \"")
-+ self._ParseMessage(message['text'], message['desc'])
-+ self._AddNontranslateableChunk("\"\n")
-+ self._AddNontranslateableChunk(" }")
-+ self._AddEndline(count < len(self.data['messages']))
-+ self._AddNontranslateableChunk(" }\n")
-+
-+ # Although we use the RegexpGatherer base class, we do not use the
-+ # _RegExpParse method of that class to implement Parse(). Instead, we
-+ # parse using a DOM parser.
-+ def Parse(self):
-+ if self.have_parsed_:
-+ return
-+ self.have_parsed_ = True
-+
-+ self.text_ = self._LoadInputFile()
-+ if util.IsExtraVerbose():
-+ print(self.text_)
-+
-+ self.data = eval(self.text_)
-+
-+ self._AddNontranslateableChunk('{\n')
-+ self._AddNontranslateableChunk(" \"policy_definitions\": [\n")
-+ self._AddItems(self.data['policy_definitions'], 'policy', None, 2)
-+ self._AddNontranslateableChunk(" ],\n")
-+ self._AddNontranslateableChunk(" \"policy_atomic_group_definitions\": [\n")
-+ if 'policy_atomic_group_definitions' in self.data:
-+ self._AddItems(self.data['policy_atomic_group_definitions'],
-+ 'policy', None, 2)
-+ self._AddNontranslateableChunk(" ],\n")
-+ self._AddMessages()
-+ self._AddNontranslateableChunk('\n}')
-+
-+ def Escape(self, text):
-+ return json.dumps(text, ensure_ascii=False)[1:-1]
-+
-+ def SetDefines(self, defines):
-+ if not defines:
-+ raise Exception('Must pass valid defines')
-+
-+ if '_chromium' in defines:
-+ self._config = {
-+ 'build': 'chromium',
-+ 'app_name': 'Chromium',
-+ 'frame_name': 'Chromium Frame',
-+ 'os_name': 'Chromium OS',
-+ }
-+ elif '_google_chrome' in defines:
-+ self._config = {
-+ 'build': 'chrome',
-+ 'app_name': 'Google Chrome',
-+ 'frame_name': 'Google Chrome Frame',
-+ 'os_name': 'Google Chrome OS',
-+ }
-+ else:
-+ raise Exception('Unknown build')
-diff --git a/tools/grit/grit/gather/policy_json_unittest.py b/tools/grit/grit/gather/policy_json_unittest.py
-new file mode 100644
-index 0000000000..214cd276aa
---- /dev/null
-+++ b/tools/grit/grit/gather/policy_json_unittest.py
-@@ -0,0 +1,347 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2011 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for grit.gather.policy_json'''
-+
-+from __future__ import print_function
-+
-+import json
-+import os
-+import re
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+import unittest
-+
-+from six import StringIO
-+
-+from grit.gather import policy_json
-+
-+class PolicyJsonUnittest(unittest.TestCase):
-+
-+ def GetExpectedOutput(self, original):
-+ expected = eval(original)
-+ for key, message in expected['messages'].items():
-+ del message['desc']
-+ return expected
-+
-+ def testEmpty(self):
-+ original = """{
-+ 'policy_definitions': [],
-+ 'policy_atomic_group_definitions': [],
-+ 'messages': {}
-+ }"""
-+ gatherer = policy_json.PolicyJson(StringIO(original))
-+ gatherer.Parse()
-+ self.failUnless(len(gatherer.GetCliques()) == 0)
-+ self.failUnless(eval(original) == json.loads(gatherer.Translate('en')))
-+
-+ def testGeneralPolicy(self):
-+ original = (
-+ "{"
-+ " 'policy_definitions': ["
-+ " {"
-+ " 'name': 'HomepageLocation',"
-+ " 'type': 'string',"
-+ " 'owners': ['foo@bar.com'],"
-+ " 'supported_on': ['chrome.*:8-'],"
-+ " 'features': {'dynamic_refresh': 1},"
-+ " 'example_value': 'http://chromium.org',"
-+ " 'caption': 'nothing special 1',"
-+ " 'desc': 'nothing special 2',"
-+ " 'label': 'nothing special 3',"
-+ " },"
-+ " ],"
-+ " 'policy_atomic_group_definitions': [],"
-+ " 'messages': {"
-+ " 'msg_identifier': {"
-+ " 'text': 'nothing special 3',"
-+ " 'desc': 'nothing special descr 3',"
-+ " }"
-+ " }"
-+ "}")
-+ gatherer = policy_json.PolicyJson(StringIO(original))
-+ gatherer.Parse()
-+ self.failUnless(len(gatherer.GetCliques()) == 4)
-+ expected = self.GetExpectedOutput(original)
-+ self.failUnless(expected == json.loads(gatherer.Translate('en')))
-+
-+ def testEnum(self):
-+ original = (
-+ "{"
-+ " 'policy_definitions': ["
-+ " {"
-+ " 'name': 'Policy1',"
-+ " 'owners': ['a@b'],"
-+ " 'items': ["
-+ " {"
-+ " 'name': 'Item1',"
-+ " 'caption': 'nothing special',"
-+ " }"
-+ " ]"
-+ " },"
-+ " ],"
-+ " 'policy_atomic_group_definitions': [],"
-+ " 'messages': {}"
-+ "}")
-+ gatherer = policy_json.PolicyJson(StringIO(original))
-+ gatherer.Parse()
-+ self.failUnless(len(gatherer.GetCliques()) == 1)
-+ expected = self.GetExpectedOutput(original)
-+ self.failUnless(expected == json.loads(gatherer.Translate('en')))
-+
-+ def testSchema(self):
-+ original = ("{"
-+ " 'policy_definitions': ["
-+ " {"
-+ " 'name': 'Policy1',"
-+ " 'schema': {"
-+ " 'type': 'object',"
-+ " 'properties': {"
-+ " 'outer': {"
-+ " 'description': 'outer description',"
-+ " 'type': 'object',"
-+ " 'inner': {"
-+ " 'description': 'inner description',"
-+ " 'type': 'integer', 'minimum': 0, 'maximum': 100"
-+ " },"
-+ " 'inner2': {"
-+ " 'description': 'inner2 description',"
-+ " 'type': 'integer',"
-+ " 'enum': [ 1, 2, 3 ],"
-+ " 'sensitiveValue': True"
-+ " },"
-+ " },"
-+ " },"
-+ " },"
-+ " 'caption': 'nothing special',"
-+ " 'owners': ['a@b']"
-+ " },"
-+ " ],"
-+ " 'policy_atomic_group_definitions': [],"
-+ " 'messages': {}"
-+ "}")
-+ gatherer = policy_json.PolicyJson(StringIO(original))
-+ gatherer.Parse()
-+ self.failUnless(len(gatherer.GetCliques()) == 4)
-+ expected = self.GetExpectedOutput(original)
-+ self.failUnless(expected == json.loads(gatherer.Translate('en')))
-+
-+ def testValidationSchema(self):
-+ original = ("{"
-+ " 'policy_definitions': ["
-+ " {"
-+ " 'name': 'Policy1',"
-+ " 'owners': ['a@b'],"
-+ " 'validation_schema': {"
-+ " 'type': 'object',"
-+ " 'properties': {"
-+ " 'description': 'properties description',"
-+ " 'type': 'object',"
-+ " },"
-+ " },"
-+ " },"
-+ " ],"
-+ " 'policy_atomic_group_definitions': [],"
-+ " 'messages': {}"
-+ "}")
-+ gatherer = policy_json.PolicyJson(StringIO(original))
-+ gatherer.Parse()
-+ self.failUnless(len(gatherer.GetCliques()) == 1)
-+ expected = self.GetExpectedOutput(original)
-+ self.failUnless(expected == json.loads(gatherer.Translate('en')))
-+
-+ def testDescriptionSchema(self):
-+ original = ("{"
-+ " 'policy_definitions': ["
-+ " {"
-+ " 'name': 'Policy1',"
-+ " 'owners': ['a@b'],"
-+ " 'description_schema': {"
-+ " 'type': 'object',"
-+ " 'properties': {"
-+ " 'description': 'properties description',"
-+ " 'type': 'object',"
-+ " },"
-+ " },"
-+ " },"
-+ " ],"
-+ " 'policy_atomic_group_definitions': [],"
-+ " 'messages': {}"
-+ "}")
-+ gatherer = policy_json.PolicyJson(StringIO(original))
-+ gatherer.Parse()
-+ self.failUnless(len(gatherer.GetCliques()) == 1)
-+ expected = self.GetExpectedOutput(original)
-+ self.failUnless(expected == json.loads(gatherer.Translate('en')))
-+
-+ # Keeping for backwards compatibility.
-+ def testSubPolicyOldFormat(self):
-+ original = (
-+ "{"
-+ " 'policy_definitions': ["
-+ " {"
-+ " 'type': 'group',"
-+ " 'policies': ["
-+ " {"
-+ " 'name': 'Policy1',"
-+ " 'caption': 'nothing special',"
-+ " 'owners': ['a@b']"
-+ " }"
-+ " ]"
-+ " }"
-+ " ],"
-+ " 'policy_atomic_group_definitions': [],"
-+ " 'messages': {}"
-+ "}")
-+ gatherer = policy_json.PolicyJson(StringIO(original))
-+ gatherer.Parse()
-+ self.failUnless(len(gatherer.GetCliques()) == 1)
-+ expected = self.GetExpectedOutput(original)
-+ self.failUnless(expected == json.loads(gatherer.Translate('en')))
-+
-+ def testSubPolicyNewFormat(self):
-+ original = (
-+ "{"
-+ " 'policy_definitions': ["
-+ " {"
-+ " 'type': 'group',"
-+ " 'policies': ['Policy1']"
-+ " },"
-+ " {"
-+ " 'name': 'Policy1',"
-+ " 'caption': 'nothing special',"
-+ " 'owners': ['a@b']"
-+ " }"
-+ " ],"
-+ " 'policy_atomic_group_definitions': [],"
-+ " 'messages': {}"
-+ "}")
-+ gatherer = policy_json.PolicyJson(StringIO(original))
-+ gatherer.Parse()
-+ self.failUnless(len(gatherer.GetCliques()) == 1)
-+ expected = self.GetExpectedOutput(original)
-+ self.failUnless(expected == json.loads(gatherer.Translate('en')))
-+
-+ def testEscapingAndLineBreaks(self):
-+ original = """{
-+ 'policy_definitions': [],
-+ 'policy_atomic_group_definitions': [],
-+ 'messages': {
-+ 'msg1': {
-+ # The following line will contain two backslash characters when it
-+ # ends up in eval().
-+ 'text': '''backslashes, Sir? \\\\''',
-+ 'desc': ''
-+ },
-+ 'msg2': {
-+ 'text': '''quotes, Madam? "''',
-+ 'desc': ''
-+ },
-+ 'msg3': {
-+ # The following line will contain two backslash characters when it
-+ # ends up in eval().
-+ 'text': 'backslashes, Sir? \\\\',
-+ 'desc': ''
-+ },
-+ 'msg4': {
-+ 'text': "quotes, Madam? '",
-+ 'desc': ''
-+ },
-+ 'msg5': {
-+ 'text': '''what happens
-+with a newline?''',
-+ 'desc': ''
-+ },
-+ 'msg6': {
-+ # The following line will contain a backslash+n when it ends up in
-+ # eval().
-+ 'text': 'what happens\\nwith a newline? (Episode 1)',
-+ 'desc': ''
-+ }
-+ }
-+}"""
-+ gatherer = policy_json.PolicyJson(StringIO(original))
-+ gatherer.Parse()
-+ self.failUnless(len(gatherer.GetCliques()) == 6)
-+ expected = self.GetExpectedOutput(original)
-+ self.failUnless(expected == json.loads(gatherer.Translate('en')))
-+
-+ def testPlaceholdersChromium(self):
-+ original = """{
-+ "policy_definitions": [
-+ {
-+ "name": "Policy1",
-+ "caption": "Please install\\n<ph name=\\"PRODUCT_NAME\\">$1<ex>Google Chrome</ex></ph>.",
-+ "owners": "a@b"
-+ }
-+ ],
-+ "policy_atomic_group_definitions": [],
-+ "messages": {}
-+}"""
-+ gatherer = policy_json.PolicyJson(StringIO(original))
-+ gatherer.SetDefines({'_chromium': True})
-+ gatherer.Parse()
-+ self.failUnless(len(gatherer.GetCliques()) == 1)
-+ expected = json.loads(re.sub('<ph.*ph>', 'Chromium', original))
-+ self.failUnless(expected == json.loads(gatherer.Translate('en')))
-+ self.failUnless(gatherer.GetCliques()[0].translateable)
-+ msg = gatherer.GetCliques()[0].GetMessage()
-+ self.failUnless(len(msg.GetPlaceholders()) == 1)
-+ ph = msg.GetPlaceholders()[0]
-+ self.failUnless(ph.GetOriginal() == 'Chromium')
-+ self.failUnless(ph.GetPresentation() == 'PRODUCT_NAME')
-+ self.failUnless(ph.GetExample() == 'Google Chrome')
-+
-+ def testPlaceholdersChrome(self):
-+ original = """{
-+ "policy_definitions": [
-+ {
-+ "name": "Policy1",
-+ "caption": "Please install\\n<ph name=\\"PRODUCT_NAME\\">$1<ex>Google Chrome</ex></ph>.",
-+ "owners": "a@b"
-+ }
-+ ],
-+ "policy_atomic_group_definitions": [],
-+ "messages": {}
-+}"""
-+ gatherer = policy_json.PolicyJson(StringIO(original))
-+ gatherer.SetDefines({'_google_chrome': True})
-+ gatherer.Parse()
-+ self.failUnless(len(gatherer.GetCliques()) == 1)
-+ expected = json.loads(re.sub('<ph.*ph>', 'Google Chrome', original))
-+ self.failUnless(expected == json.loads(gatherer.Translate('en')))
-+ self.failUnless(gatherer.GetCliques()[0].translateable)
-+ msg = gatherer.GetCliques()[0].GetMessage()
-+ self.failUnless(len(msg.GetPlaceholders()) == 1)
-+ ph = msg.GetPlaceholders()[0]
-+ self.failUnless(ph.GetOriginal() == 'Google Chrome')
-+ self.failUnless(ph.GetPresentation() == 'PRODUCT_NAME')
-+ self.failUnless(ph.GetExample() == 'Google Chrome')
-+
-+ def testGetDescription(self):
-+ gatherer = policy_json.PolicyJson({})
-+ gatherer.SetDefines({'_google_chrome': True})
-+ self.assertEquals(
-+ gatherer._GetDescription({'name': 'Policy1', 'owners': ['a@b']},
-+ 'policy', None, 'desc'),
-+ 'Description of the policy named Policy1 [owner(s): a@b]')
-+ self.assertEquals(
-+ gatherer._GetDescription({'name': 'Plcy2', 'owners': ['a@b', 'c@d']},
-+ 'policy', None, 'caption'),
-+ 'Caption of the policy named Plcy2 [owner(s): a@b,c@d]')
-+ self.assertEquals(
-+ gatherer._GetDescription({'name': 'Plcy3', 'owners': ['a@b']},
-+ 'policy', None, 'label'),
-+ 'Label of the policy named Plcy3 [owner(s): a@b]')
-+ self.assertEquals(
-+ gatherer._GetDescription({'name': 'Item'}, 'enum_item',
-+ {'name': 'Plcy', 'owners': ['a@b']}, 'caption'),
-+ 'Caption of the option named Item in policy Plcy [owner(s): a@b]')
-+
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/gather/rc.py b/tools/grit/grit/gather/rc.py
-new file mode 100644
-index 0000000000..dd091d1e18
---- /dev/null
-+++ b/tools/grit/grit/gather/rc.py
-@@ -0,0 +1,343 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Support for gathering resources from RC files.
-+'''
-+
-+from __future__ import print_function
-+
-+import re
-+
-+from grit import exception
-+from grit import lazy_re
-+from grit import tclib
-+
-+from grit.gather import regexp
-+
-+
-+# Find portions that need unescaping in resource strings. We need to be
-+# careful that a \\n is matched _first_ as a \\ rather than matching as
-+# a \ followed by a \n.
-+# TODO(joi) Handle ampersands if we decide to change them into <ph>
-+# TODO(joi) May need to handle other control characters than \n
-+_NEED_UNESCAPE = lazy_re.compile(r'""|\\\\|\\n|\\t')
-+
-+# Find portions that need escaping to encode string as a resource string.
-+_NEED_ESCAPE = lazy_re.compile(r'"|\n|\t|\\|\&nbsp\;')
-+
-+# How to escape certain characters
-+_ESCAPE_CHARS = {
-+ '"' : '""',
-+ '\n' : '\\n',
-+ '\t' : '\\t',
-+ '\\' : '\\\\',
-+ '&nbsp;' : ' '
-+}
-+
-+# How to unescape certain strings
-+_UNESCAPE_CHARS = dict([[value, key] for key, value in _ESCAPE_CHARS.items()])
-+
-+
-+
-+class Section(regexp.RegexpGatherer):
-+ '''A section from a resource file.'''
-+
-+ @staticmethod
-+ def Escape(text):
-+ '''Returns a version of 'text' with characters escaped that need to be
-+ for inclusion in a resource section.'''
-+ def Replace(match):
-+ return _ESCAPE_CHARS[match.group()]
-+ return _NEED_ESCAPE.sub(Replace, text)
-+
-+ @staticmethod
-+ def UnEscape(text):
-+ '''Returns a version of 'text' with escaped characters unescaped.'''
-+ def Replace(match):
-+ return _UNESCAPE_CHARS[match.group()]
-+ return _NEED_UNESCAPE.sub(Replace, text)
-+
-+ def _RegExpParse(self, rexp, text_to_parse):
-+ '''Overrides _RegExpParse to add shortcut group handling. Otherwise
-+ the same.
-+ '''
-+ super(Section, self)._RegExpParse(rexp, text_to_parse)
-+
-+ if not self.is_skeleton and len(self.GetTextualIds()) > 0:
-+ group_name = self.GetTextualIds()[0]
-+ for c in self.GetCliques():
-+ c.AddToShortcutGroup(group_name)
-+
-+ def ReadSection(self):
-+ rc_text = self._LoadInputFile()
-+
-+ out = ''
-+ begin_count = 0
-+ assert self.extkey
-+ first_line_re = re.compile(r'\s*' + self.extkey + r'\b')
-+ for line in rc_text.splitlines(True):
-+ if out or first_line_re.match(line):
-+ out += line
-+
-+ # we stop once we reach the END for the outermost block.
-+ begin_count_was = begin_count
-+ if len(out) > 0 and line.strip() == 'BEGIN':
-+ begin_count += 1
-+ elif len(out) > 0 and line.strip() == 'END':
-+ begin_count -= 1
-+ if begin_count_was == 1 and begin_count == 0:
-+ break
-+
-+ if len(out) == 0:
-+ raise exception.SectionNotFound('%s in file %s' % (self.extkey, self.rc_file))
-+
-+ self.text_ = out.strip()
-+
-+
-+class Dialog(Section):
-+ '''A resource section that contains a dialog resource.'''
-+
-+ # A typical dialog resource section looks like this:
-+ #
-+ # IDD_ABOUTBOX DIALOGEX 22, 17, 230, 75
-+ # STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
-+ # CAPTION "About"
-+ # FONT 8, "System", 0, 0, 0x0
-+ # BEGIN
-+ # ICON IDI_KLONK,IDC_MYICON,14,9,20,20
-+ # LTEXT "klonk Version ""yibbee"" 1.0",IDC_STATIC,49,10,119,8,
-+ # SS_NOPREFIX
-+ # LTEXT "Copyright (C) 2005",IDC_STATIC,49,20,119,8
-+ # DEFPUSHBUTTON "OK",IDOK,195,6,30,11,WS_GROUP
-+ # CONTROL "Jack ""Black"" Daniels",IDC_RADIO1,"Button",
-+ # BS_AUTORADIOBUTTON,46,51,84,10
-+ # END
-+
-+ # We are using a sorted set of keys, and we assume that the
-+ # group name used for descriptions (type) will come after the "text"
-+ # group in alphabetical order. We also assume that there cannot be
-+ # more than one description per regular expression match.
-+ # If that's not the case some descriptions will be clobbered.
-+ dialog_re_ = lazy_re.compile(r'''
-+ # The dialog's ID in the first line
-+ (?P<id1>[A-Z0-9_]+)\s+DIALOG(EX)?
-+ |
-+ # The caption of the dialog
-+ (?P<type1>CAPTION)\s+"(?P<text1>.*?([^"]|""))"\s
-+ |
-+ # Lines for controls that have text and an ID
-+ \s+(?P<type2>[A-Z]+)\s+"(?P<text2>.*?([^"]|"")?)"\s*,\s*(?P<id2>[A-Z0-9_]+)\s*,
-+ |
-+ # Lines for controls that have text only
-+ \s+(?P<type3>[A-Z]+)\s+"(?P<text3>.*?([^"]|"")?)"\s*,
-+ |
-+ # Lines for controls that reference other resources
-+ \s+[A-Z]+\s+[A-Z0-9_]+\s*,\s*(?P<id3>[A-Z0-9_]*[A-Z][A-Z0-9_]*)
-+ |
-+ # This matches "NOT SOME_STYLE" so that it gets consumed and doesn't get
-+ # matched by the next option (controls that have only an ID and then just
-+ # numbers)
-+ \s+NOT\s+[A-Z][A-Z0-9_]+
-+ |
-+ # Lines for controls that have only an ID and then just numbers
-+ \s+[A-Z]+\s+(?P<id4>[A-Z0-9_]*[A-Z][A-Z0-9_]*)\s*,
-+ ''', re.MULTILINE | re.VERBOSE)
-+
-+ def Parse(self):
-+ '''Knows how to parse dialog resource sections.'''
-+ self.ReadSection()
-+ self._RegExpParse(self.dialog_re_, self.text_)
-+
-+
-+class Menu(Section):
-+ '''A resource section that contains a menu resource.'''
-+
-+ # A typical menu resource section looks something like this:
-+ #
-+ # IDC_KLONK MENU
-+ # BEGIN
-+ # POPUP "&File"
-+ # BEGIN
-+ # MENUITEM "E&xit", IDM_EXIT
-+ # MENUITEM "This be ""Klonk"" me like", ID_FILE_THISBE
-+ # POPUP "gonk"
-+ # BEGIN
-+ # MENUITEM "Klonk && is ""good""", ID_GONK_KLONKIS
-+ # END
-+ # END
-+ # POPUP "&Help"
-+ # BEGIN
-+ # MENUITEM "&About ...", IDM_ABOUT
-+ # END
-+ # END
-+
-+ # Description used for the messages generated for menus, to explain to
-+ # the translators how to handle them.
-+ MENU_MESSAGE_DESCRIPTION = (
-+ 'This message represents a menu. Each of the items appears in sequence '
-+ '(some possibly within sub-menus) in the menu. The XX01XX placeholders '
-+ 'serve to separate items. Each item contains an & (ampersand) character '
-+ 'in front of the keystroke that should be used as a shortcut for that item '
-+ 'in the menu. Please make sure that no two items in the same menu share '
-+ 'the same shortcut.'
-+ )
-+
-+ # A dandy regexp to suck all the IDs and translateables out of a menu
-+ # resource
-+ menu_re_ = lazy_re.compile(r'''
-+ # Match the MENU ID on the first line
-+ ^(?P<id1>[A-Z0-9_]+)\s+MENU
-+ |
-+ # Match the translateable caption for a popup menu
-+ POPUP\s+"(?P<text1>.*?([^"]|""))"\s
-+ |
-+ # Match the caption & ID of a MENUITEM
-+ MENUITEM\s+"(?P<text2>.*?([^"]|""))"\s*,\s*(?P<id2>[A-Z0-9_]+)
-+ ''', re.MULTILINE | re.VERBOSE)
-+
-+ def Parse(self):
-+ '''Knows how to parse menu resource sections. Because it is important that
-+ menu shortcuts are unique within the menu, we return each menu as a single
-+ message with placeholders to break up the different menu items, rather than
-+ return a single message per menu item. we also add an automatic description
-+ with instructions for the translators.'''
-+ self.ReadSection()
-+ self.single_message_ = tclib.Message(description=self.MENU_MESSAGE_DESCRIPTION)
-+ self._RegExpParse(self.menu_re_, self.text_)
-+
-+
-+class Version(Section):
-+ '''A resource section that contains a VERSIONINFO resource.'''
-+
-+ # A typical version info resource can look like this:
-+ #
-+ # VS_VERSION_INFO VERSIONINFO
-+ # FILEVERSION 1,0,0,1
-+ # PRODUCTVERSION 1,0,0,1
-+ # FILEFLAGSMASK 0x3fL
-+ # #ifdef _DEBUG
-+ # FILEFLAGS 0x1L
-+ # #else
-+ # FILEFLAGS 0x0L
-+ # #endif
-+ # FILEOS 0x4L
-+ # FILETYPE 0x2L
-+ # FILESUBTYPE 0x0L
-+ # BEGIN
-+ # BLOCK "StringFileInfo"
-+ # BEGIN
-+ # BLOCK "040904e4"
-+ # BEGIN
-+ # VALUE "CompanyName", "TODO: <Company name>"
-+ # VALUE "FileDescription", "TODO: <File description>"
-+ # VALUE "FileVersion", "1.0.0.1"
-+ # VALUE "LegalCopyright", "TODO: (c) <Company name>. All rights reserved."
-+ # VALUE "InternalName", "res_format_test.dll"
-+ # VALUE "OriginalFilename", "res_format_test.dll"
-+ # VALUE "ProductName", "TODO: <Product name>"
-+ # VALUE "ProductVersion", "1.0.0.1"
-+ # END
-+ # END
-+ # BLOCK "VarFileInfo"
-+ # BEGIN
-+ # VALUE "Translation", 0x409, 1252
-+ # END
-+ # END
-+ #
-+ #
-+ # In addition to the above fields, VALUE fields named "Comments" and
-+ # "LegalTrademarks" may also be translateable.
-+
-+ version_re_ = lazy_re.compile(r'''
-+ # Match the ID on the first line
-+ ^(?P<id1>[A-Z0-9_]+)\s+VERSIONINFO
-+ |
-+ # Match all potentially translateable VALUE sections
-+ \s+VALUE\s+"
-+ (
-+ CompanyName|FileDescription|LegalCopyright|
-+ ProductName|Comments|LegalTrademarks
-+ )",\s+"(?P<text1>.*?([^"]|""))"\s
-+ ''', re.MULTILINE | re.VERBOSE)
-+
-+ def Parse(self):
-+ '''Knows how to parse VERSIONINFO resource sections.'''
-+ self.ReadSection()
-+ self._RegExpParse(self.version_re_, self.text_)
-+
-+ # TODO(joi) May need to override the Translate() method to change the
-+ # "Translation" VALUE block to indicate the correct language code.
-+
-+
-+class RCData(Section):
-+ '''A resource section that contains some data .'''
-+
-+ # A typical rcdataresource section looks like this:
-+ #
-+ # IDR_BLAH RCDATA { 1, 2, 3, 4 }
-+
-+ dialog_re_ = lazy_re.compile(r'''
-+ ^(?P<id1>[A-Z0-9_]+)\s+RCDATA\s+(DISCARDABLE)?\s+\{.*?\}
-+ ''', re.MULTILINE | re.VERBOSE | re.DOTALL)
-+
-+ def Parse(self):
-+ '''Implementation for resource types w/braces (not BEGIN/END)
-+ '''
-+ rc_text = self._LoadInputFile()
-+
-+ out = ''
-+ begin_count = 0
-+ openbrace_count = 0
-+ assert self.extkey
-+ first_line_re = re.compile(r'\s*' + self.extkey + r'\b')
-+ for line in rc_text.splitlines(True):
-+ if out or first_line_re.match(line):
-+ out += line
-+
-+ # We stop once the braces balance (could happen in one line).
-+ begin_count_was = begin_count
-+ if len(out) > 0:
-+ openbrace_count += line.count('{')
-+ begin_count += line.count('{')
-+ begin_count -= line.count('}')
-+ if ((begin_count_was == 1 and begin_count == 0) or
-+ (openbrace_count > 0 and begin_count == 0)):
-+ break
-+
-+ if len(out) == 0:
-+ raise exception.SectionNotFound('%s in file %s' % (self.extkey, self.rc_file))
-+
-+ self.text_ = out
-+
-+ self._RegExpParse(self.dialog_re_, out)
-+
-+
-+class Accelerators(Section):
-+ '''An ACCELERATORS table.
-+ '''
-+
-+ # A typical ACCELERATORS section looks like this:
-+ #
-+ # IDR_ACCELERATOR1 ACCELERATORS
-+ # BEGIN
-+ # "^C", ID_ACCELERATOR32770, ASCII, NOINVERT
-+ # "^V", ID_ACCELERATOR32771, ASCII, NOINVERT
-+ # VK_INSERT, ID_ACCELERATOR32772, VIRTKEY, CONTROL, NOINVERT
-+ # END
-+
-+ accelerators_re_ = lazy_re.compile(r'''
-+ # Match the ID on the first line
-+ ^(?P<id1>[A-Z0-9_]+)\s+ACCELERATORS\s+
-+ |
-+ # Match accelerators specified as VK_XXX
-+ \s+VK_[A-Z0-9_]+,\s*(?P<id2>[A-Z0-9_]+)\s*,
-+ |
-+ # Match accelerators specified as e.g. "^C"
-+ \s+"[^"]*",\s+(?P<id3>[A-Z0-9_]+)\s*,
-+ ''', re.MULTILINE | re.VERBOSE)
-+
-+ def Parse(self):
-+ '''Knows how to parse ACCELERATORS resource sections.'''
-+ self.ReadSection()
-+ self._RegExpParse(self.accelerators_re_, self.text_)
-diff --git a/tools/grit/grit/gather/rc_unittest.py b/tools/grit/grit/gather/rc_unittest.py
-new file mode 100644
-index 0000000000..3c26a4342a
---- /dev/null
-+++ b/tools/grit/grit/gather/rc_unittest.py
-@@ -0,0 +1,372 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for grit.gather.rc'''
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+import unittest
-+
-+from six import StringIO
-+
-+from grit.gather import rc
-+from grit import util
-+
-+
-+class RcUnittest(unittest.TestCase):
-+
-+ part_we_want = '''IDC_KLONKACC ACCELERATORS
-+BEGIN
-+ "?", IDM_ABOUT, ASCII, ALT
-+ "/", IDM_ABOUT, ASCII, ALT
-+END'''
-+
-+ def testSectionFromFile(self):
-+ buf = '''IDC_SOMETHINGELSE BINGO
-+BEGIN
-+ BLA BLA
-+ BLA BLA
-+END
-+%s
-+
-+IDC_KLONK BINGOBONGO
-+BEGIN
-+ HONGO KONGO
-+END
-+''' % self.part_we_want
-+
-+ f = StringIO(buf)
-+
-+ out = rc.Section(f, 'IDC_KLONKACC')
-+ out.ReadSection()
-+ self.failUnless(out.GetText() == self.part_we_want)
-+
-+ out = rc.Section(util.PathFromRoot(r'grit/testdata/klonk.rc'),
-+ 'IDC_KLONKACC',
-+ encoding='utf-16')
-+ out.ReadSection()
-+ out_text = out.GetText().replace('\t', '')
-+ out_text = out_text.replace(' ', '')
-+ self.part_we_want = self.part_we_want.replace(' ', '')
-+ self.failUnless(out_text.strip() == self.part_we_want.strip())
-+
-+
-+ def testDialog(self):
-+ dlg = rc.Dialog(StringIO('''IDD_ABOUTBOX DIALOGEX 22, 17, 230, 75
-+STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
-+CAPTION "About"
-+FONT 8, "System", 0, 0, 0x0
-+BEGIN
-+ ICON IDI_KLONK,IDC_MYICON,14,9,20,20
-+ LTEXT "klonk Version ""yibbee"" 1.0",IDC_STATIC,49,10,119,8,
-+ SS_NOPREFIX
-+ LTEXT "Copyright (C) 2005",IDC_STATIC,49,20,119,8
-+ DEFPUSHBUTTON "OK",IDOK,195,6,30,11,WS_GROUP
-+ CONTROL "Jack ""Black"" Daniels",IDC_RADIO1,"Button",
-+ BS_AUTORADIOBUTTON,46,51,84,10
-+ // try a line where the ID is on the continuation line
-+ LTEXT "blablablabla blablabla blablablablablablablabla blablabla",
-+ ID_SMURF, whatever...
-+END
-+'''), 'IDD_ABOUTBOX')
-+ dlg.Parse()
-+ self.failUnless(len(dlg.GetTextualIds()) == 7)
-+ self.failUnless(len(dlg.GetCliques()) == 6)
-+ self.failUnless(dlg.GetCliques()[1].GetMessage().GetRealContent() ==
-+ 'klonk Version "yibbee" 1.0')
-+
-+ transl = dlg.Translate('en')
-+ self.failUnless(transl.strip() == dlg.GetText().strip())
-+
-+ def testAlternateSkeleton(self):
-+ dlg = rc.Dialog(StringIO('''IDD_ABOUTBOX DIALOGEX 22, 17, 230, 75
-+STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
-+CAPTION "About"
-+FONT 8, "System", 0, 0, 0x0
-+BEGIN
-+ LTEXT "Yipee skippy",IDC_STATIC,49,10,119,8,
-+ SS_NOPREFIX
-+END
-+'''), 'IDD_ABOUTBOX')
-+ dlg.Parse()
-+
-+ alt_dlg = rc.Dialog(StringIO('''IDD_ABOUTBOX DIALOGEX 040704, 17, 230, 75
-+STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
-+CAPTION "XXXXXXXXX"
-+FONT 8, "System", 0, 0, 0x0
-+BEGIN
-+ LTEXT "XXXXXXXXXXXXXXXXX",IDC_STATIC,110978,10,119,8,
-+ SS_NOPREFIX
-+END
-+'''), 'IDD_ABOUTBOX')
-+ alt_dlg.Parse()
-+
-+ transl = dlg.Translate('en', skeleton_gatherer=alt_dlg)
-+ self.failUnless(transl.count('040704') and
-+ transl.count('110978'))
-+ self.failUnless(transl.count('Yipee skippy'))
-+
-+ def testMenu(self):
-+ menu = rc.Menu(StringIO('''IDC_KLONK MENU
-+BEGIN
-+ POPUP "&File """
-+ BEGIN
-+ MENUITEM "E&xit", IDM_EXIT
-+ MENUITEM "This be ""Klonk"" me like", ID_FILE_THISBE
-+ POPUP "gonk"
-+ BEGIN
-+ MENUITEM "Klonk && is ""good""", ID_GONK_KLONKIS
-+ END
-+ MENUITEM "This is a very long menu caption to try to see if we can make the ID go to a continuation line, blablabla blablabla bla blabla blablabla blablabla blablabla blablabla...",
-+ ID_FILE_THISISAVERYLONGMENUCAPTIONTOTRYTOSEEIFWECANMAKETHEIDGOTOACONTINUATIONLINE
-+ END
-+ POPUP "&Help"
-+ BEGIN
-+ MENUITEM "&About ...", IDM_ABOUT
-+ END
-+END'''), 'IDC_KLONK')
-+
-+ menu.Parse()
-+ self.failUnless(len(menu.GetTextualIds()) == 6)
-+ self.failUnless(len(menu.GetCliques()) == 1)
-+ self.failUnless(len(menu.GetCliques()[0].GetMessage().GetPlaceholders()) ==
-+ 9)
-+
-+ transl = menu.Translate('en')
-+ self.failUnless(transl.strip() == menu.GetText().strip())
-+
-+ def testVersion(self):
-+ version = rc.Version(StringIO('''
-+VS_VERSION_INFO VERSIONINFO
-+ FILEVERSION 1,0,0,1
-+ PRODUCTVERSION 1,0,0,1
-+ FILEFLAGSMASK 0x3fL
-+#ifdef _DEBUG
-+ FILEFLAGS 0x1L
-+#else
-+ FILEFLAGS 0x0L
-+#endif
-+ FILEOS 0x4L
-+ FILETYPE 0x2L
-+ FILESUBTYPE 0x0L
-+BEGIN
-+ BLOCK "StringFileInfo"
-+ BEGIN
-+ BLOCK "040904e4"
-+ BEGIN
-+ VALUE "CompanyName", "TODO: <Company name>"
-+ VALUE "FileDescription", "TODO: <File description>"
-+ VALUE "FileVersion", "1.0.0.1"
-+ VALUE "LegalCopyright", "TODO: (c) <Company name>. All rights reserved."
-+ VALUE "InternalName", "res_format_test.dll"
-+ VALUE "OriginalFilename", "res_format_test.dll"
-+ VALUE "ProductName", "TODO: <Product name>"
-+ VALUE "ProductVersion", "1.0.0.1"
-+ END
-+ END
-+ BLOCK "VarFileInfo"
-+ BEGIN
-+ VALUE "Translation", 0x409, 1252
-+ END
-+END
-+'''.strip()), 'VS_VERSION_INFO')
-+ version.Parse()
-+ self.failUnless(len(version.GetTextualIds()) == 1)
-+ self.failUnless(len(version.GetCliques()) == 4)
-+
-+ transl = version.Translate('en')
-+ self.failUnless(transl.strip() == version.GetText().strip())
-+
-+
-+ def testRegressionDialogBox(self):
-+ dialog = rc.Dialog(StringIO('''
-+IDD_SIDEBAR_WEATHER_PANEL_PROPPAGE DIALOGEX 0, 0, 205, 157
-+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
-+FONT 8, "MS Shell Dlg", 400, 0, 0x1
-+BEGIN
-+ EDITTEXT IDC_SIDEBAR_WEATHER_NEW_CITY,3,27,112,14,ES_AUTOHSCROLL
-+ DEFPUSHBUTTON "Add Location",IDC_SIDEBAR_WEATHER_ADD,119,27,50,14
-+ LISTBOX IDC_SIDEBAR_WEATHER_CURR_CITIES,3,48,127,89,
-+ LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP
-+ PUSHBUTTON "Move Up",IDC_SIDEBAR_WEATHER_MOVE_UP,134,104,50,14
-+ PUSHBUTTON "Move Down",IDC_SIDEBAR_WEATHER_MOVE_DOWN,134,121,50,14
-+ PUSHBUTTON "Remove",IDC_SIDEBAR_WEATHER_DELETE,134,48,50,14
-+ LTEXT "To see current weather conditions and forecasts in the USA, enter the zip code (example: 94043) or city and state (example: Mountain View, CA).",
-+ IDC_STATIC,3,0,199,25
-+ CONTROL "Fahrenheit",IDC_SIDEBAR_WEATHER_FAHRENHEIT,"Button",
-+ BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,3,144,51,10
-+ CONTROL "Celsius",IDC_SIDEBAR_WEATHER_CELSIUS,"Button",
-+ BS_AUTORADIOBUTTON,57,144,38,10
-+END'''.strip()), 'IDD_SIDEBAR_WEATHER_PANEL_PROPPAGE')
-+ dialog.Parse()
-+ self.failUnless(len(dialog.GetTextualIds()) == 10)
-+
-+
-+ def testRegressionDialogBox2(self):
-+ dialog = rc.Dialog(StringIO('''
-+IDD_SIDEBAR_EMAIL_PANEL_PROPPAGE DIALOG DISCARDABLE 0, 0, 264, 220
-+STYLE WS_CHILD
-+FONT 8, "MS Shell Dlg"
-+BEGIN
-+ GROUPBOX "Email Filters",IDC_STATIC,7,3,250,190
-+ LTEXT "Click Add Filter to create the email filter.",IDC_STATIC,16,41,130,9
-+ PUSHBUTTON "Add Filter...",IDC_SIDEBAR_EMAIL_ADD_FILTER,196,38,50,14
-+ PUSHBUTTON "Remove",IDC_SIDEBAR_EMAIL_REMOVE,196,174,50,14
-+ PUSHBUTTON "", IDC_SIDEBAR_EMAIL_HIDDEN, 200, 178, 5, 5, NOT WS_VISIBLE
-+ LISTBOX IDC_SIDEBAR_EMAIL_LIST,16,60,230,108,
-+ LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP
-+ LTEXT "You can prevent certain emails from showing up in the sidebar with a filter.",
-+ IDC_STATIC,16,18,234,18
-+END'''.strip()), 'IDD_SIDEBAR_EMAIL_PANEL_PROPPAGE')
-+ dialog.Parse()
-+ self.failUnless('IDC_SIDEBAR_EMAIL_HIDDEN' in dialog.GetTextualIds())
-+
-+
-+ def testRegressionMenuId(self):
-+ menu = rc.Menu(StringIO('''
-+IDR_HYPERMENU_FOLDER MENU
-+BEGIN
-+ POPUP "HyperFolder"
-+ BEGIN
-+ MENUITEM "Open Containing Folder", IDM_OPENFOLDER
-+ END
-+END'''.strip()), 'IDR_HYPERMENU_FOLDER')
-+ menu.Parse()
-+ self.failUnless(len(menu.GetTextualIds()) == 2)
-+
-+ def testRegressionNewlines(self):
-+ menu = rc.Menu(StringIO('''
-+IDR_HYPERMENU_FOLDER MENU
-+BEGIN
-+ POPUP "Hyper\\nFolder"
-+ BEGIN
-+ MENUITEM "Open Containing Folder", IDM_OPENFOLDER
-+ END
-+END'''.strip()), 'IDR_HYPERMENU_FOLDER')
-+ menu.Parse()
-+ transl = menu.Translate('en')
-+ # Shouldn't find \\n (the \n shouldn't be changed to \\n)
-+ self.failUnless(transl.find('\\\\n') == -1)
-+
-+ def testRegressionTabs(self):
-+ menu = rc.Menu(StringIO('''
-+IDR_HYPERMENU_FOLDER MENU
-+BEGIN
-+ POPUP "Hyper\\tFolder"
-+ BEGIN
-+ MENUITEM "Open Containing Folder", IDM_OPENFOLDER
-+ END
-+END'''.strip()), 'IDR_HYPERMENU_FOLDER')
-+ menu.Parse()
-+ transl = menu.Translate('en')
-+ # Shouldn't find \\t (the \t shouldn't be changed to \\t)
-+ self.failUnless(transl.find('\\\\t') == -1)
-+
-+ def testEscapeUnescape(self):
-+ original = 'Hello "bingo"\n How\\are\\you\\n?'
-+ escaped = rc.Section.Escape(original)
-+ self.failUnless(escaped == 'Hello ""bingo""\\n How\\\\are\\\\you\\\\n?')
-+ unescaped = rc.Section.UnEscape(escaped)
-+ self.failUnless(unescaped == original)
-+
-+ def testRegressionPathsWithSlashN(self):
-+ original = '..\\\\..\\\\trs\\\\res\\\\nav_first.gif'
-+ unescaped = rc.Section.UnEscape(original)
-+ self.failUnless(unescaped == '..\\..\\trs\\res\\nav_first.gif')
-+
-+ def testRegressionDialogItemsTextOnly(self):
-+ dialog = rc.Dialog(StringIO('''IDD_OPTIONS_SEARCH DIALOGEX 0, 0, 280, 292
-+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP |
-+ WS_DISABLED | WS_CAPTION | WS_SYSMENU
-+CAPTION "Search"
-+FONT 8, "MS Shell Dlg", 400, 0, 0x1
-+BEGIN
-+ GROUPBOX "Select search buttons and options",-1,7,5,266,262
-+ CONTROL "",IDC_OPTIONS,"SysTreeView32",TVS_DISABLEDRAGDROP |
-+ WS_BORDER | WS_TABSTOP | 0x800,16,19,248,218
-+ LTEXT "Use Google site:",-1,26,248,52,8
-+ COMBOBOX IDC_GOOGLE_HOME,87,245,177,256,CBS_DROPDOWNLIST |
-+ WS_VSCROLL | WS_TABSTOP
-+ PUSHBUTTON "Restore Defaults...",IDC_RESET,187,272,86,14
-+END'''), 'IDD_OPTIONS_SEARCH')
-+ dialog.Parse()
-+ translateables = [c.GetMessage().GetRealContent()
-+ for c in dialog.GetCliques()]
-+ self.failUnless('Select search buttons and options' in translateables)
-+ self.failUnless('Use Google site:' in translateables)
-+
-+ def testAccelerators(self):
-+ acc = rc.Accelerators(StringIO('''\
-+IDR_ACCELERATOR1 ACCELERATORS
-+BEGIN
-+ "^C", ID_ACCELERATOR32770, ASCII, NOINVERT
-+ "^V", ID_ACCELERATOR32771, ASCII, NOINVERT
-+ VK_INSERT, ID_ACCELERATOR32772, VIRTKEY, CONTROL, NOINVERT
-+END
-+'''), 'IDR_ACCELERATOR1')
-+ acc.Parse()
-+ self.failUnless(len(acc.GetTextualIds()) == 4)
-+ self.failUnless(len(acc.GetCliques()) == 0)
-+
-+ transl = acc.Translate('en')
-+ self.failUnless(transl.strip() == acc.GetText().strip())
-+
-+
-+ def testRegressionEmptyString(self):
-+ dlg = rc.Dialog(StringIO('''\
-+IDD_CONFIRM_QUIT_GD_DLG DIALOGEX 0, 0, 267, 108
-+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP |
-+ WS_CAPTION
-+EXSTYLE WS_EX_TOPMOST
-+CAPTION "Google Desktop"
-+FONT 8, "MS Shell Dlg", 400, 0, 0x1
-+BEGIN
-+ DEFPUSHBUTTON "&Yes",IDYES,82,87,50,14
-+ PUSHBUTTON "&No",IDNO,136,87,50,14
-+ ICON 32514,IDC_STATIC,7,9,21,20
-+ EDITTEXT IDC_TEXTBOX,34,7,231,60,ES_MULTILINE | ES_READONLY | NOT WS_BORDER
-+ CONTROL "",
-+ IDC_ENABLE_GD_AUTOSTART,"Button",BS_AUTOCHECKBOX |
-+ WS_TABSTOP,33,70,231,10
-+END'''), 'IDD_CONFIRM_QUIT_GD_DLG')
-+ dlg.Parse()
-+
-+ def Check():
-+ self.failUnless(transl.count('IDC_ENABLE_GD_AUTOSTART'))
-+ self.failUnless(transl.count('END'))
-+
-+ transl = dlg.Translate('de', pseudo_if_not_available=True,
-+ fallback_to_english=True)
-+ Check()
-+ transl = dlg.Translate('de', pseudo_if_not_available=True,
-+ fallback_to_english=False)
-+ Check()
-+ transl = dlg.Translate('de', pseudo_if_not_available=False,
-+ fallback_to_english=True)
-+ Check()
-+ transl = dlg.Translate('de', pseudo_if_not_available=False,
-+ fallback_to_english=False)
-+ Check()
-+ transl = dlg.Translate('en', pseudo_if_not_available=True,
-+ fallback_to_english=True)
-+ Check()
-+ transl = dlg.Translate('en', pseudo_if_not_available=True,
-+ fallback_to_english=False)
-+ Check()
-+ transl = dlg.Translate('en', pseudo_if_not_available=False,
-+ fallback_to_english=True)
-+ Check()
-+ transl = dlg.Translate('en', pseudo_if_not_available=False,
-+ fallback_to_english=False)
-+ Check()
-+
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/gather/regexp.py b/tools/grit/grit/gather/regexp.py
-new file mode 100644
-index 0000000000..97ce2cfbf7
---- /dev/null
-+++ b/tools/grit/grit/gather/regexp.py
-@@ -0,0 +1,82 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''A baseclass for simple gatherers based on regular expressions.
-+'''
-+
-+from __future__ import print_function
-+
-+from grit.gather import skeleton_gatherer
-+
-+
-+class RegexpGatherer(skeleton_gatherer.SkeletonGatherer):
-+ '''Common functionality of gatherers based on parsing using a single
-+ regular expression.
-+ '''
-+
-+ DescriptionMapping_ = {
-+ 'CAPTION' : 'This is a caption for a dialog',
-+ 'CHECKBOX' : 'This is a label for a checkbox',
-+ 'CONTROL': 'This is the text on a control',
-+ 'CTEXT': 'This is a label for a control',
-+ 'DEFPUSHBUTTON': 'This is a button definition',
-+ 'GROUPBOX': 'This is a label for a grouping',
-+ 'ICON': 'This is a label for an icon',
-+ 'LTEXT': 'This is the text for a label',
-+ 'PUSHBUTTON': 'This is the text for a button',
-+ }
-+
-+ # Contextualization elements. Used for adding additional information
-+ # to the message bundle description string from RC files.
-+ def AddDescriptionElement(self, string):
-+ if string in self.DescriptionMapping_:
-+ description = self.DescriptionMapping_[string]
-+ else:
-+ description = string
-+ if self.single_message_:
-+ self.single_message_.SetDescription(description)
-+ else:
-+ if (self.translatable_chunk_):
-+ message = self.skeleton_[len(self.skeleton_) - 1].GetMessage()
-+ message.SetDescription(description)
-+
-+ def _RegExpParse(self, regexp, text_to_parse):
-+ '''An implementation of Parse() that can be used for resource sections that
-+ can be parsed using a single multi-line regular expression.
-+
-+ All translateables must be in named groups that have names starting with
-+ 'text'. All textual IDs must be in named groups that have names starting
-+ with 'id'. All type definitions that can be included in the description
-+ field for contextualization purposes should have a name that starts with
-+ 'type'.
-+
-+ Args:
-+ regexp: re.compile('...', re.MULTILINE)
-+ text_to_parse:
-+ '''
-+ chunk_start = 0
-+ for match in regexp.finditer(text_to_parse):
-+ groups = match.groupdict()
-+ keys = sorted(groups.keys())
-+ self.translatable_chunk_ = False
-+ for group in keys:
-+ if group.startswith('id') and groups[group]:
-+ self._AddTextualId(groups[group])
-+ elif group.startswith('text') and groups[group]:
-+ self._AddNontranslateableChunk(
-+ text_to_parse[chunk_start : match.start(group)])
-+ chunk_start = match.end(group) # Next chunk will start after the match
-+ self._AddTranslateableChunk(groups[group])
-+ elif group.startswith('type') and groups[group]:
-+ # Add the description to the skeleton_ list. This works because
-+ # we are using a sort set of keys, and because we assume that the
-+ # group name used for descriptions (type) will come after the "text"
-+ # group in alphabetical order. We also assume that there cannot be
-+ # more than one description per regular expression match.
-+ self.AddDescriptionElement(groups[group])
-+
-+ self._AddNontranslateableChunk(text_to_parse[chunk_start:])
-+
-+ if self.single_message_:
-+ self.skeleton_.append(self.uberclique.MakeClique(self.single_message_))
-diff --git a/tools/grit/grit/gather/skeleton_gatherer.py b/tools/grit/grit/gather/skeleton_gatherer.py
-new file mode 100644
-index 0000000000..b11862b314
---- /dev/null
-+++ b/tools/grit/grit/gather/skeleton_gatherer.py
-@@ -0,0 +1,149 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''A baseclass for simple gatherers that store their gathered resource in a
-+list.
-+'''
-+
-+from __future__ import print_function
-+
-+import six
-+
-+from grit.gather import interface
-+from grit import clique
-+from grit import exception
-+from grit import tclib
-+
-+
-+class SkeletonGatherer(interface.GathererBase):
-+ '''Common functionality of gatherers that parse their input as a skeleton of
-+ translatable and nontranslatable chunks.
-+ '''
-+
-+ def __init__(self, *args, **kwargs):
-+ super(SkeletonGatherer, self).__init__(*args, **kwargs)
-+ # List of parts of the document. Translateable parts are
-+ # clique.MessageClique objects, nontranslateable parts are plain strings.
-+ # Translated messages are inserted back into the skeleton using the quoting
-+ # rules defined by self.Escape()
-+ self.skeleton_ = []
-+ # A list of the names of IDs that need to be defined for this resource
-+ # section to compile correctly.
-+ self.ids_ = []
-+ # True if Parse() has already been called.
-+ self.have_parsed_ = False
-+ # True if a translatable chunk has been added
-+ self.translatable_chunk_ = False
-+ # If not None, all parts of the document will be put into this single
-+ # message; otherwise the normal skeleton approach is used.
-+ self.single_message_ = None
-+ # Number to use for the next placeholder name. Used only if single_message
-+ # is not None
-+ self.ph_counter_ = 1
-+
-+ def GetText(self):
-+ '''Returns the original text of the section'''
-+ return self.text_
-+
-+ def Escape(self, text):
-+ '''Subclasses can override. Base impl is identity.
-+ '''
-+ return text
-+
-+ def UnEscape(self, text):
-+ '''Subclasses can override. Base impl is identity.
-+ '''
-+ return text
-+
-+ def GetTextualIds(self):
-+ '''Returns the list of textual IDs that need to be defined for this
-+ resource section to compile correctly.'''
-+ return self.ids_
-+
-+ def _AddTextualId(self, id):
-+ self.ids_.append(id)
-+
-+ def GetCliques(self):
-+ '''Returns the message cliques for each translateable message in the
-+ resource section.'''
-+ return [x for x in self.skeleton_ if isinstance(x, clique.MessageClique)]
-+
-+ def Translate(self, lang, pseudo_if_not_available=True,
-+ skeleton_gatherer=None, fallback_to_english=False):
-+ if len(self.skeleton_) == 0:
-+ raise exception.NotReady()
-+ if skeleton_gatherer:
-+ assert len(skeleton_gatherer.skeleton_) == len(self.skeleton_)
-+
-+ out = []
-+ for ix in range(len(self.skeleton_)):
-+ if isinstance(self.skeleton_[ix], six.string_types):
-+ if skeleton_gatherer:
-+ # Make sure the skeleton is like the original
-+ assert(isinstance(skeleton_gatherer.skeleton_[ix], six.string_types))
-+ out.append(skeleton_gatherer.skeleton_[ix])
-+ else:
-+ out.append(self.skeleton_[ix])
-+ else:
-+ if skeleton_gatherer: # Make sure the skeleton is like the original
-+ assert(not isinstance(skeleton_gatherer.skeleton_[ix],
-+ six.string_types))
-+ msg = self.skeleton_[ix].MessageForLanguage(lang,
-+ pseudo_if_not_available,
-+ fallback_to_english)
-+
-+ def MyEscape(text):
-+ return self.Escape(text)
-+ text = msg.GetRealContent(escaping_function=MyEscape)
-+ out.append(text)
-+ return ''.join(out)
-+
-+ def Parse(self):
-+ '''Parses the section. Implemented by subclasses. Idempotent.'''
-+ raise NotImplementedError()
-+
-+ def _AddNontranslateableChunk(self, chunk):
-+ '''Adds a nontranslateable chunk.'''
-+ if self.single_message_:
-+ ph = tclib.Placeholder('XX%02dXX' % self.ph_counter_, chunk, chunk)
-+ self.ph_counter_ += 1
-+ self.single_message_.AppendPlaceholder(ph)
-+ else:
-+ self.skeleton_.append(chunk)
-+
-+ def _AddTranslateableChunk(self, chunk):
-+ '''Adds a translateable chunk. It will be unescaped before being added.'''
-+ # We don't want empty messages since they are redundant and the TC
-+ # doesn't allow them.
-+ if chunk == '':
-+ return
-+
-+ unescaped_text = self.UnEscape(chunk)
-+ if self.single_message_:
-+ self.single_message_.AppendText(unescaped_text)
-+ else:
-+ self.skeleton_.append(self.uberclique.MakeClique(
-+ tclib.Message(text=unescaped_text)))
-+ self.translatable_chunk_ = True
-+
-+ def SubstituteMessages(self, substituter):
-+ '''Applies substitutions to all messages in the tree.
-+
-+ Goes through the skeleton and finds all MessageCliques.
-+
-+ Args:
-+ substituter: a grit.util.Substituter object.
-+ '''
-+ if self.single_message_:
-+ self.single_message_ = substituter.SubstituteMessage(self.single_message_)
-+ new_skel = []
-+ for chunk in self.skeleton_:
-+ if isinstance(chunk, clique.MessageClique):
-+ old_message = chunk.GetMessage()
-+ new_message = substituter.SubstituteMessage(old_message)
-+ if new_message is not old_message:
-+ new_skel.append(self.uberclique.MakeClique(new_message))
-+ continue
-+ new_skel.append(chunk)
-+ self.skeleton_ = new_skel
-diff --git a/tools/grit/grit/gather/tr_html.py b/tools/grit/grit/gather/tr_html.py
-new file mode 100644
-index 0000000000..60a9bfaf4e
---- /dev/null
-+++ b/tools/grit/grit/gather/tr_html.py
-@@ -0,0 +1,743 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''A gatherer for the TotalRecall brand of HTML templates with replaceable
-+portions. We wanted to reuse extern.tclib.api.handlers.html.TCHTMLParser
-+but this proved impossible due to the fact that the TotalRecall HTML templates
-+are in general quite far from parseable HTML and the TCHTMLParser derives
-+
-+from HTMLParser.HTMLParser which requires relatively well-formed HTML. Some
-+examples of "HTML" from the TotalRecall HTML templates that wouldn't be
-+parseable include things like:
-+
-+ <a [PARAMS]>blabla</a> (not parseable because attributes are invalid)
-+
-+ <table><tr><td>[LOTSOFSTUFF]</tr></table> (not parseable because closing
-+ </td> is in the HTML [LOTSOFSTUFF]
-+ is replaced by)
-+
-+The other problem with using general parsers (such as TCHTMLParser) is that
-+we want to make sure we output the TotalRecall template with as little changes
-+as possible in terms of whitespace characters, layout etc. With any parser
-+that generates a parse tree, and generates output by dumping the parse tree,
-+we would always have little inconsistencies which could cause bugs (the
-+TotalRecall template stuff is quite brittle and can break if e.g. a tab
-+character is replaced with spaces).
-+
-+The solution, which may be applicable to some other HTML-like template
-+languages floating around Google, is to create a parser with a simple state
-+machine that keeps track of what kind of tag it's inside, and whether it's in
-+a translateable section or not. Translateable sections are:
-+
-+a) text (including [BINGO] replaceables) inside of tags that
-+ can contain translateable text (which is all tags except
-+ for a few)
-+
-+b) text inside of an 'alt' attribute in an <image> element, or
-+ the 'value' attribute of a <submit>, <button> or <text>
-+ element.
-+
-+The parser does not build up a parse tree but rather a "skeleton" which
-+is a list of nontranslateable strings intermingled with grit.clique.MessageClique
-+objects. This simplifies the parser considerably compared to a regular HTML
-+parser. To output a translated document, each item in the skeleton is
-+printed out, with the relevant Translation from each MessageCliques being used
-+for the requested language.
-+
-+This implementation borrows some code, constants and ideas from
-+extern.tclib.api.handlers.html.TCHTMLParser.
-+'''
-+
-+from __future__ import print_function
-+
-+import re
-+
-+import six
-+
-+from grit import clique
-+from grit import exception
-+from grit import lazy_re
-+from grit import util
-+from grit import tclib
-+
-+from grit.gather import interface
-+
-+
-+# HTML tags which break (separate) chunks.
-+_BLOCK_TAGS = ['script', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'br',
-+ 'body', 'style', 'head', 'title', 'table', 'tr', 'td', 'th',
-+ 'ul', 'ol', 'dl', 'nl', 'li', 'div', 'object', 'center',
-+ 'html', 'link', 'form', 'select', 'textarea',
-+ 'button', 'option', 'map', 'area', 'blockquote', 'pre',
-+ 'meta', 'xmp', 'noscript', 'label', 'tbody', 'thead',
-+ 'script', 'style', 'pre', 'iframe', 'img', 'input', 'nowrap',
-+ 'fieldset', 'legend']
-+
-+# HTML tags which may appear within a chunk.
-+_INLINE_TAGS = ['b', 'i', 'u', 'tt', 'code', 'font', 'a', 'span', 'small',
-+ 'key', 'nobr', 'url', 'em', 's', 'sup', 'strike',
-+ 'strong']
-+
-+# HTML tags within which linebreaks are significant.
-+_PREFORMATTED_TAGS = ['textarea', 'xmp', 'pre']
-+
-+# An array mapping some of the inline HTML tags to more meaningful
-+# names for those tags. This will be used when generating placeholders
-+# representing these tags.
-+_HTML_PLACEHOLDER_NAMES = { 'a' : 'link', 'br' : 'break', 'b' : 'bold',
-+ 'i' : 'italic', 'li' : 'item', 'ol' : 'ordered_list', 'p' : 'paragraph',
-+ 'ul' : 'unordered_list', 'img' : 'image', 'em' : 'emphasis' }
-+
-+# We append each of these characters in sequence to distinguish between
-+# different placeholders with basically the same name (e.g. BOLD1, BOLD2).
-+# Keep in mind that a placeholder name must not be a substring of any other
-+# placeholder name in the same message, so we can't simply count (BOLD_1
-+# would be a substring of BOLD_10).
-+_SUFFIXES = '123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
-+
-+# Matches whitespace in an HTML document. Also matches HTML comments, which are
-+# treated as whitespace.
-+_WHITESPACE = lazy_re.compile(r'(\s|&nbsp;|\\n|\\r|<!--\s*desc\s*=.*?-->)+',
-+ re.DOTALL)
-+
-+# Matches whitespace sequences which can be folded into a single whitespace
-+# character. This matches single characters so that non-spaces are replaced
-+# with spaces.
-+_FOLD_WHITESPACE = lazy_re.compile(r'\s+')
-+
-+# Finds a non-whitespace character
-+_NON_WHITESPACE = lazy_re.compile(r'\S')
-+
-+# Matches two or more &nbsp; in a row (a single &nbsp is not changed into
-+# placeholders because different languages require different numbers of spaces
-+# and placeholders must match exactly; more than one is probably a "special"
-+# whitespace sequence and should be turned into a placeholder).
-+_NBSP = lazy_re.compile(r'&nbsp;(&nbsp;)+')
-+
-+# Matches nontranslateable chunks of the document
-+_NONTRANSLATEABLES = lazy_re.compile(r'''
-+ <\s*script.+?<\s*/\s*script\s*>
-+ |
-+ <\s*style.+?<\s*/\s*style\s*>
-+ |
-+ <!--.+?-->
-+ |
-+ <\?IMPORT\s.+?> # import tag
-+ |
-+ <\s*[a-zA-Z_]+:.+?> # custom tag (open)
-+ |
-+ <\s*/\s*[a-zA-Z_]+:.+?> # custom tag (close)
-+ |
-+ <!\s*[A-Z]+\s*([^>]+|"[^"]+"|'[^']+')*?>
-+ ''', re.MULTILINE | re.DOTALL | re.VERBOSE | re.IGNORECASE)
-+
-+# Matches a tag and its attributes
-+_ELEMENT = lazy_re.compile(r'''
-+ # Optional closing /, element name
-+ <\s*(?P<closing>/)?\s*(?P<element>[a-zA-Z0-9]+)\s*
-+ # Attributes and/or replaceables inside the tag, if any
-+ (?P<atts>(
-+ \s*([a-zA-Z_][-:.a-zA-Z_0-9]*) # Attribute name
-+ (\s*=\s*(\'[^\']*\'|"[^"]*"|[-a-zA-Z0-9./,:;+*%?!&$\(\)_#=~\'"@]*))?
-+ |
-+ \s*\[(\$?\~)?([A-Z0-9-_]+?)(\~\$?)?\]
-+ )*)
-+ \s*(?P<empty>/)?\s*> # Optional empty-tag closing /, and tag close
-+ ''',
-+ re.MULTILINE | re.DOTALL | re.VERBOSE)
-+
-+# Matches elements that may have translateable attributes. The value of these
-+# special attributes is given by group 'value1' or 'value2'. Note that this
-+# regexp demands that the attribute value be quoted; this is necessary because
-+# the non-tree-building nature of the parser means we don't know when we're
-+# writing out attributes, so we wouldn't know to escape spaces.
-+_SPECIAL_ELEMENT = lazy_re.compile(r'''
-+ <\s*(
-+ input[^>]+?value\s*=\s*(\'(?P<value3>[^\']*)\'|"(?P<value4>[^"]*)")
-+ [^>]+type\s*=\s*"?'?(button|reset|text|submit)'?"?
-+ |
-+ (
-+ table[^>]+?title\s*=
-+ |
-+ img[^>]+?alt\s*=
-+ |
-+ input[^>]+?type\s*=\s*"?'?(button|reset|text|submit)'?"?[^>]+?value\s*=
-+ )
-+ \s*(\'(?P<value1>[^\']*)\'|"(?P<value2>[^"]*)")
-+ )[^>]*?>
-+ ''', re.MULTILINE | re.DOTALL | re.VERBOSE | re.IGNORECASE)
-+
-+# Matches stuff that is translateable if it occurs in the right context
-+# (between tags). This includes all characters and character entities.
-+# Note that this also matches &nbsp; which needs to be handled as whitespace
-+# before this regexp is applied.
-+_CHARACTERS = lazy_re.compile(r'''
-+ (
-+ \w
-+ |
-+ [\!\@\#\$\%\^\*\(\)\-\=\_\+\[\]\{\}\\\|\;\:\'\"\,\.\/\?\`\~]
-+ |
-+ &(\#[0-9]+|\#x[0-9a-fA-F]+|[A-Za-z0-9]+);
-+ )+
-+ ''', re.MULTILINE | re.DOTALL | re.VERBOSE)
-+
-+# Matches Total Recall's "replaceable" tags, which are just any text
-+# in capitals enclosed by delimiters like [] or [~~] or [$~~$] (e.g. [HELLO],
-+# [~HELLO~] and [$~HELLO~$]).
-+_REPLACEABLE = lazy_re.compile(r'\[(\$?\~)?(?P<name>[A-Z0-9-_]+?)(\~\$?)?\]',
-+ re.MULTILINE)
-+
-+
-+# Matches the silly [!]-prefixed "header" that is used in some TotalRecall
-+# templates.
-+_SILLY_HEADER = lazy_re.compile(r'\[!\]\ntitle\t(?P<title>[^\n]+?)\n.+?\n\n',
-+ re.MULTILINE | re.DOTALL)
-+
-+
-+# Matches a comment that provides a description for the message it occurs in.
-+_DESCRIPTION_COMMENT = lazy_re.compile(
-+ r'<!--\s*desc\s*=\s*(?P<description>.+?)\s*-->', re.DOTALL)
-+
-+# Matches a comment which is used to break apart multiple messages.
-+_MESSAGE_BREAK_COMMENT = lazy_re.compile(r'<!--\s*message-break\s*-->',
-+ re.DOTALL)
-+
-+# Matches a comment which is used to prevent block tags from splitting a message
-+_MESSAGE_NO_BREAK_COMMENT = re.compile(r'<!--\s*message-no-break\s*-->',
-+ re.DOTALL)
-+
-+
-+_DEBUG = 0
-+def _DebugPrint(text):
-+ if _DEBUG:
-+ print(text.encode('utf-8'))
-+
-+
-+class HtmlChunks(object):
-+ '''A parser that knows how to break an HTML-like document into a list of
-+ chunks, where each chunk is either translateable or non-translateable.
-+ The chunks are unmodified sections of the original document, so concatenating
-+ the text of all chunks would result in the original document.'''
-+
-+ def InTranslateable(self):
-+ return self.last_translateable != -1
-+
-+ def Rest(self):
-+ return self.text_[self.current:]
-+
-+ def StartTranslateable(self):
-+ assert not self.InTranslateable()
-+ if self.current != 0:
-+ # Append a nontranslateable chunk
-+ chunk_text = self.text_[self.chunk_start : self.last_nontranslateable + 1]
-+ # Needed in the case where document starts with a translateable.
-+ if len(chunk_text) > 0:
-+ self.AddChunk(False, chunk_text)
-+ self.chunk_start = self.last_nontranslateable + 1
-+ self.last_translateable = self.current
-+ self.last_nontranslateable = -1
-+
-+ def EndTranslateable(self):
-+ assert self.InTranslateable()
-+ # Append a translateable chunk
-+ self.AddChunk(True,
-+ self.text_[self.chunk_start : self.last_translateable + 1])
-+ self.chunk_start = self.last_translateable + 1
-+ self.last_translateable = -1
-+ self.last_nontranslateable = self.current
-+
-+ def AdvancePast(self, match):
-+ self.current += match.end()
-+
-+ def AddChunk(self, translateable, text):
-+ '''Adds a chunk to self, removing linebreaks and duplicate whitespace
-+ if appropriate.
-+ '''
-+ m = _DESCRIPTION_COMMENT.search(text)
-+ if m:
-+ self.last_description = m.group('description')
-+ # Remove the description from the output text
-+ text = _DESCRIPTION_COMMENT.sub('', text)
-+
-+ m = _MESSAGE_BREAK_COMMENT.search(text)
-+ if m:
-+ # Remove the coment from the output text. It should already effectively
-+ # break apart messages.
-+ text = _MESSAGE_BREAK_COMMENT.sub('', text)
-+
-+ if translateable and not self.last_element_ in _PREFORMATTED_TAGS:
-+ if self.fold_whitespace_:
-+ # Fold whitespace sequences if appropriate. This is optional because it
-+ # alters the output strings.
-+ text = _FOLD_WHITESPACE.sub(' ', text)
-+ else:
-+ text = text.replace('\n', ' ')
-+ text = text.replace('\r', ' ')
-+ # This whitespace folding doesn't work in all cases, thus the
-+ # fold_whitespace flag to support backwards compatibility.
-+ text = text.replace(' ', ' ')
-+ text = text.replace(' ', ' ')
-+
-+ if translateable:
-+ description = self.last_description
-+ self.last_description = ''
-+ else:
-+ description = ''
-+
-+ if text != '':
-+ self.chunks_.append((translateable, text, description))
-+
-+ def Parse(self, text, fold_whitespace):
-+ '''Parses self.text_ into an intermediate format stored in self.chunks_
-+ which is translateable and nontranslateable chunks. Also returns
-+ self.chunks_
-+
-+ Args:
-+ text: The HTML for parsing.
-+ fold_whitespace: Whether whitespace sequences should be folded into a
-+ single space.
-+
-+ Return:
-+ [chunk1, chunk2, chunk3, ...] (instances of class Chunk)
-+ '''
-+ #
-+ # Chunker state
-+ #
-+
-+ self.text_ = text
-+ self.fold_whitespace_ = fold_whitespace
-+
-+ # A list of tuples (is_translateable, text) which represents the document
-+ # after chunking.
-+ self.chunks_ = []
-+
-+ # Start index of the last chunk, whether translateable or not
-+ self.chunk_start = 0
-+
-+ # Index of the last for-sure translateable character if we are parsing
-+ # a translateable chunk, -1 to indicate we are not in a translateable chunk.
-+ # This is needed so that we don't include trailing whitespace in the
-+ # translateable chunk (whitespace is neutral).
-+ self.last_translateable = -1
-+
-+ # Index of the last for-sure nontranslateable character if we are parsing
-+ # a nontranslateable chunk, -1 if we are not in a nontranslateable chunk.
-+ # This is needed to make sure we can group e.g. "<b>Hello</b> there"
-+ # together instead of just "Hello</b> there" which would be much worse
-+ # for translation.
-+ self.last_nontranslateable = -1
-+
-+ # Index of the character we're currently looking at.
-+ self.current = 0
-+
-+ # The name of the last block element parsed.
-+ self.last_element_ = ''
-+
-+ # The last explicit description we found.
-+ self.last_description = ''
-+
-+ # Whether no-break was the last chunk seen
-+ self.last_nobreak = False
-+
-+ while self.current < len(self.text_):
-+ _DebugPrint('REST: %s' % self.text_[self.current:self.current+60])
-+
-+ m = _MESSAGE_NO_BREAK_COMMENT.match(self.Rest())
-+ if m:
-+ self.AdvancePast(m)
-+ self.last_nobreak = True
-+ continue
-+
-+ # Try to match whitespace
-+ m = _WHITESPACE.match(self.Rest())
-+ if m:
-+ # Whitespace is neutral, it just advances 'current' and does not switch
-+ # between translateable/nontranslateable. If we are in a
-+ # nontranslateable section that extends to the current point, we extend
-+ # it to include the whitespace. If we are in a translateable section,
-+ # we do not extend it until we find
-+ # more translateable parts, because we never want a translateable chunk
-+ # to end with whitespace.
-+ if (not self.InTranslateable() and
-+ self.last_nontranslateable == self.current - 1):
-+ self.last_nontranslateable = self.current + m.end() - 1
-+ self.AdvancePast(m)
-+ continue
-+
-+ # Then we try to match nontranslateables
-+ m = _NONTRANSLATEABLES.match(self.Rest())
-+ if m:
-+ if self.InTranslateable():
-+ self.EndTranslateable()
-+ self.last_nontranslateable = self.current + m.end() - 1
-+ self.AdvancePast(m)
-+ continue
-+
-+ # Now match all other HTML element tags (opening, closing, or empty, we
-+ # don't care).
-+ m = _ELEMENT.match(self.Rest())
-+ if m:
-+ element_name = m.group('element').lower()
-+ if element_name in _BLOCK_TAGS:
-+ self.last_element_ = element_name
-+ if self.InTranslateable():
-+ if self.last_nobreak:
-+ self.last_nobreak = False
-+ else:
-+ self.EndTranslateable()
-+
-+ # Check for "special" elements, i.e. ones that have a translateable
-+ # attribute, and handle them correctly. Note that all of the
-+ # "special" elements are block tags, so no need to check for this
-+ # if the tag is not a block tag.
-+ sm = _SPECIAL_ELEMENT.match(self.Rest())
-+ if sm:
-+ # Get the appropriate group name
-+ for group in sm.groupdict():
-+ if sm.groupdict()[group]:
-+ break
-+
-+ # First make a nontranslateable chunk up to and including the
-+ # quote before the translateable attribute value
-+ self.AddChunk(False, self.text_[
-+ self.chunk_start : self.current + sm.start(group)])
-+ # Then a translateable for the translateable bit
-+ self.AddChunk(True, self.Rest()[sm.start(group) : sm.end(group)])
-+ # Finally correct the data invariant for the parser
-+ self.chunk_start = self.current + sm.end(group)
-+
-+ self.last_nontranslateable = self.current + m.end() - 1
-+ elif self.InTranslateable():
-+ # We're in a translateable and the tag is an inline tag, so we
-+ # need to include it in the translateable.
-+ self.last_translateable = self.current + m.end() - 1
-+ self.AdvancePast(m)
-+ continue
-+
-+ # Anything else we find must be translateable, so we advance one character
-+ # at a time until one of the above matches.
-+ if not self.InTranslateable():
-+ self.StartTranslateable()
-+ else:
-+ self.last_translateable = self.current
-+ self.current += 1
-+
-+ # Close the final chunk
-+ if self.InTranslateable():
-+ self.AddChunk(True, self.text_[self.chunk_start : ])
-+ else:
-+ self.AddChunk(False, self.text_[self.chunk_start : ])
-+
-+ return self.chunks_
-+
-+
-+def HtmlToMessage(html, include_block_tags=False, description=''):
-+ '''Takes a bit of HTML, which must contain only "inline" HTML elements,
-+ and changes it into a tclib.Message. This involves escaping any entities and
-+ replacing any HTML code with placeholders.
-+
-+ If include_block_tags is true, no error will be given if block tags (e.g.
-+ <p> or <br>) are included in the HTML.
-+
-+ Args:
-+ html: 'Hello <b>[USERNAME]</b>, how&nbsp;<i>are</i> you?'
-+ include_block_tags: False
-+
-+ Return:
-+ tclib.Message('Hello START_BOLD1USERNAMEEND_BOLD, '
-+ 'howNBSPSTART_ITALICareEND_ITALIC you?',
-+ [ Placeholder('START_BOLD', '<b>', ''),
-+ Placeholder('USERNAME', '[USERNAME]', ''),
-+ Placeholder('END_BOLD', '</b>', ''),
-+ Placeholder('START_ITALIC', '<i>', ''),
-+ Placeholder('END_ITALIC', '</i>', ''), ])
-+ '''
-+ # Approach is:
-+ # - first placeholderize, finding <elements>, [REPLACEABLES] and &nbsp;
-+ # - then escape all character entities in text in-between placeholders
-+
-+ parts = [] # List of strings (for text chunks) and tuples (ID, original)
-+ # for placeholders
-+
-+ count_names = {} # Map of base names to number of times used
-+ end_names = {} # Map of base names to stack of end tags (for correct nesting)
-+
-+ def MakeNameClosure(base, type = ''):
-+ '''Returns a closure that can be called once all names have been allocated
-+ to return the final name of the placeholder. This allows us to minimally
-+ number placeholders for non-overlap.
-+
-+ Also ensures that END_XXX_Y placeholders have the same Y as the
-+ corresponding BEGIN_XXX_Y placeholder when we have nested tags of the same
-+ type.
-+
-+ Args:
-+ base: 'phname'
-+ type: '' | 'begin' | 'end'
-+
-+ Return:
-+ Closure()
-+ '''
-+ name = base.upper()
-+ if type != '':
-+ name = ('%s_%s' % (type, base)).upper()
-+
-+ count_names.setdefault(name, 0)
-+ count_names[name] += 1
-+
-+ def MakeFinalName(name_ = name, index = count_names[name] - 1):
-+ if type.lower() == 'end' and end_names.get(base):
-+ return end_names[base].pop(-1) # For correct nesting
-+ if count_names[name_] != 1:
-+ name_ = '%s_%s' % (name_, _SUFFIXES[index])
-+ # We need to use a stack to ensure that the end-tag suffixes match
-+ # the begin-tag suffixes. Only needed when more than one tag of the
-+ # same type.
-+ if type == 'begin':
-+ end_name = ('END_%s_%s' % (base, _SUFFIXES[index])).upper()
-+ if base in end_names:
-+ end_names[base].append(end_name)
-+ else:
-+ end_names[base] = [end_name]
-+
-+ return name_
-+
-+ return MakeFinalName
-+
-+ current = 0
-+ last_nobreak = False
-+
-+ while current < len(html):
-+ m = _MESSAGE_NO_BREAK_COMMENT.match(html[current:])
-+ if m:
-+ last_nobreak = True
-+ current += m.end()
-+ continue
-+
-+ m = _NBSP.match(html[current:])
-+ if m:
-+ parts.append((MakeNameClosure('SPACE'), m.group()))
-+ current += m.end()
-+ continue
-+
-+ m = _REPLACEABLE.match(html[current:])
-+ if m:
-+ # Replaceables allow - but placeholders don't, so replace - with _
-+ ph_name = MakeNameClosure('X_%s_X' % m.group('name').replace('-', '_'))
-+ parts.append((ph_name, m.group()))
-+ current += m.end()
-+ continue
-+
-+ m = _SPECIAL_ELEMENT.match(html[current:])
-+ if m:
-+ if not include_block_tags:
-+ if last_nobreak:
-+ last_nobreak = False
-+ else:
-+ raise exception.BlockTagInTranslateableChunk(html)
-+ element_name = 'block' # for simplification
-+ # Get the appropriate group name
-+ for group in m.groupdict():
-+ if m.groupdict()[group]:
-+ break
-+ parts.append((MakeNameClosure(element_name, 'begin'),
-+ html[current : current + m.start(group)]))
-+ parts.append(m.group(group))
-+ parts.append((MakeNameClosure(element_name, 'end'),
-+ html[current + m.end(group) : current + m.end()]))
-+ current += m.end()
-+ continue
-+
-+ m = _ELEMENT.match(html[current:])
-+ if m:
-+ element_name = m.group('element').lower()
-+ if not include_block_tags and not element_name in _INLINE_TAGS:
-+ if last_nobreak:
-+ last_nobreak = False
-+ else:
-+ raise exception.BlockTagInTranslateableChunk(html[current:])
-+ if element_name in _HTML_PLACEHOLDER_NAMES: # use meaningful names
-+ element_name = _HTML_PLACEHOLDER_NAMES[element_name]
-+
-+ # Make a name for the placeholder
-+ type = ''
-+ if not m.group('empty'):
-+ if m.group('closing'):
-+ type = 'end'
-+ else:
-+ type = 'begin'
-+ parts.append((MakeNameClosure(element_name, type), m.group()))
-+ current += m.end()
-+ continue
-+
-+ if len(parts) and isinstance(parts[-1], six.string_types):
-+ parts[-1] += html[current]
-+ else:
-+ parts.append(html[current])
-+ current += 1
-+
-+ msg_text = ''
-+ placeholders = []
-+ for part in parts:
-+ if isinstance(part, tuple):
-+ final_name = part[0]()
-+ original = part[1]
-+ msg_text += final_name
-+ placeholders.append(tclib.Placeholder(final_name, original, '(HTML code)'))
-+ else:
-+ msg_text += part
-+
-+ msg = tclib.Message(text=msg_text, placeholders=placeholders,
-+ description=description)
-+ content = msg.GetContent()
-+ for ix in range(len(content)):
-+ if isinstance(content[ix], six.string_types):
-+ content[ix] = util.UnescapeHtml(content[ix], replace_nbsp=False)
-+
-+ return msg
-+
-+
-+class TrHtml(interface.GathererBase):
-+ '''Represents a document or message in the template format used by
-+ Total Recall for HTML documents.'''
-+
-+ def __init__(self, *args, **kwargs):
-+ super(TrHtml, self).__init__(*args, **kwargs)
-+ self.have_parsed_ = False
-+ self.skeleton_ = [] # list of strings and MessageClique objects
-+ self.fold_whitespace_ = False
-+
-+ def SetAttributes(self, attrs):
-+ '''Sets node attributes used by the gatherer.
-+
-+ This checks the fold_whitespace attribute.
-+
-+ Args:
-+ attrs: The mapping of node attributes.
-+ '''
-+ self.fold_whitespace_ = ('fold_whitespace' in attrs and
-+ attrs['fold_whitespace'] == 'true')
-+
-+ def GetText(self):
-+ '''Returns the original text of the HTML document'''
-+ return self.text_
-+
-+ def GetTextualIds(self):
-+ return [self.extkey]
-+
-+ def GetCliques(self):
-+ '''Returns the message cliques for each translateable message in the
-+ document.'''
-+ return [x for x in self.skeleton_ if isinstance(x, clique.MessageClique)]
-+
-+ def Translate(self, lang, pseudo_if_not_available=True,
-+ skeleton_gatherer=None, fallback_to_english=False):
-+ '''Returns this document with translateable messages filled with
-+ the translation for language 'lang'.
-+
-+ Args:
-+ lang: 'en'
-+ pseudo_if_not_available: True
-+
-+ Return:
-+ 'ID_THIS_SECTION TYPE\n...BEGIN\n "Translated message"\n......\nEND
-+
-+ Raises:
-+ grit.exception.NotReady() if used before Parse() has been successfully
-+ called.
-+ grit.exception.NoSuchTranslation() if 'pseudo_if_not_available' is false
-+ and there is no translation for the requested language.
-+ '''
-+ if len(self.skeleton_) == 0:
-+ raise exception.NotReady()
-+
-+ # TODO(joi) Implement support for skeleton gatherers here.
-+
-+ out = []
-+ for item in self.skeleton_:
-+ if isinstance(item, six.string_types):
-+ out.append(item)
-+ else:
-+ msg = item.MessageForLanguage(lang,
-+ pseudo_if_not_available,
-+ fallback_to_english)
-+ for content in msg.GetContent():
-+ if isinstance(content, tclib.Placeholder):
-+ out.append(content.GetOriginal())
-+ else:
-+ # We escape " characters to increase the chance that attributes
-+ # will be properly escaped.
-+ out.append(util.EscapeHtml(content, True))
-+
-+ return ''.join(out)
-+
-+ def Parse(self):
-+ if self.have_parsed_:
-+ return
-+ self.have_parsed_ = True
-+
-+ text = self._LoadInputFile()
-+
-+ # Ignore the BOM character if the document starts with one.
-+ if text.startswith(u'\ufeff'):
-+ text = text[1:]
-+
-+ self.text_ = text
-+
-+ # Parsing is done in two phases: First, we break the document into
-+ # translateable and nontranslateable chunks. Second, we run through each
-+ # translateable chunk and insert placeholders for any HTML elements,
-+ # unescape escaped characters, etc.
-+
-+ # First handle the silly little [!]-prefixed header because it's not
-+ # handled by our HTML parsers.
-+ m = _SILLY_HEADER.match(text)
-+ if m:
-+ self.skeleton_.append(text[:m.start('title')])
-+ self.skeleton_.append(self.uberclique.MakeClique(
-+ tclib.Message(text=text[m.start('title'):m.end('title')])))
-+ self.skeleton_.append(text[m.end('title') : m.end()])
-+ text = text[m.end():]
-+
-+ chunks = HtmlChunks().Parse(text, self.fold_whitespace_)
-+
-+ for chunk in chunks:
-+ if chunk[0]: # Chunk is translateable
-+ self.skeleton_.append(self.uberclique.MakeClique(
-+ HtmlToMessage(chunk[1], description=chunk[2])))
-+ else:
-+ self.skeleton_.append(chunk[1])
-+
-+ # Go through the skeleton and change any messages that consist solely of
-+ # placeholders and whitespace into nontranslateable strings.
-+ for ix in range(len(self.skeleton_)):
-+ got_text = False
-+ if isinstance(self.skeleton_[ix], clique.MessageClique):
-+ msg = self.skeleton_[ix].GetMessage()
-+ for item in msg.GetContent():
-+ if (isinstance(item, six.string_types)
-+ and _NON_WHITESPACE.search(item) and item != '&nbsp;'):
-+ got_text = True
-+ break
-+ if not got_text:
-+ self.skeleton_[ix] = msg.GetRealContent()
-+
-+ def SubstituteMessages(self, substituter):
-+ '''Applies substitutions to all messages in the tree.
-+
-+ Goes through the skeleton and finds all MessageCliques.
-+
-+ Args:
-+ substituter: a grit.util.Substituter object.
-+ '''
-+ new_skel = []
-+ for chunk in self.skeleton_:
-+ if isinstance(chunk, clique.MessageClique):
-+ old_message = chunk.GetMessage()
-+ new_message = substituter.SubstituteMessage(old_message)
-+ if new_message is not old_message:
-+ new_skel.append(self.uberclique.MakeClique(new_message))
-+ continue
-+ new_skel.append(chunk)
-+ self.skeleton_ = new_skel
-diff --git a/tools/grit/grit/gather/tr_html_unittest.py b/tools/grit/grit/gather/tr_html_unittest.py
-new file mode 100644
-index 0000000000..1194853d9a
---- /dev/null
-+++ b/tools/grit/grit/gather/tr_html_unittest.py
-@@ -0,0 +1,524 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for grit.gather.tr_html'''
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+import unittest
-+
-+import six
-+from six import StringIO
-+
-+from grit.gather import tr_html
-+from grit import clique
-+from grit import util
-+
-+
-+class ParserUnittest(unittest.TestCase):
-+ def testChunkingWithoutFoldWhitespace(self):
-+ self.VerifyChunking(False)
-+
-+ def testChunkingWithFoldWhitespace(self):
-+ self.VerifyChunking(True)
-+
-+ def VerifyChunking(self, fold_whitespace):
-+ """Use a single function to run all chunking testing.
-+
-+ This makes it easier to run chunking with fold_whitespace both on and off,
-+ to make sure the outputs are the same.
-+
-+ Args:
-+ fold_whitespace: Whether whitespace sequences should be folded into a
-+ single space.
-+ """
-+ self.VerifyChunkingBasic(fold_whitespace)
-+ self.VerifyChunkingDescriptions(fold_whitespace)
-+ self.VerifyChunkingReplaceables(fold_whitespace)
-+ self.VerifyChunkingLineBreaks(fold_whitespace)
-+ self.VerifyChunkingMessageBreak(fold_whitespace)
-+ self.VerifyChunkingMessageNoBreak(fold_whitespace)
-+
-+ def VerifyChunkingBasic(self, fold_whitespace):
-+ p = tr_html.HtmlChunks()
-+ chunks = p.Parse('<p>Hello <b>dear</b> how <i>are</i>you?<p>Fine!',
-+ fold_whitespace)
-+ self.failUnlessEqual(chunks, [
-+ (False, '<p>', ''), (True, 'Hello <b>dear</b> how <i>are</i>you?', ''),
-+ (False, '<p>', ''), (True, 'Fine!', '')])
-+
-+ chunks = p.Parse('<p> Hello <b>dear</b> how <i>are</i>you? <p>Fine!',
-+ fold_whitespace)
-+ self.failUnlessEqual(chunks, [
-+ (False, '<p> ', ''), (True, 'Hello <b>dear</b> how <i>are</i>you?', ''),
-+ (False, ' <p>', ''), (True, 'Fine!', '')])
-+
-+ chunks = p.Parse('<p> Hello <b>dear how <i>are you? <p> Fine!',
-+ fold_whitespace)
-+ self.failUnlessEqual(chunks, [
-+ (False, '<p> ', ''), (True, 'Hello <b>dear how <i>are you?', ''),
-+ (False, ' <p> ', ''), (True, 'Fine!', '')])
-+
-+ # Ensure translateable sections that start with inline tags contain
-+ # the starting inline tag.
-+ chunks = p.Parse('<b>Hello!</b> how are you?<p><i>I am fine.</i>',
-+ fold_whitespace)
-+ self.failUnlessEqual(chunks, [
-+ (True, '<b>Hello!</b> how are you?', ''), (False, '<p>', ''),
-+ (True, '<i>I am fine.</i>', '')])
-+
-+ # Ensure translateable sections that end with inline tags contain
-+ # the ending inline tag.
-+ chunks = p.Parse("Hello! How are <b>you?</b><p><i>I'm fine!</i>",
-+ fold_whitespace)
-+ self.failUnlessEqual(chunks, [
-+ (True, 'Hello! How are <b>you?</b>', ''), (False, '<p>', ''),
-+ (True, "<i>I'm fine!</i>", '')])
-+
-+ def VerifyChunkingDescriptions(self, fold_whitespace):
-+ p = tr_html.HtmlChunks()
-+ # Check capitals and explicit descriptions
-+ chunks = p.Parse('<!-- desc=bingo! --><B>Hello!</B> how are you?<P>'
-+ '<I>I am fine.</I>', fold_whitespace)
-+ self.failUnlessEqual(chunks, [
-+ (True, '<B>Hello!</B> how are you?', 'bingo!'), (False, '<P>', ''),
-+ (True, '<I>I am fine.</I>', '')])
-+ chunks = p.Parse('<B><!-- desc=bingo! -->Hello!</B> how are you?<P>'
-+ '<I>I am fine.</I>', fold_whitespace)
-+ self.failUnlessEqual(chunks, [
-+ (True, '<B>Hello!</B> how are you?', 'bingo!'), (False, '<P>', ''),
-+ (True, '<I>I am fine.</I>', '')])
-+ # Linebreaks get handled by the tclib message.
-+ chunks = p.Parse('<B>Hello!</B> <!-- desc=bi\nngo\n! -->how are you?<P>'
-+ '<I>I am fine.</I>', fold_whitespace)
-+ self.failUnlessEqual(chunks, [
-+ (True, '<B>Hello!</B> how are you?', 'bi\nngo\n!'), (False, '<P>', ''),
-+ (True, '<I>I am fine.</I>', '')])
-+
-+ # In this case, because the explicit description appears after the first
-+ # translateable, it will actually apply to the second translateable.
-+ chunks = p.Parse('<B>Hello!</B> how are you?<!-- desc=bingo! --><P>'
-+ '<I>I am fine.</I>', fold_whitespace)
-+ self.failUnlessEqual(chunks, [
-+ (True, '<B>Hello!</B> how are you?', ''), (False, '<P>', ''),
-+ (True, '<I>I am fine.</I>', 'bingo!')])
-+
-+ def VerifyChunkingReplaceables(self, fold_whitespace):
-+ # Check that replaceables within block tags (where attributes would go) are
-+ # handled correctly.
-+ p = tr_html.HtmlChunks()
-+ chunks = p.Parse('<b>Hello!</b> how are you?<p [BINGO] [$~BONGO~$]>'
-+ '<i>I am fine.</i>', fold_whitespace)
-+ self.failUnlessEqual(chunks, [
-+ (True, '<b>Hello!</b> how are you?', ''),
-+ (False, '<p [BINGO] [$~BONGO~$]>', ''),
-+ (True, '<i>I am fine.</i>', '')])
-+
-+ def VerifyChunkingLineBreaks(self, fold_whitespace):
-+ # Check that the contents of preformatted tags preserve line breaks.
-+ p = tr_html.HtmlChunks()
-+ chunks = p.Parse('<textarea>Hello\nthere\nhow\nare\nyou?</textarea>',
-+ fold_whitespace)
-+ self.failUnlessEqual(chunks, [(False, '<textarea>', ''),
-+ (True, 'Hello\nthere\nhow\nare\nyou?', ''), (False, '</textarea>', '')])
-+
-+ # ...and that other tags' line breaks are converted to spaces
-+ chunks = p.Parse('<p>Hello\nthere\nhow\nare\nyou?</p>', fold_whitespace)
-+ self.failUnlessEqual(chunks, [(False, '<p>', ''),
-+ (True, 'Hello there how are you?', ''), (False, '</p>', '')])
-+
-+ def VerifyChunkingMessageBreak(self, fold_whitespace):
-+ p = tr_html.HtmlChunks()
-+ # Make sure that message-break comments work properly.
-+ chunks = p.Parse('Break<!-- message-break --> apart '
-+ '<!--message-break-->messages', fold_whitespace)
-+ self.failUnlessEqual(chunks, [(True, 'Break', ''),
-+ (False, ' ', ''),
-+ (True, 'apart', ''),
-+ (False, ' ', ''),
-+ (True, 'messages', '')])
-+
-+ # Make sure message-break comments work in an inline tag.
-+ chunks = p.Parse('<a href=\'google.com\'><!-- message-break -->Google'
-+ '<!--message-break--></a>', fold_whitespace)
-+ self.failUnlessEqual(chunks, [(False, '<a href=\'google.com\'>', ''),
-+ (True, 'Google', ''),
-+ (False, '</a>', '')])
-+
-+ def VerifyChunkingMessageNoBreak(self, fold_whitespace):
-+ p = tr_html.HtmlChunks()
-+ # Make sure that message-no-break comments work properly.
-+ chunks = p.Parse('Please <!-- message-no-break --> <br />don\'t break',
-+ fold_whitespace)
-+ self.failUnlessEqual(chunks, [(True, 'Please <!-- message-no-break --> '
-+ '<br />don\'t break', '')])
-+
-+ chunks = p.Parse('Please <br /> break. <!-- message-no-break --> <br /> '
-+ 'But not this time.', fold_whitespace)
-+ self.failUnlessEqual(chunks, [(True, 'Please', ''),
-+ (False, ' <br /> ', ''),
-+ (True, 'break. <!-- message-no-break --> '
-+ '<br /> But not this time.', '')])
-+
-+ def testTranslateableAttributes(self):
-+ p = tr_html.HtmlChunks()
-+
-+ # Check that the translateable attributes in <img>, <submit>, <button> and
-+ # <text> elements buttons are handled correctly.
-+ chunks = p.Parse('<img src=bingo.jpg alt="hello there">'
-+ '<input type=submit value="hello">'
-+ '<input type="button" value="hello">'
-+ '<input type=\'text\' value=\'Howdie\'>', False)
-+ self.failUnlessEqual(chunks, [
-+ (False, '<img src=bingo.jpg alt="', ''), (True, 'hello there', ''),
-+ (False, '"><input type=submit value="', ''), (True, 'hello', ''),
-+ (False, '"><input type="button" value="', ''), (True, 'hello', ''),
-+ (False, '"><input type=\'text\' value=\'', ''), (True, 'Howdie', ''),
-+ (False, '\'>', '')])
-+
-+
-+ def testTranslateableHtmlToMessage(self):
-+ msg = tr_html.HtmlToMessage(
-+ 'Hello <b>[USERNAME]</b>, &lt;how&gt;&nbsp;<i>are</i> you?')
-+ pres = msg.GetPresentableContent()
-+ self.failUnless(pres ==
-+ 'Hello BEGIN_BOLDX_USERNAME_XEND_BOLD, '
-+ '<how>&nbsp;BEGIN_ITALICareEND_ITALIC you?')
-+
-+ msg = tr_html.HtmlToMessage('<b>Hello</b><I>Hello</I><b>Hello</b>')
-+ pres = msg.GetPresentableContent()
-+ self.failUnless(pres ==
-+ 'BEGIN_BOLD_1HelloEND_BOLD_1BEGIN_ITALICHelloEND_ITALIC'
-+ 'BEGIN_BOLD_2HelloEND_BOLD_2')
-+
-+ # Check that nesting (of the <font> tags) is handled correctly - i.e. that
-+ # the closing placeholder numbers match the opening placeholders.
-+ msg = tr_html.HtmlToMessage(
-+ '''<font size=-1><font color=#FF0000>Update!</font> '''
-+ '''<a href='http://desktop.google.com/whatsnew.html?hl=[$~LANG~$]'>'''
-+ '''New Features</a>: Now search PDFs, MP3s, Firefox web history, and '''
-+ '''more</font>''')
-+ pres = msg.GetPresentableContent()
-+ self.failUnless(pres ==
-+ 'BEGIN_FONT_1BEGIN_FONT_2Update!END_FONT_2 BEGIN_LINK'
-+ 'New FeaturesEND_LINK: Now search PDFs, MP3s, Firefox '
-+ 'web history, and moreEND_FONT_1')
-+
-+ msg = tr_html.HtmlToMessage('''<a href='[$~URL~$]'><b>[NUM][CAT]</b></a>''')
-+ pres = msg.GetPresentableContent()
-+ self.failUnless(pres == 'BEGIN_LINKBEGIN_BOLDX_NUM_XX_CAT_XEND_BOLDEND_LINK')
-+
-+ msg = tr_html.HtmlToMessage(
-+ '''<font size=-1><a class=q onClick='return window.qs?qs(this):1' '''
-+ '''href='http://[WEBSERVER][SEARCH_URI]'>Desktop</a></font>&nbsp;&nbsp;'''
-+ '''&nbsp;&nbsp;''')
-+ pres = msg.GetPresentableContent()
-+ self.failUnless(pres ==
-+ '''BEGIN_FONTBEGIN_LINKDesktopEND_LINKEND_FONTSPACE''')
-+
-+ msg = tr_html.HtmlToMessage(
-+ '''<br><br><center><font size=-2>&copy;2005 Google </font></center>''', 1)
-+ pres = msg.GetPresentableContent()
-+ self.failUnless(pres ==
-+ u'BEGIN_BREAK_1BEGIN_BREAK_2BEGIN_CENTERBEGIN_FONT\xa92005'
-+ u' Google END_FONTEND_CENTER')
-+
-+ msg = tr_html.HtmlToMessage(
-+ '''&nbsp;-&nbsp;<a class=c href=[$~CACHE~$]>Cached</a>''')
-+ pres = msg.GetPresentableContent()
-+ self.failUnless(pres ==
-+ '&nbsp;-&nbsp;BEGIN_LINKCachedEND_LINK')
-+
-+ # Check that upper-case tags are handled correctly.
-+ msg = tr_html.HtmlToMessage(
-+ '''You can read the <A HREF='http://desktop.google.com/privacypolicy.'''
-+ '''html?hl=[LANG_CODE]'>Privacy Policy</A> and <A HREF='http://desktop'''
-+ '''.google.com/privacyfaq.html?hl=[LANG_CODE]'>Privacy FAQ</A> online.''')
-+ pres = msg.GetPresentableContent()
-+ self.failUnless(pres ==
-+ 'You can read the BEGIN_LINK_1Privacy PolicyEND_LINK_1 and '
-+ 'BEGIN_LINK_2Privacy FAQEND_LINK_2 online.')
-+
-+ # Check that tags with linebreaks immediately preceding them are handled
-+ # correctly.
-+ msg = tr_html.HtmlToMessage(
-+ '''You can read the
-+<A HREF='http://desktop.google.com/privacypolicy.html?hl=[LANG_CODE]'>Privacy Policy</A>
-+and <A HREF='http://desktop.google.com/privacyfaq.html?hl=[LANG_CODE]'>Privacy FAQ</A> online.''')
-+ pres = msg.GetPresentableContent()
-+ self.failUnless(pres == '''You can read the
-+BEGIN_LINK_1Privacy PolicyEND_LINK_1
-+and BEGIN_LINK_2Privacy FAQEND_LINK_2 online.''')
-+
-+ # Check that message-no-break comments are handled correctly.
-+ msg = tr_html.HtmlToMessage('''Please <!-- message-no-break --><br /> don't break''')
-+ pres = msg.GetPresentableContent()
-+ self.failUnlessEqual(pres, '''Please BREAK don't break''')
-+
-+class TrHtmlUnittest(unittest.TestCase):
-+ def testSetAttributes(self):
-+ html = tr_html.TrHtml(StringIO(''))
-+ self.failUnlessEqual(html.fold_whitespace_, False)
-+ html.SetAttributes({})
-+ self.failUnlessEqual(html.fold_whitespace_, False)
-+ html.SetAttributes({'fold_whitespace': 'false'})
-+ self.failUnlessEqual(html.fold_whitespace_, False)
-+ html.SetAttributes({'fold_whitespace': 'true'})
-+ self.failUnlessEqual(html.fold_whitespace_, True)
-+
-+ def testFoldWhitespace(self):
-+ text = '<td> Test Message </td>'
-+
-+ html = tr_html.TrHtml(StringIO(text))
-+ html.Parse()
-+ self.failUnlessEqual(html.skeleton_[1].GetMessage().GetPresentableContent(),
-+ 'Test Message')
-+
-+ html = tr_html.TrHtml(StringIO(text))
-+ html.fold_whitespace_ = True
-+ html.Parse()
-+ self.failUnlessEqual(html.skeleton_[1].GetMessage().GetPresentableContent(),
-+ 'Test Message')
-+
-+ def testTable(self):
-+ html = tr_html.TrHtml(StringIO('''<table class="shaded-header"><tr>
-+<td class="header-element b expand">Preferences</td>
-+<td class="header-element s">
-+<a href="http://desktop.google.com/preferences.html">Preferences&nbsp;Help</a>
-+</td>
-+</tr></table>'''))
-+ html.Parse()
-+ self.failUnless(html.skeleton_[3].GetMessage().GetPresentableContent() ==
-+ 'BEGIN_LINKPreferences&nbsp;HelpEND_LINK')
-+
-+ def testSubmitAttribute(self):
-+ html = tr_html.TrHtml(StringIO('''</td>
-+<td class="header-element"><input type=submit value="Save Preferences"
-+name=submit2></td>
-+</tr></table>'''))
-+ html.Parse()
-+ self.failUnless(html.skeleton_[1].GetMessage().GetPresentableContent() ==
-+ 'Save Preferences')
-+
-+ def testWhitespaceAfterInlineTag(self):
-+ '''Test that even if there is whitespace after an inline tag at the start
-+ of a translateable section the inline tag will be included.
-+ '''
-+ html = tr_html.TrHtml(
-+ StringIO('''<label for=DISPLAYNONE><font size=-1> Hello</font>'''))
-+ html.Parse()
-+ self.failUnless(html.skeleton_[1].GetMessage().GetRealContent() ==
-+ '<font size=-1> Hello</font>')
-+
-+ def testSillyHeader(self):
-+ html = tr_html.TrHtml(StringIO('''[!]
-+title\tHello
-+bingo
-+bongo
-+bla
-+
-+<p>Other stuff</p>'''))
-+ html.Parse()
-+ content = html.skeleton_[1].GetMessage().GetRealContent()
-+ self.failUnless(content == 'Hello')
-+ self.failUnless(html.skeleton_[-1] == '</p>')
-+ # Right after the translateable the nontranslateable should start with
-+ # a linebreak (this catches a bug we had).
-+ self.failUnless(html.skeleton_[2][0] == '\n')
-+
-+
-+ def testExplicitDescriptions(self):
-+ html = tr_html.TrHtml(
-+ StringIO('Hello [USER]<br/><!-- desc=explicit -->'
-+ '<input type="button">Go!</input>'))
-+ html.Parse()
-+ msg = html.GetCliques()[1].GetMessage()
-+ self.failUnlessEqual(msg.GetDescription(), 'explicit')
-+ self.failUnlessEqual(msg.GetRealContent(), 'Go!')
-+
-+ html = tr_html.TrHtml(
-+ StringIO('Hello [USER]<br/><!-- desc=explicit\nmultiline -->'
-+ '<input type="button">Go!</input>'))
-+ html.Parse()
-+ msg = html.GetCliques()[1].GetMessage()
-+ self.failUnlessEqual(msg.GetDescription(), 'explicit multiline')
-+ self.failUnlessEqual(msg.GetRealContent(), 'Go!')
-+
-+
-+ def testRegressionInToolbarAbout(self):
-+ html = tr_html.TrHtml(util.PathFromRoot(r'grit/testdata/toolbar_about.html'))
-+ html.Parse()
-+ cliques = html.GetCliques()
-+ for cl in cliques:
-+ content = cl.GetMessage().GetRealContent()
-+ if content.count('De parvis grandis acervus erit'):
-+ self.failIf(content.count('$/translate'))
-+
-+
-+ def HtmlFromFileWithManualCheck(self, f):
-+ html = tr_html.TrHtml(f)
-+ html.Parse()
-+
-+ # For manual results inspection only...
-+ list = []
-+ for item in html.skeleton_:
-+ if isinstance(item, six.string_types):
-+ list.append(item)
-+ else:
-+ list.append(item.GetMessage().GetPresentableContent())
-+
-+ return html
-+
-+
-+ def testPrivacyHtml(self):
-+ html = self.HtmlFromFileWithManualCheck(
-+ util.PathFromRoot(r'grit/testdata/privacy.html'))
-+
-+ self.failUnless(html.skeleton_[1].GetMessage().GetRealContent() ==
-+ 'Privacy and Google Desktop Search')
-+ self.failUnless(html.skeleton_[3].startswith('<'))
-+ self.failUnless(len(html.skeleton_) > 10)
-+
-+
-+ def testPreferencesHtml(self):
-+ html = self.HtmlFromFileWithManualCheck(
-+ util.PathFromRoot(r'grit/testdata/preferences.html'))
-+
-+ # Verify that we don't get '[STATUS-MESSAGE]' as the original content of
-+ # one of the MessageClique objects (it would be a placeholder-only message
-+ # and we're supposed to have stripped those).
-+
-+ for item in [x for x in html.skeleton_
-+ if isinstance(x, clique.MessageClique)]:
-+ if (item.GetMessage().GetRealContent() == '[STATUS-MESSAGE]' or
-+ item.GetMessage().GetRealContent() == '[ADDIN-DO] [ADDIN-OPTIONS]'):
-+ self.fail()
-+
-+ self.failUnless(len(html.skeleton_) > 100)
-+
-+ def AssertNumberOfTranslateables(self, files, num):
-+ '''Fails if any of the files in files don't have exactly
-+ num translateable sections.
-+
-+ Args:
-+ files: ['file1', 'file2']
-+ num: 3
-+ '''
-+ for f in files:
-+ f = util.PathFromRoot(r'grit/testdata/%s' % f)
-+ html = self.HtmlFromFileWithManualCheck(f)
-+ self.failUnless(len(html.GetCliques()) == num)
-+
-+ def testFewTranslateables(self):
-+ self.AssertNumberOfTranslateables(['browser.html', 'email_thread.html',
-+ 'header.html', 'mini.html',
-+ 'oneclick.html', 'script.html',
-+ 'time_related.html', 'versions.html'], 0)
-+ self.AssertNumberOfTranslateables(['footer.html', 'hover.html'], 1)
-+
-+ def testOtherHtmlFilesForManualInspection(self):
-+ files = [
-+ 'about.html', 'bad_browser.html', 'cache_prefix.html',
-+ 'cache_prefix_file.html', 'chat_result.html', 'del_footer.html',
-+ 'del_header.html', 'deleted.html', 'details.html', 'email_result.html',
-+ 'error.html', 'explicit_web.html', 'footer.html',
-+ 'homepage.html', 'indexing_speed.html',
-+ 'install_prefs.html', 'install_prefs2.html',
-+ 'oem_enable.html', 'oem_non_admin.html', 'onebox.html',
-+ 'password.html', 'quit_apps.html', 'recrawl.html',
-+ 'searchbox.html', 'sidebar_h.html', 'sidebar_v.html', 'status.html',
-+ ]
-+ for f in files:
-+ self.HtmlFromFileWithManualCheck(
-+ util.PathFromRoot(r'grit/testdata/%s' % f))
-+
-+ def testTranslate(self):
-+ # Note that the English translation of documents that use character
-+ # literals (e.g. &copy;) will not be the same as the original document
-+ # because the character literal will be transformed into the Unicode
-+ # character itself. So for this test we choose some relatively complex
-+ # HTML without character entities (but with &nbsp; because that's handled
-+ # specially).
-+ html = tr_html.TrHtml(StringIO(''' <script>
-+ <!--
-+ function checkOffice() { var w = document.getElementById("h7");
-+ var e = document.getElementById("h8"); var o = document.getElementById("h10");
-+ if (!(w.checked || e.checked)) { o.checked=0;o.disabled=1;} else {o.disabled=0;} }
-+ // -->
-+ </script>
-+ <input type=checkbox [CHECK-DOC] name=DOC id=h7 onclick='checkOffice()'>
-+ <label for=h7> Word</label><br>
-+ <input type=checkbox [CHECK-XLS] name=XLS id=h8 onclick='checkOffice()'>
-+ <label for=h8> Excel</label><br>
-+ <input type=checkbox [CHECK-PPT] name=PPT id=h9>
-+ <label for=h9> PowerPoint</label><br>
-+ </span></td><td nowrap valign=top><span class="s">
-+ <input type=checkbox [CHECK-PDF] name=PDF id=hpdf>
-+ <label for=hpdf> PDF</label><br>
-+ <input type=checkbox [CHECK-TXT] name=TXT id=h6>
-+ <label for=h6> Text, media, and other files</label><br>
-+ </tr>&nbsp;&nbsp;
-+ <tr><td nowrap valign=top colspan=3><span class="s"><br />
-+ <input type=checkbox [CHECK-SECUREOFFICE] name=SECUREOFFICE id=h10>
-+ <label for=h10> Password-protected Office documents (Word, Excel)</label><br />
-+ <input type=checkbox [DISABLED-HTTPS] [CHECK-HTTPS] name=HTTPS id=h12><label
-+ for=h12> Secure pages (HTTPS) in web history</label></span></td></tr>
-+ </table>'''))
-+ html.Parse()
-+ trans = html.Translate('en')
-+ if (html.GetText() != trans):
-+ self.fail()
-+
-+
-+ def testHtmlToMessageWithBlockTags(self):
-+ msg = tr_html.HtmlToMessage(
-+ 'Hello<p>Howdie<img alt="bingo" src="image.gif">', True)
-+ result = msg.GetPresentableContent()
-+ self.failUnless(
-+ result == 'HelloBEGIN_PARAGRAPHHowdieBEGIN_BLOCKbingoEND_BLOCK')
-+
-+ msg = tr_html.HtmlToMessage(
-+ 'Hello<p>Howdie<input type="button" value="bingo">', True)
-+ result = msg.GetPresentableContent()
-+ self.failUnless(
-+ result == 'HelloBEGIN_PARAGRAPHHowdieBEGIN_BLOCKbingoEND_BLOCK')
-+
-+
-+ def testHtmlToMessageRegressions(self):
-+ msg = tr_html.HtmlToMessage(' - ', True)
-+ result = msg.GetPresentableContent()
-+ self.failUnless(result == ' - ')
-+
-+
-+ def testEscapeUnescaped(self):
-+ text = '&copy;&nbsp; & &quot;&lt;hello&gt;&quot;'
-+ unescaped = util.UnescapeHtml(text)
-+ self.failUnless(unescaped == u'\u00a9\u00a0 & "<hello>"')
-+ escaped_unescaped = util.EscapeHtml(unescaped, True)
-+ self.failUnless(escaped_unescaped ==
-+ u'\u00a9\u00a0 &amp; &quot;&lt;hello&gt;&quot;')
-+
-+ def testRegressionCjkHtmlFile(self):
-+ # TODO(joi) Fix this problem where unquoted attributes that
-+ # have a value that is CJK characters causes the regular expression
-+ # match never to return. (culprit is the _ELEMENT regexp(
-+ if False:
-+ html = self.HtmlFromFileWithManualCheck(util.PathFromRoot(
-+ r'grit/testdata/ko_oem_enable_bug.html'))
-+ self.failUnless(True)
-+
-+ def testRegressionCpuHang(self):
-+ # If this regression occurs, the unit test will never return
-+ html = tr_html.TrHtml(StringIO(
-+ '''<input type=text size=12 id=advFileTypeEntry [~SHOW-FILETYPE-BOX~] value="[EXT]" name=ext>'''))
-+ html.Parse()
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/gather/txt.py b/tools/grit/grit/gather/txt.py
-new file mode 100644
-index 0000000000..e5c10abc28
---- /dev/null
-+++ b/tools/grit/grit/gather/txt.py
-@@ -0,0 +1,38 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Supports making amessage from a text file.
-+'''
-+
-+from __future__ import print_function
-+
-+from grit.gather import interface
-+from grit import tclib
-+
-+
-+class TxtFile(interface.GathererBase):
-+ '''A text file gatherer. Very simple, all text from the file becomes a
-+ single clique.
-+ '''
-+
-+ def Parse(self):
-+ self.text_ = self._LoadInputFile()
-+ self.clique_ = self.uberclique.MakeClique(tclib.Message(text=self.text_))
-+
-+ def GetText(self):
-+ '''Returns the text of what is being gathered.'''
-+ return self.text_
-+
-+ def GetTextualIds(self):
-+ return [self.extkey]
-+
-+ def GetCliques(self):
-+ '''Returns the MessageClique objects for all translateable portions.'''
-+ return [self.clique_]
-+
-+ def Translate(self, lang, pseudo_if_not_available=True,
-+ skeleton_gatherer=None, fallback_to_english=False):
-+ return self.clique_.MessageForLanguage(lang,
-+ pseudo_if_not_available,
-+ fallback_to_english).GetRealContent()
-diff --git a/tools/grit/grit/gather/txt_unittest.py b/tools/grit/grit/gather/txt_unittest.py
-new file mode 100644
-index 0000000000..abb9ed98d7
---- /dev/null
-+++ b/tools/grit/grit/gather/txt_unittest.py
-@@ -0,0 +1,35 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for TxtFile gatherer'''
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+
-+import unittest
-+
-+from six import StringIO
-+
-+from grit.gather import txt
-+
-+
-+class TxtUnittest(unittest.TestCase):
-+ def testGather(self):
-+ input = StringIO('Hello there\nHow are you?')
-+ gatherer = txt.TxtFile(input)
-+ gatherer.Parse()
-+ self.failUnless(gatherer.GetText() == input.getvalue())
-+ self.failUnless(len(gatherer.GetCliques()) == 1)
-+ self.failUnless(gatherer.GetCliques()[0].GetMessage().GetRealContent() ==
-+ input.getvalue())
-+
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/grd_reader.py b/tools/grit/grit/grd_reader.py
-new file mode 100644
-index 0000000000..b7bb782977
---- /dev/null
-+++ b/tools/grit/grit/grd_reader.py
-@@ -0,0 +1,238 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Class for reading GRD files into memory, without processing them.
-+'''
-+
-+from __future__ import print_function
-+
-+import os.path
-+import sys
-+import xml.sax
-+import xml.sax.handler
-+
-+import six
-+
-+from grit import exception
-+from grit import util
-+from grit.node import mapping
-+from grit.node import misc
-+
-+
-+class StopParsingException(Exception):
-+ '''An exception used to stop parsing.'''
-+ pass
-+
-+
-+class GrdContentHandler(xml.sax.handler.ContentHandler):
-+ def __init__(self, stop_after, debug, dir, defines, tags_to_ignore,
-+ target_platform, source):
-+ # Invariant of data:
-+ # 'root' is the root of the parse tree being created, or None if we haven't
-+ # parsed out any elements.
-+ # 'stack' is the a stack of elements that we push new nodes onto and
-+ # pop from when they finish parsing, or [] if we are not currently parsing.
-+ # 'stack[-1]' is the top of the stack.
-+ self.root = None
-+ self.stack = []
-+ self.stop_after = stop_after
-+ self.debug = debug
-+ self.dir = dir
-+ self.defines = defines
-+ self.tags_to_ignore = tags_to_ignore or set()
-+ self.ignore_depth = 0
-+ self.target_platform = target_platform
-+ self.source = source
-+
-+ def startElement(self, name, attrs):
-+ if self.ignore_depth or name in self.tags_to_ignore:
-+ if self.debug and self.ignore_depth == 0:
-+ print("Ignoring element %s and its children" % name)
-+ self.ignore_depth += 1
-+ return
-+
-+ if self.debug:
-+ attr_list = ' '.join('%s="%s"' % kv for kv in attrs.items())
-+ print("Starting parsing of element %s with attributes %r" %
-+ (name, attr_list or '(none)'))
-+
-+ typeattr = attrs.get('type')
-+ node = mapping.ElementToClass(name, typeattr)()
-+ node.source = self.source
-+
-+ if self.stack:
-+ self.stack[-1].AddChild(node)
-+ node.StartParsing(name, self.stack[-1])
-+ else:
-+ assert self.root is None
-+ self.root = node
-+ if isinstance(self.root, misc.GritNode):
-+ if self.target_platform:
-+ self.root.SetTargetPlatform(self.target_platform)
-+ node.StartParsing(name, None)
-+ if self.defines:
-+ node.SetDefines(self.defines)
-+ self.stack.append(node)
-+
-+ for attr, attrval in attrs.items():
-+ node.HandleAttribute(attr, attrval)
-+
-+ def endElement(self, name):
-+ if self.ignore_depth:
-+ self.ignore_depth -= 1
-+ return
-+
-+ if name == 'part':
-+ partnode = self.stack[-1]
-+ partnode.started_inclusion = True
-+ # Add the contents of the sub-grd file as children of the <part> node.
-+ partname = os.path.join(self.dir, partnode.GetInputPath())
-+ # Check the GRDP file exists.
-+ if not os.path.exists(partname):
-+ raise exception.FileNotFound(partname)
-+ # Exceptions propagate to the handler in grd_reader.Parse().
-+ oldsource = self.source
-+ try:
-+ self.source = partname
-+ xml.sax.parse(partname, GrdPartContentHandler(self))
-+ finally:
-+ self.source = oldsource
-+
-+ if self.debug:
-+ print("End parsing of element %s" % name)
-+ self.stack.pop().EndParsing()
-+
-+ if name == self.stop_after:
-+ raise StopParsingException()
-+
-+ def characters(self, content):
-+ if self.ignore_depth == 0:
-+ if self.stack[-1]:
-+ self.stack[-1].AppendContent(content)
-+
-+ def ignorableWhitespace(self, whitespace):
-+ # TODO(joi): This is not supported by expat. Should use a different XML
-+ # parser?
-+ pass
-+
-+
-+class GrdPartContentHandler(xml.sax.handler.ContentHandler):
-+ def __init__(self, parent):
-+ self.parent = parent
-+ self.depth = 0
-+
-+ def startElement(self, name, attrs):
-+ if self.depth:
-+ self.parent.startElement(name, attrs)
-+ else:
-+ if name != 'grit-part':
-+ raise exception.MissingElement("root tag must be <grit-part>")
-+ if attrs:
-+ raise exception.UnexpectedAttribute(
-+ "<grit-part> tag must not have attributes")
-+ self.depth += 1
-+
-+ def endElement(self, name):
-+ self.depth -= 1
-+ if self.depth:
-+ self.parent.endElement(name)
-+
-+ def characters(self, content):
-+ self.parent.characters(content)
-+
-+ def ignorableWhitespace(self, whitespace):
-+ self.parent.ignorableWhitespace(whitespace)
-+
-+
-+def Parse(filename_or_stream, dir=None, stop_after=None, first_ids_file=None,
-+ debug=False, defines=None, tags_to_ignore=None, target_platform=None,
-+ predetermined_ids_file=None):
-+ '''Parses a GRD file into a tree of nodes (from grit.node).
-+
-+ If filename_or_stream is a stream, 'dir' should point to the directory
-+ notionally containing the stream (this feature is only used in unit tests).
-+
-+ If 'stop_after' is provided, the parsing will stop once the first node
-+ with this name has been fully parsed (including all its contents).
-+
-+ If 'debug' is true, lots of information about the parsing events will be
-+ printed out during parsing of the file.
-+
-+ If 'first_ids_file' is non-empty, it is used to override the setting for the
-+ first_ids_file attribute of the <grit> root node. Note that the first_ids_file
-+ parameter should be relative to the cwd, even though the first_ids_file
-+ attribute of the <grit> node is relative to the grd file.
-+
-+ If 'target_platform' is set, this is used to determine the target
-+ platform of builds, instead of using |sys.platform|.
-+
-+ Args:
-+ filename_or_stream: './bla.xml'
-+ dir: None (if filename_or_stream is a filename) or '.'
-+ stop_after: 'inputs'
-+ first_ids_file: 'GRIT_DIR/../gritsettings/resource_ids'
-+ debug: False
-+ defines: dictionary of defines, like {'chromeos': '1'}
-+ target_platform: None or the value that would be returned by sys.platform
-+ on your target platform.
-+ predetermined_ids_file: File path to a file containing a pre-determined
-+ mapping from resource names to resource ids which will be used to assign
-+ resource ids to those resources.
-+
-+ Return:
-+ Subclass of grit.node.base.Node
-+
-+ Throws:
-+ grit.exception.Parsing
-+ '''
-+
-+ if isinstance(filename_or_stream, six.string_types):
-+ source = filename_or_stream
-+ if dir is None:
-+ dir = util.dirname(filename_or_stream)
-+ else:
-+ source = None
-+
-+ handler = GrdContentHandler(stop_after=stop_after, debug=debug, dir=dir,
-+ defines=defines, tags_to_ignore=tags_to_ignore,
-+ target_platform=target_platform, source=source)
-+ try:
-+ xml.sax.parse(filename_or_stream, handler)
-+ except StopParsingException:
-+ assert stop_after
-+ pass
-+ except:
-+ if not debug:
-+ print("parse exception: run GRIT with the -x flag to debug .grd problems")
-+ raise
-+
-+ if handler.root.name != 'grit':
-+ raise exception.MissingElement("root tag must be <grit>")
-+
-+ if hasattr(handler.root, 'SetOwnDir'):
-+ # Fix up the base_dir so it is relative to the input file.
-+ assert dir is not None
-+ handler.root.SetOwnDir(dir)
-+
-+ if isinstance(handler.root, misc.GritNode):
-+ handler.root.SetPredeterminedIdsFile(predetermined_ids_file)
-+ if first_ids_file:
-+ # Make the path to the first_ids_file relative to the grd file,
-+ # unless it begins with GRIT_DIR.
-+ GRIT_DIR_PREFIX = 'GRIT_DIR'
-+ if not (first_ids_file.startswith(GRIT_DIR_PREFIX)
-+ and first_ids_file[len(GRIT_DIR_PREFIX)] in ['/', '\\']):
-+ rel_dir = os.path.relpath(os.getcwd(), dir)
-+ first_ids_file = util.normpath(os.path.join(rel_dir, first_ids_file))
-+ handler.root.attrs['first_ids_file'] = first_ids_file
-+ # Assign first ids to the nodes that don't have them.
-+ handler.root.AssignFirstIds(filename_or_stream, defines)
-+
-+ return handler.root
-+
-+
-+if __name__ == '__main__':
-+ util.ChangeStdoutEncoding()
-+ print(six.text_type(Parse(sys.argv[1])))
-diff --git a/tools/grit/grit/grd_reader_unittest.py b/tools/grit/grit/grd_reader_unittest.py
-new file mode 100644
-index 0000000000..920a92f9c0
---- /dev/null
-+++ b/tools/grit/grit/grd_reader_unittest.py
-@@ -0,0 +1,346 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for grd_reader package'''
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
-+
-+import unittest
-+
-+import six
-+from six import StringIO
-+
-+from grit import exception
-+from grit import grd_reader
-+from grit import util
-+from grit.node import empty
-+from grit.node import message
-+
-+
-+class GrdReaderUnittest(unittest.TestCase):
-+ def testParsingAndXmlOutput(self):
-+ input = u'''<?xml version="1.0" encoding="UTF-8"?>
-+<grit base_dir="." current_release="3" latest_public_release="2" source_lang_id="en-US">
-+ <release seq="3">
-+ <includes>
-+ <include file="images/logo.gif" name="ID_LOGO" type="gif" />
-+ </includes>
-+ <messages>
-+ <if expr="True">
-+ <message desc="Printed to greet the currently logged in user" name="IDS_GREETING">
-+ Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, how are you doing today?
-+ </message>
-+ </if>
-+ </messages>
-+ <structures>
-+ <structure file="rc_files/dialogs.rc" name="IDD_NARROW_DIALOG" type="dialog">
-+ <skeleton expr="lang == 'fr-FR'" file="bla.rc" variant_of_revision="3" />
-+ </structure>
-+ <structure file="rc_files/version.rc" name="VS_VERSION_INFO" type="version" />
-+ </structures>
-+ </release>
-+ <translations>
-+ <file lang="nl" path="nl_translations.xtb" />
-+ </translations>
-+ <outputs>
-+ <output filename="resource.h" type="rc_header" />
-+ <output filename="resource.rc" lang="en-US" type="rc_all" />
-+ </outputs>
-+</grit>'''
-+ pseudo_file = StringIO(input)
-+ tree = grd_reader.Parse(pseudo_file, '.')
-+ output = six.text_type(tree)
-+ expected_output = input.replace(u' base_dir="."', u'')
-+ self.assertEqual(expected_output, output)
-+ self.failUnless(tree.GetNodeById('IDS_GREETING'))
-+
-+
-+ def testStopAfter(self):
-+ input = u'''<?xml version="1.0" encoding="UTF-8"?>
-+<grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
-+ <outputs>
-+ <output filename="resource.h" type="rc_header" />
-+ <output filename="resource.rc" lang="en-US" type="rc_all" />
-+ </outputs>
-+ <release seq="3">
-+ <includes>
-+ <include type="gif" name="ID_LOGO" file="images/logo.gif"/>
-+ </includes>
-+ </release>
-+</grit>'''
-+ pseudo_file = StringIO(input)
-+ tree = grd_reader.Parse(pseudo_file, '.', stop_after='outputs')
-+ # only an <outputs> child
-+ self.failUnless(len(tree.children) == 1)
-+ self.failUnless(tree.children[0].name == 'outputs')
-+
-+ def testLongLinesWithComments(self):
-+ input = u'''<?xml version="1.0" encoding="UTF-8"?>
-+<grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
-+ <release seq="3">
-+ <messages>
-+ <message name="IDS_GREETING" desc="Printed to greet the currently logged in user">
-+ This is a very long line with no linebreaks yes yes it stretches on <!--
-+ -->and on <!--
-+ -->and on!
-+ </message>
-+ </messages>
-+ </release>
-+</grit>'''
-+ pseudo_file = StringIO(input)
-+ tree = grd_reader.Parse(pseudo_file, '.')
-+
-+ greeting = tree.GetNodeById('IDS_GREETING')
-+ self.failUnless(greeting.GetCliques()[0].GetMessage().GetRealContent() ==
-+ 'This is a very long line with no linebreaks yes yes it '
-+ 'stretches on and on and on!')
-+
-+ def doTestAssignFirstIds(self, first_ids_path):
-+ input = u'''<?xml version="1.0" encoding="UTF-8"?>
-+<grit latest_public_release="2" source_lang_id="en-US" current_release="3"
-+ base_dir="." first_ids_file="%s">
-+ <release seq="3">
-+ <messages>
-+ <message name="IDS_TEST" desc="test">
-+ test
-+ </message>
-+ </messages>
-+ </release>
-+</grit>''' % first_ids_path
-+ pseudo_file = StringIO(input)
-+ grit_root_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)),
-+ '..')
-+ fake_input_path = os.path.join(
-+ grit_root_dir, "grit/testdata/chrome/app/generated_resources.grd")
-+ root = grd_reader.Parse(pseudo_file, os.path.split(fake_input_path)[0])
-+ root.AssignFirstIds(fake_input_path, {})
-+ messages_node = root.children[0].children[0]
-+ self.failUnless(isinstance(messages_node, empty.MessagesNode))
-+ self.failUnless(messages_node.attrs["first_id"] !=
-+ empty.MessagesNode().DefaultAttributes()["first_id"])
-+
-+ def testAssignFirstIds(self):
-+ self.doTestAssignFirstIds("../../tools/grit/resource_ids")
-+
-+ def testAssignFirstIdsUseGritDir(self):
-+ self.doTestAssignFirstIds("GRIT_DIR/grit/testdata/tools/grit/resource_ids")
-+
-+ def testAssignFirstIdsMultipleMessages(self):
-+ """If there are multiple messages sections, the resource_ids file
-+ needs to list multiple first_id values."""
-+ input = u'''<?xml version="1.0" encoding="UTF-8"?>
-+<grit latest_public_release="2" source_lang_id="en-US" current_release="3"
-+ base_dir="." first_ids_file="resource_ids">
-+ <release seq="3">
-+ <messages>
-+ <message name="IDS_TEST" desc="test">
-+ test
-+ </message>
-+ </messages>
-+ <messages>
-+ <message name="IDS_TEST2" desc="test">
-+ test2
-+ </message>
-+ </messages>
-+ </release>
-+</grit>'''
-+ pseudo_file = StringIO(input)
-+ grit_root_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)),
-+ '..')
-+ fake_input_path = os.path.join(grit_root_dir, "grit/testdata/test.grd")
-+
-+ root = grd_reader.Parse(pseudo_file, os.path.split(fake_input_path)[0])
-+ root.AssignFirstIds(fake_input_path, {})
-+ messages_node = root.children[0].children[0]
-+ self.assertTrue(isinstance(messages_node, empty.MessagesNode))
-+ self.assertEqual('100', messages_node.attrs["first_id"])
-+ messages_node = root.children[0].children[1]
-+ self.assertTrue(isinstance(messages_node, empty.MessagesNode))
-+ self.assertEqual('10000', messages_node.attrs["first_id"])
-+
-+ def testUseNameForIdAndPpIfdef(self):
-+ input = u'''<?xml version="1.0" encoding="UTF-8"?>
-+<grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
-+ <release seq="3">
-+ <messages>
-+ <if expr="pp_ifdef('hello')">
-+ <message name="IDS_HELLO" use_name_for_id="true">
-+ Hello!
-+ </message>
-+ </if>
-+ </messages>
-+ </release>
-+</grit>'''
-+ pseudo_file = StringIO(input)
-+ root = grd_reader.Parse(pseudo_file, '.', defines={'hello': '1'})
-+
-+ # Check if the ID is set to the name. In the past, there was a bug
-+ # that caused the ID to be a generated number.
-+ hello = root.GetNodeById('IDS_HELLO')
-+ self.failUnless(hello.GetCliques()[0].GetId() == 'IDS_HELLO')
-+
-+ def testUseNameForIdWithIfElse(self):
-+ input = u'''<?xml version="1.0" encoding="UTF-8"?>
-+<grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
-+ <release seq="3">
-+ <messages>
-+ <if expr="pp_ifdef('hello')">
-+ <then>
-+ <message name="IDS_HELLO" use_name_for_id="true">
-+ Hello!
-+ </message>
-+ </then>
-+ <else>
-+ <message name="IDS_HELLO" use_name_for_id="true">
-+ Yellow!
-+ </message>
-+ </else>
-+ </if>
-+ </messages>
-+ </release>
-+</grit>'''
-+ pseudo_file = StringIO(input)
-+ root = grd_reader.Parse(pseudo_file, '.', defines={'hello': '1'})
-+
-+ # Check if the ID is set to the name. In the past, there was a bug
-+ # that caused the ID to be a generated number.
-+ hello = root.GetNodeById('IDS_HELLO')
-+ self.failUnless(hello.GetCliques()[0].GetId() == 'IDS_HELLO')
-+
-+ def testPartInclusionAndCorrectSource(self):
-+ arbitrary_path_grd = u'''\
-+ <grit-part>
-+ <message name="IDS_TEST5" desc="test5">test5</message>
-+ </grit-part>'''
-+ tmp_dir = util.TempDir({'arbitrary_path.grp': arbitrary_path_grd})
-+ arbitrary_path_grd_file = tmp_dir.GetPath('arbitrary_path.grp')
-+ top_grd = u'''\
-+ <grit latest_public_release="2" current_release="3">
-+ <release seq="3">
-+ <messages>
-+ <message name="IDS_TEST" desc="test">
-+ test
-+ </message>
-+ <part file="sub.grp" />
-+ <part file="%s" />
-+ </messages>
-+ </release>
-+ </grit>''' % arbitrary_path_grd_file
-+ sub_grd = u'''\
-+ <grit-part>
-+ <message name="IDS_TEST2" desc="test2">test2</message>
-+ <part file="subsub.grp" />
-+ <message name="IDS_TEST3" desc="test3">test3</message>
-+ </grit-part>'''
-+ subsub_grd = u'''\
-+ <grit-part>
-+ <message name="IDS_TEST4" desc="test4">test4</message>
-+ </grit-part>'''
-+ expected_output = u'''\
-+ <grit current_release="3" latest_public_release="2">
-+ <release seq="3">
-+ <messages>
-+ <message desc="test" name="IDS_TEST">
-+ test
-+ </message>
-+ <part file="sub.grp">
-+ <message desc="test2" name="IDS_TEST2">
-+ test2
-+ </message>
-+ <part file="subsub.grp">
-+ <message desc="test4" name="IDS_TEST4">
-+ test4
-+ </message>
-+ </part>
-+ <message desc="test3" name="IDS_TEST3">
-+ test3
-+ </message>
-+ </part>
-+ <part file="%s">
-+ <message desc="test5" name="IDS_TEST5">
-+ test5
-+ </message>
-+ </part>
-+ </messages>
-+ </release>
-+ </grit>''' % arbitrary_path_grd_file
-+
-+ with util.TempDir({'sub.grp': sub_grd,
-+ 'subsub.grp': subsub_grd}) as tmp_sub_dir:
-+ output = grd_reader.Parse(StringIO(top_grd),
-+ tmp_sub_dir.GetPath())
-+ correct_sources = {
-+ 'IDS_TEST': None,
-+ 'IDS_TEST2': tmp_sub_dir.GetPath('sub.grp'),
-+ 'IDS_TEST3': tmp_sub_dir.GetPath('sub.grp'),
-+ 'IDS_TEST4': tmp_sub_dir.GetPath('subsub.grp'),
-+ 'IDS_TEST5': arbitrary_path_grd_file,
-+ }
-+
-+ for node in output.ActiveDescendants():
-+ with node:
-+ if isinstance(node, message.MessageNode):
-+ self.assertEqual(correct_sources[node.attrs.get('name')], node.source)
-+ self.assertEqual(expected_output.split(), output.FormatXml().split())
-+ tmp_dir.CleanUp()
-+
-+ def testPartInclusionFailure(self):
-+ template = u'''
-+ <grit latest_public_release="2" current_release="3">
-+ <outputs>
-+ %s
-+ </outputs>
-+ </grit>'''
-+
-+ part_failures = [
-+ (exception.UnexpectedContent, u'<part file="x">fnord</part>'),
-+ (exception.UnexpectedChild,
-+ u'<part file="x"><output filename="x" type="y" /></part>'),
-+ (exception.FileNotFound, u'<part file="yet_created_x" />'),
-+ ]
-+ for raises, data in part_failures:
-+ data = StringIO(template % data)
-+ self.assertRaises(raises, grd_reader.Parse, data, '.')
-+
-+ gritpart_failures = [
-+ (exception.UnexpectedAttribute, u'<grit-part file="xyz"></grit-part>'),
-+ (exception.MissingElement, u'<output filename="x" type="y" />'),
-+ ]
-+ for raises, data in gritpart_failures:
-+ top_grd = StringIO(template % u'<part file="bad.grp" />')
-+ with util.TempDir({'bad.grp': data}) as temp_dir:
-+ self.assertRaises(raises, grd_reader.Parse, top_grd, temp_dir.GetPath())
-+
-+ def testEarlyEnoughPlatformSpecification(self):
-+ # This is a regression test for issue
-+ # https://code.google.com/p/grit-i18n/issues/detail?id=23
-+ grd_text = u'''<?xml version="1.0" encoding="UTF-8"?>
-+ <grit latest_public_release="1" current_release="1">
-+ <release seq="1">
-+ <messages>
-+ <if expr="not pp_ifdef('use_titlecase')">
-+ <message name="IDS_XYZ">foo</message>
-+ </if>
-+ <!-- The assumption is that use_titlecase is never true for
-+ this platform. When the platform isn't set to 'android'
-+ early enough, we get a duplicate message name. -->
-+ <if expr="os == '%s'">
-+ <message name="IDS_XYZ">boo</message>
-+ </if>
-+ </messages>
-+ </release>
-+ </grit>''' % sys.platform
-+ with util.TempDir({}) as temp_dir:
-+ grd_reader.Parse(StringIO(grd_text), temp_dir.GetPath(),
-+ target_platform='android')
-+
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/grit-todo.xml b/tools/grit/grit/grit-todo.xml
-new file mode 100644
-index 0000000000..b8c20fdfad
---- /dev/null
-+++ b/tools/grit/grit/grit-todo.xml
-@@ -0,0 +1,62 @@
-+<?xml version="1.0" encoding="windows-1252"?>
-+<TODOLIST FILEFORMAT="6" PROJECTNAME="GRIT" NEXTUNIQUEID="56" FILEVERSION="69" LASTMODIFIED="2005-08-19">
-+ <TASK STARTDATESTRING="2005-04-08" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38453.49975694" TITLE="check 'name' attribute is unique" TIMEESTUNITS="H" ID="2" PERCENTDONE="100" STARTDATE="38450.00000000" DONEDATESTRING="2005-04-11" POS="22" DONEDATE="38453.00000000"/>
-+ <TASK STARTDATESTRING="2005-04-08" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38488.48189815" TITLE="import id-calculating code" TIMEESTUNITS="H" ID="3" PERCENTDONE="100" STARTDATE="38450.00000000" DONEDATESTRING="2005-05-16" POS="13" DONEDATE="38488.00000000"/>
-+ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38488.48209491" TITLE="Import tool for existing translations" TIMEESTUNITS="H" ID="6" PERCENTDONE="100" STARTDATE="38488.00000000" DONEDATESTRING="2005-06-16" POS="12" DONEDATE="38519.00000000"/>
-+ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38511.00805556" TITLE="Export XMBs" TIMEESTUNITS="H" ID="8" PERCENTDONE="100" STARTDATE="38488.00000000" DONEDATESTRING="2005-06-08" POS="20" DONEDATE="38511.00000000"/>
-+ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38511.00924769" TITLE="Initial Integration" TIMEESTUNITS="H" ID="10" PERCENTDONE="100" STARTDATE="38488.00000000" DONEDATESTRING="2005-06-08" POS="10" DONEDATE="38511.00000000">
-+ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38496.54048611" TITLE="parser for %s strings" TIMEESTUNITS="H" ID="4" PERCENTDONE="100" STARTDATE="38488.00000000" DONEDATESTRING="2005-05-24" POS="2" DONEDATE="38496.00000000"/>
-+ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38497.00261574" TITLE="import tool for existing RC files" TIMEESTUNITS="H" ID="5" PERCENTDONE="100" STARTDATE="38488.00000000" DONEDATESTRING="2005-05-25" POS="4" DONEDATE="38497.00000000">
-+ <TASK STARTDATESTRING="2005-05-24" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38496.92990741" TITLE="handle button value= and img alt= in message HTML text" TIMEESTUNITS="H" ID="22" PERCENTDONE="100" STARTDATE="38496.00000000" DONEDATESTRING="2005-05-24" POS="1" DONEDATE="38496.00000000"/>
-+ <TASK STARTDATESTRING="2005-05-24" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38497.00258102" TITLE="&amp;nbsp; bug" TIMEESTUNITS="H" ID="23" PERCENTDONE="100" STARTDATE="38496.00000000" DONEDATESTRING="2005-05-25" POS="2" DONEDATE="38497.00000000"/>
-+ </TASK>
-+ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38490.61171296" TITLE="grit build" TIMEESTUNITS="H" ID="7" PERCENTDONE="100" STARTDATE="38488.00000000" DONEDATESTRING="2005-05-18" POS="6" DONEDATE="38490.00000000">
-+ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38490.61168981" TITLE="use IDs gathered from gatherers for .h file" TIMEESTUNITS="H" ID="20" PERCENTDONE="100" STARTDATE="38488.00000000" DONEDATESTRING="2005-05-18" POS="1" DONEDATE="38490.00000000"/>
-+ </TASK>
-+ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38504.55199074" TITLE="SCons Integration" TIMEESTUNITS="H" ID="9" PERCENTDONE="100" STARTDATE="38488.00000000" DONEDATESTRING="2005-06-01" POS="1" DONEDATE="38504.00000000"/>
-+ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38490.61181713" TITLE="handle includes" TIMEESTUNITS="H" ID="12" PERCENTDONE="100" STARTDATE="38488.00000000" DONEDATESTRING="2005-05-18" POS="5" DONEDATE="38490.00000000"/>
-+ <TASK STARTDATESTRING="2005-05-24" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38507.98567130" TITLE="output translated HTML templates" TIMEESTUNITS="H" ID="25" PERCENTDONE="100" STARTDATE="38496.00000000" DONEDATESTRING="2005-06-04" POS="3" DONEDATE="38507.00000000"/>
-+ <TASK STARTDATESTRING="2005-05-31" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38507.99394676" TITLE="bug: re-escape too much in RC dialogs etc." TIMEESTUNITS="H" ID="38" PERCENTDONE="100" STARTDATE="38503.00000000" DONEDATESTRING="2005-06-04" POS="7" DONEDATE="38507.00000000"/>
-+ </TASK>
-+ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38489.46444444" TITLE="handle structure variants" TIMEESTUNITS="H" ID="11" PERCENTDONE="100" STARTDATE="38488.00000000" DONEDATESTRING="2005-06-16" POS="15" DONEDATE="38519.00000000"/>
-+ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38489.46456019" TITLE="handle include variants" TIMEESTUNITS="H" ID="13" PERCENTDONE="100" STARTDATE="38488.00000000" DONEDATESTRING="2005-06-16" POS="17" DONEDATE="38519.00000000"/>
-+ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38489.46537037" TITLE="handle translateable text for includes (e.g. image text)" TIMEESTUNITS="H" ID="14" PERCENTDONE="100" STARTDATE="38488.00000000" DONEDATESTRING="2005-06-16" POS="14" DONEDATE="38519.00000000"/>
-+ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38489.46712963" TITLE="ddoc" TIMEESTUNITS="H" ID="15" STARTDATE="38488.00000000" POS="4">
-+ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38489.46718750" TITLE="review comments miket" TIMEESTUNITS="H" ID="16" STARTDATE="38488.00000000" POS="2"/>
-+ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38489.46722222" TITLE="review comments pdoyle" TIMEESTUNITS="H" ID="17" STARTDATE="38488.00000000" POS="1"/>
-+ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38489.46732639" TITLE="remove 'extkey' from structure" TIMEESTUNITS="H" ID="18" STARTDATE="38488.00000000" POS="3"/>
-+ <TASK STARTDATESTRING="2005-05-16" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38489.53537037" TITLE="add 'encoding' to structure" TIMEESTUNITS="H" ID="19" STARTDATE="38488.00000000" POS="6"/>
-+ <TASK STARTDATESTRING="2005-05-31" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38503.55304398" TITLE="document limitation: emitter doesn't emit the translated HTML templates" TIMEESTUNITS="H" ID="30" STARTDATE="38503.00000000" POS="4"/>
-+ <TASK STARTDATESTRING="2005-05-31" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38504.58541667" TITLE="add 'internal_comment' to &lt;message&gt;" TIMEESTUNITS="H" ID="32" STARTDATE="38503.00000000" POS="5"/>
-+ <TASK STARTDATESTRING="2005-05-31" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38505.73391204" TITLE="&lt;outputs&gt; can not have paths (because of SCons integration - goes to build dir)" TIMEESTUNITS="H" ID="36" STARTDATE="38503.00000000" POS="9"/>
-+ <TASK STARTDATESTRING="2005-05-31" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38506.64265046" TITLE="&lt;identifers&gt; and &lt;identifier&gt; nodes" TIMEESTUNITS="H" ID="37" STARTDATE="38503.00000000" POS="10"/>
-+ <TASK STARTDATESTRING="2005-06-23" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38526.62344907" TITLE="&lt;structure&gt; can have 'exclude_from_rc' attribute (default false)" TIMEESTUNITS="H" ID="47" STARTDATE="38526.00000000" POS="8"/>
-+ <TASK STARTDATESTRING="2005-06-23" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38531.94135417" TITLE="add 'enc_check' to &lt;grit&gt;" TIMEESTUNITS="H" ID="48" STARTDATE="38526.00000000" POS="7"/>
-+ </TASK>
-+ <TASK STARTDATESTRING="2005-05-18" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38492.51549769" TITLE="handle nontranslateable messages (in MessageClique?)" TIMEESTUNITS="H" ID="21" PERCENTDONE="100" STARTDATE="38490.00000000" DONEDATESTRING="2005-06-16" POS="16" DONEDATE="38519.00000000"/>
-+ <TASK STARTDATESTRING="2005-05-24" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38505.70454861" TITLE="ask cprince about SCons builder in new mk system" TIMEESTUNITS="H" ID="24" PERCENTDONE="100" STARTDATE="38496.00000000" DONEDATESTRING="2005-06-02" POS="25" DONEDATE="38505.00000000"/>
-+ <TASK STARTDATESTRING="2005-05-24" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38504.57436343" TITLE="fix AOL resource in trunk (&quot;???????&quot;)" TIMEESTUNITS="H" ID="26" PERCENTDONE="100" STARTDATE="38496.00000000" DONEDATESTRING="2005-06-01" POS="19" DONEDATE="38504.00000000"/>
-+ <TASK STARTDATESTRING="2005-05-24" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38498.53893519" TITLE="rc_all vs. rc_translateable vs. rc_nontranslateable" TIMEESTUNITS="H" ID="27" PERCENTDONE="100" STARTDATE="38496.00000000" DONEDATESTRING="2005-06-16" POS="6" DONEDATE="38519.00000000"/>
-+ <TASK STARTDATESTRING="2005-05-31" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38509.45532407" TITLE="make separate .grb &quot;outputs&quot; file (and change SCons integ) (??)" TIMEESTUNITS="H" ID="28" PERCENTDONE="100" STARTDATE="38503.00000000" DONEDATESTRING="2005-06-06" POS="8" DONEDATE="38509.00000000"/>
-+ <TASK STARTDATESTRING="2005-05-31" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38511.00939815" TITLE="fix unit tests so they run from any directory" TIMEESTUNITS="H" ID="33" PERCENTDONE="100" STARTDATE="38503.00000000" DONEDATESTRING="2005-06-08" POS="18" DONEDATE="38511.00000000"/>
-+ <TASK STARTDATESTRING="2005-05-31" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38508.96640046" TITLE="Change R4 tool to CC correct team(s) on GRIT changes" TIMEESTUNITS="H" ID="39" PERCENTDONE="100" STARTDATE="38503.00000000" DONEDATESTRING="2005-06-05" POS="23" DONEDATE="38508.00000000"/>
-+ <TASK STARTDATESTRING="2005-06-07" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38511.00881944" TITLE="Document why wrapper.rc" TIMEESTUNITS="H" ID="40" PERCENTDONE="100" STARTDATE="38510.00000000" DONEDATESTRING="2005-06-08" POS="21" DONEDATE="38511.00000000"/>
-+ <TASK STARTDATESTRING="2005-06-08" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38511.00804398" TITLE="import XTBs" TIMEESTUNITS="H" ID="41" PERCENTDONE="100" STARTDATE="38511.00000000" DONEDATESTRING="2005-06-16" POS="11" DONEDATE="38519.00000000"/>
-+ <TASK STARTDATESTRING="2005-06-08" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38511.00875000" TITLE="Nightly build integration" TIMEESTUNITS="H" ID="42" STARTDATE="38511.00000000" POS="3"/>
-+ <TASK STARTDATESTRING="2005-06-08" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38511.00891204" TITLE="BUGS" TIMEESTUNITS="H" ID="43" STARTDATE="38511.00000000" POS="24">
-+ <TASK STARTDATESTRING="2005-06-08" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38513.03375000" TITLE="Should report error if RC-section structure refers to does not exist" TIMEESTUNITS="H" ID="44" PERCENTDONE="100" STARTDATE="38511.00000000" DONEDATESTRING="2005-06-10" POS="1" DONEDATE="38513.00000000"/>
-+ </TASK>
-+ <TASK STARTDATESTRING="2005-06-08" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38511.00981481" TITLE="NEW FEATURES" TIMEESTUNITS="H" ID="45" PERCENTDONE="100" STARTDATE="38511.00000000" DONEDATESTRING="2005-06-16" POS="7" DONEDATE="38519.00000000">
-+ <TASK STARTDATESTRING="2005-05-31" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38505.70077546" TITLE="Implement line-continuation feature (\ at end of line?)" TIMEESTUNITS="H" ID="34" PERCENTDONE="100" STARTDATE="38503.00000000" DONEDATESTRING="2005-06-16" POS="1" DONEDATE="38519.00000000"/>
-+ <TASK STARTDATESTRING="2005-05-31" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38505.70262731" TITLE="Implement conditional inclusion &amp; reflect the conditionals from R3 RC file" TIMEESTUNITS="H" ID="35" PERCENTDONE="100" STARTDATE="38503.00000000" DONEDATESTRING="2005-06-16" POS="2" DONEDATE="38519.00000000"/>
-+ </TASK>
-+ <TASK STARTDATESTRING="2005-06-08" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38511.01046296" TITLE="TC integration (one-way TO the TC)" TIMEESTUNITS="H" ID="46" PERCENTDONE="100" STARTDATE="38511.00000000" DONEDATESTRING="2005-06-16" POS="5" DONEDATE="38519.00000000"/>
-+ <TASK STARTDATESTRING="2005-06-30" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38533.59072917" TITLE="bazaar20 ad for GRIT help" TIMEESTUNITS="H" ID="49" STARTDATE="38533.00000000" POS="2">
-+ <TASK STARTDATESTRING="2005-08-19" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38583.72346065" TITLE="bazaar20 ideas" TIMEESTUNITS="H" ID="51" STARTDATE="38583.00000000" POS="1">
-+ <TASK STARTDATESTRING="2005-08-19" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38583.72354167" TITLE="GUI for adding/editing messages" TIMEESTUNITS="H" ID="52" STARTDATE="38583.00000000" POS="2"/>
-+ <TASK STARTDATESTRING="2005-08-19" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38583.72365741" TITLE="XLIFF import/export" TIMEESTUNITS="H" ID="54" STARTDATE="38583.00000000" POS="1"/>
-+ </TASK>
-+ </TASK>
-+ <TASK STARTDATESTRING="2005-06-30" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38583.73721065" TITLE="internal_comment for all resource nodes (not just &lt;message&gt;)" TIMEESTUNITS="H" ID="50" PERCENTDONE="100" STARTDATE="38533.00000000" DONEDATESTRING="2005-08-19" POS="9" DONEDATE="38583.73721065"/>
-+ <TASK STARTDATESTRING="2005-08-19" PRIORITY="5" TIMEESPENTUNITS="H" LASTMOD="38583.73743056" TITLE="Preserve XML comments - this gives us line continuation and more" TIMEESTUNITS="H" ID="55" STARTDATE="38583.72326389" POS="1"/>
-+</TODOLIST>
-diff --git a/tools/grit/grit/grit_runner.py b/tools/grit/grit/grit_runner.py
-new file mode 100644
-index 0000000000..26aa0d58c4
---- /dev/null
-+++ b/tools/grit/grit/grit_runner.py
-@@ -0,0 +1,334 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+"""Command processor for GRIT. This is the script you invoke to run the various
-+GRIT tools.
-+"""
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
-+
-+import getopt
-+
-+from grit import util
-+
-+import grit.extern.FP
-+
-+# Tool info factories; these import only within each factory to avoid
-+# importing most of the GRIT code until required.
-+def ToolFactoryBuild():
-+ import grit.tool.build
-+ return grit.tool.build.RcBuilder()
-+
-+def ToolFactoryBuildInfo():
-+ import grit.tool.buildinfo
-+ return grit.tool.buildinfo.DetermineBuildInfo()
-+
-+def ToolFactoryCount():
-+ import grit.tool.count
-+ return grit.tool.count.CountMessage()
-+
-+def ToolFactoryDiffStructures():
-+ import grit.tool.diff_structures
-+ return grit.tool.diff_structures.DiffStructures()
-+
-+def ToolFactoryMenuTranslationsFromParts():
-+ import grit.tool.menu_from_parts
-+ return grit.tool.menu_from_parts.MenuTranslationsFromParts()
-+
-+def ToolFactoryNewGrd():
-+ import grit.tool.newgrd
-+ return grit.tool.newgrd.NewGrd()
-+
-+def ToolFactoryResizeDialog():
-+ import grit.tool.resize
-+ return grit.tool.resize.ResizeDialog()
-+
-+def ToolFactoryRc2Grd():
-+ import grit.tool.rc2grd
-+ return grit.tool.rc2grd.Rc2Grd()
-+
-+def ToolFactoryTest():
-+ import grit.tool.test
-+ return grit.tool.test.TestTool()
-+
-+def ToolFactoryTranslationToTc():
-+ import grit.tool.transl2tc
-+ return grit.tool.transl2tc.TranslationToTc()
-+
-+def ToolFactoryUnit():
-+ import grit.tool.unit
-+ return grit.tool.unit.UnitTestTool()
-+
-+
-+def ToolFactoryUpdateResourceIds():
-+ import grit.tool.update_resource_ids
-+ return grit.tool.update_resource_ids.UpdateResourceIds()
-+
-+
-+def ToolFactoryXmb():
-+ import grit.tool.xmb
-+ return grit.tool.xmb.OutputXmb()
-+
-+def ToolAndroid2Grd():
-+ import grit.tool.android2grd
-+ return grit.tool.android2grd.Android2Grd()
-+
-+# Keys for the following map
-+_FACTORY = 1
-+_REQUIRES_INPUT = 2
-+_HIDDEN = 3 # optional key - presence indicates tool is hidden
-+
-+# Maps tool names to the tool's module. Done as a list of (key, value) tuples
-+# instead of a map to preserve ordering.
-+_TOOLS = [
-+ ['android2grd', {
-+ _FACTORY: ToolAndroid2Grd,
-+ _REQUIRES_INPUT: False
-+ }],
-+ ['build', {
-+ _FACTORY: ToolFactoryBuild,
-+ _REQUIRES_INPUT: True
-+ }],
-+ ['buildinfo', {
-+ _FACTORY: ToolFactoryBuildInfo,
-+ _REQUIRES_INPUT: True
-+ }],
-+ ['count', {
-+ _FACTORY: ToolFactoryCount,
-+ _REQUIRES_INPUT: True
-+ }],
-+ [
-+ 'menufromparts',
-+ {
-+ _FACTORY: ToolFactoryMenuTranslationsFromParts,
-+ _REQUIRES_INPUT: True,
-+ _HIDDEN: True
-+ }
-+ ],
-+ ['newgrd', {
-+ _FACTORY: ToolFactoryNewGrd,
-+ _REQUIRES_INPUT: False
-+ }],
-+ ['rc2grd', {
-+ _FACTORY: ToolFactoryRc2Grd,
-+ _REQUIRES_INPUT: False
-+ }],
-+ ['resize', {
-+ _FACTORY: ToolFactoryResizeDialog,
-+ _REQUIRES_INPUT: True
-+ }],
-+ ['sdiff', {
-+ _FACTORY: ToolFactoryDiffStructures,
-+ _REQUIRES_INPUT: False
-+ }],
-+ ['test', {
-+ _FACTORY: ToolFactoryTest,
-+ _REQUIRES_INPUT: True,
-+ _HIDDEN: True
-+ }],
-+ [
-+ 'transl2tc',
-+ {
-+ _FACTORY: ToolFactoryTranslationToTc,
-+ _REQUIRES_INPUT: False
-+ }
-+ ],
-+ ['unit', {
-+ _FACTORY: ToolFactoryUnit,
-+ _REQUIRES_INPUT: False
-+ }],
-+ [
-+ 'update_resource_ids',
-+ {
-+ _FACTORY: ToolFactoryUpdateResourceIds,
-+ _REQUIRES_INPUT: False
-+ }
-+ ],
-+ ['xmb', {
-+ _FACTORY: ToolFactoryXmb,
-+ _REQUIRES_INPUT: True
-+ }],
-+]
-+
-+
-+def PrintUsage():
-+ tool_list = ''
-+ for (tool, info) in _TOOLS:
-+ if not _HIDDEN in info:
-+ tool_list += ' %-12s %s\n' % (
-+ tool, info[_FACTORY]().ShortDescription())
-+
-+ print("""GRIT - the Google Resource and Internationalization Tool
-+
-+Usage: grit [GLOBALOPTIONS] TOOL [args to tool]
-+
-+Global options:
-+
-+ -i INPUT Specifies the INPUT file to use (a .grd file). If this is not
-+ specified, GRIT will look for the environment variable GRIT_INPUT.
-+ If it is not present either, GRIT will try to find an input file
-+ named 'resource.grd' in the current working directory.
-+
-+ -h MODULE Causes GRIT to use MODULE.UnsignedFingerPrint instead of
-+ grit.extern.FP.UnsignedFingerprint. MODULE must be
-+ available somewhere in the PYTHONPATH search path.
-+
-+ -v Print more verbose runtime information.
-+
-+ -x Print extremely verbose runtime information. Implies -v
-+
-+ -p FNAME Specifies that GRIT should profile its execution and output the
-+ results to the file FNAME.
-+
-+Tools:
-+
-+ TOOL can be one of the following:
-+%s
-+ For more information on how to use a particular tool, and the specific
-+ arguments you can send to that tool, execute 'grit help TOOL'
-+""" % (tool_list))
-+
-+
-+class Options(object):
-+ """Option storage and parsing."""
-+
-+ def __init__(self):
-+ self.hash = None
-+ self.input = None
-+ self.verbose = False
-+ self.extra_verbose = False
-+ self.output_stream = sys.stdout
-+ self.profile_dest = None
-+
-+ def ReadOptions(self, args):
-+ """Reads options from the start of args and returns the remainder."""
-+ (opts, args) = getopt.getopt(args, 'vxi:p:h:', ('help',))
-+ for (key, val) in opts:
-+ if key == '-h': self.hash = val
-+ elif key == '-i': self.input = val
-+ elif key == '-v':
-+ self.verbose = True
-+ util.verbose = True
-+ elif key == '-x':
-+ self.verbose = True
-+ util.verbose = True
-+ self.extra_verbose = True
-+ util.extra_verbose = True
-+ elif key == '-p': self.profile_dest = val
-+ elif key == '--help':
-+ PrintUsage()
-+ sys.exit(0)
-+
-+ if not self.input:
-+ if 'GRIT_INPUT' in os.environ:
-+ self.input = os.environ['GRIT_INPUT']
-+ else:
-+ self.input = 'resource.grd'
-+
-+ return args
-+
-+ def __repr__(self):
-+ return '(verbose: %d, input: %s)' % (
-+ self.verbose, self.input)
-+
-+
-+def _GetToolInfo(tool):
-+ """Returns the info map for the tool named 'tool' or None if there is no
-+ such tool."""
-+ matches = [t for t in _TOOLS if t[0] == tool]
-+ if not matches:
-+ return None
-+ else:
-+ return matches[0][1]
-+
-+
-+def Main(args=None):
-+ """Parses arguments and does the appropriate thing."""
-+ util.ChangeStdoutEncoding()
-+
-+ # Support for setuptools console wrappers.
-+ if args is None:
-+ args = sys.argv[1:]
-+
-+ options = Options()
-+ try:
-+ args = options.ReadOptions(args) # args may be shorter after this
-+ except getopt.GetoptError as e:
-+ print("grit:", str(e))
-+ print("Try running 'grit help' for valid options.")
-+ return 1
-+ if not args:
-+ print("No tool provided. Try running 'grit help' for a list of tools.")
-+ return 2
-+
-+ tool = args[0]
-+ if tool == 'help':
-+ if len(args) == 1:
-+ PrintUsage()
-+ return 0
-+ else:
-+ tool = args[1]
-+ if not _GetToolInfo(tool):
-+ print("No such tool. Try running 'grit help' for a list of tools.")
-+ return 2
-+
-+ print("Help for 'grit %s' (for general help, run 'grit help'):\n" %
-+ (tool,))
-+ _GetToolInfo(tool)[_FACTORY]().ShowUsage()
-+ return 0
-+ if not _GetToolInfo(tool):
-+ print("No such tool. Try running 'grit help' for a list of tools.")
-+ return 2
-+
-+ try:
-+ if _GetToolInfo(tool)[_REQUIRES_INPUT]:
-+ os.stat(options.input)
-+ except OSError:
-+ print('Input file %s not found.\n'
-+ 'To specify a different input file:\n'
-+ ' 1. Use the GRIT_INPUT environment variable.\n'
-+ ' 2. Use the -i command-line option. This overrides '
-+ 'GRIT_INPUT.\n'
-+ ' 3. Specify neither GRIT_INPUT or -i and GRIT will try to load '
-+ "'resource.grd'\n"
-+ ' from the current directory.' % options.input)
-+ return 2
-+
-+ if options.hash:
-+ grit.extern.FP.UseUnsignedFingerPrintFromModule(options.hash)
-+
-+ try:
-+ toolobject = _GetToolInfo(tool)[_FACTORY]()
-+ if options.profile_dest:
-+ import hotshot
-+ prof = hotshot.Profile(options.profile_dest)
-+ return prof.runcall(toolobject.Run, options, args[1:])
-+ else:
-+ return toolobject.Run(options, args[1:])
-+ except getopt.GetoptError as e:
-+ print("grit: %s: %s" % (tool, str(e)))
-+ print("Try running 'grit help %s' for valid options." % (tool,))
-+ return 1
-+
-+
-+if __name__ == '__main__':
-+ sys.path.append(
-+ os.path.join(
-+ os.path.dirname(
-+ os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
-+ 'diagnosis'))
-+ try:
-+ import crbug_1001171
-+ with crbug_1001171.DumpStateOnLookupError():
-+ sys.exit(Main(sys.argv[1:]))
-+ except ImportError:
-+ pass
-+
-+ sys.exit(Main(sys.argv[1:]))
-diff --git a/tools/grit/grit/grit_runner_unittest.py b/tools/grit/grit/grit_runner_unittest.py
-new file mode 100644
-index 0000000000..1487001d81
---- /dev/null
-+++ b/tools/grit/grit/grit_runner_unittest.py
-@@ -0,0 +1,42 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for grit.py'''
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
-+
-+import unittest
-+
-+from six import StringIO
-+
-+from grit import util
-+import grit.grit_runner
-+
-+class OptionArgsUnittest(unittest.TestCase):
-+ def setUp(self):
-+ self.buf = StringIO()
-+ self.old_stdout = sys.stdout
-+ sys.stdout = self.buf
-+
-+ def tearDown(self):
-+ sys.stdout = self.old_stdout
-+
-+ def testSimple(self):
-+ grit.grit_runner.Main(['-i',
-+ util.PathFromRoot('grit/testdata/simple-input.xml'),
-+ 'test', 'bla', 'voff', 'ga'])
-+ output = self.buf.getvalue()
-+ self.failUnless(output.count("'test'") == 0) # tool name doesn't occur
-+ self.failUnless(output.count('bla'))
-+ self.failUnless(output.count('simple-input.xml'))
-+
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/lazy_re.py b/tools/grit/grit/lazy_re.py
-new file mode 100644
-index 0000000000..5c461e87e7
---- /dev/null
-+++ b/tools/grit/grit/lazy_re.py
-@@ -0,0 +1,46 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''In GRIT, we used to compile a lot of regular expressions at parse
-+time. Since many of them never get used, we use lazy_re to compile
-+them on demand the first time they are used, thus speeding up startup
-+time in some cases.
-+'''
-+
-+from __future__ import print_function
-+
-+import re
-+
-+
-+class LazyRegexObject(object):
-+ '''This object creates a RegexObject with the arguments passed in
-+ its constructor, the first time any attribute except the several on
-+ the class itself is accessed. This accomplishes lazy compilation of
-+ the regular expression while maintaining a nearly-identical
-+ interface.
-+ '''
-+
-+ def __init__(self, *args, **kwargs):
-+ self._stash_args = args
-+ self._stash_kwargs = kwargs
-+ self._lazy_re = None
-+
-+ def _LazyInit(self):
-+ if not self._lazy_re:
-+ self._lazy_re = re.compile(*self._stash_args, **self._stash_kwargs)
-+
-+ def __getattribute__(self, name):
-+ if name in ('_LazyInit', '_lazy_re', '_stash_args', '_stash_kwargs'):
-+ return object.__getattribute__(self, name)
-+ else:
-+ self._LazyInit()
-+ return getattr(self._lazy_re, name)
-+
-+
-+def compile(*args, **kwargs):
-+ '''Creates a LazyRegexObject that, when invoked on, will compile a
-+ re.RegexObject (via re.compile) with the same arguments passed to
-+ this function, and delegate almost all of its methods to it.
-+ '''
-+ return LazyRegexObject(*args, **kwargs)
-diff --git a/tools/grit/grit/lazy_re_unittest.py b/tools/grit/grit/lazy_re_unittest.py
-new file mode 100644
-index 0000000000..8488b454ee
---- /dev/null
-+++ b/tools/grit/grit/lazy_re_unittest.py
-@@ -0,0 +1,40 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit test for lazy_re.
-+'''
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
-+
-+import re
-+import unittest
-+
-+from grit import lazy_re
-+
-+
-+class LazyReUnittest(unittest.TestCase):
-+
-+ def testCreatedOnlyOnDemand(self):
-+ rex = lazy_re.compile('bingo')
-+ self.assertEqual(None, rex._lazy_re)
-+ self.assertTrue(rex.match('bingo'))
-+ self.assertNotEqual(None, rex._lazy_re)
-+
-+ def testJustKwargsWork(self):
-+ rex = lazy_re.compile(flags=re.I, pattern='BiNgO')
-+ self.assertTrue(rex.match('bingo'))
-+
-+ def testPositionalAndKwargsWork(self):
-+ rex = lazy_re.compile('BiNgO', flags=re.I)
-+ self.assertTrue(rex.match('bingo'))
-+
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/node/__init__.py b/tools/grit/grit/node/__init__.py
-new file mode 100644
-index 0000000000..2fc0d3360c
---- /dev/null
-+++ b/tools/grit/grit/node/__init__.py
-@@ -0,0 +1,8 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Package 'grit.node'
-+'''
-+
-+pass
-diff --git a/tools/grit/grit/node/base.py b/tools/grit/grit/node/base.py
-new file mode 100644
-index 0000000000..40859d301d
---- /dev/null
-+++ b/tools/grit/grit/node/base.py
-@@ -0,0 +1,670 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Base types for nodes in a GRIT resource tree.
-+'''
-+
-+from __future__ import print_function
-+
-+import ast
-+import os
-+import struct
-+import sys
-+from xml.sax import saxutils
-+
-+import six
-+
-+from grit import constants
-+from grit import clique
-+from grit import exception
-+from grit import util
-+from grit.node import brotli_util
-+import grit.format.gzip_string
-+
-+
-+class Node(object):
-+ '''An item in the tree that has children.'''
-+
-+ # Valid content types that can be returned by _ContentType()
-+ _CONTENT_TYPE_NONE = 0 # No CDATA content but may have children
-+ _CONTENT_TYPE_CDATA = 1 # Only CDATA, no children.
-+ _CONTENT_TYPE_MIXED = 2 # CDATA and children, possibly intermingled
-+
-+ # Types of files to be compressed by default.
-+ _COMPRESS_BY_DEFAULT_EXTENSIONS = ('.js', '.html', '.css', '.svg')
-+
-+ # Default nodes to not whitelist skipped
-+ _whitelist_marked_as_skip = False
-+
-+ # A class-static cache to speed up EvaluateExpression().
-+ # Keys are expressions (e.g. 'is_ios and lang == "fr"'). Values are tuples
-+ # (code, variables_in_expr) where code is the compiled expression and can be
-+ # directly eval'd, and variables_in_expr is the list of variable and method
-+ # names used in the expression (e.g. ['is_ios', 'lang']).
-+ eval_expr_cache = {}
-+
-+ def __init__(self):
-+ self.children = [] # A list of child elements
-+ self.mixed_content = [] # A list of u'' and/or child elements (this
-+ # duplicates 'children' but
-+ # is needed to preserve markup-type content).
-+ self.name = u'' # The name of this element
-+ self.attrs = {} # The set of attributes (keys to values)
-+ self.parent = None # Our parent unless we are the root element.
-+ self.uberclique = None # Allows overriding uberclique for parts of tree
-+ self.source = None # File that this node was parsed from
-+
-+ # This context handler allows you to write "with node:" and get a
-+ # line identifying the offending node if an exception escapes from the body
-+ # of the with statement.
-+ def __enter__(self):
-+ return self
-+
-+ def __exit__(self, exc_type, exc_value, traceback):
-+ if exc_type is not None:
-+ print(u'Error processing node %s: %s' % (six.text_type(self), exc_value))
-+
-+ def __iter__(self):
-+ '''A preorder iteration through the tree that this node is the root of.'''
-+ return self.Preorder()
-+
-+ def Preorder(self):
-+ '''Generator that generates first this node, then the same generator for
-+ any child nodes.'''
-+ yield self
-+ for child in self.children:
-+ for iterchild in child.Preorder():
-+ yield iterchild
-+
-+ def ActiveChildren(self):
-+ '''Returns the children of this node that should be included in the current
-+ configuration. Overridden by <if>.'''
-+ return [node for node in self.children if not node.WhitelistMarkedAsSkip()]
-+
-+ def ActiveDescendants(self):
-+ '''Yields the current node and all descendants that should be included in
-+ the current configuration, in preorder.'''
-+ yield self
-+ for child in self.ActiveChildren():
-+ for descendant in child.ActiveDescendants():
-+ yield descendant
-+
-+ def GetRoot(self):
-+ '''Returns the root Node in the tree this Node belongs to.'''
-+ curr = self
-+ while curr.parent:
-+ curr = curr.parent
-+ return curr
-+
-+ # TODO(joi) Use this (currently untested) optimization?:
-+ #if hasattr(self, '_root'):
-+ # return self._root
-+ #curr = self
-+ #while curr.parent and not hasattr(curr, '_root'):
-+ # curr = curr.parent
-+ #if curr.parent:
-+ # self._root = curr._root
-+ #else:
-+ # self._root = curr
-+ #return self._root
-+
-+ def StartParsing(self, name, parent):
-+ '''Called at the start of parsing.
-+
-+ Args:
-+ name: u'elementname'
-+ parent: grit.node.base.Node or subclass or None
-+ '''
-+ assert isinstance(name, six.string_types)
-+ assert not parent or isinstance(parent, Node)
-+ self.name = name
-+ self.parent = parent
-+
-+ def AddChild(self, child):
-+ '''Adds a child to the list of children of this node, if it is a valid
-+ child for the node.'''
-+ assert isinstance(child, Node)
-+ if (not self._IsValidChild(child) or
-+ self._ContentType() == self._CONTENT_TYPE_CDATA):
-+ explanation = 'invalid child %s for parent %s' % (str(child), self.name)
-+ raise exception.UnexpectedChild(explanation)
-+ self.children.append(child)
-+ self.mixed_content.append(child)
-+
-+ def RemoveChild(self, child_id):
-+ '''Removes the first node that has a "name" attribute which
-+ matches "child_id" in the list of immediate children of
-+ this node.
-+
-+ Args:
-+ child_id: String identifying the child to be removed
-+ '''
-+ index = 0
-+ # Safe not to copy since we only remove the first element found
-+ for child in self.children:
-+ name_attr = child.attrs['name']
-+ if name_attr == child_id:
-+ self.children.pop(index)
-+ self.mixed_content.pop(index)
-+ break
-+ index += 1
-+
-+ def AppendContent(self, content):
-+ '''Appends a chunk of text as content of this node.
-+
-+ Args:
-+ content: u'hello'
-+
-+ Return:
-+ None
-+ '''
-+ assert isinstance(content, six.string_types)
-+ if self._ContentType() != self._CONTENT_TYPE_NONE:
-+ self.mixed_content.append(content)
-+ elif content.strip() != '':
-+ raise exception.UnexpectedContent()
-+
-+ def HandleAttribute(self, attrib, value):
-+ '''Informs the node of an attribute that was parsed out of the GRD file
-+ for it.
-+
-+ Args:
-+ attrib: 'name'
-+ value: 'fooblat'
-+
-+ Return:
-+ None
-+ '''
-+ assert isinstance(attrib, six.string_types)
-+ assert isinstance(value, six.string_types)
-+ if self._IsValidAttribute(attrib, value):
-+ self.attrs[attrib] = value
-+ else:
-+ raise exception.UnexpectedAttribute(attrib)
-+
-+ def EndParsing(self):
-+ '''Called at the end of parsing.'''
-+
-+ # TODO(joi) Rewrite this, it's extremely ugly!
-+ if len(self.mixed_content):
-+ if isinstance(self.mixed_content[0], six.string_types):
-+ # Remove leading and trailing chunks of pure whitespace.
-+ while (len(self.mixed_content) and
-+ isinstance(self.mixed_content[0], six.string_types) and
-+ self.mixed_content[0].strip() == ''):
-+ self.mixed_content = self.mixed_content[1:]
-+ # Strip leading and trailing whitespace from mixed content chunks
-+ # at front and back.
-+ if (len(self.mixed_content) and
-+ isinstance(self.mixed_content[0], six.string_types)):
-+ self.mixed_content[0] = self.mixed_content[0].lstrip()
-+ # Remove leading and trailing ''' (used to demarcate whitespace)
-+ if (len(self.mixed_content) and
-+ isinstance(self.mixed_content[0], six.string_types)):
-+ if self.mixed_content[0].startswith("'''"):
-+ self.mixed_content[0] = self.mixed_content[0][3:]
-+ if len(self.mixed_content):
-+ if isinstance(self.mixed_content[-1], six.string_types):
-+ # Same stuff all over again for the tail end.
-+ while (len(self.mixed_content) and
-+ isinstance(self.mixed_content[-1], six.string_types) and
-+ self.mixed_content[-1].strip() == ''):
-+ self.mixed_content = self.mixed_content[:-1]
-+ if (len(self.mixed_content) and
-+ isinstance(self.mixed_content[-1], six.string_types)):
-+ self.mixed_content[-1] = self.mixed_content[-1].rstrip()
-+ if (len(self.mixed_content) and
-+ isinstance(self.mixed_content[-1], six.string_types)):
-+ if self.mixed_content[-1].endswith("'''"):
-+ self.mixed_content[-1] = self.mixed_content[-1][:-3]
-+
-+ # Check that all mandatory attributes are there.
-+ for node_mandatt in self.MandatoryAttributes():
-+ mandatt_list = []
-+ if node_mandatt.find('|') >= 0:
-+ mandatt_list = node_mandatt.split('|')
-+ else:
-+ mandatt_list.append(node_mandatt)
-+
-+ mandatt_option_found = False
-+ for mandatt in mandatt_list:
-+ assert mandatt not in self.DefaultAttributes()
-+ if mandatt in self.attrs:
-+ if not mandatt_option_found:
-+ mandatt_option_found = True
-+ else:
-+ raise exception.MutuallyExclusiveMandatoryAttribute(mandatt)
-+
-+ if not mandatt_option_found:
-+ raise exception.MissingMandatoryAttribute(mandatt)
-+
-+ # Add default attributes if not specified in input file.
-+ for defattr in self.DefaultAttributes():
-+ if not defattr in self.attrs:
-+ self.attrs[defattr] = self.DefaultAttributes()[defattr]
-+
-+ def GetCdata(self):
-+ '''Returns all CDATA of this element, concatenated into a single
-+ string. Note that this ignores any elements embedded in CDATA.'''
-+ return ''.join([c for c in self.mixed_content
-+ if isinstance(c, six.string_types)])
-+
-+ def __str__(self):
-+ '''Returns this node and all nodes below it as an XML document in a Unicode
-+ string.'''
-+ header = u'<?xml version="1.0" encoding="UTF-8"?>\n'
-+ return header + self.FormatXml()
-+
-+ # Some Python 2 glue.
-+ __unicode__ = __str__
-+
-+ def FormatXml(self, indent = u'', one_line = False):
-+ '''Returns this node and all nodes below it as an XML
-+ element in a Unicode string. This differs from __unicode__ in that it does
-+ not include the <?xml> stuff at the top of the string. If one_line is true,
-+ children and CDATA are layed out in a way that preserves internal
-+ whitespace.
-+ '''
-+ assert isinstance(indent, six.string_types)
-+
-+ content_one_line = (one_line or
-+ self._ContentType() == self._CONTENT_TYPE_MIXED)
-+ inside_content = self.ContentsAsXml(indent, content_one_line)
-+
-+ # Then the attributes for this node.
-+ attribs = u''
-+ default_attribs = self.DefaultAttributes()
-+ for attrib, value in sorted(self.attrs.items()):
-+ # Only print an attribute if it is other than the default value.
-+ if attrib not in default_attribs or value != default_attribs[attrib]:
-+ attribs += u' %s=%s' % (attrib, saxutils.quoteattr(value))
-+
-+ # Finally build the XML for our node and return it
-+ if len(inside_content) > 0:
-+ if one_line:
-+ return u'<%s%s>%s</%s>' % (self.name, attribs, inside_content,
-+ self.name)
-+ elif content_one_line:
-+ return u'%s<%s%s>\n%s %s\n%s</%s>' % (
-+ indent, self.name, attribs,
-+ indent, inside_content,
-+ indent, self.name)
-+ else:
-+ return u'%s<%s%s>\n%s\n%s</%s>' % (
-+ indent, self.name, attribs,
-+ inside_content,
-+ indent, self.name)
-+ else:
-+ return u'%s<%s%s />' % (indent, self.name, attribs)
-+
-+ def ContentsAsXml(self, indent, one_line):
-+ '''Returns the contents of this node (CDATA and child elements) in XML
-+ format. If 'one_line' is true, the content will be laid out on one line.'''
-+ assert isinstance(indent, six.string_types)
-+
-+ # Build the contents of the element.
-+ inside_parts = []
-+ last_item = None
-+ for mixed_item in self.mixed_content:
-+ if isinstance(mixed_item, Node):
-+ inside_parts.append(mixed_item.FormatXml(indent + u' ', one_line))
-+ if not one_line:
-+ inside_parts.append(u'\n')
-+ else:
-+ message = mixed_item
-+ # If this is the first item and it starts with whitespace, we add
-+ # the ''' delimiter.
-+ if not last_item and message.lstrip() != message:
-+ message = u"'''" + message
-+ inside_parts.append(util.EncodeCdata(message))
-+ last_item = mixed_item
-+
-+ # If there are only child nodes and no cdata, there will be a spurious
-+ # trailing \n
-+ if len(inside_parts) and inside_parts[-1] == '\n':
-+ inside_parts = inside_parts[:-1]
-+
-+ # If the last item is a string (not a node) and ends with whitespace,
-+ # we need to add the ''' delimiter.
-+ if (isinstance(last_item, six.string_types) and
-+ last_item.rstrip() != last_item):
-+ inside_parts[-1] = inside_parts[-1] + u"'''"
-+
-+ return u''.join(inside_parts)
-+
-+ def SubstituteMessages(self, substituter):
-+ '''Applies substitutions to all messages in the tree.
-+
-+ Called as a final step of RunGatherers.
-+
-+ Args:
-+ substituter: a grit.util.Substituter object.
-+ '''
-+ for child in self.children:
-+ child.SubstituteMessages(substituter)
-+
-+ def _IsValidChild(self, child):
-+ '''Returns true if 'child' is a valid child of this node.
-+ Overridden by subclasses.'''
-+ return False
-+
-+ def _IsValidAttribute(self, name, value):
-+ '''Returns true if 'name' is the name of a valid attribute of this element
-+ and 'value' is a valid value for that attribute. Overriden by
-+ subclasses unless they have only mandatory attributes.'''
-+ return (name in self.MandatoryAttributes() or
-+ name in self.DefaultAttributes())
-+
-+ def _ContentType(self):
-+ '''Returns the type of content this element can have. Overridden by
-+ subclasses. The content type can be one of the _CONTENT_TYPE_XXX constants
-+ above.'''
-+ return self._CONTENT_TYPE_NONE
-+
-+ def MandatoryAttributes(self):
-+ '''Returns a list of attribute names that are mandatory (non-optional)
-+ on the current element. One can specify a list of
-+ "mutually exclusive mandatory" attributes by specifying them as one
-+ element in the list, separated by a "|" character.
-+ '''
-+ return []
-+
-+ def DefaultAttributes(self):
-+ '''Returns a dictionary of attribute names that have defaults, mapped to
-+ the default value. Overridden by subclasses.'''
-+ return {}
-+
-+ def GetCliques(self):
-+ '''Returns all MessageClique objects belonging to this node. Overridden
-+ by subclasses.
-+
-+ Return:
-+ [clique1, clique2] or []
-+ '''
-+ return []
-+
-+ def ToRealPath(self, path_from_basedir):
-+ '''Returns a real path (which can be absolute or relative to the current
-+ working directory), given a path that is relative to the base directory
-+ set for the GRIT input file.
-+
-+ Args:
-+ path_from_basedir: '..'
-+
-+ Return:
-+ 'resource'
-+ '''
-+ return util.normpath(os.path.join(self.GetRoot().GetBaseDir(),
-+ os.path.expandvars(path_from_basedir)))
-+
-+ def GetInputPath(self):
-+ '''Returns a path, relative to the base directory set for the grd file,
-+ that points to the file the node refers to.
-+ '''
-+ # This implementation works for most nodes that have an input file.
-+ return self.attrs['file']
-+
-+ def UberClique(self):
-+ '''Returns the uberclique that should be used for messages originating in
-+ a given node. If the node itself has its uberclique set, that is what we
-+ use, otherwise we search upwards until we find one. If we do not find one
-+ even at the root node, we set the root node's uberclique to a new
-+ uberclique instance.
-+ '''
-+ node = self
-+ while not node.uberclique and node.parent:
-+ node = node.parent
-+ if not node.uberclique:
-+ node.uberclique = clique.UberClique()
-+ return node.uberclique
-+
-+ def IsTranslateable(self):
-+ '''Returns false if the node has contents that should not be translated,
-+ otherwise returns false (even if the node has no contents).
-+ '''
-+ if not 'translateable' in self.attrs:
-+ return True
-+ else:
-+ return self.attrs['translateable'] == 'true'
-+
-+ def IsAccessibilityWithNoUI(self):
-+ '''Returns true if the node is marked as an accessibility label and the
-+ message isn't shown in the UI. Otherwise returns false. This label is
-+ used to determine if the text requires screenshots.'''
-+ if not 'is_accessibility_with_no_ui' in self.attrs:
-+ return False
-+ else:
-+ return self.attrs['is_accessibility_with_no_ui'] == 'true'
-+
-+ def GetNodeById(self, id):
-+ '''Returns the node in the subtree parented by this node that has a 'name'
-+ attribute matching 'id'. Returns None if no such node is found.
-+ '''
-+ for node in self:
-+ if 'name' in node.attrs and node.attrs['name'] == id:
-+ return node
-+ return None
-+
-+ def GetChildrenOfType(self, type):
-+ '''Returns a list of all subnodes (recursing to all leaves) of this node
-+ that are of the indicated type (or tuple of types).
-+
-+ Args:
-+ type: A type you could use with isinstance().
-+
-+ Return:
-+ A list, possibly empty.
-+ '''
-+ return [child for child in self if isinstance(child, type)]
-+
-+ def GetTextualIds(self):
-+ '''Returns a list of the textual ids of this node.
-+ '''
-+ if 'name' in self.attrs:
-+ return [self.attrs['name']]
-+ return []
-+
-+ @classmethod
-+ def EvaluateExpression(cls, expr, defs, target_platform, extra_variables={}):
-+ '''Worker for EvaluateCondition (below) and conditions in XTB files.'''
-+ if expr in cls.eval_expr_cache:
-+ code, variables_in_expr = cls.eval_expr_cache[expr]
-+ else:
-+ # Get a list of all variable and method names used in the expression.
-+ syntax_tree = ast.parse(expr, mode='eval')
-+ variables_in_expr = [node.id for node in ast.walk(syntax_tree) if
-+ isinstance(node, ast.Name) and node.id not in ('True', 'False')]
-+ code = compile(syntax_tree, filename='<string>', mode='eval')
-+ cls.eval_expr_cache[expr] = code, variables_in_expr
-+
-+ # Set values only for variables that are needed to eval the expression.
-+ variable_map = {}
-+ for name in variables_in_expr:
-+ if name == 'os':
-+ value = target_platform
-+ elif name == 'defs':
-+ value = defs
-+
-+ elif name == 'is_linux':
-+ value = target_platform.startswith('linux')
-+ elif name == 'is_macosx':
-+ value = target_platform == 'darwin'
-+ elif name == 'is_win':
-+ value = target_platform in ('cygwin', 'win32')
-+ elif name == 'is_android':
-+ value = target_platform == 'android'
-+ elif name == 'is_ios':
-+ value = target_platform == 'ios'
-+ elif name == 'is_bsd':
-+ value = 'bsd' in target_platform
-+ elif name == 'is_posix':
-+ value = (target_platform in ('darwin', 'linux2', 'linux3', 'sunos5',
-+ 'android', 'ios')
-+ or 'bsd' in target_platform)
-+
-+ elif name == 'pp_ifdef':
-+ def pp_ifdef(symbol):
-+ return symbol in defs
-+ value = pp_ifdef
-+ elif name == 'pp_if':
-+ def pp_if(symbol):
-+ return defs.get(symbol, False)
-+ value = pp_if
-+
-+ elif name in defs:
-+ value = defs[name]
-+ elif name in extra_variables:
-+ value = extra_variables[name]
-+ else:
-+ # Undefined variables default to False.
-+ value = False
-+
-+ variable_map[name] = value
-+
-+ eval_result = eval(code, {}, variable_map)
-+ assert isinstance(eval_result, bool)
-+ return eval_result
-+
-+ def EvaluateCondition(self, expr):
-+ '''Returns true if and only if the Python expression 'expr' evaluates
-+ to true.
-+
-+ The expression is given a few local variables:
-+ - 'lang' is the language currently being output
-+ (the 'lang' attribute of the <output> element).
-+ - 'context' is the current output context
-+ (the 'context' attribute of the <output> element).
-+ - 'defs' is a map of C preprocessor-style symbol names to their values.
-+ - 'os' is the current platform (likely 'linux2', 'win32' or 'darwin').
-+ - 'pp_ifdef(symbol)' is a shorthand for "symbol in defs".
-+ - 'pp_if(symbol)' is a shorthand for "symbol in defs and defs[symbol]".
-+ - 'is_linux', 'is_macosx', 'is_win', 'is_posix' are true if 'os'
-+ matches the given platform.
-+ '''
-+ root = self.GetRoot()
-+ lang = getattr(root, 'output_language', '')
-+ context = getattr(root, 'output_context', '')
-+ defs = getattr(root, 'defines', {})
-+ target_platform = getattr(root, 'target_platform', '')
-+ extra_variables = {
-+ 'lang': lang,
-+ 'context': context,
-+ }
-+ return Node.EvaluateExpression(
-+ expr, defs, target_platform, extra_variables)
-+
-+ def OnlyTheseTranslations(self, languages):
-+ '''Turns off loading of translations for languages not in the provided list.
-+
-+ Attrs:
-+ languages: ['fr', 'zh_cn']
-+ '''
-+ for node in self:
-+ if (hasattr(node, 'IsTranslation') and
-+ node.IsTranslation() and
-+ node.GetLang() not in languages):
-+ node.DisableLoading()
-+
-+ def FindBooleanAttribute(self, attr, default, skip_self):
-+ '''Searches all ancestors of the current node for the nearest enclosing
-+ definition of the given boolean attribute.
-+
-+ Args:
-+ attr: 'fallback_to_english'
-+ default: What to return if no node defines the attribute.
-+ skip_self: Don't check the current node, only its parents.
-+ '''
-+ p = self.parent if skip_self else self
-+ while p:
-+ value = p.attrs.get(attr, 'default').lower()
-+ if value != 'default':
-+ return (value == 'true')
-+ p = p.parent
-+ return default
-+
-+ def PseudoIsAllowed(self):
-+ '''Returns true if this node is allowed to use pseudo-translations. This
-+ is true by default, unless this node is within a <release> node that has
-+ the allow_pseudo attribute set to false.
-+ '''
-+ return self.FindBooleanAttribute('allow_pseudo',
-+ default=True, skip_self=True)
-+
-+ def ShouldFallbackToEnglish(self):
-+ '''Returns true iff this node should fall back to English when
-+ pseudotranslations are disabled and no translation is available for a
-+ given message.
-+ '''
-+ return self.FindBooleanAttribute('fallback_to_english',
-+ default=False, skip_self=True)
-+
-+ def WhitelistMarkedAsSkip(self):
-+ '''Returns true if the node is marked to be skipped in the output by a
-+ whitelist.
-+ '''
-+ return self._whitelist_marked_as_skip
-+
-+ def SetWhitelistMarkedAsSkip(self, mark_skipped):
-+ '''Sets WhitelistMarkedAsSkip.
-+ '''
-+ self._whitelist_marked_as_skip = mark_skipped
-+
-+ def ExpandVariables(self):
-+ '''Whether we need to expand variables on a given node.'''
-+ return False
-+
-+ def IsResourceMapSource(self):
-+ '''Whether this node is a resource map source.'''
-+ return False
-+
-+ def CompressDataIfNeeded(self, data):
-+ '''Compress data using the format specified in the compress attribute.
-+
-+ Args:
-+ data: The data to compressed.
-+ Returns:
-+ The data in gzipped or brotli compressed format. If the format is
-+ unspecified then this returns the data uncompressed.
-+ '''
-+
-+ compress = self.attrs.get('compress')
-+
-+ # Compress JS, HTML, CSS and SVG files by default (gzip), unless |compress|
-+ # is explicitly specified.
-+ compress_by_default = (compress == 'default'
-+ and self.attrs.get('file').endswith(
-+ self._COMPRESS_BY_DEFAULT_EXTENSIONS))
-+
-+ if compress == 'gzip' or compress_by_default:
-+ # We only use rsyncable compression on Linux.
-+ # We exclude ChromeOS since ChromeOS bots are Linux based but do not have
-+ # the --rsyncable option built in for gzip. See crbug.com/617950.
-+ if sys.platform == 'linux2' and 'chromeos' not in self.GetRoot().defines:
-+ return grit.format.gzip_string.GzipStringRsyncable(data)
-+ return grit.format.gzip_string.GzipString(data)
-+
-+ if compress == 'brotli':
-+ # The length of the uncompressed data as 8 bytes little-endian.
-+ size_bytes = struct.pack("<q", len(data))
-+ data = brotli_util.BrotliCompress(data)
-+ # BROTLI_CONST is prepended to brotli decompressed data in order to
-+ # easily check if a resource has been brotli compressed.
-+ # The length of the uncompressed data is also appended to the start,
-+ # truncated to 6 bytes, little-endian. size_bytes is 8 bytes,
-+ # need to truncate further to 6.
-+ formatter = b'%ds %dx %ds' % (6, 2, len(size_bytes) - 8)
-+ return (constants.BROTLI_CONST +
-+ b''.join(struct.unpack(formatter, size_bytes)) +
-+ data)
-+
-+ if compress == 'false' or compress == 'default':
-+ return data
-+
-+ raise Exception('Invalid value for compression')
-+
-+
-+class ContentNode(Node):
-+ '''Convenience baseclass for nodes that can have content.'''
-+ def _ContentType(self):
-+ return self._CONTENT_TYPE_MIXED
-diff --git a/tools/grit/grit/node/base_unittest.py b/tools/grit/grit/node/base_unittest.py
-new file mode 100644
-index 0000000000..32a5a0ca59
---- /dev/null
-+++ b/tools/grit/grit/node/base_unittest.py
-@@ -0,0 +1,259 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for base.Node functionality (as used in various subclasses)'''
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+import unittest
-+
-+from six import StringIO
-+
-+from grit import grd_reader
-+from grit import util
-+from grit.node import base
-+from grit.node import message
-+
-+
-+def MakePlaceholder(phname='BINGO'):
-+ ph = message.PhNode()
-+ ph.StartParsing(u'ph', None)
-+ ph.HandleAttribute(u'name', phname)
-+ ph.AppendContent(u'bongo')
-+ ph.EndParsing()
-+ return ph
-+
-+
-+class NodeUnittest(unittest.TestCase):
-+ def testWhitespaceHandling(self):
-+ # We test using the Message node type.
-+ node = message.MessageNode()
-+ node.StartParsing(u'hello', None)
-+ node.HandleAttribute(u'name', u'bla')
-+ node.AppendContent(u" ''' two spaces ")
-+ node.EndParsing()
-+ self.failUnless(node.GetCdata() == u' two spaces')
-+
-+ node = message.MessageNode()
-+ node.StartParsing(u'message', None)
-+ node.HandleAttribute(u'name', u'bla')
-+ node.AppendContent(u" two spaces ''' ")
-+ node.EndParsing()
-+ self.failUnless(node.GetCdata() == u'two spaces ')
-+
-+ def testWhitespaceHandlingWithChildren(self):
-+ # We test using the Message node type.
-+ node = message.MessageNode()
-+ node.StartParsing(u'message', None)
-+ node.HandleAttribute(u'name', u'bla')
-+ node.AppendContent(u" ''' two spaces ")
-+ node.AddChild(MakePlaceholder())
-+ node.AppendContent(u' space before and after ')
-+ node.AddChild(MakePlaceholder('BONGO'))
-+ node.AppendContent(u" space before two after '''")
-+ node.EndParsing()
-+ self.failUnless(node.mixed_content[0] == u' two spaces ')
-+ self.failUnless(node.mixed_content[2] == u' space before and after ')
-+ self.failUnless(node.mixed_content[-1] == u' space before two after ')
-+
-+ def testXmlFormatMixedContent(self):
-+ # Again test using the Message node type, because it is the only mixed
-+ # content node.
-+ node = message.MessageNode()
-+ node.StartParsing(u'message', None)
-+ node.HandleAttribute(u'name', u'name')
-+ node.AppendContent(u'Hello <young> ')
-+
-+ ph = message.PhNode()
-+ ph.StartParsing(u'ph', None)
-+ ph.HandleAttribute(u'name', u'USERNAME')
-+ ph.AppendContent(u'$1')
-+ ex = message.ExNode()
-+ ex.StartParsing(u'ex', None)
-+ ex.AppendContent(u'Joi')
-+ ex.EndParsing()
-+ ph.AddChild(ex)
-+ ph.EndParsing()
-+
-+ node.AddChild(ph)
-+ node.EndParsing()
-+
-+ non_indented_xml = node.FormatXml()
-+ self.failUnless(non_indented_xml == u'<message name="name">\n Hello '
-+ u'&lt;young&gt; <ph name="USERNAME">$1<ex>Joi</ex></ph>'
-+ u'\n</message>')
-+
-+ indented_xml = node.FormatXml(u' ')
-+ self.failUnless(indented_xml == u' <message name="name">\n Hello '
-+ u'&lt;young&gt; <ph name="USERNAME">$1<ex>Joi</ex></ph>'
-+ u'\n </message>')
-+
-+ def testXmlFormatMixedContentWithLeadingWhitespace(self):
-+ # Again test using the Message node type, because it is the only mixed
-+ # content node.
-+ node = message.MessageNode()
-+ node.StartParsing(u'message', None)
-+ node.HandleAttribute(u'name', u'name')
-+ node.AppendContent(u"''' Hello <young> ")
-+
-+ ph = message.PhNode()
-+ ph.StartParsing(u'ph', None)
-+ ph.HandleAttribute(u'name', u'USERNAME')
-+ ph.AppendContent(u'$1')
-+ ex = message.ExNode()
-+ ex.StartParsing(u'ex', None)
-+ ex.AppendContent(u'Joi')
-+ ex.EndParsing()
-+ ph.AddChild(ex)
-+ ph.EndParsing()
-+
-+ node.AddChild(ph)
-+ node.AppendContent(u" yessiree '''")
-+ node.EndParsing()
-+
-+ non_indented_xml = node.FormatXml()
-+ self.failUnless(non_indented_xml ==
-+ u"<message name=\"name\">\n ''' Hello"
-+ u' &lt;young&gt; <ph name="USERNAME">$1<ex>Joi</ex></ph>'
-+ u" yessiree '''\n</message>")
-+
-+ indented_xml = node.FormatXml(u' ')
-+ self.failUnless(indented_xml ==
-+ u" <message name=\"name\">\n ''' Hello"
-+ u' &lt;young&gt; <ph name="USERNAME">$1<ex>Joi</ex></ph>'
-+ u" yessiree '''\n </message>")
-+
-+ self.failUnless(node.GetNodeById('name'))
-+
-+ def testXmlFormatContentWithEntities(self):
-+ '''Tests a bug where &nbsp; would not be escaped correctly.'''
-+ from grit import tclib
-+ msg_node = message.MessageNode.Construct(None, tclib.Message(
-+ text = 'BEGIN_BOLDHelloWHITESPACEthere!END_BOLD Bingo!',
-+ placeholders = [
-+ tclib.Placeholder('BEGIN_BOLD', '<b>', 'bla'),
-+ tclib.Placeholder('WHITESPACE', '&nbsp;', 'bla'),
-+ tclib.Placeholder('END_BOLD', '</b>', 'bla')]),
-+ 'BINGOBONGO')
-+ xml = msg_node.FormatXml()
-+ self.failUnless(xml.find('&nbsp;') == -1, 'should have no entities')
-+
-+ def testIter(self):
-+ # First build a little tree of message and ph nodes.
-+ node = message.MessageNode()
-+ node.StartParsing(u'message', None)
-+ node.HandleAttribute(u'name', u'bla')
-+ node.AppendContent(u" ''' two spaces ")
-+ node.AppendContent(u' space before and after ')
-+ ph = message.PhNode()
-+ ph.StartParsing(u'ph', None)
-+ ph.AddChild(message.ExNode())
-+ ph.HandleAttribute(u'name', u'BINGO')
-+ ph.AppendContent(u'bongo')
-+ node.AddChild(ph)
-+ node.AddChild(message.PhNode())
-+ node.AppendContent(u" space before two after '''")
-+
-+ order = [message.MessageNode, message.PhNode, message.ExNode, message.PhNode]
-+ for n in node:
-+ self.failUnless(type(n) == order[0])
-+ order = order[1:]
-+ self.failUnless(len(order) == 0)
-+
-+ def testGetChildrenOfType(self):
-+ xml = '''<?xml version="1.0" encoding="UTF-8"?>
-+ <grit latest_public_release="2" source_lang_id="en-US"
-+ current_release="3" base_dir=".">
-+ <outputs>
-+ <output filename="resource.h" type="rc_header" />
-+ <output filename="en/generated_resources.rc" type="rc_all"
-+ lang="en" />
-+ <if expr="pp_if('NOT_TRUE')">
-+ <output filename="de/generated_resources.rc" type="rc_all"
-+ lang="de" />
-+ </if>
-+ </outputs>
-+ <release seq="3">
-+ <messages>
-+ <message name="ID_HELLO">Hello!</message>
-+ </messages>
-+ </release>
-+ </grit>'''
-+ grd = grd_reader.Parse(StringIO(xml),
-+ util.PathFromRoot('grit/test/data'))
-+ from grit.node import node_io
-+ output_nodes = grd.GetChildrenOfType(node_io.OutputNode)
-+ self.failUnlessEqual(len(output_nodes), 3)
-+ self.failUnlessEqual(output_nodes[2].attrs['filename'],
-+ 'de/generated_resources.rc')
-+
-+ def testEvaluateExpression(self):
-+ def AssertExpr(expected_value, expr, defs, target_platform,
-+ extra_variables):
-+ self.failUnlessEqual(expected_value, base.Node.EvaluateExpression(
-+ expr, defs, target_platform, extra_variables))
-+
-+ AssertExpr(True, "True", {}, 'linux', {})
-+ AssertExpr(False, "False", {}, 'linux', {})
-+ AssertExpr(True, "True or False", {}, 'linux', {})
-+ AssertExpr(False, "True and False", {}, 'linux', {})
-+ AssertExpr(True, "os == 'linux'", {}, 'linux', {})
-+ AssertExpr(False, "os == 'linux'", {}, 'ios', {})
-+ AssertExpr(True, "'foo' in defs", {'foo': 'bar'}, 'ios', {})
-+ AssertExpr(False, "'foo' in defs", {'baz': 'bar'}, 'ios', {})
-+ AssertExpr(False, "'foo' in defs", {}, 'ios', {})
-+ AssertExpr(True, "is_linux", {}, 'linux2', {})
-+ AssertExpr(False, "is_linux", {}, 'win32', {})
-+ AssertExpr(True, "is_macosx", {}, 'darwin', {})
-+ AssertExpr(False, "is_macosx", {}, 'ios', {})
-+ AssertExpr(True, "is_win", {}, 'win32', {})
-+ AssertExpr(False, "is_win", {}, 'darwin', {})
-+ AssertExpr(True, "is_android", {}, 'android', {})
-+ AssertExpr(False, "is_android", {}, 'linux3', {})
-+ AssertExpr(True, "is_ios", {}, 'ios', {})
-+ AssertExpr(False, "is_ios", {}, 'darwin', {})
-+ AssertExpr(True, "is_posix", {}, 'linux2', {})
-+ AssertExpr(True, "is_posix", {}, 'darwin', {})
-+ AssertExpr(True, "is_posix", {}, 'android', {})
-+ AssertExpr(True, "is_posix", {}, 'ios', {})
-+ AssertExpr(True, "is_posix", {}, 'freebsd7', {})
-+ AssertExpr(False, "is_posix", {}, 'win32', {})
-+ AssertExpr(True, "pp_ifdef('foo')", {'foo': True}, 'win32', {})
-+ AssertExpr(True, "pp_ifdef('foo')", {'foo': False}, 'win32', {})
-+ AssertExpr(False, "pp_ifdef('foo')", {'bar': True}, 'win32', {})
-+ AssertExpr(True, "pp_if('foo')", {'foo': True}, 'win32', {})
-+ AssertExpr(False, "pp_if('foo')", {'foo': False}, 'win32', {})
-+ AssertExpr(False, "pp_if('foo')", {'bar': True}, 'win32', {})
-+ AssertExpr(True, "foo", {'foo': True}, 'win32', {})
-+ AssertExpr(False, "foo", {'foo': False}, 'win32', {})
-+ AssertExpr(False, "foo", {'bar': True}, 'win32', {})
-+ AssertExpr(True, "foo == 'baz'", {'foo': 'baz'}, 'win32', {})
-+ AssertExpr(False, "foo == 'baz'", {'foo': True}, 'win32', {})
-+ AssertExpr(False, "foo == 'baz'", {}, 'win32', {})
-+ AssertExpr(True, "lang == 'de'", {}, 'win32', {'lang': 'de'})
-+ AssertExpr(False, "lang == 'de'", {}, 'win32', {'lang': 'fr'})
-+ AssertExpr(False, "lang == 'de'", {}, 'win32', {})
-+
-+ # Test a couple more complex expressions for good measure.
-+ AssertExpr(True, "is_ios and (lang in ['de', 'fr'] or foo)",
-+ {'foo': 'bar'}, 'ios', {'lang': 'fr', 'context': 'today'})
-+ AssertExpr(False, "is_ios and (lang in ['de', 'fr'] or foo)",
-+ {'foo': False}, 'linux2', {'lang': 'fr', 'context': 'today'})
-+ AssertExpr(False, "is_ios and (lang in ['de', 'fr'] or foo)",
-+ {'baz': 'bar'}, 'ios', {'lang': 'he', 'context': 'today'})
-+ AssertExpr(True, "foo == 'bar' or not baz",
-+ {'foo': 'bar', 'fun': True}, 'ios', {'lang': 'en'})
-+ AssertExpr(True, "foo == 'bar' or not baz",
-+ {}, 'ios', {'lang': 'en', 'context': 'java'})
-+ AssertExpr(False, "foo == 'bar' or not baz",
-+ {'foo': 'ruz', 'baz': True}, 'ios', {'lang': 'en'})
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/node/brotli_util.py b/tools/grit/grit/node/brotli_util.py
-new file mode 100644
-index 0000000000..77f70e49d5
---- /dev/null
-+++ b/tools/grit/grit/node/brotli_util.py
-@@ -0,0 +1,29 @@
-+# Copyright 2019 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+"""Framework for compressing resources using Brotli."""
-+
-+import subprocess
-+
-+__brotli_executable = None
-+
-+
-+def SetBrotliCommand(brotli):
-+ # brotli is a list. In production it contains the path to the Brotli executable.
-+ # During testing it contains [python, mock_brotli.py] for testing on Windows.
-+ global __brotli_executable
-+ __brotli_executable = brotli
-+
-+
-+def BrotliCompress(data):
-+ if not __brotli_executable:
-+ raise Exception('Add "use_brotli = true" to you GN grit(...) target ' +
-+ 'if you want to use brotli.')
-+ compress = subprocess.Popen(__brotli_executable + ['-', '-f'],
-+ stdin=subprocess.PIPE, stdout=subprocess.PIPE)
-+ return compress.communicate(data)[0]
-+
-+def IsInitialized():
-+ global __brotli_executable
-+ return __brotli_executable is not None
-diff --git a/tools/grit/grit/node/custom/__init__.py b/tools/grit/grit/node/custom/__init__.py
-new file mode 100644
-index 0000000000..e179cf7730
---- /dev/null
-+++ b/tools/grit/grit/node/custom/__init__.py
-@@ -0,0 +1,8 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Package 'grit.node.custom'
-+'''
-+
-+pass
-diff --git a/tools/grit/grit/node/custom/filename.py b/tools/grit/grit/node/custom/filename.py
-new file mode 100644
-index 0000000000..55a27e58c1
---- /dev/null
-+++ b/tools/grit/grit/node/custom/filename.py
-@@ -0,0 +1,29 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''A CustomType for filenames.'''
-+
-+from __future__ import print_function
-+
-+from grit import clique
-+from grit import lazy_re
-+
-+
-+class WindowsFilename(clique.CustomType):
-+ '''Validates that messages can be used as Windows filenames, and strips
-+ illegal characters out of translations.
-+ '''
-+
-+ BANNED = lazy_re.compile(r'\+|:|\/|\\\\|\*|\?|\"|\<|\>|\|')
-+
-+ def Validate(self, message):
-+ return not self.BANNED.search(message.GetPresentableContent())
-+
-+ def ValidateAndModify(self, lang, translation):
-+ is_ok = self.Validate(translation)
-+ self.ModifyEachTextPart(lang, translation)
-+ return is_ok
-+
-+ def ModifyTextPart(self, lang, text):
-+ return self.BANNED.sub(' ', text)
-diff --git a/tools/grit/grit/node/custom/filename_unittest.py b/tools/grit/grit/node/custom/filename_unittest.py
-new file mode 100644
-index 0000000000..8e2a6dd64a
---- /dev/null
-+++ b/tools/grit/grit/node/custom/filename_unittest.py
-@@ -0,0 +1,34 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for grit.node.custom.filename'''
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../../..'))
-+
-+import unittest
-+from grit.node.custom import filename
-+from grit import clique
-+from grit import tclib
-+
-+
-+class WindowsFilenameUnittest(unittest.TestCase):
-+
-+ def testValidate(self):
-+ factory = clique.UberClique()
-+ msg = tclib.Message(text='Bingo bongo')
-+ c = factory.MakeClique(msg)
-+ c.SetCustomType(filename.WindowsFilename())
-+ translation = tclib.Translation(id=msg.GetId(), text='Bilingo bolongo:')
-+ c.AddTranslation(translation, 'fr')
-+ self.failUnless(c.MessageForLanguage('fr').GetRealContent() == 'Bilingo bolongo ')
-+
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/node/empty.py b/tools/grit/grit/node/empty.py
-new file mode 100644
-index 0000000000..e19d2c4ddb
---- /dev/null
-+++ b/tools/grit/grit/node/empty.py
-@@ -0,0 +1,64 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Container nodes that don't have any logic.
-+'''
-+
-+from __future__ import print_function
-+
-+from grit.node import base
-+from grit.node import include
-+from grit.node import message
-+from grit.node import misc
-+from grit.node import node_io
-+from grit.node import structure
-+
-+
-+class GroupingNode(base.Node):
-+ '''Base class for all the grouping elements (<structures>, <includes>,
-+ <messages> and <identifiers>).'''
-+ def DefaultAttributes(self):
-+ return {
-+ 'first_id' : '',
-+ 'comment' : '',
-+ 'fallback_to_english' : 'false',
-+ 'fallback_to_low_resolution' : 'false',
-+ }
-+
-+
-+class IncludesNode(GroupingNode):
-+ '''The <includes> element.'''
-+ def _IsValidChild(self, child):
-+ return isinstance(child, (include.IncludeNode, misc.IfNode, misc.PartNode))
-+
-+
-+class MessagesNode(GroupingNode):
-+ '''The <messages> element.'''
-+ def _IsValidChild(self, child):
-+ return isinstance(child, (message.MessageNode, misc.IfNode, misc.PartNode))
-+
-+
-+class StructuresNode(GroupingNode):
-+ '''The <structures> element.'''
-+ def _IsValidChild(self, child):
-+ return isinstance(child, (structure.StructureNode,
-+ misc.IfNode, misc.PartNode))
-+
-+
-+class TranslationsNode(base.Node):
-+ '''The <translations> element.'''
-+ def _IsValidChild(self, child):
-+ return isinstance(child, (node_io.FileNode, misc.IfNode, misc.PartNode))
-+
-+
-+class OutputsNode(base.Node):
-+ '''The <outputs> element.'''
-+ def _IsValidChild(self, child):
-+ return isinstance(child, (node_io.OutputNode, misc.IfNode, misc.PartNode))
-+
-+
-+class IdentifiersNode(GroupingNode):
-+ '''The <identifiers> element.'''
-+ def _IsValidChild(self, child):
-+ return isinstance(child, misc.IdentifierNode)
-diff --git a/tools/grit/grit/node/include.py b/tools/grit/grit/node/include.py
-new file mode 100644
-index 0000000000..b06b9889bb
---- /dev/null
-+++ b/tools/grit/grit/node/include.py
-@@ -0,0 +1,170 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+"""Handling of the <include> element.
-+"""
-+
-+from __future__ import print_function
-+
-+import os
-+
-+from grit import util
-+import grit.format.html_inline
-+import grit.format.rc
-+from grit.format import minifier
-+from grit.node import base
-+
-+class IncludeNode(base.Node):
-+ """An <include> element."""
-+
-+ def __init__(self):
-+ super(IncludeNode, self).__init__()
-+
-+ # Cache flattened data so that we don't flatten the same file
-+ # multiple times.
-+ self._flattened_data = None
-+ # Also keep track of the last filename we flattened to, so we can
-+ # avoid doing it more than once.
-+ self._last_flat_filename = None
-+
-+ def _IsValidChild(self, child):
-+ return False
-+
-+ def _GetFlattenedData(
-+ self, allow_external_script=False, preprocess_only=False):
-+ if not self._flattened_data:
-+ filename = self.ToRealPath(self.GetInputPath())
-+ self._flattened_data = (
-+ grit.format.html_inline.InlineToString(filename, self,
-+ preprocess_only=preprocess_only,
-+ allow_external_script=allow_external_script))
-+ return self._flattened_data.encode('utf-8')
-+
-+ def MandatoryAttributes(self):
-+ return ['name', 'type', 'file']
-+
-+ def DefaultAttributes(self):
-+ """Attributes:
-+ translateable: False if the node has contents that should not be
-+ translated.
-+ preprocess: Takes the same code path as flattenhtml, but it
-+ disables any processing/inlining outside of <if>
-+ and <include>.
-+ compress: The format to compress the data with, e.g. 'gzip'
-+ or 'false' if data should not be compressed.
-+ skip_minify: If true, skips minifying the node's contents.
-+ skip_in_resource_map: If true, do not add to the resource map.
-+ """
-+ return {
-+ 'translateable': 'true',
-+ 'generateid': 'true',
-+ 'filenameonly': 'false',
-+ 'mkoutput': 'false',
-+ 'preprocess': 'false',
-+ 'flattenhtml': 'false',
-+ 'compress': 'default',
-+ 'allowexternalscript': 'false',
-+ 'relativepath': 'false',
-+ 'use_base_dir': 'true',
-+ 'skip_minify': 'false',
-+ 'skip_in_resource_map': 'false',
-+ }
-+
-+ def GetInputPath(self):
-+ # Do not mess with absolute paths, that would make them invalid.
-+ if os.path.isabs(os.path.expandvars(self.attrs['file'])):
-+ return self.attrs['file']
-+
-+ # We have no control over code that calls ToRealPath later, so convert
-+ # the path to be relative against our basedir.
-+ if self.attrs.get('use_base_dir', 'true') != 'true':
-+ # Normalize the directory path to use the appropriate OS separator.
-+ # GetBaseDir() may return paths\like\this or paths/like/this, since it is
-+ # read from the base_dir attribute in the grd file.
-+ norm_base_dir = util.normpath(self.GetRoot().GetBaseDir())
-+ return os.path.relpath(self.attrs['file'], norm_base_dir)
-+
-+ return self.attrs['file']
-+
-+ def FileForLanguage(self, lang, output_dir):
-+ """Returns the file for the specified language. This allows us to return
-+ different files for different language variants of the include file.
-+ """
-+ input_path = self.GetInputPath()
-+ if input_path is None:
-+ return None
-+
-+ return self.ToRealPath(input_path)
-+
-+ def GetDataPackValue(self, lang, encoding):
-+ '''Returns bytes or a str represenation for a data_pack entry.'''
-+ filename = self.ToRealPath(self.GetInputPath())
-+ if self.attrs['flattenhtml'] == 'true':
-+ allow_external_script = self.attrs['allowexternalscript'] == 'true'
-+ data = self._GetFlattenedData(allow_external_script=allow_external_script)
-+ elif self.attrs['preprocess'] == 'true':
-+ data = self._GetFlattenedData(preprocess_only=True)
-+ else:
-+ data = util.ReadFile(filename, util.BINARY)
-+
-+ if self.attrs['skip_minify'] != 'true':
-+ # Note that the minifier will only do anything if a minifier command
-+ # has been set in the command line.
-+ data = minifier.Minify(data, filename)
-+
-+ # Include does not care about the encoding, because it only returns binary
-+ # data.
-+ return self.CompressDataIfNeeded(data)
-+
-+ def Process(self, output_dir):
-+ """Rewrite file references to be base64 encoded data URLs. The new file
-+ will be written to output_dir and the name of the new file is returned."""
-+ filename = self.ToRealPath(self.GetInputPath())
-+ flat_filename = os.path.join(output_dir,
-+ self.attrs['name'] + '_' + os.path.basename(filename))
-+
-+ if self._last_flat_filename == flat_filename:
-+ return
-+
-+ with open(flat_filename, 'wb') as outfile:
-+ outfile.write(self._GetFlattenedData())
-+
-+ self._last_flat_filename = flat_filename
-+ return os.path.basename(flat_filename)
-+
-+ def GetHtmlResourceFilenames(self):
-+ """Returns a set of all filenames inlined by this file."""
-+ allow_external_script = self.attrs['allowexternalscript'] == 'true'
-+ return grit.format.html_inline.GetResourceFilenames(
-+ self.ToRealPath(self.GetInputPath()),
-+ self,
-+ allow_external_script=allow_external_script)
-+
-+ def IsResourceMapSource(self):
-+ skip = self.attrs.get('skip_in_resource_map', 'false') == 'true'
-+ return not skip
-+
-+ @staticmethod
-+ def Construct(parent, name, type, file, translateable=True,
-+ filenameonly=False, mkoutput=False, relativepath=False):
-+ """Creates a new node which is a child of 'parent', with attributes set
-+ by parameters of the same name.
-+ """
-+ # Convert types to appropriate strings
-+ translateable = util.BoolToString(translateable)
-+ filenameonly = util.BoolToString(filenameonly)
-+ mkoutput = util.BoolToString(mkoutput)
-+ relativepath = util.BoolToString(relativepath)
-+
-+ node = IncludeNode()
-+ node.StartParsing('include', parent)
-+ node.HandleAttribute('name', name)
-+ node.HandleAttribute('type', type)
-+ node.HandleAttribute('file', file)
-+ node.HandleAttribute('translateable', translateable)
-+ node.HandleAttribute('filenameonly', filenameonly)
-+ node.HandleAttribute('mkoutput', mkoutput)
-+ node.HandleAttribute('relativepath', relativepath)
-+ node.EndParsing()
-+ return node
-diff --git a/tools/grit/grit/node/include_unittest.py b/tools/grit/grit/node/include_unittest.py
-new file mode 100644
-index 0000000000..4c658f1ffe
---- /dev/null
-+++ b/tools/grit/grit/node/include_unittest.py
-@@ -0,0 +1,134 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2013 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for include.IncludeNode'''
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+import unittest
-+import zlib
-+
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+from grit.node import misc
-+from grit.node import include
-+from grit.node import empty
-+from grit import util
-+
-+
-+def checkIsGzipped(filename, compress_attr):
-+ test_data_root = util.PathFromRoot('grit/testdata')
-+ root = util.ParseGrdForUnittest(
-+ '''
-+ <includes>
-+ <include name="TEST_TXT" file="%s" %s type="BINDATA"/>
-+ </includes>''' % (filename, compress_attr),
-+ base_dir=test_data_root)
-+ node, = root.GetChildrenOfType(include.IncludeNode)
-+ compressed = node.GetDataPackValue(lang='en', encoding=util.BINARY)
-+
-+ decompressed_data = zlib.decompress(compressed, 16 + zlib.MAX_WBITS)
-+ expected = util.ReadFile(os.path.join(test_data_root, filename), util.BINARY)
-+ return expected == decompressed_data
-+
-+
-+class IncludeNodeUnittest(unittest.TestCase):
-+ def testGetPath(self):
-+ root = misc.GritNode()
-+ root.StartParsing(u'grit', None)
-+ root.HandleAttribute(u'latest_public_release', u'0')
-+ root.HandleAttribute(u'current_release', u'1')
-+ root.HandleAttribute(u'base_dir', r'..\resource')
-+ release = misc.ReleaseNode()
-+ release.StartParsing(u'release', root)
-+ release.HandleAttribute(u'seq', u'1')
-+ root.AddChild(release)
-+ includes = empty.IncludesNode()
-+ includes.StartParsing(u'includes', release)
-+ release.AddChild(includes)
-+ include_node = include.IncludeNode()
-+ include_node.StartParsing(u'include', includes)
-+ include_node.HandleAttribute(u'file', r'flugel\kugel.pdf')
-+ includes.AddChild(include_node)
-+ root.EndParsing()
-+
-+ self.assertEqual(root.ToRealPath(include_node.GetInputPath()),
-+ util.normpath(
-+ os.path.join(r'../resource', r'flugel/kugel.pdf')))
-+
-+ def testGetPathNoBasedir(self):
-+ root = misc.GritNode()
-+ root.StartParsing(u'grit', None)
-+ root.HandleAttribute(u'latest_public_release', u'0')
-+ root.HandleAttribute(u'current_release', u'1')
-+ root.HandleAttribute(u'base_dir', r'..\resource')
-+ release = misc.ReleaseNode()
-+ release.StartParsing(u'release', root)
-+ release.HandleAttribute(u'seq', u'1')
-+ root.AddChild(release)
-+ includes = empty.IncludesNode()
-+ includes.StartParsing(u'includes', release)
-+ release.AddChild(includes)
-+ include_node = include.IncludeNode()
-+ include_node.StartParsing(u'include', includes)
-+ include_node.HandleAttribute(u'file', r'flugel\kugel.pdf')
-+ include_node.HandleAttribute(u'use_base_dir', u'false')
-+ includes.AddChild(include_node)
-+ root.EndParsing()
-+
-+ last_dir = os.path.basename(os.getcwd())
-+ expected_path = util.normpath(os.path.join(
-+ u'..', last_dir, u'flugel/kugel.pdf'))
-+ self.assertEqual(root.ToRealPath(include_node.GetInputPath()),
-+ expected_path)
-+
-+ def testCompressGzip(self):
-+ self.assertTrue(checkIsGzipped('test_text.txt', 'compress="gzip"'))
-+
-+ def testCompressGzipByDefault(self):
-+ self.assertTrue(checkIsGzipped('test_html.html', ''))
-+ self.assertTrue(checkIsGzipped('test_js.js', ''))
-+ self.assertTrue(checkIsGzipped('test_css.css', ''))
-+ self.assertTrue(checkIsGzipped('test_svg.svg', ''))
-+
-+ self.assertTrue(checkIsGzipped('test_html.html', 'compress="default"'))
-+ self.assertTrue(checkIsGzipped('test_js.js', 'compress="default"'))
-+ self.assertTrue(checkIsGzipped('test_css.css', 'compress="default"'))
-+ self.assertTrue(checkIsGzipped('test_svg.svg', 'compress="default"'))
-+
-+ def testSkipInResourceMap(self):
-+ root = util.ParseGrdForUnittest('''
-+ <includes>
-+ <include name="TEST1_TXT" file="test1_text.txt" type="BINDATA"/>
-+ <include name="TEST2_TXT" file="test1_text.txt" type="BINDATA"
-+ skip_in_resource_map="true"/>
-+ <include name="TEST3_TXT" file="test1_text.txt" type="BINDATA"
-+ skip_in_resource_map="false"/>
-+ </includes>''', base_dir = util.PathFromRoot('grit/testdata'))
-+ inc = root.GetChildrenOfType(include.IncludeNode)
-+ self.assertTrue(inc[0].IsResourceMapSource())
-+ self.assertFalse(inc[1].IsResourceMapSource())
-+ self.assertTrue(inc[2].IsResourceMapSource())
-+
-+ def testAcceptsPreprocess(self):
-+ root = util.ParseGrdForUnittest(
-+ '''
-+ <includes>
-+ <include name="PREPROCESS_TEST" file="preprocess_test.html"
-+ preprocess="true" compress="false" type="chrome_html"/>
-+ </includes>''',
-+ base_dir=util.PathFromRoot('grit/testdata'))
-+ inc, = root.GetChildrenOfType(include.IncludeNode)
-+ result = inc.GetDataPackValue(lang='en', encoding=util.BINARY)
-+ self.assertIn(b'should be kept', result)
-+ self.assertIn(b'in the middle...', result)
-+ self.assertNotIn(b'should be removed', result)
-+
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/node/mapping.py b/tools/grit/grit/node/mapping.py
-new file mode 100644
-index 0000000000..6297f0b666
---- /dev/null
-+++ b/tools/grit/grit/node/mapping.py
-@@ -0,0 +1,60 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Maps each node type to an implementation class.
-+When adding a new node type, you add to this mapping.
-+'''
-+
-+from __future__ import print_function
-+
-+from grit import exception
-+
-+from grit.node import empty
-+from grit.node import include
-+from grit.node import message
-+from grit.node import misc
-+from grit.node import node_io
-+from grit.node import structure
-+from grit.node import variant
-+
-+
-+_ELEMENT_TO_CLASS = {
-+ 'identifiers' : empty.IdentifiersNode,
-+ 'includes' : empty.IncludesNode,
-+ 'messages' : empty.MessagesNode,
-+ 'outputs' : empty.OutputsNode,
-+ 'structures' : empty.StructuresNode,
-+ 'translations' : empty.TranslationsNode,
-+ 'include' : include.IncludeNode,
-+ 'emit' : node_io.EmitNode,
-+ 'file' : node_io.FileNode,
-+ 'output' : node_io.OutputNode,
-+ 'ex' : message.ExNode,
-+ 'message' : message.MessageNode,
-+ 'ph' : message.PhNode,
-+ 'else' : misc.ElseNode,
-+ 'grit' : misc.GritNode,
-+ 'identifier' : misc.IdentifierNode,
-+ 'if' : misc.IfNode,
-+ 'part' : misc.PartNode,
-+ 'release' : misc.ReleaseNode,
-+ 'then' : misc.ThenNode,
-+ 'structure' : structure.StructureNode,
-+ 'skeleton' : variant.SkeletonNode,
-+}
-+
-+
-+def ElementToClass(name, typeattr):
-+ '''Maps an element to a class that handles the element.
-+
-+ Args:
-+ name: 'element' (the name of the element)
-+ typeattr: 'type' (the value of the type attribute, if present, else None)
-+
-+ Return:
-+ type
-+ '''
-+ if name not in _ELEMENT_TO_CLASS:
-+ raise exception.UnknownElement()
-+ return _ELEMENT_TO_CLASS[name]
-diff --git a/tools/grit/grit/node/message.py b/tools/grit/grit/node/message.py
-new file mode 100644
-index 0000000000..4fa83cf26b
---- /dev/null
-+++ b/tools/grit/grit/node/message.py
-@@ -0,0 +1,362 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Handling of the <message> element.
-+'''
-+
-+from __future__ import print_function
-+
-+import re
-+
-+import six
-+
-+from grit.node import base
-+
-+from grit import clique
-+from grit import exception
-+from grit import lazy_re
-+from grit import tclib
-+from grit import util
-+
-+
-+# Matches exactly three dots ending a line or followed by whitespace.
-+_ELLIPSIS_PATTERN = lazy_re.compile(r'(?<!\.)\.\.\.(?=$|\s)')
-+_ELLIPSIS_SYMBOL = u'\u2026' # Ellipsis
-+
-+# Finds whitespace at the start and end of a string which can be multiline.
-+_WHITESPACE = lazy_re.compile(r'(?P<start>\s*)(?P<body>.+?)(?P<end>\s*)\Z',
-+ re.DOTALL | re.MULTILINE)
-+
-+# <ph> placeholder elements should contain the special character formatters
-+# used to format <ph> element content.
-+# Android format.
-+_ANDROID_FORMAT = (r'%[1-9]+\$'
-+ r'([-#+ 0,(]*)([0-9]+)?(\.[0-9]+)?'
-+ r'([bBhHsScCdoxXeEfgGaAtT%n])')
-+# Chrome l10n format.
-+_CHROME_FORMAT = r'\$+\d'
-+# Windows EWT numeric and GRIT %s %d formats.
-+_OTHER_FORMAT = r'%[0-9sd]'
-+
-+# Finds formatters that must be in a placeholder (<ph>) element.
-+_FORMATTERS = lazy_re.compile(
-+ '(%s)|(%s)|(%s)' % (_ANDROID_FORMAT, _CHROME_FORMAT, _OTHER_FORMAT))
-+_BAD_PLACEHOLDER_MSG = ('ERROR: Placeholder formatter found outside of <ph> '
-+ 'tag in message "%s" in %s.')
-+_INVALID_PH_CHAR_MSG = ('ERROR: Invalid format characters found in message '
-+ '"%s" <ph> tag in %s.')
-+
-+# Finds HTML tag tokens.
-+_HTMLTOKEN = lazy_re.compile(r'<[/]?[a-z][a-z0-9]*[^>]*>', re.I)
-+
-+# Finds HTML entities.
-+_HTMLENTITY = lazy_re.compile(r'&[^\s]*;')
-+
-+
-+class MessageNode(base.ContentNode):
-+ '''A <message> element.'''
-+
-+ # For splitting a list of things that can be separated by commas or
-+ # whitespace
-+ _SPLIT_RE = lazy_re.compile(r'\s*,\s*|\s+')
-+
-+ def __init__(self):
-+ super(MessageNode, self).__init__()
-+ # Valid after EndParsing, this is the MessageClique that contains the
-+ # source message and any translations of it that have been loaded.
-+ self.clique = None
-+
-+ # We don't send leading and trailing whitespace into the translation
-+ # console, but rather tack it onto the source message and any
-+ # translations when formatting them into RC files or what have you.
-+ self.ws_at_start = '' # Any whitespace characters at the start of the text
-+ self.ws_at_end = '' # --"-- at the end of the text
-+
-+ # A list of "shortcut groups" this message is in. We check to make sure
-+ # that shortcut keys (e.g. &J) within each shortcut group are unique.
-+ self.shortcut_groups_ = []
-+
-+ # Formatter-specific data used to control the output of individual strings.
-+ # formatter_data is a space separated list of C preprocessor-style
-+ # definitions. Names without values are given the empty string value.
-+ # Example: "foo=5 bar baz=100"
-+ self.formatter_data = {}
-+
-+ # Whether or not to convert ... -> U+2026 within Translate().
-+ self._replace_ellipsis = False
-+
-+ def _IsValidChild(self, child):
-+ return isinstance(child, (PhNode))
-+
-+ def _IsValidAttribute(self, name, value):
-+ if name not in [
-+ 'name', 'offset', 'translateable', 'desc', 'meaning',
-+ 'internal_comment', 'shortcut_groups', 'custom_type', 'validation_expr',
-+ 'use_name_for_id', 'sub_variable', 'formatter_data',
-+ 'is_accessibility_with_no_ui'
-+ ]:
-+ return False
-+ if (name in ('translateable', 'sub_variable') and
-+ value not in ['true', 'false']):
-+ return False
-+ return True
-+
-+ def SetReplaceEllipsis(self, value):
-+ r'''Sets whether to replace ... with \u2026.
-+ '''
-+ self._replace_ellipsis = value
-+
-+ def MandatoryAttributes(self):
-+ return ['name|offset']
-+
-+ def DefaultAttributes(self):
-+ return {
-+ 'custom_type': '',
-+ 'desc': '',
-+ 'formatter_data': '',
-+ 'internal_comment': '',
-+ 'is_accessibility_with_no_ui': 'false',
-+ 'meaning': '',
-+ 'shortcut_groups': '',
-+ 'sub_variable': 'false',
-+ 'translateable': 'true',
-+ 'use_name_for_id': 'false',
-+ 'validation_expr': '',
-+ }
-+
-+ def HandleAttribute(self, attrib, value):
-+ base.ContentNode.HandleAttribute(self, attrib, value)
-+ if attrib != 'formatter_data':
-+ return
-+
-+ # Parse value, a space-separated list of defines, into a dict.
-+ # Example: "foo=5 bar" -> {'foo':'5', 'bar':''}
-+ for item in value.split():
-+ name, _, val = item.partition('=')
-+ self.formatter_data[name] = val
-+
-+ def GetTextualIds(self):
-+ '''
-+ Returns the concatenation of the parent's node first_id and
-+ this node's offset if it has one, otherwise just call the
-+ superclass' implementation
-+ '''
-+ if 'offset' not in self.attrs:
-+ return super(MessageNode, self).GetTextualIds()
-+
-+ # we search for the first grouping node in the parents' list
-+ # to take care of the case where the first parent is an <if> node
-+ grouping_parent = self.parent
-+ import grit.node.empty
-+ while grouping_parent and not isinstance(grouping_parent,
-+ grit.node.empty.GroupingNode):
-+ grouping_parent = grouping_parent.parent
-+
-+ assert 'first_id' in grouping_parent.attrs
-+ return [grouping_parent.attrs['first_id'] + '_' + self.attrs['offset']]
-+
-+ def IsTranslateable(self):
-+ return self.attrs['translateable'] == 'true'
-+
-+ def EndParsing(self):
-+ super(MessageNode, self).EndParsing()
-+
-+ # Make the text (including placeholder references) and list of placeholders,
-+ # verify placeholder formats, then strip and store leading and trailing
-+ # whitespace and create the tclib.Message() and a clique to contain it.
-+
-+ text = ''
-+ placeholders = []
-+
-+ for item in self.mixed_content:
-+ if isinstance(item, six.string_types):
-+ # Not a <ph> element: fail if any <ph> formatters are detected.
-+ if _FORMATTERS.search(item):
-+ print(_BAD_PLACEHOLDER_MSG % (item, self.source))
-+ raise exception.PlaceholderNotInsidePhNode
-+ text += item
-+ else:
-+ # Extract the <ph> element components.
-+ presentation = item.attrs['name'].upper()
-+ text += presentation
-+ ex = ' ' # <ex> example element cdata if present.
-+ if len(item.children):
-+ ex = item.children[0].GetCdata()
-+ original = item.GetCdata()
-+
-+ # Sanity check the <ph> element content.
-+ cdata = original
-+ # Replace all HTML tag tokens in cdata.
-+ match = _HTMLTOKEN.search(cdata)
-+ while match:
-+ cdata = cdata.replace(match.group(0), '_')
-+ match = _HTMLTOKEN.search(cdata)
-+ # Replace all HTML entities in cdata.
-+ match = _HTMLENTITY.search(cdata)
-+ while match:
-+ cdata = cdata.replace(match.group(0), '_')
-+ match = _HTMLENTITY.search(cdata)
-+ # Remove first matching formatter from cdata.
-+ match = _FORMATTERS.search(cdata)
-+ if match:
-+ cdata = cdata.replace(match.group(0), '')
-+ # Fail if <ph> special chars remain in cdata.
-+ if re.search(r'[%\$]', cdata):
-+ message_id = self.attrs['name'] + ' ' + original;
-+ print(_INVALID_PH_CHAR_MSG % (message_id, self.source))
-+ raise exception.InvalidCharactersInsidePhNode
-+
-+ # Otherwise, accept this <ph> placeholder.
-+ placeholders.append(tclib.Placeholder(presentation, original, ex))
-+
-+ m = _WHITESPACE.match(text)
-+ if m:
-+ self.ws_at_start = m.group('start')
-+ self.ws_at_end = m.group('end')
-+ text = m.group('body')
-+
-+ self.shortcut_groups_ = self._SPLIT_RE.split(self.attrs['shortcut_groups'])
-+ self.shortcut_groups_ = [i for i in self.shortcut_groups_ if i != '']
-+
-+ description_or_id = self.attrs['desc']
-+ if description_or_id == '' and 'name' in self.attrs:
-+ description_or_id = 'ID: %s' % self.attrs['name']
-+
-+ assigned_id = None
-+ if self.attrs['use_name_for_id'] == 'true':
-+ assigned_id = self.attrs['name']
-+ message = tclib.Message(text=text, placeholders=placeholders,
-+ description=description_or_id,
-+ meaning=self.attrs['meaning'],
-+ assigned_id=assigned_id)
-+ self.InstallMessage(message)
-+
-+ def InstallMessage(self, message):
-+ '''Sets this node's clique from a tclib.Message instance.
-+
-+ Args:
-+ message: A tclib.Message.
-+ '''
-+ self.clique = self.UberClique().MakeClique(message, self.IsTranslateable())
-+ for group in self.shortcut_groups_:
-+ self.clique.AddToShortcutGroup(group)
-+ if self.attrs['custom_type'] != '':
-+ self.clique.SetCustomType(util.NewClassInstance(self.attrs['custom_type'],
-+ clique.CustomType))
-+ elif self.attrs['validation_expr'] != '':
-+ self.clique.SetCustomType(
-+ clique.OneOffCustomType(self.attrs['validation_expr']))
-+
-+ def SubstituteMessages(self, substituter):
-+ '''Applies substitution to this message.
-+
-+ Args:
-+ substituter: a grit.util.Substituter object.
-+ '''
-+ message = substituter.SubstituteMessage(self.clique.GetMessage())
-+ if message is not self.clique.GetMessage():
-+ self.InstallMessage(message)
-+
-+ def GetCliques(self):
-+ return [self.clique] if self.clique else []
-+
-+ def Translate(self, lang):
-+ '''Returns a translated version of this message.
-+ '''
-+ assert self.clique
-+ msg = self.clique.MessageForLanguage(lang,
-+ self.PseudoIsAllowed(),
-+ self.ShouldFallbackToEnglish()
-+ ).GetRealContent()
-+ if self._replace_ellipsis:
-+ msg = _ELLIPSIS_PATTERN.sub(_ELLIPSIS_SYMBOL, msg)
-+ # Always remove all byte order marks (\uFEFF) https://crbug.com/1033305
-+ msg = msg.replace(u'\uFEFF','')
-+ return msg.replace('[GRITLANGCODE]', lang)
-+
-+ def NameOrOffset(self):
-+ key = 'name' if 'name' in self.attrs else 'offset'
-+ return self.attrs[key]
-+
-+ def ExpandVariables(self):
-+ '''We always expand variables on Messages.'''
-+ return True
-+
-+ def GetDataPackValue(self, lang, encoding):
-+ '''Returns a str represenation for a data_pack entry.'''
-+ message = self.ws_at_start + self.Translate(lang) + self.ws_at_end
-+ return util.Encode(message, encoding)
-+
-+ def IsResourceMapSource(self):
-+ return True
-+
-+ @staticmethod
-+ def Construct(parent, message, name, desc='', meaning='', translateable=True):
-+ '''Constructs a new message node that is a child of 'parent', with the
-+ name, desc, meaning and translateable attributes set using the same-named
-+ parameters and the text of the message and any placeholders taken from
-+ 'message', which must be a tclib.Message() object.'''
-+ # Convert type to appropriate string
-+ translateable = 'true' if translateable else 'false'
-+
-+ node = MessageNode()
-+ node.StartParsing('message', parent)
-+ node.HandleAttribute('name', name)
-+ node.HandleAttribute('desc', desc)
-+ node.HandleAttribute('meaning', meaning)
-+ node.HandleAttribute('translateable', translateable)
-+
-+ items = message.GetContent()
-+ for ix, item in enumerate(items):
-+ if isinstance(item, six.string_types):
-+ # Ensure whitespace at front and back of message is correctly handled.
-+ if ix == 0:
-+ item = "'''" + item
-+ if ix == len(items) - 1:
-+ item = item + "'''"
-+
-+ node.AppendContent(item)
-+ else:
-+ phnode = PhNode()
-+ phnode.StartParsing('ph', node)
-+ phnode.HandleAttribute('name', item.GetPresentation())
-+ phnode.AppendContent(item.GetOriginal())
-+
-+ if len(item.GetExample()) and item.GetExample() != ' ':
-+ exnode = ExNode()
-+ exnode.StartParsing('ex', phnode)
-+ exnode.AppendContent(item.GetExample())
-+ exnode.EndParsing()
-+ phnode.AddChild(exnode)
-+
-+ phnode.EndParsing()
-+ node.AddChild(phnode)
-+
-+ node.EndParsing()
-+ return node
-+
-+
-+class PhNode(base.ContentNode):
-+ '''A <ph> element.'''
-+
-+ def _IsValidChild(self, child):
-+ return isinstance(child, ExNode)
-+
-+ def MandatoryAttributes(self):
-+ return ['name']
-+
-+ def EndParsing(self):
-+ super(PhNode, self).EndParsing()
-+ # We only allow a single example for each placeholder
-+ if len(self.children) > 1:
-+ raise exception.TooManyExamples()
-+
-+ def GetTextualIds(self):
-+ # The 'name' attribute is not an ID.
-+ return []
-+
-+
-+class ExNode(base.ContentNode):
-+ '''An <ex> element.'''
-+ pass
-diff --git a/tools/grit/grit/node/message_unittest.py b/tools/grit/grit/node/message_unittest.py
-new file mode 100644
-index 0000000000..7a4cbbedc2
---- /dev/null
-+++ b/tools/grit/grit/node/message_unittest.py
-@@ -0,0 +1,380 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for grit.node.message'''
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+import unittest
-+
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+from grit import exception
-+from grit import tclib
-+from grit import util
-+from grit.node import message
-+
-+class MessageUnittest(unittest.TestCase):
-+ def testMessage(self):
-+ root = util.ParseGrdForUnittest('''
-+ <messages>
-+ <message name="IDS_GREETING"
-+ desc="Printed to greet the currently logged in user">
-+ Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, how are you doing today?
-+ </message>
-+ </messages>''')
-+ msg, = root.GetChildrenOfType(message.MessageNode)
-+ cliques = msg.GetCliques()
-+ content = cliques[0].GetMessage().GetPresentableContent()
-+ self.failUnless(content == 'Hello USERNAME, how are you doing today?')
-+
-+ def testMessageWithWhitespace(self):
-+ root = util.ParseGrdForUnittest("""\
-+ <messages>
-+ <message name="IDS_BLA" desc="">
-+ ''' Hello there <ph name="USERNAME">%s</ph> '''
-+ </message>
-+ </messages>""")
-+ msg, = root.GetChildrenOfType(message.MessageNode)
-+ content = msg.GetCliques()[0].GetMessage().GetPresentableContent()
-+ self.failUnless(content == 'Hello there USERNAME')
-+ self.failUnless(msg.ws_at_start == ' ')
-+ self.failUnless(msg.ws_at_end == ' ')
-+
-+ def testConstruct(self):
-+ msg = tclib.Message(text=" Hello USERNAME, how are you? BINGO\t\t",
-+ placeholders=[tclib.Placeholder('USERNAME', '%s', 'Joi'),
-+ tclib.Placeholder('BINGO', '%d', '11')])
-+ msg_node = message.MessageNode.Construct(None, msg, 'BINGOBONGO')
-+ self.failUnless(msg_node.children[0].name == 'ph')
-+ self.failUnless(msg_node.children[0].children[0].name == 'ex')
-+ self.failUnless(msg_node.children[0].children[0].GetCdata() == 'Joi')
-+ self.failUnless(msg_node.children[1].children[0].GetCdata() == '11')
-+ self.failUnless(msg_node.ws_at_start == ' ')
-+ self.failUnless(msg_node.ws_at_end == '\t\t')
-+
-+ def testUnicodeConstruct(self):
-+ text = u'Howdie \u00fe'
-+ msg = tclib.Message(text=text)
-+ msg_node = message.MessageNode.Construct(None, msg, 'BINGOBONGO')
-+ msg_from_node = msg_node.GetCdata()
-+ self.failUnless(msg_from_node == text)
-+
-+ def testFormatterData(self):
-+ root = util.ParseGrdForUnittest("""\
-+ <messages>
-+ <message name="IDS_BLA" desc="" formatter_data=" foo=123 bar qux=low">
-+ Text
-+ </message>
-+ </messages>""")
-+ msg, = root.GetChildrenOfType(message.MessageNode)
-+ expected_formatter_data = {
-+ 'foo': '123',
-+ 'bar': '',
-+ 'qux': 'low'}
-+
-+ # Can't use assertDictEqual, not available in Python 2.6, so do it
-+ # by hand.
-+ self.failUnlessEqual(len(expected_formatter_data),
-+ len(msg.formatter_data))
-+ for key in expected_formatter_data:
-+ self.failUnlessEqual(expected_formatter_data[key],
-+ msg.formatter_data[key])
-+
-+ def testReplaceEllipsis(self):
-+ root = util.ParseGrdForUnittest('''
-+ <messages>
-+ <message name="IDS_GREETING" desc="">
-+ A...B.... <ph name="PH">%s<ex>A</ex></ph>... B... C...
-+ </message>
-+ </messages>''')
-+ msg, = root.GetChildrenOfType(message.MessageNode)
-+ msg.SetReplaceEllipsis(True)
-+ content = msg.Translate('en')
-+ self.failUnlessEqual(u'A...B.... %s\u2026 B\u2026 C\u2026', content)
-+
-+ def testRemoveByteOrderMark(self):
-+ root = util.ParseGrdForUnittest(u'''
-+ <messages>
-+ <message name="IDS_HAS_BOM" desc="">
-+ \uFEFFThis\uFEFF i\uFEFFs OK\uFEFF
-+ </message>
-+ </messages>''')
-+ msg, = root.GetChildrenOfType(message.MessageNode)
-+ content = msg.Translate('en')
-+ self.failUnlessEqual(u'This is OK', content)
-+
-+ def testPlaceholderHasTooManyExamples(self):
-+ try:
-+ util.ParseGrdForUnittest("""\
-+ <messages>
-+ <message name="IDS_FOO" desc="foo">
-+ Hi <ph name="NAME">$1<ex>Joi</ex><ex>Joy</ex></ph>
-+ </message>
-+ </messages>""")
-+ except exception.TooManyExamples:
-+ return
-+ self.fail('Should have gotten exception')
-+
-+ def testPlaceholderHasInvalidName(self):
-+ try:
-+ util.ParseGrdForUnittest("""\
-+ <messages>
-+ <message name="IDS_FOO" desc="foo">
-+ Hi <ph name="ABC!">$1</ph>
-+ </message>
-+ </messages>""")
-+ except exception.InvalidPlaceholderName:
-+ return
-+ self.fail('Should have gotten exception')
-+
-+ def testChromeLocalizedFormatIsInsidePhNode(self):
-+ try:
-+ util.ParseGrdForUnittest("""\
-+ <messages>
-+ <message name="IDS_CHROME_L10N" desc="l10n format">
-+ This message is missing the ph node: $1
-+ </message>
-+ </messages>""")
-+ except exception.PlaceholderNotInsidePhNode:
-+ return
-+ self.fail('Should have gotten exception')
-+
-+ def testAndroidStringFormatIsInsidePhNode(self):
-+ try:
-+ util.ParseGrdForUnittest("""\
-+ <messages>
-+ <message name="IDS_ANDROID" desc="string format">
-+ This message is missing a ph node: %1$s
-+ </message>
-+ </messages>""")
-+ except exception.PlaceholderNotInsidePhNode:
-+ return
-+ self.fail('Should have gotten exception')
-+
-+ def testAndroidIntegerFormatIsInsidePhNode(self):
-+ try:
-+ util.ParseGrdForUnittest("""\
-+ <messages>
-+ <message name="IDS_ANDROID" desc="integer format">
-+ This message is missing a ph node: %2$d
-+ </message>
-+ </messages>""")
-+ except exception.PlaceholderNotInsidePhNode:
-+ return
-+ self.fail('Should have gotten exception')
-+
-+ def testAndroidIntegerWidthFormatIsInsidePhNode(self):
-+ try:
-+ util.ParseGrdForUnittest("""\
-+ <messages>
-+ <message name="IDS_ANDROID" desc="integer width format">
-+ This message is missing a ph node: %2$3d
-+ </message>
-+ </messages>""")
-+ except exception.PlaceholderNotInsidePhNode:
-+ return
-+ self.fail('Should have gotten exception')
-+
-+ def testValidAndroidIntegerWidthFormatInPhNode(self):
-+ try:
-+ util.ParseGrdForUnittest("""\
-+ <messages>
-+ <message name="IDS_ANDROID_WIDTH">
-+ <ph name="VALID">%2$3d<ex>042</ex></ph>
-+ </message>
-+ </messages>""")
-+ except:
-+ self.fail('Should not have gotten exception')
-+
-+ def testAndroidFloatFormatIsInsidePhNode(self):
-+ try:
-+ util.ParseGrdForUnittest("""\
-+ <messages>
-+ <message name="IDS_ANDROID" desc="float number format">
-+ This message is missing a ph node: %3$4.5f
-+ </message>
-+ </messages>""")
-+ except exception.PlaceholderNotInsidePhNode:
-+ return
-+ self.fail('Should have gotten exception')
-+
-+ def testGritStringFormatIsInsidePhNode(self):
-+ try:
-+ util.ParseGrdForUnittest("""\
-+ <messages>
-+ <message name="IDS_GRIT_STRING" desc="grit string format">
-+ This message is missing the ph node: %s
-+ </message>
-+ </messages>""")
-+ except exception.PlaceholderNotInsidePhNode:
-+ return
-+ self.fail('Should have gotten exception')
-+
-+ def testGritIntegerFormatIsInsidePhNode(self):
-+ try:
-+ util.ParseGrdForUnittest("""\
-+ <messages>
-+ <message name="IDS_GRIT_INTEGER" desc="grit integer format">
-+ This message is missing the ph node: %d
-+ </message>
-+ </messages>""")
-+ except exception.PlaceholderNotInsidePhNode:
-+ return
-+ self.fail('Should have gotten exception')
-+
-+ def testWindowsETWIntegerFormatIsInsidePhNode(self):
-+ try:
-+ util.ParseGrdForUnittest("""\
-+ <messages>
-+ <message name="IDS_WINDOWS_ETW" desc="ETW tracing integer">
-+ This message is missing the ph node: %1
-+ </message>
-+ </messages>""")
-+ except exception.PlaceholderNotInsidePhNode:
-+ return
-+ self.fail('Should have gotten exception')
-+
-+ def testValidMultipleFormattersInsidePhNodes(self):
-+ root = util.ParseGrdForUnittest("""\
-+ <messages>
-+ <message name="IDS_MULTIPLE_FORMATTERS">
-+ <ph name="ERROR_COUNT">%1$d<ex>1</ex></ph> error, <ph name="WARNING_COUNT">%2$d<ex>1</ex></ph> warning
-+ </message>
-+ </messages>""")
-+ msg, = root.GetChildrenOfType(message.MessageNode)
-+ cliques = msg.GetCliques()
-+ content = cliques[0].GetMessage().GetPresentableContent()
-+ self.failUnless(content == 'ERROR_COUNT error, WARNING_COUNT warning')
-+
-+ def testMultipleFormattersAreInsidePhNodes(self):
-+ failed = True
-+ try:
-+ util.ParseGrdForUnittest("""\
-+ <messages>
-+ <message name="IDS_MULTIPLE_FORMATTERS">
-+ %1$d error, %2$d warning
-+ </message>
-+ </messages>""")
-+ except exception.PlaceholderNotInsidePhNode:
-+ failed = False
-+ if failed:
-+ self.fail('Should have gotten exception')
-+ return
-+
-+ failed = True
-+ try:
-+ util.ParseGrdForUnittest("""\
-+ <messages>
-+ <message name="IDS_MULTIPLE_FORMATTERS">
-+ <ph name="ERROR_COUNT">%1$d<ex>1</ex></ph> error, %2$d warning
-+ </message>
-+ </messages>""")
-+ except exception.PlaceholderNotInsidePhNode:
-+ failed = False
-+ if failed:
-+ self.fail('Should have gotten exception')
-+ return
-+
-+ failed = True
-+ try:
-+ util.ParseGrdForUnittest("""\
-+ <messages>
-+ <message name="IDS_MULTIPLE_FORMATTERS">
-+ <ph name="INVALID">%1$d %2$d</ph>
-+ </message>
-+ </messages>""")
-+ except exception.InvalidCharactersInsidePhNode:
-+ failed = False
-+ if failed:
-+ self.fail('Should have gotten exception')
-+ return
-+
-+ def testValidHTMLFormatInsidePhNode(self):
-+ try:
-+ util.ParseGrdForUnittest("""\
-+ <messages>
-+ <message name="IDS_HTML">
-+ <ph name="VALID">&lt;span&gt;$1&lt;/span&gt;<ex>1</ex></ph>
-+ </message>
-+ </messages>""")
-+ except:
-+ self.fail('Should not have gotten exception')
-+
-+ def testValidHTMLWithAttributesFormatInsidePhNode(self):
-+ try:
-+ util.ParseGrdForUnittest("""\
-+ <messages>
-+ <message name="IDS_HTML_ATTRIBUTE">
-+ <ph name="VALID">&lt;span attribute="js:$this %"&gt;$2&lt;/span&gt;<ex>2</ex></ph>
-+ </message>
-+ </messages>""")
-+ except:
-+ self.fail('Should not have gotten exception')
-+
-+ def testValidHTMLEntityFormatInsidePhNode(self):
-+ try:
-+ util.ParseGrdForUnittest("""\
-+ <messages>
-+ <message name="IDS_ENTITY">
-+ <ph name="VALID">&gt;%1$d&lt;<ex>1</ex></ph>
-+ </message>
-+ </messages>""")
-+ except:
-+ self.fail('Should not have gotten exception')
-+
-+ def testValidMultipleDollarFormatInsidePhNode(self):
-+ try:
-+ util.ParseGrdForUnittest("""\
-+ <messages>
-+ <message name="IDS_DOLLARS" desc="l10n dollars format">
-+ <ph name="VALID">$$1</ph>
-+ </message>
-+ </messages>""")
-+ except:
-+ self.fail('Should not have gotten exception')
-+
-+ def testInvalidDollarCharacterInsidePhNode(self):
-+ try:
-+ util.ParseGrdForUnittest("""\
-+ <messages>
-+ <message name="IDS_BAD_DOLLAR">
-+ <ph name="INVALID">%1$d $</ph>
-+ </message>
-+ </messages>""")
-+ except exception.InvalidCharactersInsidePhNode:
-+ return
-+ self.fail('Should have gotten exception')
-+
-+ def testInvalidPercentCharacterInsidePhNode(self):
-+ try:
-+ util.ParseGrdForUnittest("""\
-+ <messages>
-+ <message name="IDS_BAD_PERCENT">
-+ <ph name="INVALID">%1$d %</ph>
-+ </message>
-+ </messages>""")
-+ except exception.InvalidCharactersInsidePhNode:
-+ return
-+ self.fail('Should have gotten exception')
-+
-+ def testInvalidMixedFormatCharactersInsidePhNode(self):
-+ try:
-+ util.ParseGrdForUnittest("""\
-+ <messages>
-+ <message name="IDS_MIXED_FORMATS">
-+ <ph name="INVALID">%1$2</ph>
-+ </message>
-+ </messages>""")
-+ except exception.InvalidCharactersInsidePhNode:
-+ return
-+ self.fail('Should have gotten exception')
-+
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/node/misc.py b/tools/grit/grit/node/misc.py
-new file mode 100644
-index 0000000000..2d8b06d6a5
---- /dev/null
-+++ b/tools/grit/grit/node/misc.py
-@@ -0,0 +1,707 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+"""Miscellaneous node types.
-+"""
-+
-+from __future__ import print_function
-+
-+import os.path
-+import re
-+import sys
-+
-+import six
-+
-+from grit import constants
-+from grit import exception
-+from grit import util
-+from grit.extern import FP
-+from grit.node import base
-+from grit.node import message
-+from grit.node import node_io
-+
-+
-+# Python 3 doesn't have long() as int() works everywhere. But we really do need
-+# the long() behavior on Python 2 as our ids are much too large for int().
-+try:
-+ long
-+except NameError:
-+ long = int
-+
-+
-+# RTL languages
-+# TODO(jennyz): remove this fixed set of RTL language array
-+# now that generic expand_variable code exists.
-+_RTL_LANGS = (
-+ 'ar', # Arabic
-+ 'fa', # Farsi
-+ 'iw', # Hebrew
-+ 'ks', # Kashmiri
-+ 'ku', # Kurdish
-+ 'ps', # Pashto
-+ 'ur', # Urdu
-+ 'yi', # Yiddish
-+)
-+
-+
-+def _ReadFirstIdsFromFile(filename, defines):
-+ """Read the starting resource id values from |filename|. We also
-+ expand variables of the form <(FOO) based on defines passed in on
-+ the command line.
-+
-+ Returns a tuple, the absolute path of SRCDIR followed by the
-+ first_ids dictionary.
-+ """
-+ first_ids_dict = eval(util.ReadFile(filename, 'utf-8'))
-+ src_root_dir = os.path.abspath(os.path.join(os.path.dirname(filename),
-+ first_ids_dict['SRCDIR']))
-+
-+ def ReplaceVariable(matchobj):
-+ for key, value in defines.items():
-+ if matchobj.group(1) == key:
-+ return value
-+ return ''
-+
-+ renames = []
-+ for grd_filename in first_ids_dict:
-+ new_grd_filename = re.sub(r'<\(([A-Za-z_]+)\)', ReplaceVariable,
-+ grd_filename)
-+ if new_grd_filename != grd_filename:
-+ abs_grd_filename = os.path.abspath(new_grd_filename)
-+ if abs_grd_filename[:len(src_root_dir)] != src_root_dir:
-+ new_grd_filename = os.path.basename(abs_grd_filename)
-+ else:
-+ new_grd_filename = abs_grd_filename[len(src_root_dir) + 1:]
-+ new_grd_filename = new_grd_filename.replace('\\', '/')
-+ renames.append((grd_filename, new_grd_filename))
-+
-+ for grd_filename, new_grd_filename in renames:
-+ first_ids_dict[new_grd_filename] = first_ids_dict[grd_filename]
-+ del(first_ids_dict[grd_filename])
-+
-+ return (src_root_dir, first_ids_dict)
-+
-+
-+def _ComputeIds(root, predetermined_tids):
-+ """Returns a dict of textual id -> numeric id for all nodes in root.
-+
-+ IDs are mostly assigned sequentially, but will vary based on:
-+ * first_id node attribute (from first_ids_file)
-+ * hash of textual id (if not first_id is defined)
-+ * offset node attribute
-+ * whether the textual id matches a system id
-+ * whether the node generates its own ID via GetId()
-+
-+ Args:
-+ predetermined_tids: Dict of textual id -> numeric id to use in return dict.
-+ """
-+ from grit.node import empty, include, misc, structure
-+
-+ ids = {} # Maps numeric id to textual id
-+ tids = {} # Maps textual id to numeric id
-+ id_reasons = {} # Maps numeric id to text id and a human-readable explanation
-+ group = None
-+ last_id = None
-+ predetermined_ids = {value: key
-+ for key, value in predetermined_tids.items()}
-+
-+ for item in root:
-+ if isinstance(item, empty.GroupingNode):
-+ # Note: this won't work if any GroupingNode can be contained inside
-+ # another.
-+ group = item
-+ last_id = None
-+ continue
-+
-+ assert not item.GetTextualIds() or isinstance(item,
-+ (include.IncludeNode, message.MessageNode,
-+ misc.IdentifierNode, structure.StructureNode))
-+
-+ # Resources that use the RES protocol don't need
-+ # any numerical ids generated, so we skip them altogether.
-+ # This is accomplished by setting the flag 'generateid' to false
-+ # in the GRD file.
-+ if item.attrs.get('generateid', 'true') == 'false':
-+ continue
-+
-+ for tid in item.GetTextualIds():
-+ if util.SYSTEM_IDENTIFIERS.match(tid):
-+ # Don't emit a new ID for predefined IDs
-+ continue
-+
-+ if tid in tids:
-+ continue
-+
-+ if predetermined_tids and tid in predetermined_tids:
-+ id = predetermined_tids[tid]
-+ reason = "from predetermined_tids map"
-+
-+ # Some identifier nodes can provide their own id,
-+ # and we use that id in the generated header in that case.
-+ elif hasattr(item, 'GetId') and item.GetId():
-+ id = long(item.GetId())
-+ reason = 'returned by GetId() method'
-+
-+ elif ('offset' in item.attrs and group and
-+ group.attrs.get('first_id', '') != ''):
-+ offset_text = item.attrs['offset']
-+ parent_text = group.attrs['first_id']
-+
-+ try:
-+ offset_id = long(offset_text)
-+ except ValueError:
-+ offset_id = tids[offset_text]
-+
-+ try:
-+ parent_id = long(parent_text)
-+ except ValueError:
-+ parent_id = tids[parent_text]
-+
-+ id = parent_id + offset_id
-+ reason = 'first_id %d + offset %d' % (parent_id, offset_id)
-+
-+ # We try to allocate IDs sequentially for blocks of items that might
-+ # be related, for instance strings in a stringtable (as their IDs might be
-+ # used e.g. as IDs for some radio buttons, in which case the IDs must
-+ # be sequential).
-+ #
-+ # We do this by having the first item in a section store its computed ID
-+ # (computed from a fingerprint) in its parent object. Subsequent children
-+ # of the same parent will then try to get IDs that sequentially follow
-+ # the currently stored ID (on the parent) and increment it.
-+ elif last_id is None:
-+ # First check if the starting ID is explicitly specified by the parent.
-+ if group and group.attrs.get('first_id', '') != '':
-+ id = long(group.attrs['first_id'])
-+ reason = "from parent's first_id attribute"
-+ else:
-+ # Automatically generate the ID based on the first clique from the
-+ # first child of the first child node of our parent (i.e. when we
-+ # first get to this location in the code).
-+
-+ # According to
-+ # http://msdn.microsoft.com/en-us/library/t2zechd4(VS.71).aspx
-+ # the safe usable range for resource IDs in Windows is from decimal
-+ # 101 to 0x7FFF.
-+
-+ id = FP.UnsignedFingerPrint(tid)
-+ id = id % (0x7FFF - 101) + 101
-+ reason = 'chosen by random fingerprint -- use first_id to override'
-+
-+ last_id = id
-+ else:
-+ id = last_id = last_id + 1
-+ reason = 'sequentially assigned'
-+
-+ reason = "%s (%s)" % (tid, reason)
-+ # Don't fail when 'offset' is specified, as the base and the 0th
-+ # offset will have the same ID.
-+ if id in id_reasons and not 'offset' in item.attrs:
-+ raise exception.IdRangeOverlap('ID %d was assigned to both %s and %s.'
-+ % (id, id_reasons[id], reason))
-+
-+ if id < 101:
-+ print('WARNING: Numeric resource IDs should be greater than 100 to\n'
-+ 'avoid conflicts with system-defined resource IDs.')
-+
-+ if tid not in predetermined_tids and id in predetermined_ids:
-+ raise exception.IdRangeOverlap('ID %d overlaps between %s and %s'
-+ % (id, tid, predetermined_ids[tid]))
-+
-+ ids[id] = tid
-+ tids[tid] = id
-+ id_reasons[id] = reason
-+
-+ return tids
-+
-+class SplicingNode(base.Node):
-+ """A node whose children should be considered to be at the same level as
-+ its siblings for most purposes. This includes <if> and <part> nodes.
-+ """
-+
-+ def _IsValidChild(self, child):
-+ assert self.parent, '<%s> node should never be root.' % self.name
-+ if isinstance(child, SplicingNode):
-+ return True # avoid O(n^2) behavior
-+ return self.parent._IsValidChild(child)
-+
-+
-+class IfNode(SplicingNode):
-+ """A node for conditional inclusion of resources.
-+ """
-+
-+ def MandatoryAttributes(self):
-+ return ['expr']
-+
-+ def _IsValidChild(self, child):
-+ return (isinstance(child, (ThenNode, ElseNode)) or
-+ super(IfNode, self)._IsValidChild(child))
-+
-+ def EndParsing(self):
-+ children = self.children
-+ self.if_then_else = False
-+ if any(isinstance(node, (ThenNode, ElseNode)) for node in children):
-+ if (len(children) != 2 or not isinstance(children[0], ThenNode) or
-+ not isinstance(children[1], ElseNode)):
-+ raise exception.UnexpectedChild(
-+ '<if> element must be <if><then>...</then><else>...</else></if>')
-+ self.if_then_else = True
-+
-+ def ActiveChildren(self):
-+ cond = self.EvaluateCondition(self.attrs['expr'])
-+ if self.if_then_else:
-+ return self.children[0 if cond else 1].ActiveChildren()
-+ else:
-+ # Equivalent to having all children inside <then> with an empty <else>
-+ return super(IfNode, self).ActiveChildren() if cond else []
-+
-+
-+class ThenNode(SplicingNode):
-+ """A <then> node. Can only appear directly inside an <if> node."""
-+ pass
-+
-+
-+class ElseNode(SplicingNode):
-+ """An <else> node. Can only appear directly inside an <if> node."""
-+ pass
-+
-+
-+class PartNode(SplicingNode):
-+ """A node for inclusion of sub-grd (*.grp) files.
-+ """
-+
-+ def __init__(self):
-+ super(PartNode, self).__init__()
-+ self.started_inclusion = False
-+
-+ def MandatoryAttributes(self):
-+ return ['file']
-+
-+ def _IsValidChild(self, child):
-+ return self.started_inclusion and super(PartNode, self)._IsValidChild(child)
-+
-+
-+class ReleaseNode(base.Node):
-+ """The <release> element."""
-+
-+ def _IsValidChild(self, child):
-+ from grit.node import empty
-+ return isinstance(child, (empty.IncludesNode, empty.MessagesNode,
-+ empty.StructuresNode, empty.IdentifiersNode))
-+
-+ def _IsValidAttribute(self, name, value):
-+ return (
-+ (name == 'seq' and int(value) <= self.GetRoot().GetCurrentRelease()) or
-+ name == 'allow_pseudo'
-+ )
-+
-+ def MandatoryAttributes(self):
-+ return ['seq']
-+
-+ def DefaultAttributes(self):
-+ return { 'allow_pseudo' : 'true' }
-+
-+
-+class GritNode(base.Node):
-+ """The <grit> root element."""
-+
-+ def __init__(self):
-+ super(GritNode, self).__init__()
-+ self.output_language = ''
-+ self.defines = {}
-+ self.substituter = None
-+ self.target_platform = sys.platform
-+ self.whitelist_support = False
-+ self._predetermined_ids_file = None
-+ self._id_map = None # Dict of textual_id -> numeric_id.
-+
-+ def _IsValidChild(self, child):
-+ from grit.node import empty
-+ return isinstance(child, (ReleaseNode, empty.TranslationsNode,
-+ empty.OutputsNode))
-+
-+ def _IsValidAttribute(self, name, value):
-+ if name not in ['base_dir', 'first_ids_file', 'source_lang_id',
-+ 'latest_public_release', 'current_release',
-+ 'enc_check', 'tc_project', 'grit_version',
-+ 'output_all_resource_defines']:
-+ return False
-+ if name in ['latest_public_release', 'current_release'] and value.strip(
-+ '0123456789') != '':
-+ return False
-+ return True
-+
-+ def MandatoryAttributes(self):
-+ return ['latest_public_release', 'current_release']
-+
-+ def DefaultAttributes(self):
-+ return {
-+ 'base_dir' : '.',
-+ 'first_ids_file': '',
-+ 'grit_version': 1,
-+ 'source_lang_id' : 'en',
-+ 'enc_check' : constants.ENCODING_CHECK,
-+ 'tc_project' : 'NEED_TO_SET_tc_project_ATTRIBUTE',
-+ }
-+
-+ def EndParsing(self):
-+ super(GritNode, self).EndParsing()
-+ if (int(self.attrs['latest_public_release'])
-+ > int(self.attrs['current_release'])):
-+ raise exception.Parsing('latest_public_release cannot have a greater '
-+ 'value than current_release')
-+
-+ self.ValidateUniqueIds()
-+
-+ # Add the encoding check if it's not present (should ensure that it's always
-+ # present in all .grd files generated by GRIT). If it's present, assert if
-+ # it's not correct.
-+ if 'enc_check' not in self.attrs or self.attrs['enc_check'] == '':
-+ self.attrs['enc_check'] = constants.ENCODING_CHECK
-+ else:
-+ assert self.attrs['enc_check'] == constants.ENCODING_CHECK, (
-+ 'Are you sure your .grd file is in the correct encoding (UTF-8)?')
-+
-+ def ValidateUniqueIds(self):
-+ """Validate that 'name' attribute is unique in all nodes in this tree
-+ except for nodes that are children of <if> nodes.
-+ """
-+ unique_names = {}
-+ duplicate_names = []
-+ # To avoid false positives from mutually exclusive <if> clauses, check
-+ # against whatever the output condition happens to be right now.
-+ # TODO(benrg): do something better.
-+ for node in self.ActiveDescendants():
-+ if node.attrs.get('generateid', 'true') == 'false':
-+ continue # Duplication not relevant in that case
-+
-+ for node_id in node.GetTextualIds():
-+ if util.SYSTEM_IDENTIFIERS.match(node_id):
-+ continue # predefined IDs are sometimes used more than once
-+
-+ if node_id in unique_names and node_id not in duplicate_names:
-+ duplicate_names.append(node_id)
-+ unique_names[node_id] = 1
-+
-+ if len(duplicate_names):
-+ raise exception.DuplicateKey(', '.join(duplicate_names))
-+
-+
-+ def GetCurrentRelease(self):
-+ """Returns the current release number."""
-+ return int(self.attrs['current_release'])
-+
-+ def GetLatestPublicRelease(self):
-+ """Returns the latest public release number."""
-+ return int(self.attrs['latest_public_release'])
-+
-+ def GetSourceLanguage(self):
-+ """Returns the language code of the source language."""
-+ return self.attrs['source_lang_id']
-+
-+ def GetTcProject(self):
-+ """Returns the name of this project in the TranslationConsole, or
-+ 'NEED_TO_SET_tc_project_ATTRIBUTE' if it is not defined."""
-+ return self.attrs['tc_project']
-+
-+ def SetOwnDir(self, dir):
-+ """Informs the 'grit' element of the directory the file it is in resides.
-+ This allows it to calculate relative paths from the input file, which is
-+ what we desire (rather than from the current path).
-+
-+ Args:
-+ dir: r'c:\bla'
-+
-+ Return:
-+ None
-+ """
-+ assert dir
-+ self.base_dir = os.path.normpath(os.path.join(dir, self.attrs['base_dir']))
-+
-+ def GetBaseDir(self):
-+ """Returns the base directory, relative to the working directory. To get
-+ the base directory as set in the .grd file, use GetOriginalBaseDir()
-+ """
-+ if hasattr(self, 'base_dir'):
-+ return self.base_dir
-+ else:
-+ return self.GetOriginalBaseDir()
-+
-+ def GetOriginalBaseDir(self):
-+ """Returns the base directory, as set in the .grd file.
-+ """
-+ return self.attrs['base_dir']
-+
-+ def IsWhitelistSupportEnabled(self):
-+ return self.whitelist_support
-+
-+ def SetWhitelistSupportEnabled(self, whitelist_support):
-+ self.whitelist_support = whitelist_support
-+
-+ def GetInputFiles(self):
-+ """Returns the list of files that are read to produce the output."""
-+
-+ # Importing this here avoids a circular dependency in the imports.
-+ # pylint: disable-msg=C6204
-+ from grit.node import include
-+ from grit.node import misc
-+ from grit.node import structure
-+ from grit.node import variant
-+
-+ # Check if the input is required for any output configuration.
-+ input_files = set()
-+ # Collect even inactive PartNodes since they affect ID assignments.
-+ for node in self:
-+ if isinstance(node, misc.PartNode):
-+ input_files.add(self.ToRealPath(node.GetInputPath()))
-+
-+ old_output_language = self.output_language
-+ for lang, ctx, fallback in self.GetConfigurations():
-+ self.SetOutputLanguage(lang or self.GetSourceLanguage())
-+ self.SetOutputContext(ctx)
-+ self.SetFallbackToDefaultLayout(fallback)
-+
-+ for node in self.ActiveDescendants():
-+ if isinstance(node, (node_io.FileNode, include.IncludeNode,
-+ structure.StructureNode, variant.SkeletonNode)):
-+ input_path = node.GetInputPath()
-+ if input_path is not None:
-+ input_files.add(self.ToRealPath(input_path))
-+
-+ # If it's a flattened node, grab inlined resources too.
-+ if ((node.name == 'structure' or node.name == 'include')
-+ and node.attrs['flattenhtml'] == 'true'):
-+ if node.name == 'structure':
-+ node.RunPreSubstitutionGatherer()
-+ input_files.update(node.GetHtmlResourceFilenames())
-+
-+ self.SetOutputLanguage(old_output_language)
-+ return sorted(input_files)
-+
-+ def GetFirstIdsFile(self):
-+ """Returns a usable path to the first_ids file, if set, otherwise
-+ returns None.
-+
-+ The first_ids_file attribute is by default relative to the
-+ base_dir of the .grd file, but may be prefixed by GRIT_DIR/,
-+ which makes it relative to the directory of grit.py
-+ (e.g. GRIT_DIR/../gritsettings/resource_ids).
-+ """
-+ if not self.attrs['first_ids_file']:
-+ return None
-+
-+ path = self.attrs['first_ids_file']
-+ GRIT_DIR_PREFIX = 'GRIT_DIR'
-+ if (path.startswith(GRIT_DIR_PREFIX)
-+ and path[len(GRIT_DIR_PREFIX)] in ['/', '\\']):
-+ return util.PathFromRoot(path[len(GRIT_DIR_PREFIX) + 1:])
-+ else:
-+ return self.ToRealPath(path)
-+
-+ def GetOutputFiles(self):
-+ """Returns the list of <output> nodes that are descendants of this node's
-+ <outputs> child and are not enclosed by unsatisfied <if> conditionals.
-+ """
-+ for child in self.children:
-+ if child.name == 'outputs':
-+ return [node for node in child.ActiveDescendants()
-+ if node.name == 'output']
-+ raise exception.MissingElement()
-+
-+ def GetConfigurations(self):
-+ """Returns the distinct (language, context, fallback_to_default_layout)
-+ triples from the output nodes.
-+ """
-+ return set((n.GetLanguage(), n.GetContext(), n.GetFallbackToDefaultLayout())
-+ for n in self.GetOutputFiles())
-+
-+ def GetSubstitutionMessages(self):
-+ """Returns the list of <message sub_variable="true"> nodes."""
-+ return [n for n in self.ActiveDescendants()
-+ if isinstance(n, message.MessageNode)
-+ and n.attrs['sub_variable'] == 'true']
-+
-+ def SetOutputLanguage(self, output_language):
-+ """Set the output language. Prepares substitutions.
-+
-+ The substitutions are reset every time the language is changed.
-+ They include messages designated as variables, and language codes for html
-+ and rc files.
-+
-+ Args:
-+ output_language: a two-letter language code (eg: 'en', 'ar'...) or ''
-+ """
-+ if not output_language:
-+ # We do not specify the output language for .grh files,
-+ # so we get an empty string as the default.
-+ # The value should match grit.clique.MessageClique.source_language.
-+ output_language = self.GetSourceLanguage()
-+ if output_language != self.output_language:
-+ self.output_language = output_language
-+ self.substituter = None # force recalculate
-+
-+ def SetOutputContext(self, output_context):
-+ self.output_context = output_context
-+ self.substituter = None # force recalculate
-+
-+ def SetFallbackToDefaultLayout(self, fallback_to_default_layout):
-+ self.fallback_to_default_layout = fallback_to_default_layout
-+ self.substituter = None # force recalculate
-+
-+ def SetDefines(self, defines):
-+ self.defines = defines
-+ self.substituter = None # force recalculate
-+
-+ def SetTargetPlatform(self, target_platform):
-+ self.target_platform = target_platform
-+
-+ def GetSubstituter(self):
-+ if self.substituter is None:
-+ self.substituter = util.Substituter()
-+ self.substituter.AddMessages(self.GetSubstitutionMessages(),
-+ self.output_language)
-+ if self.output_language in _RTL_LANGS:
-+ direction = 'dir="RTL"'
-+ else:
-+ direction = 'dir="LTR"'
-+ self.substituter.AddSubstitutions({
-+ 'GRITLANGCODE': self.output_language,
-+ 'GRITDIR': direction,
-+ })
-+ from grit.format import rc # avoid circular dep
-+ rc.RcSubstitutions(self.substituter, self.output_language)
-+ return self.substituter
-+
-+ def AssignFirstIds(self, filename_or_stream, defines):
-+ """Assign first ids to each grouping node based on values from the
-+ first_ids file (if specified on the <grit> node).
-+ """
-+ assert self._id_map is None, 'AssignFirstIds() after InitializeIds()'
-+ # If the input is a stream, then we're probably in a unit test and
-+ # should skip this step.
-+ if not isinstance(filename_or_stream, six.string_types):
-+ return
-+
-+ # Nothing to do if the first_ids_filename attribute isn't set.
-+ first_ids_filename = self.GetFirstIdsFile()
-+ if not first_ids_filename:
-+ return
-+
-+ src_root_dir, first_ids = _ReadFirstIdsFromFile(first_ids_filename,
-+ defines)
-+ from grit.node import empty
-+ for node in self.Preorder():
-+ if isinstance(node, empty.GroupingNode):
-+ abs_filename = os.path.abspath(filename_or_stream)
-+ if abs_filename[:len(src_root_dir)] != src_root_dir:
-+ filename = os.path.basename(filename_or_stream)
-+ else:
-+ filename = abs_filename[len(src_root_dir) + 1:]
-+ filename = filename.replace('\\', '/')
-+
-+ if node.attrs['first_id'] != '':
-+ raise Exception(
-+ "Don't set the first_id attribute when using the first_ids_file "
-+ "attribute on the <grit> node, update %s instead." %
-+ first_ids_filename)
-+
-+ try:
-+ id_list = first_ids[filename][node.name]
-+ except KeyError as e:
-+ print('-' * 78)
-+ print('Resource id not set for %s (%s)!' % (filename, node.name))
-+ print('Please update %s to include an entry for %s. See the '
-+ 'comments in resource_ids for information on why you need to '
-+ 'update that file.' % (first_ids_filename, filename))
-+ print('-' * 78)
-+ raise e
-+
-+ try:
-+ node.attrs['first_id'] = str(id_list.pop(0))
-+ except IndexError as e:
-+ raise Exception('Please update %s and add a first id for %s (%s).'
-+ % (first_ids_filename, filename, node.name))
-+
-+ def GetIdMap(self):
-+ '''Return a dictionary mapping textual ids to numeric ids.'''
-+ return self._id_map
-+
-+ def SetPredeterminedIdsFile(self, predetermined_ids_file):
-+ assert self._id_map is None, (
-+ 'SetPredeterminedIdsFile() after InitializeIds()')
-+ self._predetermined_ids_file = predetermined_ids_file
-+
-+ def InitializeIds(self):
-+ '''Initializes the text ID -> numeric ID mapping.'''
-+ predetermined_id_map = {}
-+ if self._predetermined_ids_file:
-+ with open(self._predetermined_ids_file) as f:
-+ for line in f:
-+ tid, nid = line.split()
-+ predetermined_id_map[tid] = int(nid)
-+ self._id_map = _ComputeIds(self, predetermined_id_map)
-+
-+ def RunGatherers(self, debug=False):
-+ '''Call RunPreSubstitutionGatherer() on every node of the tree, then apply
-+ substitutions, then call RunPostSubstitutionGatherer() on every node.
-+
-+ The substitutions step requires that the output language has been set.
-+ Locally, get the Substitution messages and add them to the substituter.
-+ Also add substitutions for language codes in the Rc.
-+
-+ Args:
-+ debug: will print information while running gatherers.
-+ '''
-+ for node in self.ActiveDescendants():
-+ if hasattr(node, 'RunPreSubstitutionGatherer'):
-+ with node:
-+ node.RunPreSubstitutionGatherer(debug=debug)
-+
-+ assert self.output_language
-+ self.SubstituteMessages(self.GetSubstituter())
-+
-+ for node in self.ActiveDescendants():
-+ if hasattr(node, 'RunPostSubstitutionGatherer'):
-+ with node:
-+ node.RunPostSubstitutionGatherer(debug=debug)
-+
-+
-+class IdentifierNode(base.Node):
-+ """A node for specifying identifiers that should appear in the resource
-+ header file, and be unique amongst all other resource identifiers, but don't
-+ have any other attributes or reference any resources.
-+ """
-+
-+ def MandatoryAttributes(self):
-+ return ['name']
-+
-+ def DefaultAttributes(self):
-+ return { 'comment' : '', 'id' : '', 'systemid': 'false' }
-+
-+ def GetId(self):
-+ """Returns the id of this identifier if it has one, None otherwise
-+ """
-+ if 'id' in self.attrs:
-+ return self.attrs['id']
-+ return None
-+
-+ def EndParsing(self):
-+ """Handles system identifiers."""
-+ super(IdentifierNode, self).EndParsing()
-+ if self.attrs['systemid'] == 'true':
-+ util.SetupSystemIdentifiers((self.attrs['name'],))
-+
-+ @staticmethod
-+ def Construct(parent, name, id, comment, systemid='false'):
-+ """Creates a new node which is a child of 'parent', with attributes set
-+ by parameters of the same name.
-+ """
-+ node = IdentifierNode()
-+ node.StartParsing('identifier', parent)
-+ node.HandleAttribute('name', name)
-+ node.HandleAttribute('id', id)
-+ node.HandleAttribute('comment', comment)
-+ node.HandleAttribute('systemid', systemid)
-+ node.EndParsing()
-+ return node
-diff --git a/tools/grit/grit/node/misc_unittest.py b/tools/grit/grit/node/misc_unittest.py
-new file mode 100644
-index 0000000000..c192b096f4
---- /dev/null
-+++ b/tools/grit/grit/node/misc_unittest.py
-@@ -0,0 +1,590 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for misc.GritNode'''
-+
-+from __future__ import print_function
-+
-+import contextlib
-+import os
-+import sys
-+import tempfile
-+import unittest
-+
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+from six import StringIO
-+
-+from grit import grd_reader
-+import grit.exception
-+from grit import util
-+from grit.format import rc
-+from grit.format import rc_header
-+from grit.node import misc
-+
-+
-+@contextlib.contextmanager
-+def _MakeTempPredeterminedIdsFile(content):
-+ """Write the |content| string to a temporary file.
-+
-+ The temporary file must be deleted by the caller.
-+
-+ Example:
-+ with _MakeTempPredeterminedIdsFile('foo') as path:
-+ ...
-+ os.remove(path)
-+
-+ Args:
-+ content: The string to write.
-+
-+ Yields:
-+ The name of the temporary file.
-+ """
-+ with tempfile.NamedTemporaryFile(mode='w', delete=False) as f:
-+ f.write(content)
-+ f.flush()
-+ f.close()
-+ yield f.name
-+
-+
-+class GritNodeUnittest(unittest.TestCase):
-+ def testUniqueNameAttribute(self):
-+ try:
-+ restree = grd_reader.Parse(
-+ util.PathFromRoot('grit/testdata/duplicate-name-input.xml'))
-+ self.fail('Expected parsing exception because of duplicate names.')
-+ except grit.exception.Parsing:
-+ pass # Expected case
-+
-+ def testReadFirstIdsFromFile(self):
-+ test_resource_ids = os.path.join(os.path.dirname(__file__), '..',
-+ 'testdata', 'resource_ids')
-+ base_dir = os.path.dirname(test_resource_ids)
-+
-+ src_dir, id_dict = misc._ReadFirstIdsFromFile(
-+ test_resource_ids,
-+ {
-+ 'FOO': os.path.join(base_dir, 'bar'),
-+ 'SHARED_INTERMEDIATE_DIR': os.path.join(base_dir,
-+ 'out/Release/obj/gen'),
-+ })
-+ self.assertEqual({}, id_dict.get('bar/file.grd', None))
-+ self.assertEqual({},
-+ id_dict.get('out/Release/obj/gen/devtools/devtools.grd', None))
-+
-+ src_dir, id_dict = misc._ReadFirstIdsFromFile(
-+ test_resource_ids,
-+ {
-+ 'SHARED_INTERMEDIATE_DIR': '/outside/src_dir',
-+ })
-+ self.assertEqual({}, id_dict.get('devtools.grd', None))
-+
-+ # Verifies that GetInputFiles() returns the correct list of files
-+ # corresponding to ChromeScaledImage nodes when assets are missing.
-+ def testGetInputFilesChromeScaledImage(self):
-+ chrome_html_path = util.PathFromRoot('grit/testdata/chrome_html.html')
-+ xml = '''<?xml version="1.0" encoding="utf-8"?>
-+ <grit latest_public_release="0" current_release="1">
-+ <outputs>
-+ <output filename="default.pak" type="data_package" context="default_100_percent" />
-+ <output filename="special.pak" type="data_package" context="special_100_percent" fallback_to_default_layout="false" />
-+ </outputs>
-+ <release seq="1">
-+ <structures fallback_to_low_resolution="true">
-+ <structure type="chrome_scaled_image" name="IDR_A" file="a.png" />
-+ <structure type="chrome_scaled_image" name="IDR_B" file="b.png" />
-+ <structure type="chrome_html" name="HTML_FILE1" file="%s" flattenhtml="true" />
-+ </structures>
-+ </release>
-+ </grit>''' % chrome_html_path
-+
-+ grd = grd_reader.Parse(StringIO(xml),
-+ util.PathFromRoot('grit/testdata'))
-+ expected = ['chrome_html.html', 'default_100_percent/a.png',
-+ 'default_100_percent/b.png', 'included_sample.html',
-+ 'special_100_percent/a.png']
-+ actual = [os.path.relpath(path, util.PathFromRoot('grit/testdata')) for
-+ path in grd.GetInputFiles()]
-+ # Convert path separator for Windows paths.
-+ actual = [path.replace('\\', '/') for path in actual]
-+ self.assertEquals(expected, actual)
-+
-+ # Verifies that GetInputFiles() returns the correct list of files
-+ # when files include other files.
-+ def testGetInputFilesFromIncludes(self):
-+ chrome_html_path = util.PathFromRoot('grit/testdata/chrome_html.html')
-+ xml = '''<?xml version="1.0" encoding="utf-8"?>
-+ <grit latest_public_release="0" current_release="1">
-+ <outputs>
-+ <output filename="default.pak" type="data_package" context="default_100_percent" />
-+ <output filename="special.pak" type="data_package" context="special_100_percent" fallback_to_default_layout="false" />
-+ </outputs>
-+ <release seq="1">
-+ <includes>
-+ <include name="IDR_TESTDATA_CHROME_HTML" file="%s" flattenhtml="true"
-+ allowexternalscript="true" type="BINDATA" />
-+ </includes>
-+ </release>
-+ </grit>''' % chrome_html_path
-+
-+ grd = grd_reader.Parse(StringIO(xml), util.PathFromRoot('grit/testdata'))
-+ expected = ['chrome_html.html', 'included_sample.html']
-+ actual = [os.path.relpath(path, util.PathFromRoot('grit/testdata')) for
-+ path in grd.GetInputFiles()]
-+ # Convert path separator for Windows paths.
-+ actual = [path.replace('\\', '/') for path in actual]
-+ self.assertEquals(expected, actual)
-+
-+ def testNonDefaultEntry(self):
-+ grd = util.ParseGrdForUnittest('''
-+ <messages>
-+ <message name="IDS_A" desc="foo">bar</message>
-+ <if expr="lang == 'fr'">
-+ <message name="IDS_B" desc="foo">bar</message>
-+ </if>
-+ </messages>''')
-+ grd.SetOutputLanguage('fr')
-+ output = ''.join(rc_header.Format(grd, 'fr', '.'))
-+ self.assertIn('#define IDS_A 2378\n#define IDS_B 2379', output)
-+
-+ def testExplicitFirstIdOverlaps(self):
-+ # second first_id will overlap preexisting range
-+ self.assertRaises(grit.exception.IdRangeOverlap,
-+ util.ParseGrdForUnittest, '''
-+ <includes first_id="300" comment="bingo">
-+ <include type="gif" name="ID_LOGO" file="images/logo.gif" />
-+ <include type="gif" name="ID_LOGO2" file="images/logo2.gif" />
-+ </includes>
-+ <messages first_id="301">
-+ <message name="IDS_GREETING" desc="Printed to greet the currently logged in user">
-+ Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, how are you doing today?
-+ </message>
-+ <message name="IDS_SMURFGEBURF">Frubegfrums</message>
-+ </messages>''')
-+
-+ def testImplicitOverlapsPreexisting(self):
-+ # second message in <messages> will overlap preexisting range
-+ self.assertRaises(grit.exception.IdRangeOverlap,
-+ util.ParseGrdForUnittest, '''
-+ <includes first_id="301" comment="bingo">
-+ <include type="gif" name="ID_LOGO" file="images/logo.gif" />
-+ <include type="gif" name="ID_LOGO2" file="images/logo2.gif" />
-+ </includes>
-+ <messages first_id="300">
-+ <message name="IDS_GREETING" desc="Printed to greet the currently logged in user">
-+ Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, how are you doing today?
-+ </message>
-+ <message name="IDS_SMURFGEBURF">Frubegfrums</message>
-+ </messages>''')
-+
-+ def testPredeterminedIds(self):
-+ with _MakeTempPredeterminedIdsFile('IDS_A 101\nIDS_B 102') as ids_file:
-+ grd = util.ParseGrdForUnittest('''
-+ <includes first_id="300" comment="bingo">
-+ <include type="gif" name="IDS_B" file="images/logo.gif" />
-+ </includes>
-+ <messages first_id="10000">
-+ <message name="IDS_GREETING" desc="Printed to greet the currently logged in user">
-+ Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, how are you doing today?
-+ </message>
-+ <message name="IDS_A">
-+ Bongo!
-+ </message>
-+ </messages>''', predetermined_ids_file=ids_file)
-+ output = rc_header.FormatDefines(grd)
-+ self.assertEqual(('#define IDS_B 102\n'
-+ '#define IDS_GREETING 10000\n'
-+ '#define IDS_A 101\n'), ''.join(output))
-+ os.remove(ids_file)
-+
-+ def testPredeterminedIdsOverlap(self):
-+ with _MakeTempPredeterminedIdsFile('ID_LOGO 10000') as ids_file:
-+ self.assertRaises(grit.exception.IdRangeOverlap,
-+ util.ParseGrdForUnittest, '''
-+ <includes first_id="300" comment="bingo">
-+ <include type="gif" name="ID_LOGO" file="images/logo.gif" />
-+ </includes>
-+ <messages first_id="10000">
-+ <message name="IDS_GREETING" desc="Printed to greet the currently logged in user">
-+ Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, how are you doing today?
-+ </message>
-+ <message name="IDS_BONGO">
-+ Bongo!
-+ </message>
-+ </messages>''', predetermined_ids_file=ids_file)
-+ os.remove(ids_file)
-+
-+
-+class IfNodeUnittest(unittest.TestCase):
-+ def testIffyness(self):
-+ grd = grd_reader.Parse(StringIO('''
-+ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
-+ <release seq="3">
-+ <messages>
-+ <if expr="'bingo' in defs">
-+ <message name="IDS_BINGO">
-+ Bingo!
-+ </message>
-+ </if>
-+ <if expr="'hello' in defs">
-+ <message name="IDS_HELLO">
-+ Hello!
-+ </message>
-+ </if>
-+ <if expr="lang == 'fr' or 'FORCE_FRENCH' in defs">
-+ <message name="IDS_HELLO" internal_comment="French version">
-+ Good morning
-+ </message>
-+ </if>
-+ <if expr="is_win">
-+ <message name="IDS_ISWIN">is_win</message>
-+ </if>
-+ </messages>
-+ </release>
-+ </grit>'''), dir='.')
-+
-+ messages_node = grd.children[0].children[0]
-+ bingo_message = messages_node.children[0].children[0]
-+ hello_message = messages_node.children[1].children[0]
-+ french_message = messages_node.children[2].children[0]
-+ is_win_message = messages_node.children[3].children[0]
-+
-+ self.assertTrue(bingo_message.name == 'message')
-+ self.assertTrue(hello_message.name == 'message')
-+ self.assertTrue(french_message.name == 'message')
-+
-+ grd.SetOutputLanguage('fr')
-+ grd.SetDefines({'hello': '1'})
-+ active = set(grd.ActiveDescendants())
-+ self.failUnless(bingo_message not in active)
-+ self.failUnless(hello_message in active)
-+ self.failUnless(french_message in active)
-+
-+ grd.SetOutputLanguage('en')
-+ grd.SetDefines({'bingo': 1})
-+ active = set(grd.ActiveDescendants())
-+ self.failUnless(bingo_message in active)
-+ self.failUnless(hello_message not in active)
-+ self.failUnless(french_message not in active)
-+
-+ grd.SetOutputLanguage('en')
-+ grd.SetDefines({'FORCE_FRENCH': '1', 'bingo': '1'})
-+ active = set(grd.ActiveDescendants())
-+ self.failUnless(bingo_message in active)
-+ self.failUnless(hello_message not in active)
-+ self.failUnless(french_message in active)
-+
-+ grd.SetOutputLanguage('en')
-+ grd.SetDefines({})
-+ self.failUnless(grd.target_platform == sys.platform)
-+ grd.SetTargetPlatform('darwin')
-+ active = set(grd.ActiveDescendants())
-+ self.failUnless(is_win_message not in active)
-+ grd.SetTargetPlatform('win32')
-+ active = set(grd.ActiveDescendants())
-+ self.failUnless(is_win_message in active)
-+
-+ def testElsiness(self):
-+ grd = util.ParseGrdForUnittest('''
-+ <messages>
-+ <if expr="True">
-+ <then> <message name="IDS_YES1"></message> </then>
-+ <else> <message name="IDS_NO1"></message> </else>
-+ </if>
-+ <if expr="True">
-+ <then> <message name="IDS_YES2"></message> </then>
-+ <else> </else>
-+ </if>
-+ <if expr="True">
-+ <then> </then>
-+ <else> <message name="IDS_NO2"></message> </else>
-+ </if>
-+ <if expr="True">
-+ <then> </then>
-+ <else> </else>
-+ </if>
-+ <if expr="False">
-+ <then> <message name="IDS_NO3"></message> </then>
-+ <else> <message name="IDS_YES3"></message> </else>
-+ </if>
-+ <if expr="False">
-+ <then> <message name="IDS_NO4"></message> </then>
-+ <else> </else>
-+ </if>
-+ <if expr="False">
-+ <then> </then>
-+ <else> <message name="IDS_YES4"></message> </else>
-+ </if>
-+ <if expr="False">
-+ <then> </then>
-+ <else> </else>
-+ </if>
-+ </messages>''')
-+ included = [msg.attrs['name'] for msg in grd.ActiveDescendants()
-+ if msg.name == 'message']
-+ self.assertEqual(['IDS_YES1', 'IDS_YES2', 'IDS_YES3', 'IDS_YES4'], included)
-+
-+ def testIffynessWithOutputNodes(self):
-+ grd = grd_reader.Parse(StringIO('''
-+ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
-+ <outputs>
-+ <output filename="uncond1.rc" type="rc_data" />
-+ <if expr="lang == 'fr' or 'hello' in defs">
-+ <output filename="only_fr.adm" type="adm" />
-+ <output filename="only_fr.plist" type="plist" />
-+ </if>
-+ <if expr="lang == 'ru'">
-+ <output filename="doc.html" type="document" />
-+ </if>
-+ <output filename="uncond2.adm" type="adm" />
-+ <output filename="iftest.h" type="rc_header">
-+ <emit emit_type='prepend'></emit>
-+ </output>
-+ </outputs>
-+ </grit>'''), dir='.')
-+
-+ outputs_node = grd.children[0]
-+ uncond1_output = outputs_node.children[0]
-+ only_fr_adm_output = outputs_node.children[1].children[0]
-+ only_fr_plist_output = outputs_node.children[1].children[1]
-+ doc_output = outputs_node.children[2].children[0]
-+ uncond2_output = outputs_node.children[0]
-+ self.assertTrue(uncond1_output.name == 'output')
-+ self.assertTrue(only_fr_adm_output.name == 'output')
-+ self.assertTrue(only_fr_plist_output.name == 'output')
-+ self.assertTrue(doc_output.name == 'output')
-+ self.assertTrue(uncond2_output.name == 'output')
-+
-+ grd.SetOutputLanguage('ru')
-+ grd.SetDefines({'hello': '1'})
-+ outputs = [output.GetFilename() for output in grd.GetOutputFiles()]
-+ self.assertEquals(
-+ outputs,
-+ ['uncond1.rc', 'only_fr.adm', 'only_fr.plist', 'doc.html',
-+ 'uncond2.adm', 'iftest.h'])
-+
-+ grd.SetOutputLanguage('ru')
-+ grd.SetDefines({'bingo': '2'})
-+ outputs = [output.GetFilename() for output in grd.GetOutputFiles()]
-+ self.assertEquals(
-+ outputs,
-+ ['uncond1.rc', 'doc.html', 'uncond2.adm', 'iftest.h'])
-+
-+ grd.SetOutputLanguage('fr')
-+ grd.SetDefines({'hello': '1'})
-+ outputs = [output.GetFilename() for output in grd.GetOutputFiles()]
-+ self.assertEquals(
-+ outputs,
-+ ['uncond1.rc', 'only_fr.adm', 'only_fr.plist', 'uncond2.adm',
-+ 'iftest.h'])
-+
-+ grd.SetOutputLanguage('en')
-+ grd.SetDefines({'bingo': '1'})
-+ outputs = [output.GetFilename() for output in grd.GetOutputFiles()]
-+ self.assertEquals(outputs, ['uncond1.rc', 'uncond2.adm', 'iftest.h'])
-+
-+ grd.SetOutputLanguage('fr')
-+ grd.SetDefines({'bingo': '1'})
-+ outputs = [output.GetFilename() for output in grd.GetOutputFiles()]
-+ self.assertNotEquals(outputs, ['uncond1.rc', 'uncond2.adm', 'iftest.h'])
-+
-+ def testChildrenAccepted(self):
-+ grd_reader.Parse(StringIO(r'''<?xml version="1.0"?>
-+ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
-+ <release seq="3">
-+ <includes>
-+ <if expr="'bingo' in defs">
-+ <include type="gif" name="ID_LOGO2" file="images/logo2.gif" />
-+ </if>
-+ <if expr="'bingo' in defs">
-+ <if expr="'hello' in defs">
-+ <include type="gif" name="ID_LOGO2" file="images/logo2.gif" />
-+ </if>
-+ </if>
-+ </includes>
-+ <structures>
-+ <if expr="'bingo' in defs">
-+ <structure type="dialog" name="IDD_ABOUTBOX" file="grit\test\data\klonk.rc" encoding="utf-16" />
-+ </if>
-+ <if expr="'bingo' in defs">
-+ <if expr="'hello' in defs">
-+ <structure type="dialog" name="IDD_ABOUTBOX" file="grit\test\data\klonk.rc" encoding="utf-16" />
-+ </if>
-+ </if>
-+ </structures>
-+ <messages>
-+ <if expr="'bingo' in defs">
-+ <message name="IDS_BINGO">Bingo!</message>
-+ </if>
-+ <if expr="'bingo' in defs">
-+ <if expr="'hello' in defs">
-+ <message name="IDS_BINGO">Bingo!</message>
-+ </if>
-+ </if>
-+ </messages>
-+ </release>
-+ <translations>
-+ <if expr="'bingo' in defs">
-+ <file lang="nl" path="nl_translations.xtb" />
-+ </if>
-+ <if expr="'bingo' in defs">
-+ <if expr="'hello' in defs">
-+ <file lang="nl" path="nl_translations.xtb" />
-+ </if>
-+ </if>
-+ </translations>
-+ </grit>'''), dir='.')
-+
-+ def testIfBadChildrenNesting(self):
-+ # includes
-+ xml = StringIO(r'''<?xml version="1.0"?>
-+ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
-+ <release seq="3">
-+ <includes>
-+ <if expr="'bingo' in defs">
-+ <structure type="dialog" name="IDD_ABOUTBOX" file="grit\test\data\klonk.rc" encoding="utf-16" />
-+ </if>
-+ </includes>
-+ </release>
-+ </grit>''')
-+ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml)
-+ # messages
-+ xml = StringIO(r'''<?xml version="1.0"?>
-+ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
-+ <release seq="3">
-+ <messages>
-+ <if expr="'bingo' in defs">
-+ <structure type="dialog" name="IDD_ABOUTBOX" file="grit\test\data\klonk.rc" encoding="utf-16" />
-+ </if>
-+ </messages>
-+ </release>
-+ </grit>''')
-+ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml)
-+ # structures
-+ xml = StringIO('''<?xml version="1.0"?>
-+ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
-+ <release seq="3">
-+ <structures>
-+ <if expr="'bingo' in defs">
-+ <message name="IDS_BINGO">Bingo!</message>
-+ </if>
-+ </structures>
-+ </release>
-+ </grit>''')
-+ # translations
-+ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml)
-+ xml = StringIO('''<?xml version="1.0"?>
-+ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
-+ <translations>
-+ <if expr="'bingo' in defs">
-+ <message name="IDS_BINGO">Bingo!</message>
-+ </if>
-+ </translations>
-+ </grit>''')
-+ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml)
-+ # same with nesting
-+ xml = StringIO(r'''<?xml version="1.0"?>
-+ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
-+ <release seq="3">
-+ <includes>
-+ <if expr="'bingo' in defs">
-+ <if expr="'hello' in defs">
-+ <structure type="dialog" name="IDD_ABOUTBOX" file="grit\test\data\klonk.rc" encoding="utf-16" />
-+ </if>
-+ </if>
-+ </includes>
-+ </release>
-+ </grit>''')
-+ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml)
-+ xml = StringIO(r'''<?xml version="1.0"?>
-+ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
-+ <release seq="3">
-+ <messages>
-+ <if expr="'bingo' in defs">
-+ <if expr="'hello' in defs">
-+ <structure type="dialog" name="IDD_ABOUTBOX" file="grit\test\data\klonk.rc" encoding="utf-16" />
-+ </if>
-+ </if>
-+ </messages>
-+ </release>
-+ </grit>''')
-+ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml)
-+ xml = StringIO('''<?xml version="1.0"?>
-+ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
-+ <release seq="3">
-+ <structures>
-+ <if expr="'bingo' in defs">
-+ <if expr="'hello' in defs">
-+ <message name="IDS_BINGO">Bingo!</message>
-+ </if>
-+ </if>
-+ </structures>
-+ </release>
-+ </grit>''')
-+ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml)
-+ xml = StringIO('''<?xml version="1.0"?>
-+ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
-+ <translations>
-+ <if expr="'bingo' in defs">
-+ <if expr="'hello' in defs">
-+ <message name="IDS_BINGO">Bingo!</message>
-+ </if>
-+ </if>
-+ </translations>
-+ </grit>''')
-+ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml)
-+
-+
-+class ReleaseNodeUnittest(unittest.TestCase):
-+ def testPseudoControl(self):
-+ grd = grd_reader.Parse(StringIO('''<?xml version="1.0" encoding="UTF-8"?>
-+ <grit latest_public_release="1" source_lang_id="en-US" current_release="2" base_dir=".">
-+ <release seq="1" allow_pseudo="false">
-+ <messages>
-+ <message name="IDS_HELLO">
-+ Hello
-+ </message>
-+ </messages>
-+ <structures>
-+ <structure type="dialog" name="IDD_ABOUTBOX" encoding="utf-16" file="klonk.rc" />
-+ </structures>
-+ </release>
-+ <release seq="2">
-+ <messages>
-+ <message name="IDS_BINGO">
-+ Bingo
-+ </message>
-+ </messages>
-+ <structures>
-+ <structure type="menu" name="IDC_KLONKMENU" encoding="utf-16" file="klonk.rc" />
-+ </structures>
-+ </release>
-+ </grit>'''), util.PathFromRoot('grit/testdata'))
-+ grd.SetOutputLanguage('en')
-+ grd.RunGatherers()
-+
-+ hello = grd.GetNodeById('IDS_HELLO')
-+ aboutbox = grd.GetNodeById('IDD_ABOUTBOX')
-+ bingo = grd.GetNodeById('IDS_BINGO')
-+ menu = grd.GetNodeById('IDC_KLONKMENU')
-+
-+ for node in [hello, aboutbox]:
-+ self.failUnless(not node.PseudoIsAllowed())
-+
-+ for node in [bingo, menu]:
-+ self.failUnless(node.PseudoIsAllowed())
-+
-+ # TODO(benrg): There was a test here that formatting hello and aboutbox with
-+ # a pseudo language should fail, but they do not fail and the test was
-+ # broken and failed to catch it. Fix this.
-+
-+ # Should not raise an exception since pseudo is allowed
-+ rc.FormatMessage(bingo, 'xyz-pseudo')
-+ rc.FormatStructure(menu, 'xyz-pseudo', '.')
-+
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/node/mock_brotli.py b/tools/grit/grit/node/mock_brotli.py
-new file mode 100644
-index 0000000000..14237aab20
---- /dev/null
-+++ b/tools/grit/grit/node/mock_brotli.py
-@@ -0,0 +1,10 @@
-+#!/usr/bin/env python
-+# Copyright 2019 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+"""Mock Brotli Executable for testing purposes."""
-+
-+import sys
-+
-+sys.stdout.write('This has been mock compressed!')
-diff --git a/tools/grit/grit/node/node_io.py b/tools/grit/grit/node/node_io.py
-new file mode 100644
-index 0000000000..ccbc2c0647
---- /dev/null
-+++ b/tools/grit/grit/node/node_io.py
-@@ -0,0 +1,117 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''The <output> and <file> elements.
-+'''
-+
-+from __future__ import print_function
-+
-+import os
-+
-+from grit import xtb_reader
-+from grit.node import base
-+
-+
-+class FileNode(base.Node):
-+ '''A <file> element.'''
-+
-+ def __init__(self):
-+ super(FileNode, self).__init__()
-+ self.re = None
-+ self.should_load_ = True
-+
-+ def IsTranslation(self):
-+ return True
-+
-+ def GetLang(self):
-+ return self.attrs['lang']
-+
-+ def DisableLoading(self):
-+ self.should_load_ = False
-+
-+ def MandatoryAttributes(self):
-+ return ['path', 'lang']
-+
-+ def RunPostSubstitutionGatherer(self, debug=False):
-+ if not self.should_load_:
-+ return
-+
-+ root = self.GetRoot()
-+ defs = getattr(root, 'defines', {})
-+ target_platform = getattr(root, 'target_platform', '')
-+
-+ xtb_file = open(self.ToRealPath(self.GetInputPath()), 'rb')
-+ try:
-+ lang = xtb_reader.Parse(xtb_file,
-+ self.UberClique().GenerateXtbParserCallback(
-+ self.attrs['lang'], debug=debug),
-+ defs=defs,
-+ target_platform=target_platform)
-+ except:
-+ print("Exception during parsing of %s" % self.GetInputPath())
-+ raise
-+ # Translation console uses non-standard language codes 'iw' and 'no' for
-+ # Hebrew and Norwegian Bokmal instead of 'he' and 'nb' used in Chrome.
-+ # Note that some Chrome's .grd still use 'no' instead of 'nb', but 'nb' is
-+ # always used for generated .pak files.
-+ ALTERNATIVE_LANG_CODE_MAP = { 'he': 'iw', 'nb': 'no' }
-+ assert (lang == self.attrs['lang'] or
-+ lang == ALTERNATIVE_LANG_CODE_MAP[self.attrs['lang']]), (
-+ 'The XTB file you reference must contain messages in the language '
-+ 'specified\nby the \'lang\' attribute.')
-+
-+ def GetInputPath(self):
-+ return os.path.expandvars(self.attrs['path'])
-+
-+
-+class OutputNode(base.Node):
-+ '''An <output> element.'''
-+
-+ def MandatoryAttributes(self):
-+ return ['filename', 'type']
-+
-+ def DefaultAttributes(self):
-+ return {
-+ 'lang' : '', # empty lang indicates all languages
-+ 'language_section' : 'neutral', # defines a language neutral section
-+ 'context' : '',
-+ 'fallback_to_default_layout' : 'true',
-+ }
-+
-+ def GetType(self):
-+ return self.attrs['type']
-+
-+ def GetLanguage(self):
-+ '''Returns the language ID, default 'en'.'''
-+ return self.attrs['lang']
-+
-+ def GetContext(self):
-+ return self.attrs['context']
-+
-+ def GetFilename(self):
-+ return self.attrs['filename']
-+
-+ def GetOutputFilename(self):
-+ path = None
-+ if hasattr(self, 'output_filename'):
-+ path = self.output_filename
-+ else:
-+ path = self.attrs['filename']
-+ return os.path.expandvars(path)
-+
-+ def GetFallbackToDefaultLayout(self):
-+ return self.attrs['fallback_to_default_layout'].lower() == 'true'
-+
-+ def _IsValidChild(self, child):
-+ return isinstance(child, EmitNode)
-+
-+class EmitNode(base.ContentNode):
-+ ''' An <emit> element.'''
-+
-+ def DefaultAttributes(self):
-+ return { 'emit_type' : 'prepend'}
-+
-+ def GetEmitType(self):
-+ '''Returns the emit_type for this node. Default is 'append'.'''
-+ return self.attrs['emit_type']
-diff --git a/tools/grit/grit/node/node_io_unittest.py b/tools/grit/grit/node/node_io_unittest.py
-new file mode 100644
-index 0000000000..1f45e51af8
---- /dev/null
-+++ b/tools/grit/grit/node/node_io_unittest.py
-@@ -0,0 +1,182 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for node_io.FileNode'''
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+import unittest
-+
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+from six import StringIO
-+
-+from grit.node import misc
-+from grit.node import node_io
-+from grit.node import empty
-+from grit import grd_reader
-+from grit import util
-+
-+
-+def _GetAllCliques(root_node):
-+ """Return all cliques in the |root_node| tree."""
-+ ret = []
-+ for node in root_node:
-+ ret.extend(node.GetCliques())
-+ return ret
-+
-+
-+class FileNodeUnittest(unittest.TestCase):
-+ def testGetPath(self):
-+ root = misc.GritNode()
-+ root.StartParsing(u'grit', None)
-+ root.HandleAttribute(u'latest_public_release', u'0')
-+ root.HandleAttribute(u'current_release', u'1')
-+ root.HandleAttribute(u'base_dir', r'..\resource')
-+ translations = empty.TranslationsNode()
-+ translations.StartParsing(u'translations', root)
-+ root.AddChild(translations)
-+ file_node = node_io.FileNode()
-+ file_node.StartParsing(u'file', translations)
-+ file_node.HandleAttribute(u'path', r'flugel\kugel.pdf')
-+ translations.AddChild(file_node)
-+ root.EndParsing()
-+
-+ self.failUnless(root.ToRealPath(file_node.GetInputPath()) ==
-+ util.normpath(
-+ os.path.join(r'../resource', r'flugel/kugel.pdf')))
-+
-+ def VerifyCliquesContainEnglishAndFrenchAndNothingElse(self, cliques):
-+ self.assertEqual(2, len(cliques))
-+ for clique in cliques:
-+ self.assertEqual({'en', 'fr'}, set(clique.clique.keys()))
-+
-+ def testLoadTranslations(self):
-+ xml = '''<?xml version="1.0" encoding="UTF-8"?>
-+ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
-+ <translations>
-+ <file path="generated_resources_fr.xtb" lang="fr" />
-+ </translations>
-+ <release seq="3">
-+ <messages>
-+ <message name="ID_HELLO">Hello!</message>
-+ <message name="ID_HELLO_USER">Hello <ph name="USERNAME">%s<ex>Joi</ex></ph></message>
-+ </messages>
-+ </release>
-+ </grit>'''
-+ grd = grd_reader.Parse(StringIO(xml),
-+ util.PathFromRoot('grit/testdata'))
-+ grd.SetOutputLanguage('en')
-+ grd.RunGatherers()
-+ self.VerifyCliquesContainEnglishAndFrenchAndNothingElse(_GetAllCliques(grd))
-+
-+ def testIffyness(self):
-+ grd = grd_reader.Parse(StringIO('''<?xml version="1.0" encoding="UTF-8"?>
-+ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
-+ <translations>
-+ <if expr="lang == 'fr'">
-+ <file path="generated_resources_fr.xtb" lang="fr" />
-+ </if>
-+ </translations>
-+ <release seq="3">
-+ <messages>
-+ <message name="ID_HELLO">Hello!</message>
-+ <message name="ID_HELLO_USER">Hello <ph name="USERNAME">%s<ex>Joi</ex></ph></message>
-+ </messages>
-+ </release>
-+ </grit>'''), util.PathFromRoot('grit/testdata'))
-+ grd.SetOutputLanguage('en')
-+ grd.RunGatherers()
-+ cliques = _GetAllCliques(grd)
-+ self.assertEqual(2, len(cliques))
-+ for clique in cliques:
-+ self.assertEqual({'en'}, set(clique.clique.keys()))
-+
-+ grd.SetOutputLanguage('fr')
-+ grd.RunGatherers()
-+ self.VerifyCliquesContainEnglishAndFrenchAndNothingElse(_GetAllCliques(grd))
-+
-+ def testConditionalLoadTranslations(self):
-+ xml = '''<?xml version="1.0" encoding="UTF-8"?>
-+ <grit latest_public_release="2" source_lang_id="en-US" current_release="3"
-+ base_dir=".">
-+ <translations>
-+ <if expr="True">
-+ <file path="generated_resources_fr.xtb" lang="fr" />
-+ </if>
-+ <if expr="False">
-+ <file path="no_such_file.xtb" lang="de" />
-+ </if>
-+ </translations>
-+ <release seq="3">
-+ <messages>
-+ <message name="ID_HELLO">Hello!</message>
-+ <message name="ID_HELLO_USER">Hello <ph name="USERNAME">%s<ex>
-+ Joi</ex></ph></message>
-+ </messages>
-+ </release>
-+ </grit>'''
-+ grd = grd_reader.Parse(StringIO(xml),
-+ util.PathFromRoot('grit/testdata'))
-+ grd.SetOutputLanguage('en')
-+ grd.RunGatherers()
-+ self.VerifyCliquesContainEnglishAndFrenchAndNothingElse(_GetAllCliques(grd))
-+
-+ def testConditionalOutput(self):
-+ xml = '''<?xml version="1.0" encoding="UTF-8"?>
-+ <grit latest_public_release="2" source_lang_id="en-US" current_release="3"
-+ base_dir=".">
-+ <outputs>
-+ <output filename="resource.h" type="rc_header" />
-+ <output filename="en/generated_resources.rc" type="rc_all"
-+ lang="en" />
-+ <if expr="pp_if('NOT_TRUE')">
-+ <output filename="de/generated_resources.rc" type="rc_all"
-+ lang="de" />
-+ </if>
-+ </outputs>
-+ <release seq="3">
-+ <messages>
-+ <message name="ID_HELLO">Hello!</message>
-+ </messages>
-+ </release>
-+ </grit>'''
-+ grd = grd_reader.Parse(StringIO(xml),
-+ util.PathFromRoot('grit/test/data'),
-+ defines={})
-+ grd.SetOutputLanguage('en')
-+ grd.RunGatherers()
-+ outputs = grd.GetChildrenOfType(node_io.OutputNode)
-+ active = set(grd.ActiveDescendants())
-+ self.failUnless(outputs[0] in active)
-+ self.failUnless(outputs[0].GetType() == 'rc_header')
-+ self.failUnless(outputs[1] in active)
-+ self.failUnless(outputs[1].GetType() == 'rc_all')
-+ self.failUnless(outputs[2] not in active)
-+ self.failUnless(outputs[2].GetType() == 'rc_all')
-+
-+ # Verify that 'iw' and 'no' language codes in xtb files are mapped to 'he' and
-+ # 'nb'.
-+ def testLangCodeMapping(self):
-+ grd = grd_reader.Parse(StringIO('''<?xml version="1.0" encoding="UTF-8"?>
-+ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
-+ <translations>
-+ <file path="generated_resources_no.xtb" lang="nb" />
-+ <file path="generated_resources_iw.xtb" lang="he" />
-+ </translations>
-+ <release seq="3">
-+ <messages></messages>
-+ </release>
-+ </grit>'''), util.PathFromRoot('grit/testdata'))
-+ grd.SetOutputLanguage('en')
-+ grd.RunGatherers()
-+ self.assertEqual([], _GetAllCliques(grd))
-+
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/node/structure.py b/tools/grit/grit/node/structure.py
-new file mode 100644
-index 0000000000..ec170faebb
---- /dev/null
-+++ b/tools/grit/grit/node/structure.py
-@@ -0,0 +1,375 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''The <structure> element.
-+'''
-+
-+from __future__ import print_function
-+
-+import os
-+import platform
-+import re
-+
-+from grit import exception
-+from grit import util
-+from grit.node import base
-+from grit.node import variant
-+
-+import grit.gather.admin_template
-+import grit.gather.chrome_html
-+import grit.gather.chrome_scaled_image
-+import grit.gather.policy_json
-+import grit.gather.rc
-+import grit.gather.tr_html
-+import grit.gather.txt
-+
-+import grit.format.rc
-+
-+# Type of the gatherer to use for each type attribute
-+_GATHERERS = {
-+ 'accelerators' : grit.gather.rc.Accelerators,
-+ 'admin_template' : grit.gather.admin_template.AdmGatherer,
-+ 'chrome_html' : grit.gather.chrome_html.ChromeHtml,
-+ 'chrome_scaled_image' : grit.gather.chrome_scaled_image.ChromeScaledImage,
-+ 'dialog' : grit.gather.rc.Dialog,
-+ 'menu' : grit.gather.rc.Menu,
-+ 'rcdata' : grit.gather.rc.RCData,
-+ 'tr_html' : grit.gather.tr_html.TrHtml,
-+ 'txt' : grit.gather.txt.TxtFile,
-+ 'version' : grit.gather.rc.Version,
-+ 'policy_template_metafile' : grit.gather.policy_json.PolicyJson,
-+}
-+
-+
-+# TODO(joi) Print a warning if the 'variant_of_revision' attribute indicates
-+# that a skeleton variant is older than the original file.
-+
-+
-+class StructureNode(base.Node):
-+ '''A <structure> element.'''
-+
-+ # Regular expression for a local variable definition. Each definition
-+ # is of the form NAME=VALUE, where NAME cannot contain '=' or ',' and
-+ # VALUE must escape all commas: ',' -> ',,'. Each variable definition
-+ # should be separated by a comma with no extra whitespace.
-+ # Example: THING1=foo,THING2=bar
-+ variable_pattern = re.compile(r'([^,=\s]+)=((?:,,|[^,])*)')
-+
-+ def __init__(self):
-+ super(StructureNode, self).__init__()
-+
-+ # Keep track of the last filename we flattened to, so we can
-+ # avoid doing it more than once.
-+ self._last_flat_filename = None
-+
-+ # See _Substitute; this substituter is used for local variables and
-+ # the root substituter is used for global variables.
-+ self.substituter = None
-+
-+ def _IsValidChild(self, child):
-+ return isinstance(child, variant.SkeletonNode)
-+
-+ def _ParseVariables(self, variables):
-+ '''Parse a variable string into a dictionary.'''
-+ matches = StructureNode.variable_pattern.findall(variables)
-+ return dict((name, value.replace(',,', ',')) for name, value in matches)
-+
-+ def EndParsing(self):
-+ super(StructureNode, self).EndParsing()
-+
-+ # Now that we have attributes and children, instantiate the gatherers.
-+ gathertype = _GATHERERS[self.attrs['type']]
-+
-+ self.gatherer = gathertype(self.attrs['file'],
-+ self.attrs['name'],
-+ self.attrs['encoding'])
-+ self.gatherer.SetGrdNode(self)
-+ self.gatherer.SetUberClique(self.UberClique())
-+ if hasattr(self.GetRoot(), 'defines'):
-+ self.gatherer.SetDefines(self.GetRoot().defines)
-+ self.gatherer.SetAttributes(self.attrs)
-+ if self.ExpandVariables():
-+ self.gatherer.SetFilenameExpansionFunction(self._Substitute)
-+
-+ # Parse local variables and instantiate the substituter.
-+ if self.attrs['variables']:
-+ variables = self.attrs['variables']
-+ self.substituter = util.Substituter()
-+ self.substituter.AddSubstitutions(self._ParseVariables(variables))
-+
-+ self.skeletons = {} # Maps expressions to skeleton gatherers
-+ for child in self.children:
-+ assert isinstance(child, variant.SkeletonNode)
-+ skel = gathertype(child.attrs['file'],
-+ self.attrs['name'],
-+ child.GetEncodingToUse(),
-+ is_skeleton=True)
-+ skel.SetGrdNode(self) # TODO(benrg): Or child? Only used for ToRealPath
-+ skel.SetUberClique(self.UberClique())
-+ if hasattr(self.GetRoot(), 'defines'):
-+ skel.SetDefines(self.GetRoot().defines)
-+ if self.ExpandVariables():
-+ skel.SetFilenameExpansionFunction(self._Substitute)
-+ self.skeletons[child.attrs['expr']] = skel
-+
-+ def MandatoryAttributes(self):
-+ return ['type', 'name', 'file']
-+
-+ def DefaultAttributes(self):
-+ return {
-+ 'encoding': 'cp1252',
-+ 'exclude_from_rc': 'false',
-+ 'line_end': 'unix',
-+ 'output_encoding': 'utf-8',
-+ 'generateid': 'true',
-+ 'expand_variables': 'false',
-+ 'output_filename': '',
-+ 'fold_whitespace': 'false',
-+ # Run an arbitrary command after translation is complete
-+ # so that it doesn't interfere with what's in translation
-+ # console.
-+ 'run_command': '',
-+ # Leave empty to run on all platforms, comma-separated
-+ # for one or more specific platforms. Values must match
-+ # output of platform.system().
-+ 'run_command_on_platforms': '',
-+ 'allowexternalscript': 'false',
-+ # preprocess takes the same code path as flattenhtml, but it
-+ # disables any processing/inlining outside of <if> and <include>.
-+ 'preprocess': 'false',
-+ 'flattenhtml': 'false',
-+ 'fallback_to_low_resolution': 'default',
-+ 'variables': '',
-+ 'compress': 'default',
-+ 'use_base_dir': 'true',
-+ }
-+
-+ def IsExcludedFromRc(self):
-+ return self.attrs['exclude_from_rc'] == 'true'
-+
-+ def Process(self, output_dir):
-+ """Writes the processed data to output_dir. In the case of a chrome_html
-+ structure this will add references to other scale factors. If flattening
-+ this will also write file references to be base64 encoded data URLs. The
-+ name of the new file is returned."""
-+ filename = self.ToRealPath(self.GetInputPath())
-+ flat_filename = os.path.join(output_dir,
-+ self.attrs['name'] + '_' + os.path.basename(filename))
-+
-+ if self._last_flat_filename == flat_filename:
-+ return
-+
-+ with open(flat_filename, 'wb') as outfile:
-+ if self.ExpandVariables():
-+ text = self.gatherer.GetText()
-+ file_contents = self._Substitute(text)
-+ else:
-+ file_contents = self.gatherer.GetData('', 'utf-8')
-+ outfile.write(file_contents.encode('utf-8'))
-+
-+ self._last_flat_filename = flat_filename
-+ return os.path.basename(flat_filename)
-+
-+ def GetLineEnd(self):
-+ '''Returns the end-of-line character or characters for files output because
-+ of this node ('\r\n', '\n', or '\r' depending on the 'line_end' attribute).
-+ '''
-+ if self.attrs['line_end'] == 'unix':
-+ return '\n'
-+ elif self.attrs['line_end'] == 'windows':
-+ return '\r\n'
-+ elif self.attrs['line_end'] == 'mac':
-+ return '\r'
-+ else:
-+ raise exception.UnexpectedAttribute(
-+ "Attribute 'line_end' must be one of 'unix' (default), 'windows' or "
-+ "'mac'")
-+
-+ def GetCliques(self):
-+ return self.gatherer.GetCliques()
-+
-+ def GetDataPackValue(self, lang, encoding):
-+ """Returns a bytes representation for a data_pack entry."""
-+ if self.ExpandVariables():
-+ text = self.gatherer.GetText()
-+ data = util.Encode(self._Substitute(text), encoding)
-+ else:
-+ data = self.gatherer.GetData(lang, encoding)
-+ if encoding != util.BINARY:
-+ data = data.encode(encoding)
-+ return self.CompressDataIfNeeded(data)
-+
-+ def GetHtmlResourceFilenames(self):
-+ """Returns a set of all filenames inlined by this node."""
-+ return self.gatherer.GetHtmlResourceFilenames()
-+
-+ def GetInputPath(self):
-+ path = self.gatherer.GetInputPath()
-+ if path is None:
-+ return path
-+
-+ # Do not mess with absolute paths, that would make them invalid.
-+ if os.path.isabs(os.path.expandvars(path)):
-+ return path
-+
-+ # We have no control over code that calls ToRealPath later, so convert
-+ # the path to be relative against our basedir.
-+ if self.attrs.get('use_base_dir', 'true') != 'true':
-+ # Normalize the directory path to use the appropriate OS separator.
-+ # GetBaseDir() may return paths\like\this or paths/like/this, since it is
-+ # read from the base_dir attribute in the grd file.
-+ norm_base_dir = util.normpath(self.GetRoot().GetBaseDir())
-+ return os.path.relpath(path, norm_base_dir)
-+
-+ return path
-+
-+ def GetTextualIds(self):
-+ if not hasattr(self, 'gatherer'):
-+ # This case is needed because this method is called by
-+ # GritNode.ValidateUniqueIds before RunGatherers has been called.
-+ # TODO(benrg): Fix this?
-+ return [self.attrs['name']]
-+ return self.gatherer.GetTextualIds()
-+
-+ def RunPreSubstitutionGatherer(self, debug=False):
-+ if debug:
-+ print('Running gatherer %s for file %s' %
-+ (type(self.gatherer), self.GetInputPath()))
-+
-+ # Note: Parse() is idempotent, therefore this method is also.
-+ self.gatherer.Parse()
-+ for skel in self.skeletons.values():
-+ skel.Parse()
-+
-+ def GetSkeletonGatherer(self):
-+ '''Returns the gatherer for the alternate skeleton that should be used,
-+ based on the expressions for selecting skeletons, or None if the skeleton
-+ from the English version of the structure should be used.
-+ '''
-+ for expr in self.skeletons:
-+ if self.EvaluateCondition(expr):
-+ return self.skeletons[expr]
-+ return None
-+
-+ def HasFileForLanguage(self):
-+ return self.attrs['type'] in ['tr_html', 'admin_template', 'txt',
-+ 'chrome_scaled_image',
-+ 'chrome_html']
-+
-+ def ExpandVariables(self):
-+ '''Variable expansion on structures is controlled by an XML attribute.
-+
-+ However, old files assume that expansion is always on for Rc files.
-+
-+ Returns:
-+ A boolean.
-+ '''
-+ attrs = self.GetRoot().attrs
-+ if 'grit_version' in attrs and attrs['grit_version'] > 1:
-+ return self.attrs['expand_variables'] == 'true'
-+ else:
-+ return (self.attrs['expand_variables'] == 'true' or
-+ self.attrs['file'].lower().endswith('.rc'))
-+
-+ def _Substitute(self, text):
-+ '''Perform local and global variable substitution.'''
-+ if self.substituter:
-+ text = self.substituter.Substitute(text)
-+ return self.GetRoot().GetSubstituter().Substitute(text)
-+
-+ def RunCommandOnCurrentPlatform(self):
-+ if self.attrs['run_command_on_platforms'] == '':
-+ return True
-+ else:
-+ target_platforms = self.attrs['run_command_on_platforms'].split(',')
-+ return platform.system() in target_platforms
-+
-+ def FileForLanguage(self, lang, output_dir, create_file=True,
-+ return_if_not_generated=True):
-+ '''Returns the filename of the file associated with this structure,
-+ for the specified language.
-+
-+ Args:
-+ lang: 'fr'
-+ output_dir: 'c:\temp'
-+ create_file: True
-+ '''
-+ assert self.HasFileForLanguage()
-+ # If the source language is requested, and no extra changes are requested,
-+ # use the existing file.
-+ if ((not lang or lang == self.GetRoot().GetSourceLanguage()) and
-+ self.attrs['expand_variables'] != 'true' and
-+ (not self.attrs['run_command'] or
-+ not self.RunCommandOnCurrentPlatform())):
-+ if return_if_not_generated:
-+ input_path = self.GetInputPath()
-+ if input_path is None:
-+ return None
-+ return self.ToRealPath(input_path)
-+ else:
-+ return None
-+
-+ if self.attrs['output_filename'] != '':
-+ filename = self.attrs['output_filename']
-+ else:
-+ filename = os.path.basename(self.attrs['file'])
-+ assert len(filename)
-+ filename = '%s_%s' % (lang, filename)
-+ filename = os.path.join(output_dir, filename)
-+
-+ # Only create the output if it was requested by the call.
-+ if create_file:
-+ text = self.gatherer.Translate(
-+ lang,
-+ pseudo_if_not_available=self.PseudoIsAllowed(),
-+ fallback_to_english=self.ShouldFallbackToEnglish(),
-+ skeleton_gatherer=self.GetSkeletonGatherer())
-+
-+ file_contents = util.FixLineEnd(text, self.GetLineEnd())
-+ if self.ExpandVariables():
-+ # Note that we reapply substitution a second time here.
-+ # This is because a) we need to look inside placeholders
-+ # b) the substitution values are language-dependent
-+ file_contents = self._Substitute(file_contents)
-+
-+ with open(filename, 'wb') as file_object:
-+ output_stream = util.WrapOutputStream(file_object,
-+ self.attrs['output_encoding'])
-+ output_stream.write(file_contents)
-+
-+ if self.attrs['run_command'] and self.RunCommandOnCurrentPlatform():
-+ # Run arbitrary commands after translation is complete so that it
-+ # doesn't interfere with what's in translation console.
-+ command = self.attrs['run_command'] % {'filename': filename}
-+ result = os.system(command)
-+ assert result == 0, '"%s" failed.' % command
-+
-+ return filename
-+
-+ def IsResourceMapSource(self):
-+ return True
-+
-+ @staticmethod
-+ def Construct(parent, name, type, file, encoding='cp1252'):
-+ '''Creates a new node which is a child of 'parent', with attributes set
-+ by parameters of the same name.
-+ '''
-+ node = StructureNode()
-+ node.StartParsing('structure', parent)
-+ node.HandleAttribute('name', name)
-+ node.HandleAttribute('type', type)
-+ node.HandleAttribute('file', file)
-+ node.HandleAttribute('encoding', encoding)
-+ node.EndParsing()
-+ return node
-+
-+ def SubstituteMessages(self, substituter):
-+ '''Propagates substitution to gatherer.
-+
-+ Args:
-+ substituter: a grit.util.Substituter object.
-+ '''
-+ assert hasattr(self, 'gatherer')
-+ if self.ExpandVariables():
-+ self.gatherer.SubstituteMessages(substituter)
-diff --git a/tools/grit/grit/node/structure_unittest.py b/tools/grit/grit/node/structure_unittest.py
-new file mode 100644
-index 0000000000..0e66dce37a
---- /dev/null
-+++ b/tools/grit/grit/node/structure_unittest.py
-@@ -0,0 +1,178 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for <structure> nodes.
-+'''
-+
-+from __future__ import print_function
-+
-+import os
-+import os.path
-+import sys
-+import zlib
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+import platform
-+import tempfile
-+import unittest
-+import struct
-+
-+from grit import constants
-+from grit import util
-+from grit.node import brotli_util
-+from grit.node import structure
-+from grit.format import rc
-+
-+
-+def checkIsGzipped(filename, compress_attr):
-+ test_data_root = util.PathFromRoot('grit/testdata')
-+ root = util.ParseGrdForUnittest(
-+ '''
-+ <structures>
-+ <structure name="TEST_TXT" file="%s" %s type="chrome_html"/>
-+ </structures>''' % (filename, compress_attr),
-+ base_dir=test_data_root)
-+ node, = root.GetChildrenOfType(structure.StructureNode)
-+ node.RunPreSubstitutionGatherer()
-+ compressed = node.GetDataPackValue(lang='en', encoding=util.BINARY)
-+
-+ decompressed_data = zlib.decompress(compressed, 16 + zlib.MAX_WBITS)
-+ expected = util.ReadFile(os.path.join(test_data_root, filename), util.BINARY)
-+ return expected == decompressed_data
-+
-+
-+class StructureUnittest(unittest.TestCase):
-+ def testSkeleton(self):
-+ grd = util.ParseGrdForUnittest('''
-+ <structures>
-+ <structure type="dialog" name="IDD_ABOUTBOX" file="klonk.rc" encoding="utf-16-le">
-+ <skeleton expr="lang == 'fr'" variant_of_revision="1" file="klonk-alternate-skeleton.rc" />
-+ </structure>
-+ </structures>''', base_dir=util.PathFromRoot('grit/testdata'))
-+ grd.SetOutputLanguage('fr')
-+ grd.RunGatherers()
-+ transl = ''.join(rc.Format(grd, 'fr', '.'))
-+ self.failUnless(transl.count('040704') and transl.count('110978'))
-+ self.failUnless(transl.count('2005",IDC_STATIC'))
-+
-+ def testRunCommandOnCurrentPlatform(self):
-+ node = structure.StructureNode()
-+ node.attrs = node.DefaultAttributes()
-+ self.failUnless(node.RunCommandOnCurrentPlatform())
-+ node.attrs['run_command_on_platforms'] = 'Nosuch'
-+ self.failIf(node.RunCommandOnCurrentPlatform())
-+ node.attrs['run_command_on_platforms'] = (
-+ 'Nosuch,%s,Othernot' % platform.system())
-+ self.failUnless(node.RunCommandOnCurrentPlatform())
-+
-+ def testVariables(self):
-+ grd = util.ParseGrdForUnittest('''
-+ <structures>
-+ <structure type="chrome_html" name="hello_tmpl" file="structure_variables.html" expand_variables="true" variables="GREETING=Hello,THINGS=foo,, bar,, baz,EQUATION=2+2==4,filename=simple" flattenhtml="true"></structure>
-+ </structures>''', base_dir=util.PathFromRoot('grit/testdata'))
-+ grd.SetOutputLanguage('en')
-+ grd.RunGatherers()
-+ node, = grd.GetChildrenOfType(structure.StructureNode)
-+ filename = node.Process(tempfile.gettempdir())
-+ filepath = os.path.join(tempfile.gettempdir(), filename)
-+ with open(filepath) as f:
-+ result = f.read()
-+ self.failUnlessEqual(('<h1>Hello!</h1>\n'
-+ 'Some cool things are foo, bar, baz.\n'
-+ 'Did you know that 2+2==4?\n'
-+ '<p>\n'
-+ ' Hello!\n'
-+ '</p>\n'), result)
-+ os.remove(filepath)
-+
-+ def testGetPath(self):
-+ base_dir = util.PathFromRoot('grit/testdata')
-+ grd = util.ParseGrdForUnittest('''
-+ <structures>
-+ <structure type="chrome_html" name="hello_tmpl" file="structure_variables.html" expand_variables="true" variables="GREETING=Hello,THINGS=foo,, bar,, baz,EQUATION=2+2==4,filename=simple" flattenhtml="true" use_base_dir="true"></structure>
-+ </structures>''', base_dir)
-+ grd.SetOutputLanguage('en')
-+ grd.RunGatherers()
-+ node, = grd.GetChildrenOfType(structure.StructureNode)
-+ self.assertEqual(grd.ToRealPath(node.GetInputPath()),
-+ os.path.abspath(os.path.join(
-+ base_dir, r'structure_variables.html')))
-+
-+ def testGetPathNoBasedir(self):
-+ base_dir = util.PathFromRoot('grit/testdata')
-+ abs_path = os.path.join(base_dir, r'structure_variables.html')
-+ rel_path = os.path.relpath(abs_path, os.getcwd())
-+ grd = util.ParseGrdForUnittest('''
-+ <structures>
-+ <structure type="chrome_html" name="hello_tmpl" file="''' + rel_path + '''" expand_variables="true" variables="GREETING=Hello,THINGS=foo,, bar,, baz,EQUATION=2+2==4,filename=simple" flattenhtml="true" use_base_dir="false"></structure>
-+ </structures>''', util.PathFromRoot('grit/testdata'))
-+ grd.SetOutputLanguage('en')
-+ grd.RunGatherers()
-+ node, = grd.GetChildrenOfType(structure.StructureNode)
-+ self.assertEqual(grd.ToRealPath(node.GetInputPath()),
-+ os.path.abspath(os.path.join(
-+ base_dir, r'structure_variables.html')))
-+
-+ def testCompressGzip(self):
-+ self.assertTrue(checkIsGzipped('test_text.txt', 'compress="gzip"'))
-+
-+ def testCompressGzipByDefault(self):
-+ self.assertTrue(checkIsGzipped('test_html.html', ''))
-+ self.assertTrue(checkIsGzipped('test_js.js', ''))
-+ self.assertTrue(checkIsGzipped('test_css.css', ''))
-+ self.assertTrue(checkIsGzipped('test_svg.svg', ''))
-+
-+ self.assertTrue(checkIsGzipped('test_html.html', 'compress="default"'))
-+ self.assertTrue(checkIsGzipped('test_js.js', 'compress="default"'))
-+ self.assertTrue(checkIsGzipped('test_css.css', 'compress="default"'))
-+ self.assertTrue(checkIsGzipped('test_svg.svg', 'compress="default"'))
-+
-+ def testCompressBrotli(self):
-+ test_data_root = util.PathFromRoot('grit/testdata')
-+ root = util.ParseGrdForUnittest(
-+ '''
-+ <structures>
-+ <structure name="TEST_TXT" file="test_text.txt"
-+ compress="brotli" type="chrome_html" />
-+ </structures>''',
-+ base_dir=test_data_root)
-+ node, = root.GetChildrenOfType(structure.StructureNode)
-+ node.RunPreSubstitutionGatherer()
-+
-+ # Using the mock brotli decompression executable.
-+ brotli_util.SetBrotliCommand([sys.executable,
-+ os.path.join(os.path.dirname(__file__),
-+ 'mock_brotli.py')])
-+ compressed = node.GetDataPackValue(lang='en', encoding=util.BINARY)
-+ # Assert that the first two bytes in compressed format is BROTLI_CONST.
-+ self.assertEqual(constants.BROTLI_CONST, compressed[0:2])
-+
-+ # Compare the actual size of the uncompressed test data with
-+ # the size appended during compression.
-+ actual_size = len(util.ReadFile(
-+ os.path.join(test_data_root, 'test_text.txt'), util.BINARY))
-+ uncompress_size = struct.unpack('<i', compressed[2:6])[0]
-+ uncompress_size += struct.unpack('<h', compressed[6:8])[0] << 4*8
-+ self.assertEqual(actual_size, uncompress_size)
-+
-+ self.assertEqual(b'This has been mock compressed!', compressed[8:])
-+
-+ def testNotCompressed(self):
-+ test_data_root = util.PathFromRoot('grit/testdata')
-+ root = util.ParseGrdForUnittest('''
-+ <structures>
-+ <structure name="TEST_TXT" file="test_text.txt" type="chrome_html" />
-+ </structures>''', base_dir=test_data_root)
-+ node, = root.GetChildrenOfType(structure.StructureNode)
-+ node.RunPreSubstitutionGatherer()
-+ data = node.GetDataPackValue(lang='en', encoding=util.BINARY)
-+
-+ self.assertEqual(util.ReadFile(
-+ os.path.join(test_data_root, 'test_text.txt'), util.BINARY), data)
-+
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/node/variant.py b/tools/grit/grit/node/variant.py
-new file mode 100644
-index 0000000000..9f5845f954
---- /dev/null
-+++ b/tools/grit/grit/node/variant.py
-@@ -0,0 +1,41 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''The <skeleton> element.
-+'''
-+
-+from __future__ import print_function
-+
-+from grit.node import base
-+
-+
-+class SkeletonNode(base.Node):
-+ '''A <skeleton> element.'''
-+
-+ # TODO(joi) Support inline skeleton variants as CDATA instead of requiring
-+ # a 'file' attribute.
-+
-+ def MandatoryAttributes(self):
-+ return ['expr', 'variant_of_revision', 'file']
-+
-+ def DefaultAttributes(self):
-+ '''If not specified, 'encoding' will actually default to the parent node's
-+ encoding.
-+ '''
-+ return {'encoding' : ''}
-+
-+ def _ContentType(self):
-+ if 'file' in self.attrs:
-+ return self._CONTENT_TYPE_NONE
-+ else:
-+ return self._CONTENT_TYPE_CDATA
-+
-+ def GetEncodingToUse(self):
-+ if self.attrs['encoding'] == '':
-+ return self.parent.attrs['encoding']
-+ else:
-+ return self.attrs['encoding']
-+
-+ def GetInputPath(self):
-+ return self.attrs['file']
-diff --git a/tools/grit/grit/pseudo.py b/tools/grit/grit/pseudo.py
-new file mode 100644
-index 0000000000..b607bfc6bb
---- /dev/null
-+++ b/tools/grit/grit/pseudo.py
-@@ -0,0 +1,129 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Pseudotranslation support. Our pseudotranslations are based on the
-+P-language, which is a simple vowel-extending language. Examples of P:
-+ - "hello" becomes "hepellopo"
-+ - "howdie" becomes "hopowdiepie"
-+ - "because" becomes "bepecaupause" (but in our implementation we don't
-+ handle the silent e at the end so it actually would return "bepecaupausepe"
-+
-+The P-language has the excellent quality of increasing the length of text
-+by around 30-50% which is great for pseudotranslations, to stress test any
-+GUI layouts etc.
-+
-+To make the pseudotranslations more obviously "not a translation" and to make
-+them exercise any code that deals with encodings, we also transform all English
-+vowels into equivalent vowels with diacriticals on them (rings, acutes,
-+diaresis, and circumflex), and we write the "p" in the P-language as a Hebrew
-+character Qof. It looks sort of like a latin character "p" but it is outside
-+the latin-1 character set which will stress character encoding bugs.
-+'''
-+
-+from __future__ import print_function
-+
-+from grit import lazy_re
-+from grit import tclib
-+
-+
-+# An RFC language code for the P pseudolanguage.
-+PSEUDO_LANG = 'x-P-pseudo'
-+
-+# Hebrew character Qof. It looks kind of like a 'p' but is outside
-+# the latin-1 character set which is good for our purposes.
-+# TODO(joi) For now using P instead of Qof, because of some bugs it used. Find
-+# a better solution, i.e. one that introduces a non-latin1 character into the
-+# pseudotranslation.
-+#_QOF = u'\u05e7'
-+_QOF = u'P'
-+
-+# How we map each vowel.
-+_VOWELS = {
-+ u'a' : u'\u00e5', # a with ring
-+ u'e' : u'\u00e9', # e acute
-+ u'i' : u'\u00ef', # i diaresis
-+ u'o' : u'\u00f4', # o circumflex
-+ u'u' : u'\u00fc', # u diaresis
-+ u'y' : u'\u00fd', # y acute
-+ u'A' : u'\u00c5', # A with ring
-+ u'E' : u'\u00c9', # E acute
-+ u'I' : u'\u00cf', # I diaresis
-+ u'O' : u'\u00d4', # O circumflex
-+ u'U' : u'\u00dc', # U diaresis
-+ u'Y' : u'\u00dd', # Y acute
-+}
-+_VOWELS_KEYS = set(_VOWELS.keys())
-+
-+# Matches vowels and P
-+_PSUB_RE = lazy_re.compile("(%s)" % '|'.join(_VOWELS_KEYS | {'P'}))
-+
-+
-+# Pseudotranslations previously created. This is important for performance
-+# reasons, especially since we routinely pseudotranslate the whole project
-+# several or many different times for each build.
-+_existing_translations = {}
-+
-+
-+def MapVowels(str, also_p = False):
-+ '''Returns a copy of 'str' where characters that exist as keys in _VOWELS
-+ have been replaced with the corresponding value. If also_p is true, this
-+ function will also change capital P characters into a Hebrew character Qof.
-+ '''
-+ def Repl(match):
-+ if match.group() == 'p':
-+ if also_p:
-+ return _QOF
-+ else:
-+ return 'p'
-+ else:
-+ return _VOWELS[match.group()]
-+ return _PSUB_RE.sub(Repl, str)
-+
-+
-+def PseudoString(str):
-+ '''Returns a pseudotranslation of the provided string, in our enhanced
-+ P-language.'''
-+ if str in _existing_translations:
-+ return _existing_translations[str]
-+
-+ outstr = u''
-+ ix = 0
-+ while ix < len(str):
-+ if str[ix] not in _VOWELS_KEYS:
-+ outstr += str[ix]
-+ ix += 1
-+ else:
-+ # We want to treat consecutive vowels as one composite vowel. This is not
-+ # always accurate e.g. in composite words but good enough.
-+ consecutive_vowels = u''
-+ while ix < len(str) and str[ix] in _VOWELS_KEYS:
-+ consecutive_vowels += str[ix]
-+ ix += 1
-+ changed_vowels = MapVowels(consecutive_vowels)
-+ outstr += changed_vowels
-+ outstr += _QOF
-+ outstr += changed_vowels
-+
-+ _existing_translations[str] = outstr
-+ return outstr
-+
-+
-+def PseudoMessage(message):
-+ '''Returns a pseudotranslation of the provided message.
-+
-+ Args:
-+ message: tclib.Message()
-+
-+ Return:
-+ tclib.Translation()
-+ '''
-+ transl = tclib.Translation()
-+
-+ for part in message.GetContent():
-+ if isinstance(part, tclib.Placeholder):
-+ transl.AppendPlaceholder(part)
-+ else:
-+ transl.AppendText(PseudoString(part))
-+
-+ return transl
-diff --git a/tools/grit/grit/pseudo_rtl.py b/tools/grit/grit/pseudo_rtl.py
-new file mode 100644
-index 0000000000..2240b571de
---- /dev/null
-+++ b/tools/grit/grit/pseudo_rtl.py
-@@ -0,0 +1,104 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Pseudo RTL, (aka Fake Bidi) support. It simply wraps each word with
-+Unicode RTL overrides.
-+More info at https://sites.google.com/a/chromium.org/dev/Home/fake-bidi
-+'''
-+
-+from __future__ import print_function
-+
-+import re
-+
-+from grit import lazy_re
-+from grit import tclib
-+
-+ACCENTED_STRINGS = {
-+ 'a': u"\u00e5", 'e': u"\u00e9", 'i': u"\u00ee", 'o': u"\u00f6",
-+ 'u': u"\u00fb", 'A': u"\u00c5", 'E': u"\u00c9", 'I': u"\u00ce",
-+ 'O': u"\u00d6", 'U': u"\u00db", 'c': u"\u00e7", 'd': u"\u00f0",
-+ 'n': u"\u00f1", 'p': u"\u00fe", 'y': u"\u00fd", 'C': u"\u00c7",
-+ 'D': u"\u00d0", 'N': u"\u00d1", 'P': u"\u00de", 'Y': u"\u00dd",
-+ 'f': u"\u0192", 's': u"\u0161", 'S': u"\u0160", 'z': u"\u017e",
-+ 'Z': u"\u017d", 'g': u"\u011d", 'G': u"\u011c", 'h': u"\u0125",
-+ 'H': u"\u0124", 'j': u"\u0135", 'J': u"\u0134", 'k': u"\u0137",
-+ 'K': u"\u0136", 'l': u"\u013c", 'L': u"\u013b", 't': u"\u0163",
-+ 'T': u"\u0162", 'w': u"\u0175", 'W': u"\u0174",
-+ '$': u"\u20ac", '?': u"\u00bf", 'R': u"\u00ae", r'!': u"\u00a1",
-+}
-+
-+# a character set containing the keys in ACCENTED_STRINGS
-+# We should not accent characters in an escape sequence such as "\n".
-+# To be safe, we assume every character following a backslash is an escaped
-+# character. We also need to consider the case like "\\n", which means
-+# a blackslash and a character "n", we will accent the character "n".
-+TO_ACCENT = lazy_re.compile(
-+ r'[%s]|\\[a-z\\]' % ''.join(ACCENTED_STRINGS.keys()))
-+
-+# Lex text so that we don't interfere with html tokens and entities.
-+# This lexing scheme will handle all well formed tags and entities, html or
-+# xhtml. It will not handle comments, CDATA sections, or the unescaping tags:
-+# script, style, xmp or listing. If any of those appear in messages,
-+# something is wrong.
-+TOKENS = [ lazy_re.compile(
-+ '^%s' % pattern, # match at the beginning of input
-+ re.I | re.S # html tokens are case-insensitive
-+ )
-+ for pattern in
-+ (
-+ # a run of non html special characters
-+ r'[^<&]+',
-+ # a tag
-+ (r'</?[a-z]\w*' # beginning of tag
-+ r'(?:\s+\w+(?:\s*=\s*' # attribute start
-+ r'(?:[^\s"\'>]+|"[^\"]*"|\'[^\']*\'))?' # attribute value
-+ r')*\s*/?>'),
-+ # an entity
-+ r'&(?:[a-z]\w+|#\d+|#x[\da-f]+);',
-+ # an html special character not part of a special sequence
-+ r'.'
-+ ) ]
-+
-+ALPHABETIC_RUN = lazy_re.compile(r'([^\W0-9_]+)')
-+
-+RLO = u'\u202e'
-+PDF = u'\u202c'
-+
-+def PseudoRTLString(text):
-+ '''Returns a fake bidirectional version of the source string. This code is
-+ based on accentString above, in turn copied from Frank Tang.
-+ '''
-+ parts = []
-+ while text:
-+ m = None
-+ for token in TOKENS:
-+ m = token.search(text)
-+ if m:
-+ part = m.group(0)
-+ text = text[len(part):]
-+ if part[0] not in ('<', '&'):
-+ # not a tag or entity, so accent
-+ part = ALPHABETIC_RUN.sub(lambda run: RLO + run.group() + PDF, part)
-+ parts.append(part)
-+ break
-+ return ''.join(parts)
-+
-+
-+def PseudoRTLMessage(message):
-+ '''Returns a pseudo-RTL (aka Fake-Bidi) translation of the provided message.
-+
-+ Args:
-+ message: tclib.Message()
-+
-+ Return:
-+ tclib.Translation()
-+ '''
-+ transl = tclib.Translation()
-+ for part in message.GetContent():
-+ if isinstance(part, tclib.Placeholder):
-+ transl.AppendPlaceholder(part)
-+ else:
-+ transl.AppendText(PseudoRTLString(part))
-+
-+ return transl
-diff --git a/tools/grit/grit/pseudo_unittest.py b/tools/grit/grit/pseudo_unittest.py
-new file mode 100644
-index 0000000000..b1d53ff401
---- /dev/null
-+++ b/tools/grit/grit/pseudo_unittest.py
-@@ -0,0 +1,55 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for grit.pseudo'''
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
-+
-+import unittest
-+
-+from grit import pseudo
-+from grit import tclib
-+
-+
-+class PseudoUnittest(unittest.TestCase):
-+ def testVowelMapping(self):
-+ self.failUnless(pseudo.MapVowels('abebibobuby') ==
-+ u'\u00e5b\u00e9b\u00efb\u00f4b\u00fcb\u00fd')
-+ self.failUnless(pseudo.MapVowels('ABEBIBOBUBY') ==
-+ u'\u00c5B\u00c9B\u00cfB\u00d4B\u00dcB\u00dd')
-+
-+ def testPseudoString(self):
-+ out = pseudo.PseudoString('hello')
-+ self.failUnless(out == pseudo.MapVowels(u'hePelloPo', True))
-+
-+ def testConsecutiveVowels(self):
-+ out = pseudo.PseudoString("beautiful weather, ain't it?")
-+ self.failUnless(out == pseudo.MapVowels(
-+ u"beauPeautiPifuPul weaPeathePer, aiPain't iPit?", 1))
-+
-+ def testCapitals(self):
-+ out = pseudo.PseudoString("HOWDIE DOODIE, DR. JONES")
-+ self.failUnless(out == pseudo.MapVowels(
-+ u"HOPOWDIEPIE DOOPOODIEPIE, DR. JOPONEPES", 1))
-+
-+ def testPseudoMessage(self):
-+ msg = tclib.Message(text='Hello USERNAME, how are you?',
-+ placeholders=[
-+ tclib.Placeholder('USERNAME', '%s', 'Joi')])
-+ trans = pseudo.PseudoMessage(msg)
-+ # TODO(joi) It would be nicer if 'you' -> 'youPou' instead of
-+ # 'you' -> 'youPyou' and if we handled the silent e in 'are'
-+ self.failUnless(trans.GetPresentableContent() ==
-+ pseudo.MapVowels(
-+ u'HePelloPo USERNAME, hoPow aParePe youPyou?', 1))
-+
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/shortcuts.py b/tools/grit/grit/shortcuts.py
-new file mode 100644
-index 0000000000..0db2ce436c
---- /dev/null
-+++ b/tools/grit/grit/shortcuts.py
-@@ -0,0 +1,93 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Stuff to prevent conflicting shortcuts.
-+'''
-+
-+from __future__ import print_function
-+
-+from grit import lazy_re
-+
-+
-+class ShortcutGroup(object):
-+ '''Manages a list of cliques that belong together in a single shortcut
-+ group. Knows how to detect conflicting shortcut keys.
-+ '''
-+
-+ # Matches shortcut keys, e.g. &J
-+ SHORTCUT_RE = lazy_re.compile('([^&]|^)(&[A-Za-z])')
-+
-+ def __init__(self, name):
-+ self.name = name
-+ # Map of language codes to shortcut keys used (which is a map of
-+ # shortcut keys to counts).
-+ self.keys_by_lang = {}
-+ # List of cliques in this group
-+ self.cliques = []
-+
-+ def AddClique(self, c):
-+ for existing_clique in self.cliques:
-+ if existing_clique.GetId() == c.GetId():
-+ # This happens e.g. when we have e.g.
-+ # <if expr1><structure 1></if> <if expr2><structure 2></if>
-+ # where only one will really be included in the output.
-+ return
-+
-+ self.cliques.append(c)
-+ for (lang, msg) in c.clique.items():
-+ if lang not in self.keys_by_lang:
-+ self.keys_by_lang[lang] = {}
-+ keymap = self.keys_by_lang[lang]
-+
-+ content = msg.GetRealContent()
-+ keys = [groups[1] for groups in self.SHORTCUT_RE.findall(content)]
-+ for key in keys:
-+ key = key.upper()
-+ if key in keymap:
-+ keymap[key] += 1
-+ else:
-+ keymap[key] = 1
-+
-+ def GenerateWarnings(self, tc_project):
-+ # For any language that has more than one occurrence of any shortcut,
-+ # make a list of the conflicting shortcuts.
-+ problem_langs = {}
-+ for (lang, keys) in self.keys_by_lang.items():
-+ for (key, count) in keys.items():
-+ if count > 1:
-+ if lang not in problem_langs:
-+ problem_langs[lang] = []
-+ problem_langs[lang].append(key)
-+
-+ warnings = []
-+ if len(problem_langs):
-+ warnings.append("WARNING - duplicate keys exist in shortcut group %s" %
-+ self.name)
-+ for (lang,keys) in problem_langs.items():
-+ warnings.append(" %6s duplicates: %s" % (lang, ', '.join(keys)))
-+ return warnings
-+
-+
-+def GenerateDuplicateShortcutsWarnings(uberclique, tc_project):
-+ '''Given an UberClique and a project name, will print out helpful warnings
-+ if there are conflicting shortcuts within shortcut groups in the provided
-+ UberClique.
-+
-+ Args:
-+ uberclique: clique.UberClique()
-+ tc_project: 'MyProjectNameInTheTranslationConsole'
-+
-+ Returns:
-+ ['warning line 1', 'warning line 2', ...]
-+ '''
-+ warnings = []
-+ groups = {}
-+ for c in uberclique.AllCliques():
-+ for group in c.shortcut_groups:
-+ if group not in groups:
-+ groups[group] = ShortcutGroup(group)
-+ groups[group].AddClique(c)
-+ for group in groups.values():
-+ warnings += group.GenerateWarnings(tc_project)
-+ return warnings
-diff --git a/tools/grit/grit/shortcuts_unittest.py b/tools/grit/grit/shortcuts_unittest.py
-new file mode 100644
-index 0000000000..30e7c4f758
---- /dev/null
-+++ b/tools/grit/grit/shortcuts_unittest.py
-@@ -0,0 +1,79 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for grit.shortcuts
-+'''
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
-+
-+import unittest
-+
-+from six import StringIO
-+
-+from grit import shortcuts
-+from grit import clique
-+from grit import tclib
-+from grit.gather import rc
-+
-+class ShortcutsUnittest(unittest.TestCase):
-+
-+ def setUp(self):
-+ self.uq = clique.UberClique()
-+
-+ def testFunctionality(self):
-+ c = self.uq.MakeClique(tclib.Message(text="Hello &there"))
-+ c.AddToShortcutGroup('group_name')
-+ c = self.uq.MakeClique(tclib.Message(text="Howdie &there partner"))
-+ c.AddToShortcutGroup('group_name')
-+
-+ warnings = shortcuts.GenerateDuplicateShortcutsWarnings(self.uq, 'PROJECT')
-+ self.failUnless(warnings)
-+
-+ def testAmpersandEscaping(self):
-+ c = self.uq.MakeClique(tclib.Message(text="Hello &there"))
-+ c.AddToShortcutGroup('group_name')
-+ c = self.uq.MakeClique(tclib.Message(text="S&&T are the &letters S and T"))
-+ c.AddToShortcutGroup('group_name')
-+
-+ warnings = shortcuts.GenerateDuplicateShortcutsWarnings(self.uq, 'PROJECT')
-+ self.failUnless(len(warnings) == 0)
-+
-+ def testDialog(self):
-+ dlg = rc.Dialog(StringIO('''\
-+IDD_SIDEBAR_RSS_PANEL_PROPPAGE DIALOGEX 0, 0, 239, 221
-+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
-+FONT 8, "MS Shell Dlg", 400, 0, 0x1
-+BEGIN
-+ PUSHBUTTON "Add &URL",IDC_SIDEBAR_RSS_ADD_URL,182,53,57,14
-+ EDITTEXT IDC_SIDEBAR_RSS_NEW_URL,0,53,178,15,ES_AUTOHSCROLL
-+ PUSHBUTTON "&Remove",IDC_SIDEBAR_RSS_REMOVE,183,200,56,14
-+ PUSHBUTTON "&Edit",IDC_SIDEBAR_RSS_EDIT,123,200,56,14
-+ CONTROL "&Automatically add commonly viewed clips",
-+ IDC_SIDEBAR_RSS_AUTO_ADD,"Button",BS_AUTOCHECKBOX |
-+ BS_MULTILINE | WS_TABSTOP,0,200,120,17
-+ PUSHBUTTON "",IDC_SIDEBAR_RSS_HIDDEN,179,208,6,6,NOT WS_VISIBLE
-+ LTEXT "You can display clips from blogs, news sites, and other online sources.",
-+ IDC_STATIC,0,0,239,10
-+ LISTBOX IDC_SIDEBAR_DISPLAYED_FEED_LIST,0,69,239,127,LBS_SORT |
-+ LBS_OWNERDRAWFIXED | LBS_HASSTRINGS |
-+ LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_HSCROLL |
-+ WS_TABSTOP
-+ LTEXT "Add a clip from a recently viewed website by clicking Add Recent Clips.",
-+ IDC_STATIC,0,13,141,19
-+ LTEXT "Or, if you know a site supports RSS or Atom, you can enter the RSS or Atom URL below and add it to your list of Web Clips.",
-+ IDC_STATIC,0,33,239,18
-+ PUSHBUTTON "Add Recent &Clips (10)...",
-+ IDC_SIDEBAR_RSS_ADD_RECENT_CLIPS,146,14,93,14
-+END'''), 'IDD_SIDEBAR_RSS_PANEL_PROPPAGE')
-+ dlg.SetUberClique(self.uq)
-+ dlg.Parse()
-+
-+ warnings = shortcuts.GenerateDuplicateShortcutsWarnings(self.uq, 'PROJECT')
-+ self.failUnless(len(warnings) == 0)
-+
-diff --git a/tools/grit/grit/tclib.py b/tools/grit/grit/tclib.py
-new file mode 100644
-index 0000000000..27ba366924
---- /dev/null
-+++ b/tools/grit/grit/tclib.py
-@@ -0,0 +1,246 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Adaptation of the extern.tclib classes for our needs.
-+'''
-+
-+from __future__ import print_function
-+
-+import functools
-+import re
-+
-+import six
-+
-+from grit import exception
-+from grit import lazy_re
-+import grit.extern.tclib
-+
-+
-+# Matches whitespace sequences which can be folded into a single whitespace
-+# character. This matches single characters so that non-spaces are replaced
-+# with spaces.
-+_FOLD_WHITESPACE = re.compile(r'\s+')
-+
-+# Caches compiled regexp used to split tags in BaseMessage.__init__()
-+_RE_CACHE = {}
-+
-+def Identity(i):
-+ return i
-+
-+
-+class BaseMessage(object):
-+ '''Base class with methods shared by Message and Translation.
-+ '''
-+
-+ def __init__(self, text='', placeholders=[], description='', meaning=''):
-+ self.parts = []
-+ self.placeholders = []
-+ self.meaning = meaning
-+ self.dirty = True # True if self.id is (or might be) wrong
-+ self.id = 0
-+ self.SetDescription(description)
-+
-+ if text != '':
-+ if not placeholders or placeholders == []:
-+ self.AppendText(text)
-+ else:
-+ tag_map = {}
-+ for placeholder in placeholders:
-+ tag_map[placeholder.GetPresentation()] = [placeholder, 0]
-+ # This creates a regexp like '(TAG1|TAG2|TAG3)'.
-+ # The tags have to be sorted in order of decreasing length, so that
-+ # longer tags are substituted before shorter tags that happen to be
-+ # substrings of the longer tag.
-+ # E.g. "EXAMPLE_FOO_NAME" must be matched before "EXAMPLE_FOO",
-+ # otherwise "EXAMPLE_FOO" splits "EXAMPLE_FOO_NAME" too.
-+ tags = sorted(tag_map.keys(),
-+ key=functools.cmp_to_key(
-+ lambda x, y: len(x) - len(y) or ((x > y) - (x < y))),
-+ reverse=True)
-+ tag_re = '(' + '|'.join(tags) + ')'
-+
-+ # This caching improves the time to build
-+ # chrome/app:generated_resources from 21.562s to 17.672s on Linux.
-+ compiled_re = _RE_CACHE.get(tag_re, None)
-+ if compiled_re is None:
-+ compiled_re = re.compile(tag_re)
-+ _RE_CACHE[tag_re] = compiled_re
-+
-+ chunked_text = compiled_re.split(text)
-+
-+ for chunk in chunked_text:
-+ if chunk: # ignore empty chunk
-+ if chunk in tag_map:
-+ self.AppendPlaceholder(tag_map[chunk][0])
-+ tag_map[chunk][1] += 1 # increase placeholder use count
-+ else:
-+ self.AppendText(chunk)
-+ for key in tag_map:
-+ assert tag_map[key][1] != 0
-+
-+ def GetRealContent(self, escaping_function=Identity):
-+ '''Returns the original content, i.e. what your application and users
-+ will see.
-+
-+ Specify a function to escape each translateable bit, if you like.
-+ '''
-+ bits = []
-+ for item in self.parts:
-+ if isinstance(item, six.string_types):
-+ bits.append(escaping_function(item))
-+ else:
-+ bits.append(item.GetOriginal())
-+ return ''.join(bits)
-+
-+ def GetPresentableContent(self):
-+ presentable_content = []
-+ for part in self.parts:
-+ if isinstance(part, Placeholder):
-+ presentable_content.append(part.GetPresentation())
-+ else:
-+ presentable_content.append(part)
-+ return ''.join(presentable_content)
-+
-+ def AppendPlaceholder(self, placeholder):
-+ assert isinstance(placeholder, Placeholder)
-+ dup = False
-+ for other in self.GetPlaceholders():
-+ if other.presentation == placeholder.presentation:
-+ assert other.original == placeholder.original
-+ dup = True
-+
-+ if not dup:
-+ self.placeholders.append(placeholder)
-+ self.parts.append(placeholder)
-+ self.dirty = True
-+
-+ def AppendText(self, text):
-+ assert isinstance(text, six.string_types)
-+ assert text != ''
-+
-+ self.parts.append(text)
-+ self.dirty = True
-+
-+ def GetContent(self):
-+ '''Returns the parts of the message. You may modify parts if you wish.
-+ Note that you must not call GetId() on this object until you have finished
-+ modifying the contents.
-+ '''
-+ self.dirty = True # user might modify content
-+ return self.parts
-+
-+ def GetDescription(self):
-+ return self.description
-+
-+ def SetDescription(self, description):
-+ self.description = _FOLD_WHITESPACE.sub(' ', description)
-+
-+ def GetMeaning(self):
-+ return self.meaning
-+
-+ def GetId(self):
-+ if self.dirty:
-+ self.id = self.GenerateId()
-+ self.dirty = False
-+ return self.id
-+
-+ def GenerateId(self):
-+ return grit.extern.tclib.GenerateMessageId(self.GetPresentableContent(),
-+ self.meaning)
-+
-+ def GetPlaceholders(self):
-+ return self.placeholders
-+
-+ def FillTclibBaseMessage(self, msg):
-+ msg.SetDescription(self.description.encode('utf-8'))
-+
-+ for part in self.parts:
-+ if isinstance(part, Placeholder):
-+ ph = grit.extern.tclib.Placeholder(
-+ part.presentation.encode('utf-8'),
-+ part.original.encode('utf-8'),
-+ part.example.encode('utf-8'))
-+ msg.AppendPlaceholder(ph)
-+ else:
-+ msg.AppendText(part.encode('utf-8'))
-+
-+
-+class Message(BaseMessage):
-+ '''A message.'''
-+
-+ def __init__(self, text='', placeholders=[], description='', meaning='',
-+ assigned_id=None):
-+ super(Message, self).__init__(text, placeholders, description, meaning)
-+ self.assigned_id = assigned_id
-+
-+ def ToTclibMessage(self):
-+ msg = grit.extern.tclib.Message('utf-8', meaning=self.meaning)
-+ self.FillTclibBaseMessage(msg)
-+ return msg
-+
-+ def GetId(self):
-+ '''Use the assigned id if we have one.'''
-+ if self.assigned_id:
-+ return self.assigned_id
-+
-+ return super(Message, self).GetId()
-+
-+ def HasAssignedId(self):
-+ '''Returns True if this message has an assigned id.'''
-+ return bool(self.assigned_id)
-+
-+
-+class Translation(BaseMessage):
-+ '''A translation.'''
-+
-+ def __init__(self, text='', id='', placeholders=[], description='', meaning=''):
-+ super(Translation, self).__init__(text, placeholders, description, meaning)
-+ self.id = id
-+
-+ def GetId(self):
-+ assert id != '', "ID has not been set."
-+ return self.id
-+
-+ def SetId(self, id):
-+ self.id = id
-+
-+ def ToTclibMessage(self):
-+ msg = grit.extern.tclib.Message(
-+ 'utf-8', id=self.id, meaning=self.meaning)
-+ self.FillTclibBaseMessage(msg)
-+ return msg
-+
-+
-+class Placeholder(grit.extern.tclib.Placeholder):
-+ '''Modifies constructor to accept a Unicode string
-+ '''
-+
-+ # Must match placeholder presentation names
-+ _NAME_RE = lazy_re.compile('^[A-Za-z0-9_]+$')
-+
-+ def __init__(self, presentation, original, example):
-+ '''Creates a new placeholder.
-+
-+ Args:
-+ presentation: 'USERNAME'
-+ original: '%s'
-+ example: 'Joi'
-+ '''
-+ assert presentation != ''
-+ assert original != ''
-+ assert example != ''
-+ if not self._NAME_RE.match(presentation):
-+ raise exception.InvalidPlaceholderName(presentation)
-+ self.presentation = presentation
-+ self.original = original
-+ self.example = example
-+
-+ def GetPresentation(self):
-+ return self.presentation
-+
-+ def GetOriginal(self):
-+ return self.original
-+
-+ def GetExample(self):
-+ return self.example
-diff --git a/tools/grit/grit/tclib_unittest.py b/tools/grit/grit/tclib_unittest.py
-new file mode 100644
-index 0000000000..7a08654e1b
---- /dev/null
-+++ b/tools/grit/grit/tclib_unittest.py
-@@ -0,0 +1,180 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for grit.tclib'''
-+
-+from __future__ import print_function
-+
-+import sys
-+import os.path
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
-+
-+import unittest
-+
-+import six
-+
-+from grit import tclib
-+
-+from grit import exception
-+import grit.extern.tclib
-+
-+
-+class TclibUnittest(unittest.TestCase):
-+ def testInit(self):
-+ msg = tclib.Message(text=u'Hello Earthlings',
-+ description='Greetings\n\t message')
-+ self.failUnlessEqual(msg.GetPresentableContent(), 'Hello Earthlings')
-+ self.failUnless(isinstance(msg.GetPresentableContent(), six.string_types))
-+ self.failUnlessEqual(msg.GetDescription(), 'Greetings message')
-+
-+ def testGetAttr(self):
-+ msg = tclib.Message()
-+ msg.AppendText(u'Hello') # Tests __getattr__
-+ self.failUnless(msg.GetPresentableContent() == 'Hello')
-+ self.failUnless(isinstance(msg.GetPresentableContent(), six.string_types))
-+
-+ def testAll(self):
-+ text = u'Howdie USERNAME'
-+ phs = [tclib.Placeholder(u'USERNAME', u'%s', 'Joi')]
-+ msg = tclib.Message(text=text, placeholders=phs)
-+ self.failUnless(msg.GetPresentableContent() == 'Howdie USERNAME')
-+
-+ trans = tclib.Translation(text=text, placeholders=phs)
-+ self.failUnless(trans.GetPresentableContent() == 'Howdie USERNAME')
-+ self.failUnless(isinstance(trans.GetPresentableContent(), six.string_types))
-+
-+ def testUnicodeReturn(self):
-+ text = u'\u00fe'
-+ msg = tclib.Message(text=text)
-+ self.failUnless(msg.GetPresentableContent() == text)
-+ from_list = msg.GetContent()[0]
-+ self.failUnless(from_list == text)
-+
-+ def testRegressionTranslationInherited(self):
-+ '''Regression tests a bug that was caused by grit.tclib.Translation
-+ inheriting from the translation console's Translation object
-+ instead of only owning an instance of it.
-+ '''
-+ msg = tclib.Message(text=u"BLA1\r\nFrom: BLA2 \u00fe BLA3",
-+ placeholders=[
-+ tclib.Placeholder('BLA1', '%s', '%s'),
-+ tclib.Placeholder('BLA2', '%s', '%s'),
-+ tclib.Placeholder('BLA3', '%s', '%s')])
-+ transl = tclib.Translation(text=msg.GetPresentableContent(),
-+ placeholders=msg.GetPlaceholders())
-+ content = transl.GetContent()
-+ self.failUnless(isinstance(content[3], six.string_types))
-+
-+ def testFingerprint(self):
-+ # This has Windows line endings. That is on purpose.
-+ id = grit.extern.tclib.GenerateMessageId(
-+ 'Google Desktop for Enterprise\r\n'
-+ 'All Rights Reserved\r\n'
-+ '\r\n'
-+ '---------\r\n'
-+ 'Contents\r\n'
-+ '---------\r\n'
-+ 'This distribution contains the following files:\r\n'
-+ '\r\n'
-+ 'GoogleDesktopSetup.msi - Installation and setup program\r\n'
-+ 'GoogleDesktop.adm - Group Policy administrative template file\r\n'
-+ 'AdminGuide.pdf - Google Desktop for Enterprise administrative guide\r\n'
-+ '\r\n'
-+ '\r\n'
-+ '--------------\r\n'
-+ 'Documentation\r\n'
-+ '--------------\r\n'
-+ 'Full documentation and installation instructions are in the \r\n'
-+ 'administrative guide, and also online at \r\n'
-+ 'http://desktop.google.com/enterprise/adminguide.html.\r\n'
-+ '\r\n'
-+ '\r\n'
-+ '------------------------\r\n'
-+ 'IBM Lotus Notes Plug-In\r\n'
-+ '------------------------\r\n'
-+ 'The Lotus Notes plug-in is included in the release of Google \r\n'
-+ 'Desktop for Enterprise. The IBM Lotus Notes Plug-in for Google \r\n'
-+ 'Desktop indexes mail, calendar, task, contact and journal \r\n'
-+ 'documents from Notes. Discussion documents including those from \r\n'
-+ 'the discussion and team room templates can also be indexed by \r\n'
-+ 'selecting an option from the preferences. Once indexed, this data\r\n'
-+ 'will be returned in Google Desktop searches. The corresponding\r\n'
-+ 'document can be opened in Lotus Notes from the Google Desktop \r\n'
-+ 'results page.\r\n'
-+ '\r\n'
-+ 'Install: The plug-in will install automatically during the Google \r\n'
-+ 'Desktop setup process if Lotus Notes is already installed. Lotus \r\n'
-+ 'Notes must not be running in order for the install to occur. \r\n'
-+ '\r\n'
-+ 'Preferences: Preferences and selection of databases to index are\r\n'
-+ 'set in the \'Google Desktop for Notes\' dialog reached through the \r\n'
-+ '\'Actions\' menu.\r\n'
-+ '\r\n'
-+ 'Reindexing: Selecting \'Reindex all databases\' will index all the \r\n'
-+ 'documents in each database again.\r\n'
-+ '\r\n'
-+ '\r\n'
-+ 'Notes Plug-in Known Issues\r\n'
-+ '---------------------------\r\n'
-+ '\r\n'
-+ 'If the \'Google Desktop for Notes\' item is not available from the \r\n'
-+ 'Lotus Notes Actions menu, then installation was not successful. \r\n'
-+ 'Installation consists of writing one file, notesgdsplugin.dll, to \r\n'
-+ 'the Notes application directory and a setting to the notes.ini \r\n'
-+ 'configuration file. The most likely cause of an unsuccessful \r\n'
-+ 'installation is that the installer was not able to locate the \r\n'
-+ 'notes.ini file. Installation will complete if the user closes Notes\r\n'
-+ 'and manually adds the following setting to this file on a new line:\r\n'
-+ 'AddinMenus=notegdsplugin.dll\r\n'
-+ '\r\n'
-+ 'If the notesgdsplugin.dll file is not in the application directory\r\n'
-+ r'(e.g., C:\Program Files\Lotus\Notes) after Google Desktop \r\n'
-+ 'installation, it is likely that Notes was not installed correctly. \r\n'
-+ '\r\n'
-+ 'Only local databases can be indexed. If they can be determined, \r\n'
-+ 'the user\'s local mail file and address book will be included in the\r\n'
-+ 'list automatically. Mail archives and other databases must be \r\n'
-+ 'added with the \'Add\' button.\r\n'
-+ '\r\n'
-+ 'Some users may experience performance issues during the initial \r\n'
-+ 'indexing of a database. The \'Perform the initial index of a \r\n'
-+ 'database only when I\'m idle\' option will limit the indexing process\r\n'
-+ 'to times when the user is not using the machine. If this does not \r\n'
-+ 'alleviate the problem or the user would like to continually index \r\n'
-+ 'but just do so more slowly or quickly, the GoogleWaitTime notes.ini\r\n'
-+ 'value can be set. Increasing the GoogleWaitTime value will slow \r\n'
-+ 'down the indexing process, and lowering the value will speed it up.\r\n'
-+ 'A value of zero causes the fastest possible indexing. Removing the\r\n'
-+ 'ini parameter altogether returns it to the default (20).\r\n'
-+ '\r\n'
-+ 'Crashes have been known to occur with certain types of history \r\n'
-+ 'bookmarks. If the Notes client seems to crash randomly, try \r\n'
-+ 'disabling the \'Index note history\' option. If it crashes before,\r\n'
-+ 'you can get to the preferences, add the following line to your \r\n'
-+ 'notes.ini file:\r\n'
-+ 'GDSNoIndexHistory=1\r\n')
-+ self.assertEqual(id, '7660964495923572726')
-+
-+ def testPlaceholderNameChecking(self):
-+ try:
-+ ph = tclib.Placeholder('BINGO BONGO', 'bla', 'bla')
-+ raise Exception("We shouldn't get here")
-+ except exception.InvalidPlaceholderName:
-+ pass # Expect exception to be thrown because presentation contained space
-+
-+ def testTagsWithCommonSubstring(self):
-+ word = 'ABCDEFGHIJ'
-+ text = ' '.join([word[:i] for i in range(1, 11)])
-+ phs = [tclib.Placeholder(word[:i], str(i), str(i)) for i in range(1, 11)]
-+ try:
-+ msg = tclib.Message(text=text, placeholders=phs)
-+ self.failUnless(msg.GetRealContent() == '1 2 3 4 5 6 7 8 9 10')
-+ except:
-+ self.fail('tclib.Message() should handle placeholders that are '
-+ 'substrings of each other')
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/test_suite_all.py b/tools/grit/grit/test_suite_all.py
-new file mode 100644
-index 0000000000..3bfe2a79d5
---- /dev/null
-+++ b/tools/grit/grit/test_suite_all.py
-@@ -0,0 +1,34 @@
-+#!/usr/bin/env python3
-+# Copyright (c) 2011 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit test suite that collects all test cases for GRIT.'''
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+
-+
-+CUR_DIR = os.path.dirname(os.path.realpath(__file__))
-+SRC_DIR = os.path.dirname(os.path.dirname(os.path.dirname(CUR_DIR)))
-+TYP_DIR = os.path.join(
-+ SRC_DIR, 'third_party', 'catapult', 'third_party', 'typ')
-+
-+if TYP_DIR not in sys.path:
-+ sys.path.insert(0, TYP_DIR)
-+
-+
-+import typ # pylint: disable=import-error,unused-import
-+
-+
-+def main(args):
-+ return typ.main(
-+ top_level_dirs=[os.path.join(CUR_DIR, '..')],
-+ skip=['grit.format.gen_predetermined_ids_unittest.*',
-+ 'grit.pseudo_unittest.*']
-+ )
-+
-+if __name__ == '__main__':
-+ sys.exit(main(sys.argv[1:]))
-diff --git a/tools/grit/grit/testdata/GoogleDesktop.adm b/tools/grit/grit/testdata/GoogleDesktop.adm
-new file mode 100644
-index 0000000000..082f56bb1a
---- /dev/null
-+++ b/tools/grit/grit/testdata/GoogleDesktop.adm
-@@ -0,0 +1,945 @@
-+CLASS MACHINE
-+ CATEGORY !!Cat_Google
-+ CATEGORY !!Cat_GoogleDesktopSearch
-+ KEYNAME "Software\Policies\Google\Google Desktop"
-+
-+ CATEGORY !!Cat_Preferences
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences"
-+
-+ CATEGORY !!Cat_IndexAndCaptureControl
-+ POLICY !!Blacklist_Email
-+ EXPLAIN !!Explain_Blacklist_Email
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
-+ VALUENAME "1"
-+ END POLICY
-+
-+ POLICY !!Blacklist_Gmail
-+ EXPLAIN !!Explain_Blacklist_Gmail
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-pop"
-+ VALUENAME "gmail"
-+ END POLICY
-+
-+ POLICY !!Blacklist_WebHistory
-+ EXPLAIN !!Explain_Blacklist_WebHistory
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
-+ VALUENAME "2"
-+ END POLICY
-+
-+ POLICY !!Blacklist_Chat
-+ EXPLAIN !!Explain_Blacklist_Chat
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
-+ ACTIONLISTON
-+ VALUENAME "3" VALUE NUMERIC 1
-+ END ACTIONLISTON
-+ END POLICY
-+
-+ POLICY !!Blacklist_Text
-+ EXPLAIN !!Explain_Blacklist_Text
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
-+ ACTIONLISTON
-+ VALUENAME "4" VALUE NUMERIC 1
-+ END ACTIONLISTON
-+ END POLICY
-+
-+ POLICY !!Blacklist_Media
-+ EXPLAIN !!Explain_Blacklist_Media
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
-+ ACTIONLISTON
-+ VALUENAME "5" VALUE NUMERIC 1
-+ END ACTIONLISTON
-+ END POLICY
-+
-+ POLICY !!Blacklist_Contact
-+ EXPLAIN !!Explain_Blacklist_Contact
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
-+ ACTIONLISTON
-+ VALUENAME "9" VALUE NUMERIC 1
-+ END ACTIONLISTON
-+ END POLICY
-+
-+ POLICY !!Blacklist_Calendar
-+ EXPLAIN !!Explain_Blacklist_Calendar
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
-+ ACTIONLISTON
-+ VALUENAME "10" VALUE NUMERIC 1
-+ END ACTIONLISTON
-+ END POLICY
-+
-+ POLICY !!Blacklist_Task
-+ EXPLAIN !!Explain_Blacklist_Task
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
-+ ACTIONLISTON
-+ VALUENAME "11" VALUE NUMERIC 1
-+ END ACTIONLISTON
-+ END POLICY
-+
-+ POLICY !!Blacklist_Note
-+ EXPLAIN !!Explain_Blacklist_Note
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
-+ ACTIONLISTON
-+ VALUENAME "12" VALUE NUMERIC 1
-+ END ACTIONLISTON
-+ END POLICY
-+
-+ POLICY !!Blacklist_Journal
-+ EXPLAIN !!Explain_Blacklist_Journal
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
-+ ACTIONLISTON
-+ VALUENAME "13" VALUE NUMERIC 1
-+ END ACTIONLISTON
-+ END POLICY
-+
-+ POLICY !!Blacklist_Word
-+ EXPLAIN !!Explain_Blacklist_Word
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-2"
-+ VALUENAME "DOC"
-+ END POLICY
-+
-+ POLICY !!Blacklist_Excel
-+ EXPLAIN !!Explain_Blacklist_Excel
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-2"
-+ VALUENAME "XLS"
-+ END POLICY
-+
-+ POLICY !!Blacklist_Powerpoint
-+ EXPLAIN !!Explain_Blacklist_Powerpoint
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-2"
-+ VALUENAME "PPT"
-+ END POLICY
-+
-+ POLICY !!Blacklist_PDF
-+ EXPLAIN !!Explain_Blacklist_PDF
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-2"
-+ VALUENAME "PDF"
-+ END POLICY
-+
-+ POLICY !!Blacklist_ZIP
-+ EXPLAIN !!Explain_Blacklist_ZIP
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-2"
-+ VALUENAME "ZIP"
-+ END POLICY
-+
-+ POLICY !!Blacklist_HTTPS
-+ EXPLAIN !!Explain_Blacklist_HTTPS
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-3"
-+ VALUENAME "HTTPS"
-+ END POLICY
-+
-+ POLICY !!Blacklist_PasswordProtectedOffice
-+ EXPLAIN !!Explain_Blacklist_PasswordProtectedOffice
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-13"
-+ VALUENAME "SECUREOFFICE"
-+ END POLICY
-+
-+ POLICY !!Blacklist_URI_Contains
-+ EXPLAIN !!Explain_Blacklist_URI_Contains
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-6"
-+ PART !!Blacklist_URI_Contains LISTBOX
-+ END PART
-+ END POLICY
-+
-+ POLICY !!Blacklist_Extensions
-+ EXPLAIN !!Explain_Blacklist_Extensions
-+ PART !!Blacklist_Extensions EDITTEXT
-+ VALUENAME "file_extensions_to_skip"
-+ END PART
-+ END POLICY
-+
-+ POLICY !!Pol_Disallow_UserSearchLocations
-+ EXPLAIN !!Explain_Disallow_UserSearchLocations
-+ VALUENAME user_search_locations
-+ VALUEON NUMERIC 1
-+ END POLICY
-+
-+ POLICY !!Pol_Search_Location_Whitelist
-+ EXPLAIN !!Explain_Search_Location_Whitelist
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\policy_search_location_whitelist"
-+ PART !!Search_Locations_Whitelist LISTBOX
-+ END PART
-+ END POLICY
-+
-+ POLICY !!Email_Retention
-+ EXPLAIN !!Explain_Email_Retention
-+ PART !!Email_Retention_Edit NUMERIC
-+ VALUENAME "email_days_to_retain"
-+ MIN 1 MAX 65535 DEFAULT 30 SPIN 1
-+ END PART
-+ END POLICY
-+
-+ POLICY !!Webpage_Retention
-+ EXPLAIN !!Explain_Webpage_Retention
-+ PART !!Webpage_Retention_Edit NUMERIC
-+ VALUENAME "webpage_days_to_retain"
-+ MIN 1 MAX 65535 DEFAULT 30 SPIN 1
-+ END PART
-+ END POLICY
-+
-+ POLICY !!File_Retention
-+ EXPLAIN !!Explain_File_Retention
-+ PART !!File_Retention_Edit NUMERIC
-+ VALUENAME "file_days_to_retain"
-+ MIN 1 MAX 65535 DEFAULT 30 SPIN 1
-+ END PART
-+ END POLICY
-+
-+ POLICY !!IM_Retention
-+ EXPLAIN !!Explain_IM_Retention
-+ PART !!IM_Retention_Edit NUMERIC
-+ VALUENAME "im_days_to_retain"
-+ MIN 1 MAX 65535 DEFAULT 30 SPIN 1
-+ END PART
-+ END POLICY
-+
-+ POLICY !!Pol_Remove_Deleted_Items
-+ EXPLAIN !!Explain_Remove_Deleted_Items
-+ VALUENAME remove_deleted_items
-+ VALUEON NUMERIC 1
-+ END POLICY
-+
-+ POLICY !!Pol_Allow_Simultaneous_Indexing
-+ EXPLAIN !!Explain_Allow_Simultaneous_Indexing
-+ VALUENAME simultaneous_indexing
-+ VALUEON NUMERIC 1
-+ END POLICY
-+
-+ END CATEGORY
-+
-+ POLICY !!Pol_TurnOffAdvancedFeatures
-+ EXPLAIN !!Explain_TurnOffAdvancedFeatures
-+ VALUENAME error_report_on
-+ VALUEON NUMERIC 0
-+ END POLICY
-+
-+ POLICY !!Pol_TurnOffImproveGd
-+ EXPLAIN !!Explain_TurnOffImproveGd
-+ VALUENAME improve_gd
-+ VALUEON NUMERIC 0
-+ VALUEOFF NUMERIC 1
-+ END POLICY
-+
-+ POLICY !!Pol_NoPersonalizationInfo
-+ EXPLAIN !!Explain_NoPersonalizationInfo
-+ VALUENAME send_personalization_info
-+ VALUEON NUMERIC 0
-+ VALUEOFF NUMERIC 1
-+ END POLICY
-+
-+ POLICY !!Pol_OneBoxMode
-+ EXPLAIN !!Explain_OneBoxMode
-+ VALUENAME onebox_mode
-+ VALUEON NUMERIC 0
-+ END POLICY
-+
-+ POLICY !!Pol_EncryptIndex
-+ EXPLAIN !!Explain_EncryptIndex
-+ VALUENAME encrypt_index
-+ VALUEON NUMERIC 1
-+ END POLICY
-+
-+ POLICY !!Pol_Hyper
-+ EXPLAIN !!Explain_Hyper
-+ VALUENAME hyper_off
-+ VALUEON NUMERIC 1
-+ END POLICY
-+
-+ POLICY !!Pol_Display_Mode
-+ EXPLAIN !!Explain_Display_Mode
-+ PART !!Pol_Display_Mode DROPDOWNLIST
-+ VALUENAME display_mode
-+ ITEMLIST
-+ NAME !!Sidebar VALUE NUMERIC 1
-+ NAME !!Deskbar VALUE NUMERIC 8
-+ NAME !!FloatingDeskbar VALUE NUMERIC 4
-+ NAME !!None VALUE NUMERIC 0
-+ END ITEMLIST
-+ END PART
-+ END POLICY
-+
-+ END CATEGORY ; Preferences
-+
-+ CATEGORY !!Cat_Enterprise
-+ KEYNAME "Software\Policies\Google\Google Desktop\Enterprise"
-+
-+ POLICY !!Pol_Autoupdate
-+ EXPLAIN !!Explain_Autoupdate
-+ VALUENAME autoupdate_host
-+ VALUEON ""
-+ END POLICY
-+
-+ POLICY !!Pol_AutoupdateAsSystem
-+ EXPLAIN !!Explain_AutoupdateAsSystem
-+ VALUENAME autoupdate_impersonate_user
-+ VALUEON NUMERIC 0
-+ VALUEOFF NUMERIC 1
-+ END POLICY
-+
-+ POLICY !!Pol_EnterpriseTab
-+ EXPLAIN !!Explain_EnterpriseTab
-+ PART !!EnterpriseTabText EDITTEXT
-+ VALUENAME enterprise_tab_text
-+ END PART
-+ PART !!EnterpriseTabHomepage EDITTEXT
-+ VALUENAME enterprise_tab_homepage
-+ END PART
-+ PART !!EnterpriseTabHomepageQuery CHECKBOX
-+ VALUENAME enterprise_tab_homepage_query
-+ END PART
-+ PART !!EnterpriseTabResults EDITTEXT
-+ VALUENAME enterprise_tab_results
-+ END PART
-+ PART !!EnterpriseTabResultsQuery CHECKBOX
-+ VALUENAME enterprise_tab_results_query
-+ END PART
-+ END POLICY
-+
-+ POLICY !!Pol_GSAHosts
-+ EXPLAIN !!Explain_GSAHosts
-+ KEYNAME "Software\Policies\Google\Google Desktop\Enterprise\GSAHosts"
-+ PART !!Pol_GSAHosts LISTBOX
-+ END PART
-+ END POLICY
-+
-+ POLICY !!Pol_PolicyUnawareClientProhibitedFlag
-+ EXPLAIN !!Explain_PolicyUnawareClientProhibitedFlag
-+ KEYNAME "Software\Policies\Google\Google Desktop"
-+ VALUENAME PolicyUnawareClientProhibitedFlag
-+ END POLICY
-+
-+ POLICY !!Pol_MinimumAllowedVersion
-+ EXPLAIN !!Explain_MinimumAllowedVersion
-+ PART !!Pol_MinimumAllowedVersion EDITTEXT
-+ VALUENAME minimum_allowed_version
-+ END PART
-+ END POLICY
-+
-+ POLICY !!Pol_MaximumAllowedVersion
-+ EXPLAIN !!Explain_MaximumAllowedVersion
-+ PART !!Pol_MaximumAllowedVersion EDITTEXT
-+ VALUENAME maximum_allowed_version
-+ END PART
-+ END POLICY
-+
-+ POLICY !!Pol_Disallow_Gadgets
-+ EXPLAIN !!Explain_Disallow_Gadgets
-+ VALUENAME disallow_gadgets
-+ VALUEON NUMERIC 1
-+ PART !!Disallow_Only_Non_Builtin_Gadgets CHECKBOX DEFCHECKED
-+ VALUENAME disallow_only_non_builtin_gadgets
-+ VALUEON NUMERIC 1
-+ VALUEOFF NUMERIC 0
-+ END PART
-+ END POLICY
-+
-+ POLICY !!Pol_Gadget_Whitelist
-+ EXPLAIN !!Explain_Gadget_Whitelist
-+ KEYNAME "Software\Policies\Google\Google Desktop\Enterprise\gadget_whitelist"
-+ PART !!Pol_Gadget_Whitelist LISTBOX
-+ END PART
-+ END POLICY
-+
-+ POLICY !!Pol_Gadget_Install_Confirmation_Whitelist
-+ EXPLAIN !!Explain_Gadget_Install_Confirmation_Whitelist
-+ KEYNAME "Software\Policies\Google\Google Desktop\Enterprise\install_confirmation_whitelist"
-+ PART !!Pol_Gadget_Install_Confirmation_Whitelist LISTBOX
-+ END PART
-+ END POLICY
-+
-+ POLICY !!Pol_Alternate_User_Data_Dir
-+ EXPLAIN !!Explain_Alternate_User_Data_Dir
-+ PART !!Pol_Alternate_User_Data_Dir EDITTEXT
-+ VALUENAME alternate_user_data_dir
-+ END PART
-+ END POLICY
-+
-+ POLICY !!Pol_MaxAllowedOutlookConnections
-+ EXPLAIN !!Explain_MaxAllowedOutlookConnections
-+ PART !!Pol_MaxAllowedOutlookConnections NUMERIC
-+ VALUENAME max_allowed_outlook_connections
-+ MIN 1 MAX 65535 DEFAULT 400 SPIN 1
-+ END PART
-+ END POLICY
-+
-+ POLICY !!Pol_DisallowSsdService
-+ EXPLAIN !!Explain_DisallowSsdService
-+ VALUENAME disallow_ssd_service
-+ VALUEON NUMERIC 1
-+ END POLICY
-+
-+ POLICY !!Pol_DisallowSsdOutbound
-+ EXPLAIN !!Explain_DisallowSsdOutbound
-+ VALUENAME disallow_ssd_outbound
-+ VALUEON NUMERIC 1
-+ END POLICY
-+
-+ POLICY !!Pol_Disallow_Store_Gadget_Service
-+ EXPLAIN !!Explain_Disallow_Store_Gadget_Service
-+ VALUENAME disallow_store_gadget_service
-+ VALUEON NUMERIC 1
-+ END POLICY
-+
-+ POLICY !!Pol_MaxExchangeIndexingRate
-+ EXPLAIN !!Explain_MaxExchangeIndexingRate
-+ PART !!Pol_MaxExchangeIndexingRate NUMERIC
-+ VALUENAME max_exchange_indexing_rate
-+ MIN 1 MAX 1000 DEFAULT 60 SPIN 1
-+ END PART
-+ END POLICY
-+
-+ POLICY !!Pol_EnableSafeweb
-+ EXPLAIN !!Explain_Safeweb
-+ VALUENAME safe_browsing
-+ VALUEON NUMERIC 1
-+ VALUEOFF NUMERIC 0
-+ END POLICY
-+
-+ END CATEGORY ; Enterprise
-+
-+ END CATEGORY ; GoogleDesktopSearch
-+ END CATEGORY ; Google
-+
-+
-+CLASS USER
-+ CATEGORY !!Cat_Google
-+ CATEGORY !!Cat_GoogleDesktopSearch
-+ KEYNAME "Software\Policies\Google\Google Desktop"
-+
-+ CATEGORY !!Cat_Preferences
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences"
-+
-+ CATEGORY !!Cat_IndexAndCaptureControl
-+ POLICY !!Blacklist_Email
-+ EXPLAIN !!Explain_Blacklist_Email
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
-+ VALUENAME "1"
-+ END POLICY
-+
-+ POLICY !!Blacklist_Gmail
-+ EXPLAIN !!Explain_Blacklist_Gmail
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-pop"
-+ VALUENAME "gmail"
-+ END POLICY
-+
-+ POLICY !!Blacklist_WebHistory
-+ EXPLAIN !!Explain_Blacklist_WebHistory
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
-+ VALUENAME "2"
-+ END POLICY
-+
-+ POLICY !!Blacklist_Chat
-+ EXPLAIN !!Explain_Blacklist_Chat
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
-+ ACTIONLISTON
-+ VALUENAME "3" VALUE NUMERIC 1
-+ END ACTIONLISTON
-+ END POLICY
-+
-+ POLICY !!Blacklist_Text
-+ EXPLAIN !!Explain_Blacklist_Text
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
-+ ACTIONLISTON
-+ VALUENAME "4" VALUE NUMERIC 1
-+ END ACTIONLISTON
-+ END POLICY
-+
-+ POLICY !!Blacklist_Media
-+ EXPLAIN !!Explain_Blacklist_Media
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
-+ ACTIONLISTON
-+ VALUENAME "5" VALUE NUMERIC 1
-+ END ACTIONLISTON
-+ END POLICY
-+
-+ POLICY !!Blacklist_Contact
-+ EXPLAIN !!Explain_Blacklist_Contact
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
-+ ACTIONLISTON
-+ VALUENAME "9" VALUE NUMERIC 1
-+ END ACTIONLISTON
-+ END POLICY
-+
-+ POLICY !!Blacklist_Calendar
-+ EXPLAIN !!Explain_Blacklist_Calendar
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
-+ ACTIONLISTON
-+ VALUENAME "10" VALUE NUMERIC 1
-+ END ACTIONLISTON
-+ END POLICY
-+
-+ POLICY !!Blacklist_Task
-+ EXPLAIN !!Explain_Blacklist_Task
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
-+ ACTIONLISTON
-+ VALUENAME "11" VALUE NUMERIC 1
-+ END ACTIONLISTON
-+ END POLICY
-+
-+ POLICY !!Blacklist_Note
-+ EXPLAIN !!Explain_Blacklist_Note
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
-+ ACTIONLISTON
-+ VALUENAME "12" VALUE NUMERIC 1
-+ END ACTIONLISTON
-+ END POLICY
-+
-+ POLICY !!Blacklist_Journal
-+ EXPLAIN !!Explain_Blacklist_Journal
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-1"
-+ ACTIONLISTON
-+ VALUENAME "13" VALUE NUMERIC 1
-+ END ACTIONLISTON
-+ END POLICY
-+
-+ POLICY !!Blacklist_Word
-+ EXPLAIN !!Explain_Blacklist_Word
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-2"
-+ VALUENAME "DOC"
-+ END POLICY
-+
-+ POLICY !!Blacklist_Excel
-+ EXPLAIN !!Explain_Blacklist_Excel
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-2"
-+ VALUENAME "XLS"
-+ END POLICY
-+
-+ POLICY !!Blacklist_Powerpoint
-+ EXPLAIN !!Explain_Blacklist_Powerpoint
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-2"
-+ VALUENAME "PPT"
-+ END POLICY
-+
-+ POLICY !!Blacklist_PDF
-+ EXPLAIN !!Explain_Blacklist_PDF
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-2"
-+ VALUENAME "PDF"
-+ END POLICY
-+
-+ POLICY !!Blacklist_ZIP
-+ EXPLAIN !!Explain_Blacklist_ZIP
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-2"
-+ VALUENAME "ZIP"
-+ END POLICY
-+
-+ POLICY !!Blacklist_HTTPS
-+ EXPLAIN !!Explain_Blacklist_HTTPS
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-3"
-+ VALUENAME "HTTPS"
-+ END POLICY
-+
-+ POLICY !!Blacklist_PasswordProtectedOffice
-+ EXPLAIN !!Explain_Blacklist_PasswordProtectedOffice
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-13"
-+ VALUENAME "SECUREOFFICE"
-+ END POLICY
-+
-+ POLICY !!Blacklist_URI_Contains
-+ EXPLAIN !!Explain_Blacklist_URI_Contains
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\blacklist-6"
-+ PART !!Blacklist_URI_Contains LISTBOX
-+ END PART
-+ END POLICY
-+
-+ POLICY !!Blacklist_Extensions
-+ EXPLAIN !!Explain_Blacklist_Extensions
-+ PART !!Blacklist_Extensions EDITTEXT
-+ VALUENAME "file_extensions_to_skip"
-+ END PART
-+ END POLICY
-+
-+ POLICY !!Pol_Disallow_UserSearchLocations
-+ EXPLAIN !!Explain_Disallow_UserSearchLocations
-+ VALUENAME user_search_locations
-+ VALUEON NUMERIC 1
-+ END POLICY
-+
-+ POLICY !!Pol_Search_Location_Whitelist
-+ EXPLAIN !!Explain_Search_Location_Whitelist
-+ KEYNAME "Software\Policies\Google\Google Desktop\Preferences\policy_search_location_whitelist"
-+ PART !!Search_Locations_Whitelist LISTBOX
-+ END PART
-+ END POLICY
-+
-+ POLICY !!Email_Retention
-+ EXPLAIN !!Explain_Email_Retention
-+ PART !!Email_Retention_Edit NUMERIC
-+ VALUENAME "email_days_to_retain"
-+ MIN 1 MAX 65535 DEFAULT 30 SPIN 1
-+ END PART
-+ END POLICY
-+
-+ POLICY !!Webpage_Retention
-+ EXPLAIN !!Explain_Webpage_Retention
-+ PART !!Webpage_Retention_Edit NUMERIC
-+ VALUENAME "webpage_days_to_retain"
-+ MIN 1 MAX 65535 DEFAULT 30 SPIN 1
-+ END PART
-+ END POLICY
-+
-+ POLICY !!File_Retention
-+ EXPLAIN !!Explain_File_Retention
-+ PART !!File_Retention_Edit NUMERIC
-+ VALUENAME "file_days_to_retain"
-+ MIN 1 MAX 65535 DEFAULT 30 SPIN 1
-+ END PART
-+ END POLICY
-+
-+ POLICY !!IM_Retention
-+ EXPLAIN !!Explain_IM_Retention
-+ PART !!IM_Retention_Edit NUMERIC
-+ VALUENAME "im_days_to_retain"
-+ MIN 1 MAX 65535 DEFAULT 30 SPIN 1
-+ END PART
-+ END POLICY
-+
-+ POLICY !!Pol_Remove_Deleted_Items
-+ EXPLAIN !!Explain_Remove_Deleted_Items
-+ VALUENAME remove_deleted_items
-+ VALUEON NUMERIC 1
-+ END POLICY
-+
-+ POLICY !!Pol_Allow_Simultaneous_Indexing
-+ EXPLAIN !!Explain_Allow_Simultaneous_Indexing
-+ VALUENAME simultaneous_indexing
-+ VALUEON NUMERIC 1
-+ END POLICY
-+
-+ END CATEGORY
-+
-+ POLICY !!Pol_TurnOffAdvancedFeatures
-+ EXPLAIN !!Explain_TurnOffAdvancedFeatures
-+ VALUENAME error_report_on
-+ VALUEON NUMERIC 0
-+ END POLICY
-+
-+ POLICY !!Pol_TurnOffImproveGd
-+ EXPLAIN !!Explain_TurnOffImproveGd
-+ VALUENAME improve_gd
-+ VALUEON NUMERIC 0
-+ VALUEOFF NUMERIC 1
-+ END POLICY
-+
-+ POLICY !!Pol_NoPersonalizationInfo
-+ EXPLAIN !!Explain_NoPersonalizationInfo
-+ VALUENAME send_personalization_info
-+ VALUEON NUMERIC 0
-+ VALUEOFF NUMERIC 1
-+ END POLICY
-+
-+ POLICY !!Pol_OneBoxMode
-+ EXPLAIN !!Explain_OneBoxMode
-+ VALUENAME onebox_mode
-+ VALUEON NUMERIC 0
-+ END POLICY
-+
-+ POLICY !!Pol_EncryptIndex
-+ EXPLAIN !!Explain_EncryptIndex
-+ VALUENAME encrypt_index
-+ VALUEON NUMERIC 1
-+ END POLICY
-+
-+ POLICY !!Pol_Hyper
-+ EXPLAIN !!Explain_Hyper
-+ VALUENAME hyper_off
-+ VALUEON NUMERIC 1
-+ END POLICY
-+
-+ POLICY !!Pol_Display_Mode
-+ EXPLAIN !!Explain_Display_Mode
-+ PART !!Pol_Display_Mode DROPDOWNLIST
-+ VALUENAME display_mode
-+ ITEMLIST
-+ NAME !!Sidebar VALUE NUMERIC 1
-+ NAME !!Deskbar VALUE NUMERIC 8
-+ NAME !!FloatingDeskbar VALUE NUMERIC 4
-+ NAME !!None VALUE NUMERIC 0
-+ END ITEMLIST
-+ END PART
-+ END POLICY
-+
-+ END CATEGORY ; Preferences
-+
-+ CATEGORY !!Cat_Enterprise
-+ KEYNAME "Software\Policies\Google\Google Desktop\Enterprise"
-+
-+ POLICY !!Pol_Autoupdate
-+ EXPLAIN !!Explain_Autoupdate
-+ VALUENAME autoupdate_host
-+ VALUEON ""
-+ END POLICY
-+
-+ POLICY !!Pol_AutoupdateAsSystem
-+ EXPLAIN !!Explain_AutoupdateAsSystem
-+ VALUENAME autoupdate_impersonate_user
-+ VALUEON NUMERIC 0
-+ VALUEOFF NUMERIC 1
-+ END POLICY
-+
-+ POLICY !!Pol_EnterpriseTab
-+ EXPLAIN !!Explain_EnterpriseTab
-+ PART !!EnterpriseTabText EDITTEXT
-+ VALUENAME enterprise_tab_text
-+ END PART
-+ PART !!EnterpriseTabHomepage EDITTEXT
-+ VALUENAME enterprise_tab_homepage
-+ END PART
-+ PART !!EnterpriseTabHomepageQuery CHECKBOX
-+ VALUENAME enterprise_tab_homepage_query
-+ END PART
-+ PART !!EnterpriseTabResults EDITTEXT
-+ VALUENAME enterprise_tab_results
-+ END PART
-+ PART !!EnterpriseTabResultsQuery CHECKBOX
-+ VALUENAME enterprise_tab_results_query
-+ END PART
-+ END POLICY
-+
-+ POLICY !!Pol_GSAHosts
-+ EXPLAIN !!Explain_GSAHosts
-+ KEYNAME "Software\Policies\Google\Google Desktop\Enterprise\GSAHosts"
-+ PART !!Pol_GSAHosts LISTBOX
-+ END PART
-+ END POLICY
-+
-+ POLICY !!Pol_Disallow_Gadgets
-+ EXPLAIN !!Explain_Disallow_Gadgets
-+ VALUENAME disallow_gadgets
-+ VALUEON NUMERIC 1
-+ PART !!Disallow_Only_Non_Builtin_Gadgets CHECKBOX DEFCHECKED
-+ VALUENAME disallow_only_non_builtin_gadgets
-+ VALUEON NUMERIC 1
-+ VALUEOFF NUMERIC 0
-+ END PART
-+ END POLICY
-+
-+ POLICY !!Pol_Gadget_Whitelist
-+ EXPLAIN !!Explain_Gadget_Whitelist
-+ KEYNAME "Software\Policies\Google\Google Desktop\Enterprise\gadget_whitelist"
-+ PART !!Pol_Gadget_Whitelist LISTBOX
-+ END PART
-+ END POLICY
-+
-+ POLICY !!Pol_Gadget_Install_Confirmation_Whitelist
-+ EXPLAIN !!Explain_Gadget_Install_Confirmation_Whitelist
-+ KEYNAME "Software\Policies\Google\Google Desktop\Enterprise\install_confirmation_whitelist"
-+ PART !!Pol_Gadget_Install_Confirmation_Whitelist LISTBOX
-+ END PART
-+ END POLICY
-+
-+ POLICY !!Pol_Alternate_User_Data_Dir
-+ EXPLAIN !!Explain_Alternate_User_Data_Dir
-+ PART !!Pol_Alternate_User_Data_Dir EDITTEXT
-+ VALUENAME alternate_user_data_dir
-+ END PART
-+ END POLICY
-+
-+ POLICY !!Pol_MaxAllowedOutlookConnections
-+ EXPLAIN !!Explain_MaxAllowedOutlookConnections
-+ PART !!Pol_MaxAllowedOutlookConnections NUMERIC
-+ VALUENAME max_allowed_outlook_connections
-+ MIN 1 MAX 65535 DEFAULT 400 SPIN 1
-+ END PART
-+ END POLICY
-+
-+ POLICY !!Pol_DisallowSsdService
-+ EXPLAIN !!Explain_DisallowSsdService
-+ VALUENAME disallow_ssd_service
-+ VALUEON NUMERIC 1
-+ END POLICY
-+
-+ POLICY !!Pol_DisallowSsdOutbound
-+ EXPLAIN !!Explain_DisallowSsdOutbound
-+ VALUENAME disallow_ssd_outbound
-+ VALUEON NUMERIC 1
-+ END POLICY
-+
-+ POLICY !!Pol_Disallow_Store_Gadget_Service
-+ EXPLAIN !!Explain_Disallow_Store_Gadget_Service
-+ VALUENAME disallow_store_gadget_service
-+ VALUEON NUMERIC 1
-+ END POLICY
-+
-+ POLICY !!Pol_MaxExchangeIndexingRate
-+ EXPLAIN !!Explain_MaxExchangeIndexingRate
-+ PART !!Pol_MaxExchangeIndexingRate NUMERIC
-+ VALUENAME max_exchange_indexing_rate
-+ MIN 1 MAX 1000 DEFAULT 60 SPIN 1
-+ END PART
-+ END POLICY
-+
-+ POLICY !!Pol_EnableSafeweb
-+ EXPLAIN !!Explain_Safeweb
-+ VALUENAME safe_browsing
-+ VALUEON NUMERIC 1
-+ VALUEOFF NUMERIC 0
-+ END POLICY
-+
-+ END CATEGORY ; Enterprise
-+
-+ END CATEGORY ; GoogleDesktopSearch
-+ END CATEGORY ; Google
-+
-+;------------------------------------------------------------------------------
-+
-+[strings]
-+Cat_Google="Google"
-+Cat_GoogleDesktopSearch="Google Desktop"
-+
-+;------------------------------------------------------------------------------
-+; Preferences
-+;------------------------------------------------------------------------------
-+Cat_Preferences="Preferences"
-+Explain_Preferences="Controls Google Desktop preferences"
-+
-+Cat_IndexAndCaptureControl="Indexing and Capture Control"
-+Explain_IndexAndCaptureControl="Controls what files, web pages, and other content will be indexed by Google Desktop."
-+
-+Blacklist_Email="Prevent indexing of email"
-+Explain_Blacklist_Email="Enabling this policy will prevent Google Desktop from indexing emails.\n\nIf this policy is not configured, the user can choose whether or not to index emails."
-+Blacklist_Gmail="Prevent indexing of Gmail"
-+Explain_Blacklist_Gmail="Enabling this policy prevents Google Desktop from indexing Gmail messages.\n\nThis policy is in effect only when the policy "Prevent indexing of email" is disabled. When that policy is enabled, all email indexing is disabled, including Gmail indexing.\n\nIf both this policy and "Prevent indexing of email" are disabled or not configured, a user can choose whether or not to index Gmail messages."
-+Blacklist_WebHistory="Prevent indexing of web pages"
-+Explain_Blacklist_WebHistory="Enabling this policy will prevent Google Desktop from indexing web pages.\n\nIf this policy is disabled or not configured, the user can choose whether or not to index web pages."
-+Blacklist_Text="Prevent indexing of text files"
-+Explain_Blacklist_Text="Enabling this policy will prevent Google Desktop from indexing text files.\n\nIf this policy is disabled or not configured, the user can choose whether or not to index text files."
-+Blacklist_Media="Prevent indexing of media files"
-+Explain_Blacklist_Media="Enabling this policy will prevent Google Desktop from indexing media files.\n\nIf this policy is disabled or not configured, the user can choose whether or not to index media files."
-+Blacklist_Contact="Prevent indexing of contacts"
-+Explain_Blacklist_Contact="Enabling this policy will prevent Google Desktop from indexing contacts.\n\nIf this policy is disabled or not configured, the user can choose whether or not to index contacts."
-+Blacklist_Calendar="Prevent indexing of calendar entries"
-+Explain_Blacklist_Calendar="Enabling this policy will prevent Google Desktop from indexing calendar entries.\n\nIf this policy is disabled or not configured, the user can choose whether or not to index calendar entries."
-+Blacklist_Task="Prevent indexing of tasks"
-+Explain_Blacklist_Task="Enabling this policy will prevent Google Desktop from indexing tasks.\n\nIf this policy is disabled or not configured, the user can choose whether or not to index tasks."
-+Blacklist_Note="Prevent indexing of notes"
-+Explain_Blacklist_Note="Enabling this policy will prevent Google Desktop from indexing notes.\n\nIf this policy is disabled or not configured, the user can choose whether or not to index notes."
-+Blacklist_Journal="Prevent indexing of journal entries"
-+Explain_Blacklist_Journal="Enabling this policy will prevent Google Desktop from indexing journal entries.\n\nIf this policy is disabled or not configured, the user can choose whether or not to index journal entries."
-+Blacklist_Word="Prevent indexing of Word documents"
-+Explain_Blacklist_Word="Enabling this policy will prevent Google Desktop from indexing Word documents.\n\nIf this policy is disabled or not configured, the user can choose whether or not to index Word documents."
-+Blacklist_Excel="Prevent indexing of Excel documents"
-+Explain_Blacklist_Excel="Enabling this policy will prevent Google Desktop from indexing Excel documents.\n\nIf this policy is disabled or not configured, the user can choose whether or not to index Excel documents."
-+Blacklist_Powerpoint="Prevent indexing of PowerPoint documents"
-+Explain_Blacklist_Powerpoint="Enabling this policy will prevent Google Desktop from indexing PowerPoint documents.\n\nIf this policy is disabled or not configured, the user can choose whether or not to index PowerPoint documents."
-+Blacklist_PDF="Prevent indexing of PDF documents"
-+Explain_Blacklist_PDF="Enabling this policy will prevent Google Desktop from indexing PDF documents.\n\nIf this policy is disabled or not configured, the user can choose whether or not to index PDF documents."
-+Blacklist_ZIP="Prevent indexing of ZIP files"
-+Explain_Blacklist_ZIP="Enabling this policy will prevent Google Desktop from indexing ZIP files.\n\nIf this policy is disabled or not configured, the user can choose whether or not to index ZIP files."
-+Blacklist_HTTPS="Prevent indexing of secure web pages"
-+Explain_Blacklist_HTTPS="Enabling this policy will prevent Google Desktop from indexing secure web pages (pages with HTTPS in the URL).\n\nIf this policy is disabled or not configured, the user can choose whether or not to index secure web pages."
-+Blacklist_URI_Contains="Prevent indexing of specific web sites and folders"
-+Explain_Blacklist_URI_Contains="This policy allows you to prevent Google Desktop from indexing specific websites or folders. If an item's URL or path name contains any of these specified strings, it will not be indexed. These restrictions will be applied in addition to any websites or folders that the user has specified.\n\nThis policy has no effect when disabled or not configured."
-+Blacklist_Chat="Prevent indexing of IM chats"
-+Explain_Blacklist_Chat="Enabling this policy will prevent Google Desktop from indexing IM chat conversations.\n\nIf this policy is disabled or not configured, the user can choose whether or not to index IM chat conversations."
-+Blacklist_PasswordProtectedOffice="Prevent indexing of password-protected Office documents (Word, Excel)"
-+Explain_Blacklist_PasswordProtectedOffice="Enabling this policy will prevent Google Desktop from indexing password-protected office documents.\n\nIf this policy is disabled or not configured, the user can choose whether or not to index password-protected office documents."
-+Blacklist_Extensions="Prevent indexing of specific file extensions"
-+Explain_Blacklist_Extensions="This policy allows you to prevent Google Desktop from indexing files with specific extensions. Enter a list of file extensions, separated by commas, that you wish to exclude from indexing.\n\nThis policy has no effect when disabled or not configured."
-+Pol_Disallow_UserSearchLocations="Disallow adding search locations for indexing"
-+Explain_Disallow_UserSearchLocations="Enabling this policy will prevent the user from specifying additional drives or networked folders to be indexed by Google Desktop.\n\nIf this policy is disabled or not configured, users may specify additional drives and networked folders to be indexed."
-+Pol_Search_Location_Whitelist="Allow indexing of specific folders"
-+Explain_Search_Location_Whitelist="This policy allows you to add additional drives and networked folders to index."
-+Search_Locations_Whitelist="Search these locations"
-+Email_Retention="Only retain emails that are less than x days old"
-+Explain_Email_Retention="This policy allows you to configure Google Desktop to only retain emails that are less than the specified number of days old in the index. Enter the number of days to retain emails for\n\nThis policy has no effect when disabled or not configured."
-+Email_Retention_Edit="Number of days to retain emails"
-+Webpage_Retention="Only retain webpages that are less than x days old"
-+Explain_Webpage_Retention="This policy allows you to configure Google Desktop to only retain webpages that are less than the specified number of days old in the index. Enter the number of days to retain webpages for\n\nThis policy has no effect when disabled or not configured."
-+Webpage_Retention_Edit="Number of days to retain webpages"
-+File_Retention="Only retain files that are less than x days old"
-+Explain_File_Retention="This policy allows you to configure Google Desktop to only retain files that are less than the specified number of days old in the index. Enter the number of days to retain files for\n\nThis policy has no effect when disabled or not configured."
-+File_Retention_Edit="Number of days to retain files"
-+IM_Retention="Only retain IM that are less than x days old"
-+Explain_IM_Retention="This policy allows you to configure Google Desktop to only retain IM that are less than the specified number of days old in the index. Enter the number of days to retain IM for\n\nThis policy has no effect when disabled or not configured."
-+IM_Retention_Edit="Number of days to retain IM"
-+
-+Pol_Remove_Deleted_Items="Remove deleted items from the index."
-+Explain_Remove_Deleted_Items="Enabling this policy will remove all deleted items from the index and cache. Any items that are deleted will no longer be searchable."
-+
-+Pol_Allow_Simultaneous_Indexing="Allow historical indexing for multiple users simultaneously."
-+Explain_Allow_Simultaneous_Indexing="Enabling this policy will allow a computer to generate first-time indexes for multiple users simultaneously. \n\nIf this policy is disabled or not configured, historical indexing will happen only for the logged-in user that was connected last; historical indexing for any other logged-in user will happen the next time that other user connects."
-+
-+Pol_TurnOffAdvancedFeatures="Turn off Advanced Features options"
-+Explain_TurnOffAdvancedFeatures="Enabling this policy will prevent Google Desktop from sending Advanced Features data to Google (for either improvements or personalization), and users won't be able to change these options. Enabling this policy also prevents older versions of Google Desktop from sending data.\n\nIf this policy is disabled or not configured and the user has a pre-5.5 version of Google Desktop, the user can choose whether or not to enable sending data to Google. If the user has version 5.5 or later, the 'Turn off Improve Google Desktop option' and 'Do not send personalization info' policies will be used instead."
-+
-+Pol_TurnOffImproveGd="Turn off Improve Google Desktop option"
-+Explain_TurnOffImproveGd="Enabling this policy will prevent Google Desktop from sending improvement data, including crash reports and anonymous usage data, to Google.\n\nIf this policy is disabled, improvement data will be sent to Google and the user won't be able to change the option.\n\nIf this policy is not configured, the user can choose whether or not to enable the Improve Google Desktop option.\n\nNote that this policy applies only to version 5.5 or later and doesn't affect previous versions of Google Desktop.\n\nAlso note that this policy can be overridden by the 'Turn off Advanced Features options' policy."
-+
-+Pol_NoPersonalizationInfo="Do not send personalization info"
-+Explain_NoPersonalizationInfo="Enabling this policy will prevent Google Desktop from displaying personalized content, such as news that reflects the user's past interest in articles. Personalized content is derived from anonymous usage data sent to Google.\n\nIf this policy is disabled, personalized content will be displayed for all users, and users won't be able to disable this feature.\n\nIf this policy is not configured, users can choose whether or not to enable personalization in each gadget that supports this feature.\n\nNote that this policy applies only to version 5.5 or later and doesn't affect previous versions of Google Desktop.\n\nAlso note that this policy can be overridden by the 'Turn off Advanced Features options' policy."
-+
-+Pol_OneBoxMode="Turn off Google Web Search Integration"
-+Explain_OneBoxMode="Enabling this policy will prevent Google Desktop from displaying Desktop Search results in queries to google.com.\n\nIf this policy is disabled or not configured, the user can choose whether or not to include Desktop Search results in queries to google.com."
-+
-+Pol_EncryptIndex="Encrypt index data"
-+Explain_EncryptIndex="Enabling this policy will cause Google Desktop to turn on Windows file encryption for the folder containing the Google Desktop index and related user data the next time it is run.\n\nNote that Windows EFS is only available on NTFS volumes. If the user's data is stored on a FAT volume, this policy will have no effect.\n\nThis policy has no effect when disabled or not configured."
-+
-+Pol_Hyper="Turn off Quick Find"
-+Explain_Hyper="Enabling this policy will cause Google Desktop to turn off Quick Find feature. Quick Find allows you to see results as you type.\n\nIf this policy is disabled or not configured, the user can choose whether or not to enable it."
-+
-+Pol_Display_Mode="Choose display option"
-+Explain_Display_Mode="This policy sets the Google Desktop display option: Sidebar, Deskbar, Floating Deskbar or none.\n\nNote that on 64-bit systems, a setting of Deskbar will be interpreted as Floating Deskbar.\n\nIf this policy is disabled or not configured, the user can choose a display option."
-+Sidebar="Sidebar"
-+Deskbar="Deskbar"
-+FloatingDeskbar="Floating Deskbar"
-+None="None"
-+
-+;------------------------------------------------------------------------------
-+; Enterprise
-+;------------------------------------------------------------------------------
-+Cat_Enterprise="Enterprise Integration"
-+Explain_Enterprise="Controls features specific to Enterprise installations of Google Desktop"
-+
-+Pol_Autoupdate="Block Auto-update"
-+Explain_Autoupdate="Enabling this policy prevents Google Desktop from automatically checking for and installing updates from google.com.\n\nIf you enable this policy, you must distribute updates to Google Desktop using Group Policy, SMS, or a similar enterprise software distribution mechanism. You should check http://desktop.google.com/enterprise/ for updates.\n\nIf this policy is disabled or not configured, Google Desktop will periodically check for updates from desktop.google.com."
-+
-+Pol_AutoupdateAsSystem="Use system proxy settings when auto-updating"
-+Explain_AutoupdateAsSystem="Enabling this policy makes Google Desktop use the machine-wide proxy settings (as specified using e.g. proxycfg.exe) when performing autoupdates (if enabled).\n\nIf this policy is disabled or not configured, Google Desktop will use the logged-on user's Internet Explorer proxy settings when checking for auto-updates (if enabled)."
-+
-+Pol_EnterpriseTab="Enterprise search tab"
-+Explain_EnterpriseTab="This policy allows you to add a search tab for your Google Search Appliance to Google Desktop and google.com web pages.\n\nYou must provide the name of the tab, such as "Intranet", as well as URLs for the search homepage and for retrieving search results. Use [DISP_QUERY] in place of the query term for the search results URL.\n\nSee the administrator's guide for more details."
-+EnterpriseTabText="Tab name"
-+EnterpriseTabHomepage="Search homepage URL"
-+EnterpriseTabHomepageQuery="Check if search homepage supports '&&q=<query>'"
-+EnterpriseTabResults="Search results URL"
-+EnterpriseTabResultsQuery="Check if search results page supports '&&q=<query>'"
-+
-+Pol_GSAHosts="Google Search Appliances"
-+Explain_GSAHosts="This policy allows you to list any Google Search Appliances in your intranet. When properly configured, Google Desktop will insert Google Desktop results into the results of queries on the Google Search Appliance"
-+
-+Pol_PolicyUnawareClientProhibitedFlag="Prohibit Policy-Unaware versions"
-+Explain_PolicyUnawareClientProhibitedFlag="Prohibits installation and execution of versions of Google Desktop that are unaware of group policy.\n\nEnabling this policy will prevent users from installing or running version 1.0 of Google Desktop.\n\nThis policy has no effect when disabled or not configured."
-+
-+Pol_MinimumAllowedVersion="Minimum allowed version"
-+Explain_MinimumAllowedVersion="This policy allows you to prevent installation and/or execution of older versions of Google Desktop by specifying the minimum version you wish to allow. When enabling this policy, you should also enable the "Prohibit Policy-Unaware versions" policy to block versions of Google Desktop that did not support group policy.\n\nThis policy has no effect when disabled or not configured."
-+
-+Pol_MaximumAllowedVersion="Maximum allowed version"
-+Explain_MaximumAllowedVersion="This policy allows you to prevent installation and/or execution of newer versions of Google Desktop by specifying the maximum version you wish to allow.\n\nThis policy has no effect when disabled or not configured."
-+
-+Pol_Disallow_Gadgets="Disallow gadgets and indexing plug-ins"
-+Explain_Disallow_Gadgets="This policy prevents the use of all Google Desktop gadgets and indexing plug-ins. The policy applies to gadgets that are included in the Google Desktop installation package (built-in gadgets), built-in indexing plug-ins (currently only the Lotus Notes plug-in), and to gadgets or indexing plug-ins that a user might want to add later (non-built-in gadgets and indexing plug-ins).\n\nYou can prohibit use of all non-built-in gadgets and indexing plug-ins, but allow use of built-in gadgets and indexing plug-ins. To do so, enable this policy and then select the option "Disallow only non-built-in gadgets and indexing plug-ins.\n\nYou can supersede this policy to allow specified built-in and non-built-in gadgets and indexing plug-ins. To do so, enable this policy and then specify the gadgets and/or indexing plug-ins you want to allow under "Gadget and Plug-in Whitelist.""
-+Disallow_Only_Non_Builtin_Gadgets="Disallow only non-built-in gadgets and indexing plug-ins"
-+
-+Pol_Gadget_Whitelist="Gadget and plug-in whitelist"
-+Explain_Gadget_Whitelist="This policy specifies a list of Google Desktop gadgets and indexing plug-ins that you want to allow, as exceptions to the "Disallow gadgets and indexing plug-ins" policy. This policy is valid only when the "Disallow gadgets and indexing plug-ins" policy is enabled.\n\nFor each gadget or indexing plug-in you wish to allow, add the CLSID or PROGID of the gadget or indexing plug-in (see the administrator's guide for more details).\n\nThis policy has no effect when disabled or not configured."
-+
-+Pol_Gadget_Install_Confirmation_Whitelist="Allow silent installation of gadgets"
-+Explain_Gadget_Install_Confirmation_Whitelist="Enabling this policy lets you specify a list of Google Desktop gadgets or indexing plug-ins that can be installed without confirmation from the user.\n\nAdd a gadget or indexing plug-in by placing its class ID (CLSID) or program identifier (PROGID) in the list, surrounded with curly braces ({ }).\n\nThis policy has no effect when disabled or not configured."
-+
-+Pol_Alternate_User_Data_Dir="Alternate user data directory"
-+Explain_Alternate_User_Data_Dir="This policy allows you to specify a directory to be used to store user data for Google Desktop (such as index data and cached documents).\n\nYou may use [USER_NAME] or [DOMAIN_NAME] in the path to specify the current user's name or domain. If [USER_NAME] is not specified, the user name will be appended at the end of the path.\n\nThis policy has no effect when disabled or not configured."
-+
-+Pol_MaxAllowedOutlookConnections="Maximum allowed Outlook connections"
-+Explain_MaxAllowedOutlookConnections="This policy specifies the maximum number of open connections that Google Desktop maintains with the Exchange server. Google Desktop opens a connection for each email folder that it indexes. If insufficient connections are allowed, Google Desktop cannot index all the user email folders.\n\nThe default value is 400. Because users rarely have as many as 400 email folders, Google Desktop rarely reaches the limit.\n\nIf you set this policy's value above 400, you must also configure the number of open connections between Outlook and the Exchange server. By default, approximately 400 connections are allowed. If Google Desktop uses too many of these connections, Outlook might be unable to access email.\n\nThis policy has no effect when disabled or not configured."
-+
-+Pol_DisallowSsdService="Disallow sharing and receiving of web history and documents across computers"
-+Explain_DisallowSsdService="Enabling this policy will prevent Google Desktop from sharing the user's web history and document contents across the user's different Google Desktop installations, and will also prevent it from receiving such shared items from the user's other machines. To allow reception but disallow sharing, use DisallowSsdOutbound.\nThis policy has no effect when disabled or not configured."
-+
-+Pol_DisallowSsdOutbound="Disallow sharing of web history and documents to user's other computers."
-+Explain_DisallowSsdOutbound="Enabling this policy will prevent Google Desktop from sending the user's web history and document contents from this machine to the user's other machines. It does not prevent reception of items from the user's other machines; to disallow both, use DisallowSsdService.\nThis policy has no effect when disabled or not configured."
-+
-+Pol_Disallow_Store_Gadget_Service="Disallow storage of gadget content and settings."
-+Explain_Disallow_Store_Gadget_Service="Enabling this policy will prevent users from storing their gadget content and settings with Google. Users will be unable to access their gadget content and settings from other computers and all content and settings will be lost if Google Desktop is uninstalled."
-+
-+Pol_MaxExchangeIndexingRate="Maximum allowed Exchange indexing rate"
-+Explain_MaxExchangeIndexingRate="This policy allows you to specify the maximum number of emails that are indexed per minute. \n\nThis policy has no effect when disabled or not configured."
-+
-+Pol_EnableSafeweb="Enable or disable safe browsing"
-+Explain_Safeweb="Google Desktop safe browsing informs the user whenever they visit any site which is a suspected forgery site or may harm their computer. Enabling this policy turns on safe browsing; disabling the policy turns it off. \n\nIf this policy is not configured, the user can select whether to turn on safe browsing."
-\ No newline at end of file
-diff --git a/tools/grit/grit/testdata/README.txt b/tools/grit/grit/testdata/README.txt
-new file mode 100644
-index 0000000000..a683b3b9e3
---- /dev/null
-+++ b/tools/grit/grit/testdata/README.txt
-@@ -0,0 +1,87 @@
-+Google Desktop for Enterprise
-+Copyright (C) 2007 Google Inc.
-+All Rights Reserved
-+
-+---------
-+Contents
-+---------
-+This distribution contains the following files:
-+
-+GoogleDesktopSetup.msi - Installation and setup program
-+GoogleDesktop.adm - Group Policy administrative template file
-+AdminGuide.pdf - Google Desktop for Enterprise administrative guide
-+
-+
-+--------------
-+Documentation
-+--------------
-+Full documentation and installation instructions are in the
-+administrative guide, and also online at
-+http://desktop.google.com/enterprise/adminguide.html.
-+
-+
-+------------------------
-+IBM Lotus Notes Plug-In
-+------------------------
-+The Lotus Notes plug-in is included in the release of Google
-+Desktop for Enterprise. The IBM Lotus Notes Plug-in for Google
-+Desktop indexes mail, calendar, task, contact and journal
-+documents from Notes. Discussion documents including those from
-+the discussion and team room templates can also be indexed by
-+selecting an option from the preferences. Once indexed, this data
-+will be returned in Google Desktop searches. The corresponding
-+document can be opened in Lotus Notes from the Google Desktop
-+results page.
-+
-+Install: The plug-in will install automatically during the Google
-+Desktop setup process if Lotus Notes is already installed. Lotus
-+Notes must not be running in order for the install to occur. The
-+Class ID for this plug-in is {8F42BDFB-33E8-427B-AFDC-A04E046D3F07}.
-+
-+Preferences: Preferences and selection of databases to index are
-+set in the 'Google Desktop for Notes' dialog reached through the
-+'Actions' menu.
-+
-+Reindexing: Selecting 'Reindex all databases' will index all the
-+documents in each database again.
-+
-+
-+Notes Plug-in Known Issues
-+---------------------------
-+
-+If the 'Google Desktop for Notes' item is not available from the
-+Lotus Notes Actions menu, then installation was not successful.
-+Installation consists of writing one file, notesgdsplugin.dll, to
-+the Notes application directory and a setting to the notes.ini
-+configuration file. The most likely cause of an unsuccessful
-+installation is that the installer was not able to locate the
-+notes.ini file. Installation will complete if the user closes Notes
-+and manually adds the following setting to this file on a new line:
-+AddinMenus=notesgdsplugin.dll
-+
-+If the notesgdsplugin.dll file is not in the application directory
-+(e.g., C:\Program Files\Lotus\Notes) after Google Desktop
-+installation, it is likely that Notes was not installed correctly.
-+
-+Only local databases can be indexed. If they can be determined,
-+the user's local mail file and address book will be included in the
-+list automatically. Mail archives and other databases must be
-+added with the 'Add' button.
-+
-+Some users may experience performance issues during the initial
-+indexing of a database. The 'Perform the initial index of a
-+database only when I'm idle' option will limit the indexing process
-+to times when the user is not using the machine. If this does not
-+alleviate the problem or the user would like to continually index
-+but just do so more slowly or quickly, the GoogleWaitTime notes.ini
-+value can be set. Increasing the GoogleWaitTime value will slow
-+down the indexing process, and lowering the value will speed it up.
-+A value of zero causes the fastest possible indexing. Removing the
-+ini parameter altogether returns it to the default (20).
-+
-+Crashes have been known to occur with certain types of history
-+bookmarks. If the Notes client seems to crash randomly, try
-+disabling the 'Index note history' option. If it crashes before,
-+you can get to the preferences, add the following line to your
-+notes.ini file:
-+GDSNoIndexHistory=1
-diff --git a/tools/grit/grit/testdata/about.html b/tools/grit/grit/testdata/about.html
-new file mode 100644
-index 0000000000..8e5fad7b2b
---- /dev/null
-+++ b/tools/grit/grit/testdata/about.html
-@@ -0,0 +1,45 @@
-+[HEADER]
-+<table cellspacing=0 cellPadding=0 width="100%" border=0><tr bgcolor=#3399cc><td align=middle height=1><img height=1 width=1></td></tr></table>
-+<table cellspacing=0 cellPadding=1 width="100%" bgcolor=#e8f4f7 border=0><tr><td height=20><font size=+1 color=#000000>&nbsp;<b>[TITLE]</b></font></td></tr></table>
-+<br><center><span style="line-height:16pt"><font color=#335cec><B>Google Desktop Search: Search your own computer.</B></font></span></center><br>
-+
-+<table cellspacing=1 cellpadding=0 width=300 align=center border=0>
-+<tr><td nowrap><font size=-1><img style="vertical-align:middle" height=16 src="outlook.gif" width=16>&nbsp;&nbsp;Outlook Email</font></td>
-+<td nowrap>&nbsp;</td>
-+<td nowrap><font size=-1><img style="vertical-align:middle" height=16 src="netscape.gif" width=16>&nbsp;&nbsp;Netscape Mail / Thunderbird</font></td></tr>
-+
-+<tr><td nowrap><font size=-1><img style="vertical-align:middle" height=16 src="oe.gif" width=16>&nbsp;&nbsp;Outlook Express</font></td>
-+<td nowrap>&nbsp;</td>
-+<td nowrap><font size=-1><img style="vertical-align:middle" height=16 src="ff.gif" width=16>&nbsp;&nbsp;Netscape / Firefox / Mozilla</font></td></tr>
-+
-+<tr><td nowrap><font size=-1><img style="vertical-align:middle" height=16 src="doc.gif" width=16>&nbsp;&nbsp;Word</font></td>
-+<td nowrap>&nbsp;</td>
-+<td nowrap><font size=-1><img style="vertical-align:middle" height=16 src="pdf.gif" width=16>&nbsp;&nbsp;PDF</font></td></tr>
-+
-+<tr><td nowrap><font size=-1><img style="vertical-align:middle" height=16 src="xls.gif" width=16>&nbsp;&nbsp;Excel</font></td>
-+<td nowrap>&nbsp;</td>
-+<td nowrap><font size=-1><img style="vertical-align:middle" height=16 src="mus.gif" width=16>&nbsp;&nbsp;Music</font></td></tr>
-+
-+<tr><td nowrap><font size=-1><img style="vertical-align:middle" height=16 src="ppt.gif" width=16>&nbsp;&nbsp;PowerPoint</font></td>
-+<td nowrap>&nbsp;</td>
-+<td nowrap><font size=-1><img style="vertical-align:middle" height=16 src="jpg.gif" width=16>&nbsp;&nbsp;Images</font></td></tr>
-+
-+<tr><td nowrap><font size=-1><img style="vertical-align:middle" height=16 src="ie.gif" width=16>&nbsp;&nbsp;Internet Explorer</font></td>
-+<td nowrap>&nbsp;</td>
-+<td nowrap><font size=-1><img style="vertical-align:middle" height=16 src="mov.gif" width=16>&nbsp;&nbsp;Video</font></td></tr>
-+
-+<tr><td nowrap><font size=-1><img style="vertical-align:middle" height=16 src="aim.gif" width=16>&nbsp;&nbsp;AOL Instant Messenger</font></td>
-+<td nowrap>&nbsp;</td>
-+<td nowrap><font size=-1><img style="vertical-align:middle" height=16 src="other.gif" width=16>&nbsp;&nbsp;Even more with <a href="http://desktop.google.com/plugins.html">these plug-ins</A></font></td></tr>
-+
-+<tr><td nowrap><font size=-1><img style="vertical-align:middle" height=16 src="txt.gif" width=16>&nbsp;&nbsp;Text and others</font></td></tr>
-+</table>
-+<center>
-+<p><table cellpadding=1>
-+<tr><td><a href="http://desktop.google.com/gettingstarted.html?hl=[LANG_CODE]"><B>Getting Started</B></A> - Learn more about using Google Desktop Search</td></tr>
-+<tr><td><a href="http://desktop.google.com/help.html?hl=[LANG_CODE]"><B>Online Help</B></A> - Up-to-date answers to your questions</td></tr>
-+<tr><td><a href="[$~PRIVACY~$]"><B>Privacy</B></A> - A few words about privacy and Google Desktop Search</td></tr>
-+<tr><td><a href="http://desktop.google.com/uninstall.html?hl=[LANG_CODE]"><B>Uninstall</B></A> - How to uninstall Google Desktop Search</td></tr>
-+<tr><td><a href="http://desktop.google.com/feedback.html?hl=[LANG_CODE]"><B>Submit Feedback</B></A> - Send us your comments and ideas</td></tr>
-+</table><br><font size=-2>Google Desktop Search [$~BUILDNUMBER~$]</font><br><br>
-+[FOOTER]
-\ No newline at end of file
-diff --git a/tools/grit/grit/testdata/android.xml b/tools/grit/grit/testdata/android.xml
-new file mode 100644
-index 0000000000..cc3b141f70
---- /dev/null
-+++ b/tools/grit/grit/testdata/android.xml
-@@ -0,0 +1,24 @@
-+<!--
-+ Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+ Use of this source code is governed by a BSD-style license that can be
-+ found in the LICENSE file.
-+-->
-+
-+<resources>
-+ <!-- A string with placeholder. -->
-+ <string xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" name="placeholders">
-+ Open <xliff:g id="FILENAME" example="internet.html">%s</xliff:g>?
-+ </string>
-+
-+ <!-- A simple string. -->
-+ <string name="simple">A simple string.</string>
-+
-+ <!-- A string with a comment. -->
-+ <string name="comment">Contains a <!-- ignore this --> comment. </string>
-+
-+ <!-- A second simple string. -->
-+ <string name="simple2"> Another simple string. </string>
-+
-+ <!-- A non-translatable string. -->
-+ <string name="constant" translatable="false">Do not translate me.</string>
-+</resources>
-diff --git a/tools/grit/grit/testdata/bad_browser.html b/tools/grit/grit/testdata/bad_browser.html
-new file mode 100644
-index 0000000000..e8cf34664d
---- /dev/null
-+++ b/tools/grit/grit/testdata/bad_browser.html
-@@ -0,0 +1,16 @@
-+<p><b>We're sorry, but we don't seem to be compatible.</b></p>
-+<p><font size="-1">Our software suggests that you're using a browser incompatible with Google Desktop Search.
-+ Google Desktop Search currently supports the following:</font></p>
-+<ul><font size="-1">
-+ <li>Microsoft IE 5 and newer (<a href="http://www.microsoft.com/windows/ie/downloads/default.asp">Download</a>)</li>
-+ <li>Mozilla (<a href="http://www.mozilla.org/products/mozilla1.x/">Download</a>)</li>
-+ <li>Mozilla Firefox (<a href="http://www.mozilla.org/products/firefox/">Download</a>)</li>
-+ <li>Netscape 7 and newer (<a href="http://channels.netscape.com/ns/browsers/download.jsp">Download</a>)</li>
-+</font></ul>
-+
-+<p><font size="-1">You may <a href="[REDIR]">click here</a> to use your
-+ unsupported browser, though you likely will encounter some areas that don't
-+ work as expected. You need to have Javascript enabled, regardless of the
-+ browser you use.</font>
-+<p><font size="-1">We hope to expand this list in the near future and announce new
-+ browsers as they become available.
-diff --git a/tools/grit/grit/testdata/browser.html b/tools/grit/grit/testdata/browser.html
-new file mode 100644
-index 0000000000..45d364d56f
---- /dev/null
-+++ b/tools/grit/grit/testdata/browser.html
-@@ -0,0 +1,42 @@
-+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-+<html><head><title>[$~TITLE~$]</title>
-+<style>
-+BODY { MARGIN-LEFT: 1em; MARGIN-RIGHT: 1em }
-+BODY, TD, DIV, A { FONT-FAMILY: arial,sans-serif}
-+DIV, TD { COLOR: #000}
-+A:link { COLOR: #00c}
-+A:visited { COLOR: #551a8b}
-+A:active { COLOR: #f00 }
-+</style>
-+</head>
-+
-+<body bgcolor="#ffffff" text="#000000" link="#0000cc" vlink="#800080" alink="#ff0000" topmargin=2>
-+
-+<table cellspacing=2 cellpadding=0 width="99%" border=0>
-+<tr>
-+ <td width="1%" rowspan=2>[$~IMAGE~$]
-+ <td>&nbsp;</td>
-+ <td rowspan=2>
-+ <table cellspacing=0 cellpadding=0 width="100%" border=0>
-+ <tr>
-+ <td bgcolor=#3399cc><img height=1 width=1></td>
-+ </tr>
-+ </table>
-+ <table cellspacing=0 cellpadding=0 width="100%" border=0 bgcolor=#efefef>
-+ <tr>
-+ <td nowrap bgcolor=#E8F4F7><font face=arial,sans-serif color=#000000 size=+1><b>&nbsp;[$~CHROME_TITLE~$]</b></font></td>
-+ </tr>
-+ </table>
-+ </td>
-+</tr>
-+</table>
-+
-+<table cellpadding=3 width="94%" align="center" cellspacing=0 border=0>
-+<tr valign="middle">
-+ <td valign="top">
-+ [$~BODY~$]
-+ </td>
-+ </tr>
-+</table>
-+[$~FOOTER~$]
-+</body></html>
-\ No newline at end of file
-diff --git a/tools/grit/grit/testdata/buildinfo.grd b/tools/grit/grit/testdata/buildinfo.grd
-new file mode 100644
-index 0000000000..80458a8265
---- /dev/null
-+++ b/tools/grit/grit/testdata/buildinfo.grd
-@@ -0,0 +1,46 @@
-+<?xml version="1.0" encoding="UTF-8"?> <!-- -*- XML -*- -->
-+<grit
-+ base_dir="."
-+ source_lang_id="en"
-+ tc_project="GoogleDesktopWindowsClient"
-+ latest_public_release="0"
-+ current_release="1"
-+ enc_check="möl">
-+ <outputs>
-+ <output filename="resource.h" type="rc_header" />
-+ <output filename="en_generated_resources.rc" type="rc_all" lang="en" />
-+ <output filename="sv_generated_resources.rc" type="rc_all" lang="sv" />
-+ </outputs>
-+ <translations>
-+ <file path="substitute.xmb" lang="sv" />
-+ </translations>
-+ <release seq="1" allow_pseudo="false">
-+ <includes>
-+ <include type="BITMAP" name="IDB_PR" file="pr.bmp" />
-+ <if expr="lang == 'sv'">
-+ <include type="BITMAP" name="IDB_PR2" file="pr2.bmp" />
-+ </if>
-+ </includes>
-+ <structures>
-+ <structure name="SIDEBAR_LOADING.HTML" encoding="utf-8" file="sidebar_loading.html" type="tr_html" generateid="false" expand_variables="false"/>
-+ <structure name="IDS_PLACEHOLDER" file="transl.rc" type="dialog" >
-+ <skeleton expr="lang == 'sv'" file="transl1.rc" variant_of_revision="1"/>
-+ </structure>
-+ <if expr="lang != 'sv'">
-+ <structure name="WELCOME_TOAST.HTML" encoding="utf-8" file="welcome_toast.html" type="tr_html" generateid="false" expand_variables="true"/>
-+ </if>
-+ </structures>
-+ <messages first_id="8192">
-+ <message name="IDS_COPYRIGHT_GOOGLE_LONG" sub_variable="true" desc="Gadget copyright notice. Needs to be updated every year.">
-+ Copyright 2008 Google Inc. All Rights Reserved.
-+ </message>
-+ <message name="IDS_NEWS_PANEL_COPYRIGHT">
-+ Google Desktop News gadget
-+[IDS_COPYRIGHT_GOOGLE_LONG]
-+View news that is personalized based on the articles you read.
-+
-+For example, if you read lots of sports news, you'll see more sports articles. If you read technology news less often, you'll see fewer of those articles.
-+ </message>
-+ </messages>
-+ </release>
-+</grit>
-diff --git a/tools/grit/grit/testdata/cache_prefix.html b/tools/grit/grit/testdata/cache_prefix.html
-new file mode 100644
-index 0000000000..b1f91dd82b
---- /dev/null
-+++ b/tools/grit/grit/testdata/cache_prefix.html
-@@ -0,0 +1,24 @@
-+<head>
-+<meta http-equiv="cache-control" content="no-cache">
-+<meta http-equiv="pragma" content="no-cache">
-+<meta http-equiv="expires" content="-1">
-+</head>
-+<body onload="[ONLOAD]">
-+<table width="100%" border=1><tr><td>
-+<table cellspacing=0 cellpadding=10 width="100%" bgcolor=#ffffff border=1 color="#ffffff">
-+<tr><td><font face="arial,sans-serif" color=black size=-1>This is one version of <a href="[$~URL~$]">
-+<font color="blue">[URL-DISP]</font></a> from your personal <a href="http://desktop.google.com/webcache.html"><font color=blue>cache</font></a>.<br>
-+The page may have changed since that time. Click here for the <a href="[$~URL~$]"><font color="blue">current page</font></a>.<br>
-+Since this page is stored on your computer, publicly linking to this page will not work.[$~EXTRA~$]<br><br>
-+<font size="-2"><i>Google may not be affiliated with the authors of this page nor responsible for its content. This page may be protected by copyright.</i></font>
-+</td>
-+</tr></table></td></tr></table>
-+<style>
-+.hl { color:black; background-color:#ffff88}
-+</style>
-+<script>
-+[$~HIGHLIGHT_SCRIPT~$]
-+window.onerror=new Function(';');
-+</script>
-+<hr id=gg_1>
-+</body>
-\ No newline at end of file
-diff --git a/tools/grit/grit/testdata/cache_prefix_file.html b/tools/grit/grit/testdata/cache_prefix_file.html
-new file mode 100644
-index 0000000000..f3eb8e0f11
---- /dev/null
-+++ b/tools/grit/grit/testdata/cache_prefix_file.html
-@@ -0,0 +1,25 @@
-+<head>
-+<meta http-equiv="cache-control" content="no-cache">
-+<meta http-equiv="pragma" content="no-cache">
-+<meta http-equiv="expires" content="-1"></head>
-+<body onload="[ONLOAD]">
-+<table width="100%" border=1>
-+<tr><td>
-+<table cellspacing=0 cellpadding=10 width="100%" bgcolor=#ffffff border=1 color="#ffffff">
-+<tr><td><font face=arial,sans-serif color=black size=-1>This is one version of <a href="[$~URL~$]"><font color=blue>[URL-DISP]</font></a>
-+from your personal <a href="http://desktop.google.com/filecache.html"><font color=blue>cache</font></a>.<br>
-+The file may have changed since that time. Click here for the <a href="[$~URL~$]"><font color=blue>current file</font></a>.<br>
-+Since this file is stored on your computer, publicly linking to it will not work.[$~EXTRA~$]<br><br>
-+<font size="-2"><i>Google may not be affiliated with the authors of this page nor responsible for its content. This page may be protected by copyright.</i></font>
-+</td></tr>
-+</table>
-+</td></tr></table>
-+<style>
-+.hl { color:black; background-color:#ffff88}
-+</style>
-+<script>
-+[$~HIGHLIGHT_SCRIPT~$]
-+window.onerror=new Function(';');
-+</script>
-+<hr id=gg_1>
-+</body>
-\ No newline at end of file
-diff --git a/tools/grit/grit/testdata/chat_result.html b/tools/grit/grit/testdata/chat_result.html
-new file mode 100644
-index 0000000000..318078bc3d
---- /dev/null
-+++ b/tools/grit/grit/testdata/chat_result.html
-@@ -0,0 +1,24 @@
-+[HEADER]
-+[CHROME]
-+<table border=0 cellpadding=2 cellspacing=2>
-+<tr><td>[$~STARTCHAT~$]</td></tr>
-+</table>
-+<blockquote id=gg_1>
-+<table bgcolor=#f0f8ff width=80% cellpadding=5><tr><td>
-+<img style="vertical-align:middle;" height=16 src="16x16_chat.gif" width=16> &nbsp; <b>[$~TITLE~$]</b>
-+<font size=-1><br><br>Participants: [USERNAME], [BUDDYNAME]<br>
-+Date: [TIME]</font></td></tr></table>
-+<br id=contents>
-+<label>[CONTENTS]</label>
-+</blockquote>
-+<table border=0 cellpadding=2 cellspacing=2>
-+<tr><td>[$~STARTCHAT~$]</td></tr>
-+</table>
-+<style>
-+.hl { color:black; background-color:#ffff88}
-+</style>
-+<script>
-+[$~HIGHLIGHT_SCRIPT~$]
-+[ONLOAD]
-+</script>
-+[FOOTER]
-diff --git a/tools/grit/grit/testdata/chrome/app/generated_resources.grd b/tools/grit/grit/testdata/chrome/app/generated_resources.grd
-new file mode 100644
-index 0000000000..c2efb77fd8
---- /dev/null
-+++ b/tools/grit/grit/testdata/chrome/app/generated_resources.grd
-@@ -0,0 +1,199 @@
-+<?xml version="1.0" encoding="UTF-8"?>
-+
-+<!--
-+This file contains definitions of resources that will be translated for each
-+locale. The variables is_win, is_macosx, is_linux, and is_posix are available
-+for making strings OS specific. Other platform defines such as use_titlecase
-+are declared in build/common.gypi.
-+-->
-+
-+<grit base_dir="." latest_public_release="0" current_release="1"
-+ source_lang_id="en" enc_check="möl">
-+ <outputs>
-+ <output filename="grit/generated_resources.h" type="rc_header">
-+ <emit emit_type='prepend'></emit>
-+ </output>
-+ <output filename="generated_resources_am.pak" type="data_package" lang="am" />
-+ <output filename="generated_resources_ar.pak" type="data_package" lang="ar" />
-+ <if expr="pp_ifdef('use_third_party_translations')">
-+ <output filename="generated_resources_ast.pak" type="data_package" lang="ast" />
-+ </if>
-+ <output filename="generated_resources_bg.pak" type="data_package" lang="bg" />
-+ <output filename="generated_resources_bn.pak" type="data_package" lang="bn" />
-+ <if expr="pp_ifdef('use_third_party_translations')">
-+ <output filename="generated_resources_bs.pak" type="data_package" lang="bs" />
-+ </if>
-+ <output filename="generated_resources_ca.pak" type="data_package" lang="ca" />
-+ <if expr="pp_ifdef('use_third_party_translations')">
-+ <output filename="generated_resources_ca@valencia.pak" type="data_package" lang="ca@valencia" />
-+ </if>
-+ <output filename="generated_resources_cs.pak" type="data_package" lang="cs" />
-+ <output filename="generated_resources_da.pak" type="data_package" lang="da" />
-+ <output filename="generated_resources_de.pak" type="data_package" lang="de" />
-+ <output filename="generated_resources_el.pak" type="data_package" lang="el" />
-+ <if expr="pp_ifdef('use_third_party_translations')">
-+ <output filename="generated_resources_en-AU.pak" type="data_package" lang="en-AU" />
-+ </if>
-+ <output filename="generated_resources_en-GB.pak" type="data_package" lang="en-GB" />
-+ <output filename="generated_resources_en-US.pak" type="data_package" lang="en" />
-+ <if expr="pp_ifdef('use_third_party_translations')">
-+ <output filename="generated_resources_eo.pak" type="data_package" lang="eo" />
-+ </if>
-+ <output filename="generated_resources_es.pak" type="data_package" lang="es" />
-+ <output filename="generated_resources_es-419.pak" type="data_package" lang="es-419" />
-+ <output filename="generated_resources_et.pak" type="data_package" lang="et" />
-+ <if expr="pp_ifdef('use_third_party_translations')">
-+ <output filename="generated_resources_eu.pak" type="data_package" lang="eu" />
-+ </if>
-+ <output filename="generated_resources_fa.pak" type="data_package" lang="fa" />
-+ <output filename="generated_resources_fake-bidi.pak" type="data_package" lang="fake-bidi" />
-+ <output filename="generated_resources_fi.pak" type="data_package" lang="fi" />
-+ <output filename="generated_resources_fil.pak" type="data_package" lang="fil" />
-+ <output filename="generated_resources_fr.pak" type="data_package" lang="fr" />
-+ <if expr="pp_ifdef('use_third_party_translations')">
-+ <output filename="generated_resources_gl.pak" type="data_package" lang="gl" />
-+ </if>
-+ <output filename="generated_resources_gu.pak" type="data_package" lang="gu" />
-+ <output filename="generated_resources_he.pak" type="data_package" lang="he" />
-+ <output filename="generated_resources_hi.pak" type="data_package" lang="hi" />
-+ <output filename="generated_resources_hr.pak" type="data_package" lang="hr" />
-+ <output filename="generated_resources_hu.pak" type="data_package" lang="hu" />
-+ <if expr="pp_ifdef('use_third_party_translations')">
-+ <output filename="generated_resources_hy.pak" type="data_package" lang="hy" />
-+ <output filename="generated_resources_ia.pak" type="data_package" lang="ia" />
-+ </if>
-+ <output filename="generated_resources_id.pak" type="data_package" lang="id" />
-+ <output filename="generated_resources_it.pak" type="data_package" lang="it" />
-+ <output filename="generated_resources_ja.pak" type="data_package" lang="ja" />
-+ <if expr="pp_ifdef('use_third_party_translations')">
-+ <output filename="generated_resources_ka.pak" type="data_package" lang="ka" />
-+ </if>
-+ <output filename="generated_resources_kn.pak" type="data_package" lang="kn" />
-+ <output filename="generated_resources_ko.pak" type="data_package" lang="ko" />
-+ <if expr="pp_ifdef('use_third_party_translations')">
-+ <output filename="generated_resources_ku.pak" type="data_package" lang="ku" />
-+ <output filename="generated_resources_kw.pak" type="data_package" lang="kw" />
-+ </if>
-+ <output filename="generated_resources_lt.pak" type="data_package" lang="lt" />
-+ <output filename="generated_resources_lv.pak" type="data_package" lang="lv" />
-+ <output filename="generated_resources_ml.pak" type="data_package" lang="ml" />
-+ <output filename="generated_resources_mr.pak" type="data_package" lang="mr" />
-+ <if expr="pp_ifdef('use_third_party_translations')">
-+ <output filename="generated_resources_ms.pak" type="data_package" lang="ms" />
-+ </if>
-+ <output filename="generated_resources_nl.pak" type="data_package" lang="nl" />
-+ <!-- The translation console uses 'no' for Norwegian Bokmål. It should
-+ be 'nb'. -->
-+ <output filename="generated_resources_nb.pak" type="data_package" lang="no" />
-+ <output filename="generated_resources_pl.pak" type="data_package" lang="pl" />
-+ <output filename="generated_resources_pt-BR.pak" type="data_package" lang="pt-BR" />
-+ <output filename="generated_resources_pt-PT.pak" type="data_package" lang="pt-PT" />
-+ <output filename="generated_resources_ro.pak" type="data_package" lang="ro" />
-+ <output filename="generated_resources_ru.pak" type="data_package" lang="ru" />
-+ <output filename="generated_resources_sk.pak" type="data_package" lang="sk" />
-+ <output filename="generated_resources_sl.pak" type="data_package" lang="sl" />
-+ <output filename="generated_resources_sr.pak" type="data_package" lang="sr" />
-+ <output filename="generated_resources_sv.pak" type="data_package" lang="sv" />
-+ <output filename="generated_resources_sw.pak" type="data_package" lang="sw" />
-+ <output filename="generated_resources_ta.pak" type="data_package" lang="ta" />
-+ <output filename="generated_resources_te.pak" type="data_package" lang="te" />
-+ <output filename="generated_resources_th.pak" type="data_package" lang="th" />
-+ <output filename="generated_resources_tr.pak" type="data_package" lang="tr" />
-+ <if expr="pp_ifdef('use_third_party_translations')">
-+ <output filename="generated_resources_ug.pak" type="data_package" lang="ug" />
-+ </if>
-+ <output filename="generated_resources_uk.pak" type="data_package" lang="uk" />
-+ <output filename="generated_resources_vi.pak" type="data_package" lang="vi" />
-+ <output filename="generated_resources_zh-CN.pak" type="data_package" lang="zh-CN" />
-+ <output filename="generated_resources_zh-TW.pak" type="data_package" lang="zh-TW" />
-+ </outputs>
-+ <translations>
-+ <file path="resources/generated_resources_am.xtb" lang="am" />
-+ <file path="resources/generated_resources_ar.xtb" lang="ar" />
-+ <file path="../../third_party/launchpad_translations/generated_resources_ast.xtb" lang="ast" />
-+ <file path="resources/generated_resources_bg.xtb" lang="bg" />
-+ <file path="resources/generated_resources_bn.xtb" lang="bn" />
-+ <file path="../../third_party/launchpad_translations/generated_resources_bs.xtb" lang="bs" />
-+ <file path="resources/generated_resources_ca.xtb" lang="ca" />
-+ <file path="../../third_party/launchpad_translations/generated_resources_ca-valencia.xtb" lang="ca@valencia" />
-+ <file path="resources/generated_resources_cs.xtb" lang="cs" />
-+ <file path="resources/generated_resources_da.xtb" lang="da" />
-+ <file path="resources/generated_resources_de.xtb" lang="de" />
-+ <file path="resources/generated_resources_el.xtb" lang="el" />
-+ <file path="../../third_party/launchpad_translations/generated_resources_en-AU.xtb" lang="en-AU" />
-+ <file path="resources/generated_resources_en-GB.xtb" lang="en-GB" />
-+ <file path="../../third_party/launchpad_translations/generated_resources_eo.xtb" lang="eo" />
-+ <file path="resources/generated_resources_es.xtb" lang="es" />
-+ <file path="resources/generated_resources_es-419.xtb" lang="es-419" />
-+ <file path="resources/generated_resources_et.xtb" lang="et" />
-+ <file path="../../third_party/launchpad_translations/generated_resources_eu.xtb" lang="eu" />
-+ <file path="resources/generated_resources_fa.xtb" lang="fa" />
-+ <file path="resources/generated_resources_fi.xtb" lang="fi" />
-+ <file path="resources/generated_resources_fil.xtb" lang="fil" />
-+ <file path="resources/generated_resources_fr.xtb" lang="fr" />
-+ <file path="../../third_party/launchpad_translations/generated_resources_gl.xtb" lang="gl" />
-+ <file path="resources/generated_resources_gu.xtb" lang="gu" />
-+ <file path="resources/generated_resources_hi.xtb" lang="hi" />
-+ <file path="resources/generated_resources_hr.xtb" lang="hr" />
-+ <file path="resources/generated_resources_hu.xtb" lang="hu" />
-+ <file path="../../third_party/launchpad_translations/generated_resources_hy.xtb" lang="hy" />
-+ <file path="../../third_party/launchpad_translations/generated_resources_ia.xtb" lang="ia" />
-+ <file path="resources/generated_resources_id.xtb" lang="id" />
-+ <file path="resources/generated_resources_it.xtb" lang="it" />
-+ <!-- The translation console uses 'iw' for Hebrew, but we use 'he'. -->
-+ <file path="resources/generated_resources_iw.xtb" lang="he" />
-+ <file path="resources/generated_resources_ja.xtb" lang="ja" />
-+ <file path="../../third_party/launchpad_translations/generated_resources_ka.xtb" lang="ka" />
-+ <file path="resources/generated_resources_kn.xtb" lang="kn" />
-+ <file path="resources/generated_resources_ko.xtb" lang="ko" />
-+ <file path="../../third_party/launchpad_translations/generated_resources_ku.xtb" lang="ku" />
-+ <file path="../../third_party/launchpad_translations/generated_resources_kw.xtb" lang="kw" />
-+ <file path="resources/generated_resources_lt.xtb" lang="lt" />
-+ <file path="resources/generated_resources_lv.xtb" lang="lv" />
-+ <file path="resources/generated_resources_ml.xtb" lang="ml" />
-+ <file path="resources/generated_resources_mr.xtb" lang="mr" />
-+ <file path="../../third_party/launchpad_translations/generated_resources_ms.xtb" lang="ms" />
-+ <file path="resources/generated_resources_nl.xtb" lang="nl" />
-+ <file path="resources/generated_resources_no.xtb" lang="no" />
-+ <file path="resources/generated_resources_pl.xtb" lang="pl" />
-+ <file path="resources/generated_resources_pt-BR.xtb" lang="pt-BR" />
-+ <file path="resources/generated_resources_pt-PT.xtb" lang="pt-PT" />
-+ <file path="resources/generated_resources_ro.xtb" lang="ro" />
-+ <file path="resources/generated_resources_ru.xtb" lang="ru" />
-+ <file path="resources/generated_resources_sk.xtb" lang="sk" />
-+ <file path="resources/generated_resources_sl.xtb" lang="sl" />
-+ <file path="resources/generated_resources_sr.xtb" lang="sr" />
-+ <file path="resources/generated_resources_sv.xtb" lang="sv" />
-+ <file path="resources/generated_resources_sw.xtb" lang="sw" />
-+ <file path="resources/generated_resources_ta.xtb" lang="ta" />
-+ <file path="resources/generated_resources_te.xtb" lang="te" />
-+ <file path="resources/generated_resources_th.xtb" lang="th" />
-+ <file path="resources/generated_resources_tr.xtb" lang="tr" />
-+ <file path="../../third_party/launchpad_translations/generated_resources_ug.xtb" lang="ug" />
-+ <file path="resources/generated_resources_uk.xtb" lang="uk" />
-+ <file path="resources/generated_resources_vi.xtb" lang="vi" />
-+ <file path="resources/generated_resources_zh-CN.xtb" lang="zh-CN" />
-+ <file path="resources/generated_resources_zh-TW.xtb" lang="zh-TW" />
-+ </translations>
-+ <release seq="1" allow_pseudo="false">
-+ <messages fallback_to_english="true">
-+ <!-- TODO add all of your "string table" messages here. Remember to
-+ change nontranslateable parts of the messages into placeholders (using the
-+ <ph> element). You can also use the 'grit add' tool to help you identify
-+ nontranslateable parts and create placeholders for them. -->
-+ <message name="IDS_BACKGROUND_APP_INSTALLED_BALLOON_TITLE" desc="The title of the balloon that is displayed when a background app is installed">
-+ New background app installed
-+ </message>
-+ <message name="IDS_BACKGROUND_APP_INSTALLED_BALLOON_BODY" desc="The contents of the balloon that is displayed when a background app is installed">
-+ <ph name="APP_NAME">$1<ex>Background App</ex></ph> will launch at system startup and continue to run in the background even once you've closed all other <ph name="PRODUCT_NAME">$2<ex>Google Chrome</ex></ph> windows.
-+ </message>
-+ </messages>
-+ <structures fallback_to_english="true">
-+ <!-- Make sure these stay in sync with the structures in generated_resources.grd. -->
-+ <structure name="IDD_CHROME_FRAME_FIND_DIALOG" file="cf_resources.rc" type="dialog" >
-+ </structure>
-+ <structure name="IDD_CHROME_FRAME_READY_PROMPT" file="cf_resources.rc" type="dialog" >
-+ </structure>
-+ </structures>
-+ </release>
-+</grit>
-diff --git a/tools/grit/grit/testdata/chrome_html.html b/tools/grit/grit/testdata/chrome_html.html
-new file mode 100644
-index 0000000000..7f7633c5cf
---- /dev/null
-+++ b/tools/grit/grit/testdata/chrome_html.html
-@@ -0,0 +1,6 @@
-+<include src="included_sample.html">
-+<style type="text/css">
-+#image {
-+ content: url('chrome://theme/IDR_SOME_FILE');
-+}
-+</style>
-diff --git a/tools/grit/grit/testdata/default_100_percent/a.png b/tools/grit/grit/testdata/default_100_percent/a.png
-new file mode 100644
-index 0000000000000000000000000000000000000000..5d5089038ca71172e95db9e7aae1e1fa5cebd505
-GIT binary patch
-literal 159
-zcmeAS@N?(olHy`uVBq!ia0vp^oFL4>0wld=oSO}#(mY)pLnNjq|2Y3)zGGzYPN&L+
-zMSC}CcCfp=Dtxv4%6W%G#Q=|R|L;6pCCLUWO)Z<5eoL%TkDTw=s4X!^d(Qa<2khAN
-zZPy!XToBAic1Ss}vcWiD27B3&`Zj^H6CO>7R1{ToQ;=ggdEYbV=IISvfHpFCy85}S
-Ib4q9e0O9jEh5!Hn
-
-literal 0
-HcmV?d00001
-
-diff --git a/tools/grit/grit/testdata/default_100_percent/b.png b/tools/grit/grit/testdata/default_100_percent/b.png
-new file mode 100644
-index 0000000000..6178079822
---- /dev/null
-+++ b/tools/grit/grit/testdata/default_100_percent/b.png
-@@ -0,0 +1 @@
-+b
-diff --git a/tools/grit/grit/testdata/del_footer.html b/tools/grit/grit/testdata/del_footer.html
-new file mode 100644
-index 0000000000..4e19950bfc
---- /dev/null
-+++ b/tools/grit/grit/testdata/del_footer.html
-@@ -0,0 +1,8 @@
-+<table cellspacing=0 cellpadding=2 width="100%" border=0>
-+<tr bgcolor=#EFEFEF><td><font size=-1>&nbsp;<b>Remove</b> checked results and <b>return to search</b>.</font></td>
-+<td align=right><font size=-1><a onClick='checkall(1)' href="#">Check all</a> - <a onClick='checkall(0)' href="#">Uncheck all</a>&nbsp;&nbsp;</font></td>
-+<td align=right width=1% nowrap><font size=-1>
-+<input onclick=deleting() type=submit value="Remove checked results" name=submit2>
-+</font></td></tr></table>
-+<center><br><font size=-1>[$~BOTTOMLINE~$] - &copy;2005 Google </font></center>
-+</body></html>
-diff --git a/tools/grit/grit/testdata/del_header.html b/tools/grit/grit/testdata/del_header.html
-new file mode 100644
-index 0000000000..72bc6756eb
---- /dev/null
-+++ b/tools/grit/grit/testdata/del_header.html
-@@ -0,0 +1,60 @@
-+<body bgcolor="#ffffff" topmargin="2" marginheight="2">
-+<table cellSpacing="2" cellPadding="0" width="100%" border="0">
-+<form action='[$~DELETE~$]' method="post" name="delform">
-+<input name="redir" type="hidden" value="[REDIR]">
-+<script>
-+<!--
-+function deleting() {
-+f=document.getElementsByName("del");
-+var num = 0;
-+if (f.length)
-+ for(i=0;i<f.length; i++)
-+ if(f[i].checked) num++;
-+ if (num == 1) alert("One checked result has been removed");
-+ else if (num > 1) alert(num + " checked results have been removed");
-+ else alert("No results were checked, so no results have been removed");
-+}
-+function checkall(v) {
-+ f=document.getElementsByName("del");
-+ if (f.length)
-+ for(i=0;i<f.length; i++)
-+ f[i].checked=v;
-+}
-+//-->
-+</script>
-+<tr>
-+<td vAlign="top" width="1%"><A href='[$~HOMEPAGE~$]'> <img alt="Go to Google Desktop Search" width="150" height="55" src="/logo3.gif" border="0" vspace="12"></A></td>
-+<td>&nbsp;</td>
-+<td noWrap>
-+ <table cellSpacing="0" cellPadding="0" width="100%" border="0">
-+ <tr>
-+ <td bgColor="#DD0000"><img height="1" alt="" width="1"></td>
-+ </tr>
-+ </table>
-+ <table cellSpacing="0" cellPadding="0" width="100%" border="0">
-+ <tr>
-+ <td noWrap bgColor="#efefef"><font size="+1"><b>&nbsp;Remove Specific Items</b></font></td>
-+ <td noWrap align="right" bgColor="#efefef"><font size="-1"><a href="http://desktop.google.com/remove.html">Help</a>&nbsp;&nbsp;</font></td>
-+ </tr>
-+ </table>
-+</td>
-+</tr>
-+</table>
-+<table cellSpacing="0" cellPadding="2" width="100%" border="0">
-+<tr bgColor="#EFEFEF">
-+<td><font size="-1">&nbsp;<B>Remove</B> checked results and <B>return to search</B>.</font></td>
-+<td align="right"><font size="-1"><a onClick='checkall(1)' href="#">Check all</a> - <a onClick='checkall(0)' href="#">
-+Uncheck all</a>&nbsp;&nbsp;</font></td>
-+<td align="right" width="1%" nowrap><font size="-1"><input onclick="deleting()" type="submit" value="Remove checked results" name="submit2"></font></td>
-+</tr>
-+</table>
-+<br>
-+<table cellspacing="0" cellpadding="2" width="100%" border="0">
-+<tr>
-+<td colSpan="3" bgcolor="#FFFFFF" style="border:solid; border-width:1px; border-color:#DD0000"><font size="-1">&nbsp;<b>Remove
-+checked items from Google Desktop Search. Other copies of the same items will not be
-+affected.<br>
-+&nbsp;If you view the item again, it will be added back to Google Desktop Search.</b></font></td>
-+</tr>
-+</table>
-+<br>
-\ No newline at end of file
-diff --git a/tools/grit/grit/testdata/deleted.html b/tools/grit/grit/testdata/deleted.html
-new file mode 100644
-index 0000000000..5ae5f355fa
---- /dev/null
-+++ b/tools/grit/grit/testdata/deleted.html
-@@ -0,0 +1,21 @@
-+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-+<html><head><title>Database Deleted</title>
-+<meta http-equiv="content-type" content="text/html; charset=utf-8">
-+<meta http-equiv="cache-control" content="no-cache">
-+<meta http-equiv="pragma" content="no-cache">
-+<meta http-equiv="expires" content="-1">
-+<style>
-+BODY,TD,A,P {FONT-FAMILY: arial,sans-serif}
-+.q {COLOR: #0000cc}
-+</style>
-+</head>
-+<BODY text=#000000 vLink=#551a8b aLink=#ff0000 link=#0000cc bgColor=#ffffff onload=sf()>
-+<center>
-+<TABLE cellSpacing=0 cellPadding=0 border=0>
-+<tr><td><a href="[$~HOMEPAGE~$]"><IMG border=0 height=110 alt="Google Desktop Search" src="hp_logo.gif" width=276></a>
-+</td></tr></table><BR>
-+<center>The database has been deleted. Click <a href="[$~HOMEPAGE~$]">here</a> to continue.</center>
-+</td></tr>
-+</table>
-+<br><FONT size=-1>[$~BOTTOMLINE~$]</font></p>
-+<p><FONT size=-2>&copy;2005 Google</font></p></center></body></html>
-\ No newline at end of file
-diff --git a/tools/grit/grit/testdata/depfile.grd b/tools/grit/grit/testdata/depfile.grd
-new file mode 100644
-index 0000000000..e2f7191218
---- /dev/null
-+++ b/tools/grit/grit/testdata/depfile.grd
-@@ -0,0 +1,18 @@
-+<?xml version="1.0" encoding="UTF-8"?> <!-- -*- XML -*- -->
-+<grit
-+ base_dir="."
-+ latest_public_release="0"
-+ current_release="1">
-+ <outputs>
-+ <output filename="default_100_percent.pak" lang="en" type="data_package" context="default_100_percent" />
-+ <output filename="special_100_percent.pak" lang="en" type="data_package" context="special_100_percent" />
-+ </outputs>
-+ <release seq="1">
-+ <structures fallback_to_low_resolution="true">
-+ <if expr="False">
-+ <part file="grit_part.grdp" />
-+ </if>
-+ <structure type="chrome_scaled_image" name="IDR_A" file="a.png" />
-+ </structures>
-+ </release>
-+</grit>
-diff --git a/tools/grit/grit/testdata/details.html b/tools/grit/grit/testdata/details.html
-new file mode 100644
-index 0000000000..0ab0e2a90c
---- /dev/null
-+++ b/tools/grit/grit/testdata/details.html
-@@ -0,0 +1,10 @@
-+[!]
-+title Improve Google Desktop Search by Sending Non-Personal Information
-+template
-+bottomline
-+hp_image
-+
-+<p><strong>This documentation is not yet available</strong></p>
-+<center><br>
-+<font size=-1>[$~BOTTOMLINE~$] - &copy;2005 Google </font>
-+</center>
-diff --git a/tools/grit/grit/testdata/duplicate-name-input.xml b/tools/grit/grit/testdata/duplicate-name-input.xml
-new file mode 100644
-index 0000000000..cc4d1d65c5
---- /dev/null
-+++ b/tools/grit/grit/testdata/duplicate-name-input.xml
-@@ -0,0 +1,26 @@
-+<?xml version="1.0" encoding="UTF-8"?>
-+<grit base_dir="." latest_public_release="2" current_release="3" source_lang_id="en-US">
-+ <release seq="3">
-+ <messages>
-+ <message name="IDS_GREETING" desc="Printed to greet the currently logged in user">
-+ Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, how are you doing today?
-+ </message>
-+ </messages>
-+ <structures>
-+ <!-- Duplicate name here -->
-+ <structure type="version" name="IDS_GREETING" file="rc_files/bla.rc" />
-+ </structures>
-+ </release>
-+ <translations>
-+ <file path="figs_nl_translations.xml" />
-+ <file path="cjk_translations.xml" />
-+ </translations>
-+ <outputs>
-+ <output filename="resource.h" type="rc_header" />
-+ <output filename="resource_en.rc" type="rc_all" lang="en-US" />
-+ <output filename="resource_fr.rc" type="rc_all" lang="fr-FR" />
-+ <output filename="resource_it.rc" type="rc_translateable" lang="it-IT" />
-+ <output filename="resource_zh_cn.rc" type="rc_translateable" lang="zh-CN" />
-+ <output filename="nontranslateable.rc" type="rc_nontranslateable" />
-+ </outputs>
-+</grit>
-diff --git a/tools/grit/grit/testdata/email_result.html b/tools/grit/grit/testdata/email_result.html
-new file mode 100644
-index 0000000000..8bb04b988c
---- /dev/null
-+++ b/tools/grit/grit/testdata/email_result.html
-@@ -0,0 +1,34 @@
-+[HEADER]
-+[CHROME]
-+<table border=0 cellpadding=2 cellspacing=2 width='100%'>
-+<tr><td><font size=-1>[CONV]
-+<a href='[$~REPLY_URL~$]'>Reply</a> | <a href='[$~REPLYALL_URL~$]'>Reply&nbsp;to&nbsp;All</a>[$~FORWARD_URL~$] | <a href='mailto:'>Compose</a>[$~OUTLOOKVIEW~$]
-+</font></td></tr>
-+</table>
-+<blockquote id=gg_1>
-+<table bgcolor=#f0f8ff width=80% cellpadding=5><tr><td>
-+<img style="vertical-align:middle;" height=16 src='/email.gif' width=16> &nbsp; <b>[SUBJECT]</b>
-+<p><font size=-1>[FROM-DISP]
-+[TO-DISP]
-+[CC-DISP]
-+[BCC-DISP]
-+[REPLYTO-DISP]
-+[DATE-DISP]
-+[VIEW-DISP]
-+[$~ATTACH~$]
-+</font></td></tr></table>
-+<p class=g><span style="width:500;"><font size=-1><label>[MESSAGE]</label></span></p>
-+</font>
-+</blockquote>
-+<table border=0 cellpadding=2 cellspacing=2 width='100%'>
-+<tr><td><font size=-1>[CONV]
-+<a href='[$~REPLY_URL~$]'>Reply</a> | <a href='[$~REPLYALL_URL~$]'>Reply&nbsp;to&nbsp;All</a>[$~FORWARD_URL~$] | <a href='mailto:'>Compose</a>[$~OUTLOOKVIEW~$]
-+</font></td></tr></table>
-+<style>
-+.hl { color:black; background-color:#ffff88}
-+</style>
-+<script>
-+[$~HIGHLIGHT_SCRIPT~$]
-+[ONLOAD]
-+</script>
-+[FOOTER]
-\ No newline at end of file
-diff --git a/tools/grit/grit/testdata/email_thread.html b/tools/grit/grit/testdata/email_thread.html
-new file mode 100644
-index 0000000000..3c7279b841
---- /dev/null
-+++ b/tools/grit/grit/testdata/email_thread.html
-@@ -0,0 +1,10 @@
-+[HEADER]
-+[CHROME]
-+<blockquote [MAXWIDTH]>
-+<b><img src=email.gif style="vertical-align:middle;" width=16 height=16> &nbsp; [SUBJECT]</b><br><br>
-+<TABLE cellSpacing=0 cellPadding=3 border=0>
-+[CONTENTS]
-+</table>
-+</blockquote>
-+[NEXT_PREV]
-+[FOOTER]
-\ No newline at end of file
-diff --git a/tools/grit/grit/testdata/error.html b/tools/grit/grit/testdata/error.html
-new file mode 100644
-index 0000000000..66875a234c
---- /dev/null
-+++ b/tools/grit/grit/testdata/error.html
-@@ -0,0 +1,8 @@
-+[HEADER]
-+[CHROME]
-+<br>
-+<blockquote>
-+[ERROR]<br><br>
-+If you think this is an error, please <a href="http://desktop.google.com/feedback.html?version=[VERSION]">contact us</a>.
-+</blockquote>
-+[FOOTER]
-\ No newline at end of file
-diff --git a/tools/grit/grit/testdata/explicit_web.html b/tools/grit/grit/testdata/explicit_web.html
-new file mode 100644
-index 0000000000..1424adc617
---- /dev/null
-+++ b/tools/grit/grit/testdata/explicit_web.html
-@@ -0,0 +1,11 @@
-+[HEADER]
-+<style>
-+.image {BORDER: #0000cc 1px solid;}
-+.imageh {BORDER: #0000cc 1px solid;}
-+</style>
-+[WEB_TOP_CHROME]
-+[$~STATUS~$]
-+[$~MESSAGE~$]
-+[WEB_FILES]
-+<br>[NEXT_PREV]
-+[FOOTER]
-\ No newline at end of file
-diff --git a/tools/grit/grit/testdata/footer.html b/tools/grit/grit/testdata/footer.html
-new file mode 100644
-index 0000000000..3372d6afac
---- /dev/null
-+++ b/tools/grit/grit/testdata/footer.html
-@@ -0,0 +1,14 @@
-+<center><br clear=all><br>
-+<table cellspacing=0 cellpadding=0 width="100%" border=0><tr bgcolor=#3399CC><td align=middle height=1><img height=1 width=1></td></tr></table>
-+<table cellspacing=0 cellpadding=0 width="100%" bgcolor=#e8f4f7 border=0>
-+<tr bgcolor=#e8f4f7>
-+<td><br>
-+<table cellpadding=1 align=center border=0 cellspacing=0 bgcolor=#e8f4f7>
-+<form action='[$~SEARCHURL~$]' method=get>
-+<tr><td noWrap>[$~BOTTOM~$]</td></tr></form>
-+</table><br>
-+</td></tr></table>
-+<table cellspacing=0 cellpadding=0 width="100%" border=0><tr bgcolor=#3399CC><td align=middle height=1><img height=1 width=1></td></tr></table><br>
-+<font size=-1>[$~BOTTOMLINE~$] - &copy;2005 Google </font></center>
-+[SCRIPT]
-+</body></html>
-diff --git a/tools/grit/grit/testdata/generated_resources_fr.xtb b/tools/grit/grit/testdata/generated_resources_fr.xtb
-new file mode 100644
-index 0000000000..373c40feea
---- /dev/null
-+++ b/tools/grit/grit/testdata/generated_resources_fr.xtb
-@@ -0,0 +1,3079 @@
-+<?xml version="1.0" ?>
-+<!DOCTYPE translationbundle>
-+<translationbundle lang="fr">
-+<translation id="1525924600121678168">Salut!</translation>
-+<translation id="5335090254790956485">Salut <ph name="USERNAME"/></translation>
-+<translation id="6779164083355903755">Supprime&amp;r</translation>
-+<translation id="6879617193011158416">Activer la barre de favoris</translation>
-+<translation id="8130276680150879341">Déconnexion du réseau privé</translation>
-+<translation id="1058418043520174283"><ph name="INDEX"/> sur <ph name="COUNT"/></translation>
-+<translation id="4480627574828695486">Déconnecter ce compte...</translation>
-+<translation id="7040807039050164757">&amp;Vérifier l'orthographe dans ce champ</translation>
-+<translation id="778579833039460630">Aucune donnée reçue.</translation>
-+<translation id="1852799913675865625">Une erreur s'est produite lors de la tentative de lecture du fichier : <ph name="ERROR_TEXT"/>.</translation>
-+<translation id="3828924085048779000">Le mot de passe multiterme est obligatoire.</translation>
-+<translation id="8265562484034134517">Importer les données d'un autre navigateur...</translation>
-+<translation id="2709516037105925701">Saisie automatique</translation>
-+<translation id="4857138207355690859">API P2P</translation>
-+<translation id="250599269244456932">Exécuter automatiquement (recommandé)</translation>
-+<translation id="3581034179710640788">Le certificat de sécurité du site a expiré !</translation>
-+<translation id="2825758591930162672">Clé publique de l'objet</translation>
-+<translation id="8275038454117074363">Importer</translation>
-+<translation id="8418445294933751433">Afficher dan&amp;s un onglet</translation>
-+<translation id="6985276906761169321">ID :</translation>
-+<translation id="859285277496340001">Le certificat n'indique aucun mécanisme permettant de vérifier s'il a été révoqué.</translation>
-+<translation id="2010799328026760191">Touches de modification...</translation>
-+<translation id="3300394989536077382">Signé par :</translation>
-+<translation id="654233263479157500">Utiliser un service Web pour résoudre les erreurs de navigation</translation>
-+<translation id="4940047036413029306">Guillemet</translation>
-+<translation id="1526811905352917883">Une nouvelle tentative de connexion avec SSL 3.0 a dû être effectuée. Cette opération indique généralement que le serveur utilise un logiciel très ancien et qu'il est susceptible de présenter d'autres problèmes de sécurité.</translation>
-+<translation id="1497897566809397301">Autoriser le stockage des données locales (recommandé)</translation>
-+<translation id="3275778913554317645">Ouvrir dans une fenêtre</translation>
-+<translation id="4553117311324416101">Google pense qu'un logiciel malveillant pourrait être installé sur votre ordinateur si vous continuez. Nous vous conseillons de ne pas continuer, même si vous avez déjà consulté ce site auparavant ou si vous avez confiance en celui-ci. Il se peut qu'il ait été piraté récemment. Réessayez demain ou utilisez un autre site.</translation>
-+<translation id="509988127256758334">&amp;Rechercher :</translation>
-+<translation id="1420684932347524586">Échec de génération de clé privée RSA aléatoire</translation>
-+<translation id="2501173422421700905">Certificat en attente</translation>
-+<translation id="2313634973119803790">Technologie réseau :</translation>
-+<translation id="2382901536325590843">Le certificat du serveur ne figure pas dans le DNS.</translation>
-+<translation id="2833791489321462313">Demander le mot de passe au retour de veille</translation>
-+<translation id="3850258314292525915">Désactiver la synchronisation</translation>
-+<translation id="2721561274224027017">Base de données indexée</translation>
-+<translation id="8208216423136871611">Ne pas enregistrer</translation>
-+<translation id="684587995079587263"><ph name="PRODUCT_NAME"/> synchronise vos données avec votre compte Google en toute sécurité. Synchronisez toutes vos données ou personnalisez les types de données synchronisées et les options de chiffrement.</translation>
-+<translation id="4405141258442788789">Le délai imparti à l'opération est dépassé.</translation>
-+<translation id="5048179823246820836">Nordique</translation>
-+<translation id="1763046204212875858">Créer des raccourcis vers des applications</translation>
-+<translation id="2105006017282194539">Pas encore chargé</translation>
-+<translation id="524759338601046922">Confirmer le nouveau code PIN :</translation>
-+<translation id="688547603556380205">L2TP/IPSec + Certificat utilisateur</translation>
-+<translation id="777702478322588152">Préfecture</translation>
-+<translation id="6562437808764959486">Extraction de l'image de récupération...</translation>
-+<translation id="561349411957324076">Terminé</translation>
-+<translation id="4764776831041365478">Il se peut que la page Web à l'adresse <ph name="URL"/> soit temporairement inaccessible ou qu'elle ait été déplacée de façon permanente à une autre adresse Web.</translation>
-+<translation id="6156863943908443225">Cache des scripts</translation>
-+<translation id="4610656722473172270">Barre d'outils Google</translation>
-+<translation id="151501797353681931">Importés depuis Safari</translation>
-+<translation id="6706684875496318067">Le plug-in <ph name="PLUGIN_NAME"/> n'est pas autorisé.</translation>
-+<translation id="586567932979200359">Vous exécutez <ph name="PRODUCT_NAME"/> à partir de son image disque. Si vous l'installez sur votre ordinateur, vous pourrez l'utiliser sans image disque et bénéficierez de mises à jour automatiques.</translation>
-+<translation id="3775432569830822555">Certificat du serveur SSL</translation>
-+<translation id="1829192082282182671">Z&amp;oom arrière</translation>
-+<translation id="6102827823267795198">Indique si la suggestion du moteur de recherche doit être entrée immédiatement via la saisie semi-automatique lorsque la fonctionnalité Recherche instantanée est activée.</translation>
-+<translation id="1467071896935429871">Mise à jour du système : <ph name="PERCENT"/> % téléchargés</translation>
-+<translation id="7881267037441701396">Les informations d'identification associées au partage de vos imprimantes via <ph name="CLOUD_PRINT_NAME"/> sont arrivées à expiration. Cliquez ici pour saisir à nouveau votre nom d'utilisateur et votre mot de passe.</translation>
-+<translation id="816055135686411707">Erreur de définition du paramètre de confiance du certificat</translation>
-+<translation id="4714531393479055912"><ph name="PRODUCT_NAME"/> peut maintenant synchroniser vos mots de passe.</translation>
-+<translation id="5704565838965461712">Sélectionnez le certificat à présenter pour l'identification :</translation>
-+<translation id="2025632980034333559"><ph name="APP_NAME"/> a planté. Cliquez sur cette info-bulle pour actualiser l'extension.</translation>
-+<translation id="4059593000330943833">Compatibilité expérimentale avec des méthodes Wi-Fi Extensible Authentication Protocol supplémentaires, telles que EAP-TLS et LEAP.</translation>
-+<translation id="6322279351188361895">Échec de lecture de la clé privée</translation>
-+<translation id="3781072658385678636">Les plug-ins suivants ont été bloqués sur cette page :</translation>
-+<translation id="4428782877951507641">Configuration de la synchronisation</translation>
-+<translation id="3648460724479383440">Case d'option cochée</translation>
-+<translation id="4654488276758583406">Très petite</translation>
-+<translation id="6647228709620733774">URL de révocation de l'autorité de certification Netscape</translation>
-+<translation id="546411240573627095">Style de pavé numérique</translation>
-+<translation id="7663002797281767775">Active les feuilles de style CSS 3D et la composition graphique haute performance des pages Web via le processeur graphique.</translation>
-+<translation id="2972581237482394796">&amp;Rétablir</translation>
-+<translation id="5895138241574237353">Redémarrer</translation>
-+<translation id="1858072074757584559">La connexion n'est pas compressée.</translation>
-+<translation id="528468243742722775">Fin</translation>
-+<translation id="1723824996674794290">&amp;Nouvelle fenêtre</translation>
-+<translation id="1313405956111467313">Configuration automatique du proxy</translation>
-+<translation id="1589055389569595240">Afficher l'orthographe et la grammaire</translation>
-+<translation id="4364779374839574930">Aucune imprimante n'a été trouvée. Veuillez en installer une.</translation>
-+<translation id="7017587484910029005">Saisissez les caractères visibles dans l'image ci-dessous.</translation>
-+<translation id="9013589315497579992">Certificat d'authentification de client SSL incorrect</translation>
-+<translation id="8595062045771121608">Le certificat du serveur ou un certificat AC intermédiaire présenté au navigateur a été signé avec un algorithme de signature faible tel que RSA-MD2. D'après des études récentes menées par des informaticiens, les algorithmes de signature seraient plus faibles qu'on ne le pensait jusqu'alors. Aujourd'hui, ils sont très rarement utilisés par les sites Web jugés dignes de confiance. Ce certificat a peut-être été contrefait. Nous vous déconseillons vivement de continuer.</translation>
-+<translation id="8666632926482119393">Rechercher le précédent</translation>
-+<translation id="7567293639574541773">I&amp;nspecter l'élément</translation>
-+<translation id="8392896330146417149">État d'itinérance :</translation>
-+<translation id="6813971406343552491">&amp;Non</translation>
-+<translation id="36224234498066874">Effacer les données de navigation...</translation>
-+<translation id="3384773155383850738">Nombre maximal de suggestions</translation>
-+<translation id="8331498498435985864">L'accessibilité est désactivée.</translation>
-+<translation id="8530339740589765688">Sélectionner par domaine</translation>
-+<translation id="8677212948402625567">Tout réduire...</translation>
-+<translation id="7600965453749440009">Ne jamais traduire les pages rédigées en <ph name="LANGUAGE"/> </translation>
-+<translation id="3208703785962634733">Non confirmé</translation>
-+<translation id="6523841952727744497">Avant de vous connecter, démarrez une session en tant qu'invité afin d'activer le réseau <ph name="NETWORK_ID"/>.</translation>
-+<translation id="7450044767321666434">La gravure de l'image est terminée.</translation>
-+<translation id="2653266418988778031">Si vous supprimez le certificat d'une autorité de certification, votre navigateur ne fera plus confiance aux certificats émis par cette autorité de certification.</translation>
-+<translation id="298068999958468740">Synchronisez toutes les données de cet ordinateur ou sélectionnez celles que vous souhaitez synchroniser.</translation>
-+<translation id="5341849548509163798"><ph name="NUMBER_MANY"/> hours ago</translation>
-+<translation id="4422428420715047158">Domaine :</translation>
-+<translation id="3602290021589620013">Aperçu</translation>
-+<translation id="7516602544578411747">Associe chaque fenêtre du navigateur à un profil et ajoute une option de sélection des profils en haut à droite. Chaque profil possède ses propres favoris, extensions, applications, etc.</translation>
-+<translation id="7082055294850503883">Ignorer le verrouillage des majuscules et saisir des minuscules par défaut</translation>
-+<translation id="1800124151523561876">Aucune parole détectée</translation>
-+<translation id="7814266509351532385">Changer de moteur de recherche par défaut</translation>
-+<translation id="5376169624176189338">Cliquer pour revenir en arrière, maintenir pour voir l'historique</translation>
-+<translation id="6310545596129886942"><ph name="NUMBER_FEW"/> secondes restantes</translation>
-+<translation id="9181716872983600413">Unicode</translation>
-+<translation id="1383861834909034572">Ouverture à la fin du téléchargement</translation>
-+<translation id="5727728807527375859">Les extensions, les applications et les thèmes peuvent endommager votre ordinateur. Voulez-vous vraiment continuer ?</translation>
-+<translation id="3857272004253733895">Schéma du pinyin double</translation>
-+<translation id="1636842079139032947">Déconnecter ce compte...</translation>
-+<translation id="6721972322305477112">&amp;Fichier</translation>
-+<translation id="1076818208934827215">Microsoft Internet Explorer</translation>
-+<translation id="9056810968620647706">Aucune correspondance trouvée</translation>
-+<translation id="1901494098092085382">État de votre commentaire</translation>
-+<translation id="2861301611394761800">Mise à jour terminée. Veuillez redémarrer le système.</translation>
-+<translation id="2231238007119540260">Lorsque vous supprimez un certificat de serveur, vous rétablissez les contrôles de sécurité habituels du serveur et un certificat valide lui est demandé.</translation>
-+<translation id="5463582782056205887">Essayez d'ajouter
-+ <ph name="PRODUCT_NAME"/>
-+ aux programmes autorisés dans les paramètres de votre pare-feu ou de votre antivirus. S'il
-+ est déjà autorisé, tentez de le supprimer de la liste et de l'ajouter à nouveau à
-+ la liste des programmes autorisés.</translation>
-+<translation id="7624154074265342755">Réseaux sans fil</translation>
-+<translation id="3315158641124845231">Masquer <ph name="PRODUCT_NAME"/></translation>
-+<translation id="3496213124478423963">Zoom arrière</translation>
-+<translation id="2296019197782308739">Méthode EAP :</translation>
-+<translation id="42981349822642051">Développer</translation>
-+<translation id="4013794286379809233">Veuillez vous connecter</translation>
-+<translation id="7693221960936265065">de n'importe quand</translation>
-+<translation id="1763138995382273070">Désactiver la validation des formulaires interactifs HTML5</translation>
-+<translation id="4920887663447894854">Le suivi de votre position géographique sur cette page a été bloqué pour les sites suivants :</translation>
-+<translation id="7690346658388844119">La gravure de l'image a été interrompue.</translation>
-+<translation id="8133676275609324831">&amp;Afficher dans le dossier</translation>
-+<translation id="645705751491738698">Continuer à bloquer JavaScript</translation>
-+<translation id="4780321648949301421">Enregistrer la page sous...</translation>
-+<translation id="9154072353677278078">Le serveur <ph name="DOMAIN"/> à l'adresse <ph name="REALM"/> requiert un nom d'utilisateur et un mot de passe.</translation>
-+<translation id="2551191967044410069">Exceptions de géolocalisation</translation>
-+<translation id="4092066334306401966">13px</translation>
-+<translation id="8178665534778830238">Contenu :</translation>
-+<translation id="153384433402665971">Le plug-in <ph name="PLUGIN_NAME"/> a été bloqué, car il n'est plus à jour.</translation>
-+<translation id="2610260699262139870">Taille ré&amp;elle</translation>
-+<translation id="4535734014498033861">Échec de la connexion au serveur proxy.</translation>
-+<translation id="558170650521898289">Vérification de pilote matériel Microsoft Windows</translation>
-+<translation id="98515147261107953">Paysage</translation>
-+<translation id="8974161578568356045">Détecter automatiquement</translation>
-+<translation id="1818606096021558659">Page</translation>
-+<translation id="5388588172257446328">Nom d'utilisateur :</translation>
-+<translation id="1657406563541664238">Nous aider à améliorer <ph name="PRODUCT_NAME"/> en envoyant automatiquement les statistiques d'utilisation et les rapports d'erreur à Google</translation>
-+<translation id="7982789257301363584">Réseau</translation>
-+<translation id="8528962588711550376">Connexion en cours</translation>
-+<translation id="2336228925368920074">Ajouter tous les onglets aux favoris...</translation>
-+<translation id="4985312428111449076">Onglets ou fenêtres</translation>
-+<translation id="7481475534986701730">Sites récemment consultés</translation>
-+<translation id="4260722247480053581">Ouvrir dans une fenêtre de navigation privée</translation>
-+<translation id="8503758797520866434">Préférences de saisie automatique...</translation>
-+<translation id="2757031529886297178">Compteur d'images par seconde</translation>
-+<translation id="6657585470893396449">Mot de passe</translation>
-+<translation id="7881483672146086348">Afficher le compte</translation>
-+<translation id="1776883657531386793"><ph name="OID"/> : <ph name="INFO"/></translation>
-+<translation id="1510030919967934016">Le suivi de votre position géographique a été bloqué pour cette page.</translation>
-+<translation id="4640525840053037973">Connexion à l'aide de votre compte Google</translation>
-+<translation id="5255315797444241226">Le mot de passe multiterme entré est incorrect.</translation>
-+<translation id="6242054993434749861">télécopie : #<ph name="FAX"/></translation>
-+<translation id="762917759028004464">Le navigateur par défaut est actuellement <ph name="BROWSER_NAME"/>.</translation>
-+<translation id="9213479837033539041"><ph name="NUMBER_MANY"/> secondes restantes</translation>
-+<translation id="300544934591011246">Mot de passe précédent</translation>
-+<translation id="5078796286268621944">Code PIN incorrect</translation>
-+<translation id="989988560359834682">Modifier l'adresse</translation>
-+<translation id="8487678622945914333">Zoom avant</translation>
-+<translation id="2972557485845626008">Micrologiciel</translation>
-+<translation id="735327918767574393">Une erreur s'est produite lors de l'affichage de cette page Web. Pour continuer, actualisez cette page ou ouvrez-en une autre.</translation>
-+<translation id="8028060951694135607">Récupération de clé Microsoft</translation>
-+<translation id="9187657844611842955">recto verso</translation>
-+<translation id="6391832066170725637">Fichier ou répertoire introuvable</translation>
-+<translation id="4694445829210540512">Aucun forfait de données <ph name="NETWORK"/> actif</translation>
-+<translation id="5494920125229734069">Tout sélectionner</translation>
-+<translation id="2857834222104759979">Le fichier manifeste est incorrect.</translation>
-+<translation id="7931071620596053769">Les pages suivantes ne répondent plus. Vous pouvez attendre qu'elles soient de nouveau accessibles ou les supprimer.</translation>
-+<translation id="1209866192426315618"><ph name="NUMBER_DEFAULT"/> minutes restantes</translation>
-+<translation id="7938958445268990899">Le certificat du serveur n'est pas encore valide.</translation>
-+<translation id="4569998400745857585">Menu contenant des extensions masquées</translation>
-+<translation id="4081383687659939437">Enregistrer les infos</translation>
-+<translation id="5786805320574273267">Configuration de l'accès à distance à cet ordinateur.</translation>
-+<translation id="1801827354178857021">Point</translation>
-+<translation id="2179052183774520942">Ajouter un moteur de recherche</translation>
-+<translation id="5498951625591520696">Impossible d'atteindre le serveur.</translation>
-+<translation id="2956948609882871496">Importer mes favoris...</translation>
-+<translation id="1621207256975573490">Enregistrer le &amp;cadre sous...</translation>
-+<translation id="4681260323810445443">Vous n'êtes pas autorisé à accéder à la page Web <ph name="URL"/>. Votre connexion peut être requise.</translation>
-+<translation id="2176444992480806665">Envoyer la capture d'écran du dernier onglet actif</translation>
-+<translation id="1165039591588034296">Erreur</translation>
-+<translation id="2064942105849061141">Utiliser le thème GTK+</translation>
-+<translation id="2278562042389100163">Ouvrir une fenêtre du navigateur</translation>
-+<translation id="5246282308050205996"><ph name="APP_NAME"/> a planté. Cliquez sur cette info-bulle pour redémarrer l'application.</translation>
-+<translation id="9218430445555521422">Définir comme navigateur par défaut</translation>
-+<translation id="5027550639139316293">Certificat de courrier électronique</translation>
-+<translation id="938582441709398163">Clavier en superposition</translation>
-+<translation id="427208986916971462">La connexion est compressée avec <ph name="COMPRESSION"/>.</translation>
-+<translation id="4589279373639964403">Exporter mes favoris...</translation>
-+<translation id="8876215549894133151">Format :</translation>
-+<translation id="5234764350956374838">Ignorer</translation>
-+<translation id="40027638859996362">Déplacer un mot</translation>
-+<translation id="5463275305984126951">Index de <ph name="LOCATION"/></translation>
-+<translation id="5154917547274118687">Mémoire</translation>
-+<translation id="1493492096534259649">Impossible d'utiliser cette langue pour corriger l'orthographe.</translation>
-+<translation id="6628463337424475685">Recherche <ph name="ENGINE"/></translation>
-+<translation id="2502105862509471425">Ajouter une autre carte de paiement...</translation>
-+<translation id="4037618776454394829">Envoyer la dernière capture d'écran enregistrée</translation>
-+<translation id="8987670145726065238">Ce fichier contient du code malveillant. Voulez-vous vraiment continuer ?</translation>
-+<translation id="182729337634291014">Erreur de synchronisation...</translation>
-+<translation id="4465830120256509958">Clavier brésilien</translation>
-+<translation id="2459861677908225199">Utiliser TLS 1.0</translation>
-+<translation id="4792711294155034829">&amp;Signaler un problème...</translation>
-+<translation id="5819484510464120153">Créer des raccourci&amp;s vers des applications...</translation>
-+<translation id="6845180713465955339">Le certificat &quot;<ph name="CERTIFICATE_NAME"/>&quot; a été émis par :</translation>
-+<translation id="7531238562312180404"><ph name="PRODUCT_NAME"/> ne contrôlant pas la façon dont les extensions gèrent vos données personnelles, toutes les extensions sont désactivées dans les fenêtres de navigation privée. Vous pouvez les réactiver individuellement dans le <ph name="BEGIN_LINK"/>gestionnaire des extensions<ph name="END_LINK"/>.</translation>
-+<translation id="5667293444945855280">Logiciels malveillants</translation>
-+<translation id="3435845180011337502">Mise en page ou mise en forme de la page</translation>
-+<translation id="3838186299160040975">Acheter davantage...</translation>
-+<translation id="6831043979455480757">Traduire</translation>
-+<translation id="3587482841069643663">Tout</translation>
-+<translation id="6698381487523150993">Créé :</translation>
-+<translation id="4684748086689879921">Annuler l'importation</translation>
-+<translation id="9130015405878219958">Le mode indiqué est incorrect.</translation>
-+<translation id="6615807189585243369"><ph name="BURNT_AMOUNT"/> copié(s) sur <ph name="TOTAL_SIZE"/></translation>
-+<translation id="8518425453349204360">L'accès à distance à cet ordinateur est activé pour <ph name="USER_EMAIL_ADDRESS"/>.</translation>
-+<translation id="4950138595962845479">Options...</translation>
-+<translation id="4653235815000740718">Un problème est survenu lors de la création du support de récupération du système d'exploitation. Le périphérique de stockage utilisé est introuvable.</translation>
-+<translation id="5516565854418269276">Toujours &amp;afficher la barre de favoris</translation>
-+<translation id="6426222199977479699">Erreur SSL</translation>
-+<translation id="7104784605502674932">Confirmer les préférences de synchronisation</translation>
-+<translation id="1788636309517085411">Utiliser les valeurs par défaut</translation>
-+<translation id="1661867754829461514">Code secret manquant</translation>
-+<translation id="7406714851119047430">L'accès à distance à cet ordinateur est désactivé.</translation>
-+<translation id="8589311641140863898">API des extensions expérimentales</translation>
-+<translation id="2804922931795102237">Inclure les informations système</translation>
-+<translation id="869891660844655955">Date d'expiration</translation>
-+<translation id="2178614541317717477">Autorité de certification compromise</translation>
-+<translation id="4449935293120761385">À propos de la saisie automatique</translation>
-+<translation id="4194570336751258953">Activer la fonction &quot;Taper pour cliquer&quot;</translation>
-+<translation id="6066742401428748382">Accès à la page Web refusé</translation>
-+<translation id="5111692334209731439">&amp;Gestionnaire de favoris</translation>
-+<translation id="8295070100601117548">Erreur serveur</translation>
-+<translation id="5661272705528507004">Cette carte SIM est désactivée et ne peut être utilisée. Veuillez demander à votre fournisseur de services de la remplacer.</translation>
-+<translation id="443008484043213881">Outils</translation>
-+<translation id="2529657954821696995">Clavier néerlandais</translation>
-+<translation id="1128128132059598906">EAP-TTLS</translation>
-+<translation id="6585234750898046415">Choisissez une image à associer à votre compte. Celle-ci s'affichera sur l'écran de connexion.</translation>
-+<translation id="7957054228628133943">Configurer le blocage des fenêtres pop-up...</translation>
-+<translation id="179767530217573436">des 4 dernières semaines</translation>
-+<translation id="2279770628980885996">Une situation inattendue s'est produite tandis que le serveur tentait de traiter la demande.</translation>
-+<translation id="8079135502601738761">Impossible d'afficher certaines parties de ce document PDF. Souhaitez-vous l'ouvrir dans Adobe Reader ?</translation>
-+<translation id="9123413579398459698">Proxy FTP</translation>
-+<translation id="3887875461425980041">Si vous utilisez la version PPAPI de Flash, exécutez-la dans chaque processus de moteur du rendu plutôt que dans un processus de plug-in dédié.</translation>
-+<translation id="8534801226027872331">Cela signifie que le certificat présenté à votre navigateur contient des erreurs et qu'il ne peut pas être compris. Il est possible que les informations sur l'identité du certificat ou que d'autres informations du certificat relatives à la sécurité de la connexion soient incompréhensibles. Ne poursuivez pas.</translation>
-+<translation id="3608527593787258723">Activer l'onglet 1</translation>
-+<translation id="4497369307931735818">Communication à distance</translation>
-+<translation id="3855676282923585394">Importer les favoris et les paramètres...</translation>
-+<translation id="1116694919640316211">À propos</translation>
-+<translation id="4422347585044846479">Modifier le favori de cette page</translation>
-+<translation id="1684638090259711957">Ajouter un format d'exception</translation>
-+<translation id="4925481733100738363">Configurer l'accès à distance...</translation>
-+<translation id="1880905663253319515">Supprimer le certificat &quot;<ph name="CERTIFICATE_NAME"/>&quot; ?</translation>
-+<translation id="8546306075665861288">Cache des images</translation>
-+<translation id="5904093760909470684">Configuration du proxy</translation>
-+<translation id="2874027208508018603">En l'absence de connexion Wi-Fi, Google Chrome utilise les données 3G.</translation>
-+<translation id="4558734465070698159">Appuyez sur <ph name="HOTKEY_NAME"/> pour sélectionner le mode de saisie précédent.</translation>
-+<translation id="3348643303702027858">La création du support de récupération du système d'exploitation a été annulée.</translation>
-+<translation id="3391060940042023865">Le plug-in suivant est bloqué : <ph name="PLUGIN_NAME"/></translation>
-+<translation id="4237016987259239829">Erreur de connexion réseau</translation>
-+<translation id="9050666287014529139">Mot de passe multiterme</translation>
-+<translation id="5197255632782567636">Internet</translation>
-+<translation id="4755860829306298968">Configurer les paramètres de blocage des plug-ins...</translation>
-+<translation id="8879284080359814990">Afficher dan&amp;s un onglet</translation>
-+<translation id="2786847742169026523">Synchroniser vos mots de passe</translation>
-+<translation id="41293960377217290">Le serveur proxy agit comme un intermédiaire entre votre ordinateur et les autres serveurs. Votre configuration système utilise actuellement un proxy, mais
-+ <ph name="PRODUCT_NAME"/>
-+ ne parvient pas à s'y connecter.</translation>
-+<translation id="4520722934040288962">Sélectionner par type d'application</translation>
-+<translation id="3873139305050062481">Procéder à l'i&amp;nspection de l'élément</translation>
-+<translation id="7445762425076701745">Impossible de valider entièrement l'identité du serveur auquel vous êtes connecté. Le nom utilisé pour cette connexion n'est valide que sur votre réseau et aucune autorité de certification externe ne peut en vérifier la propriété. Certaines autorités de certification délivrent tout de même des certificats pour ces types de nom, par conséquent nous ne sommes pas en mesure de vérifier que vous êtes connecté au site voulu et non à un site malveillant.</translation>
-+<translation id="1556537182262721003">Impossible de déplacer le répertoire d'extensions dans le profil.</translation>
-+<translation id="5866557323934807206">Supprimer ces paramètres pour les prochaines visites</translation>
-+<translation id="6506104645588011859">L'accessibilité est activée.</translation>
-+<translation id="5355351445385646029">Appuyer sur la touche Espace pour sélectionner la suggestion</translation>
-+<translation id="6978622699095559061">Vos favoris</translation>
-+<translation id="6370820475163108109"><ph name="ORGANIZATION_NAME"/> (<ph name="DOMAIN_NAME"/>)</translation>
-+<translation id="5453029940327926427">Fermer les onglets</translation>
-+<translation id="406070391919917862">Applications en arrière-plan</translation>
-+<translation id="8820817407110198400">Favoris</translation>
-+<translation id="3214837514330816581">Supprimer les données synchronisées de Google Dashboard</translation>
-+<translation id="2580170710466019930">Veuillez patienter pendant que <ph name="PRODUCT_NAME"/> installe les dernières mises à jour système.</translation>
-+<translation id="7428061718435085649">Utilisez les touches Maj gauche et droite pour sélectionner les 2e et 3e propositions</translation>
-+<translation id="1070066693520972135">WEP</translation>
-+<translation id="206683469794463668">Mode Zhuyin complet. La sélection automatique de la suggestion et les options associées sont désactivées ou ignorées.</translation>
-+<translation id="5191625995327478163">&amp;Paramètres linguistiques...</translation>
-+<translation id="8833054222610756741">CRX-less Web Apps</translation>
-+<translation id="4031729365043810780">Connexion au réseau</translation>
-+<translation id="3332115613788466465">Reliure bord long</translation>
-+<translation id="1985136186573666099"><ph name="PRODUCT_NAME"/> utilise les paramètres proxy du système pour se connecter au réseau.</translation>
-+<translation id="6508261954199872201">Application : <ph name="APP_NAME"/></translation>
-+<translation id="5585645215698205895">&amp;Descendre</translation>
-+<translation id="8366757838691703947">? Toutes les données présentes sur le périphérique seront supprimées.</translation>
-+<translation id="6596816719288285829">Adresse IP</translation>
-+<translation id="7747704580171477003">Active le nouveau design de la page &quot;Nouvel onglet&quot; (en cours de développement).</translation>
-+<translation id="4508265954913339219">Échec de l'activation</translation>
-+<translation id="8656768832129462377">Ne pas vérifier</translation>
-+<translation id="715487527529576698">Le chinois simplifié est le mode de saisie initial</translation>
-+<translation id="1674989413181946727">Paramètres SSL sur tout l'ordinateur :</translation>
-+<translation id="8703575177326907206">Votre connexion à <ph name="DOMAIN"/> n'est pas chiffrée.</translation>
-+<translation id="8472623782143987204">matériel requis</translation>
-+<translation id="4865571580044923428">Gérer les exceptions...</translation>
-+<translation id="2526619973349913024">Rechercher des mises à jour</translation>
-+<translation id="3874070094967379652">Utiliser un mot de passe multiterme pour chiffrer mes données de synchronisation</translation>
-+<translation id="4864369630010738180">Connexion en cours...</translation>
-+<translation id="6500116422101723010">Le serveur ne parvient pas à traiter la demande pour le moment. Ce code indique une situation temporaire. Le serveur sera de nouveau opérationnel ultérieurement.</translation>
-+<translation id="1644574205037202324">Historique</translation>
-+<translation id="1297175357211070620">Destination</translation>
-+<translation id="6219983382864672018">Web audio</translation>
-+<translation id="479280082949089240">Cookies placés par cette page</translation>
-+<translation id="4198861010405014042">Accès partagé</translation>
-+<translation id="6204930791202015665">Afficher...</translation>
-+<translation id="5941343993301164315">Veuillez vous connecter à <ph name="TOKEN_NAME"/>.</translation>
-+<translation id="4417229845571722044">Ajouter un nouvel e-mail</translation>
-+<translation id="8049151370369915255">Personnaliser les polices...</translation>
-+<translation id="2886862922374605295">Matériel :</translation>
-+<translation id="4497097279402334319">Erreur de connexion au réseau.</translation>
-+<translation id="5303618139271450299">Cette page Web est introuvable.</translation>
-+<translation id="4256316378292851214">En&amp;registrer la vidéo sous...</translation>
-+<translation id="3528171143076753409">Le certificat du serveur n'est pas approuvé.</translation>
-+<translation id="6518014396551869914">Cop&amp;ier l'image</translation>
-+<translation id="3236997602556743698">Sebeol-sik 390</translation>
-+<translation id="542155483965056918"><ph name="NUMBER_ZERO"/> mins ago</translation>
-+<translation id="289426338439836048">Autre réseau mobile...</translation>
-+<translation id="3986287159189541211">Erreur HTTP <ph name="ERROR_NUMBER"/> (<ph name="ERROR_NAME"/>) : <ph name="ERROR_TEXT"/></translation>
-+<translation id="3225319735946384299">Signature du code</translation>
-+<translation id="3118319026408854581">Aide <ph name="PRODUCT_NAME"/></translation>
-+<translation id="2422426094670600218">&lt;sans nom&gt;</translation>
-+<translation id="2012766523151663935">Version du micrologiciel :</translation>
-+<translation id="4120898696391891645">La page ne se charge pas</translation>
-+<translation id="6060685159320643512">Attention, ces fonctionnalités expérimentales peuvent mordre.</translation>
-+<translation id="5829990587040054282">Verrouiller l'écran ou éteindre</translation>
-+<translation id="7800304661137206267">La connexion est chiffrée au moyen de <ph name="CIPHER"/>, avec <ph name="MAC"/> pour l'authentification des messages et <ph name="KX"/> pour la méthode d'échange de clés.</translation>
-+<translation id="7706319470528945664">Clavier portugais</translation>
-+<translation id="5584537427775243893">Importation</translation>
-+<translation id="9128870381267983090">Connexion au réseau</translation>
-+<translation id="4181841719683918333">Langues</translation>
-+<translation id="6535131196824081346">Cette erreur peut se produire lors de la connexion à un serveur sécurisé (HTTPS).
-+ Elle indique que le serveur tente d'établir une connexion sécurisée, mais
-+ que celle-ci ne sera pas du tout sécurisée en raison d'une grave erreur de configuration.
-+ <ph name="LINE_BREAK"/> Dans ce cas, une intervention
-+ est requise sur le serveur.
-+ <ph name="PRODUCT_NAME"/>
-+ n'utilise pas de connexion non sécurisée pour protéger la confidentialité
-+ de vos données.</translation>
-+<translation id="5235889404533735074">La synchronisation de <ph name="PRODUCT_NAME"/> vous permet de partager vos données (favoris, préférences) sur vos ordinateurs en toute simplicité. Pour ce faire, <ph name="PRODUCT_NAME"/> enregistre vos données en ligne via Google lorsque vous vous connectez à votre compte.</translation>
-+<translation id="6533668113756472185">Format ou mise en forme de la page</translation>
-+<translation id="5640179856859982418">Clavier suisse</translation>
-+<translation id="5910363049092958439">En&amp;registrer l'image sous...</translation>
-+<translation id="1363055550067308502">Basculer en mode pleine chasse ou demi-chasse</translation>
-+<translation id="3108967419958202225">Sélectionner...</translation>
-+<translation id="6451650035642342749">Effacer les paramètres d'ouverture automatique</translation>
-+<translation id="5948544841277865110">Ajouter un réseau privé</translation>
-+<translation id="7121570032414343252"><ph name="NUMBER_TWO"/> secondes</translation>
-+<translation id="1378451347523657898">Ne pas envoyer de capture d'écran</translation>
-+<translation id="5098629044894065541">Hébreu</translation>
-+<translation id="7751559664766943798">Toujours afficher la barre de favoris</translation>
-+<translation id="5098647635849512368">Impossible de trouver le chemin d'accès absolu du répertoire à empaqueter.</translation>
-+<translation id="780617032715125782">Créer un profil</translation>
-+<translation id="933712198907837967">Diners Club</translation>
-+<translation id="6380224340023442078">Paramètres de contenu...</translation>
-+<translation id="950108145290971791">Activer la recherche instantanée pour accélérer la recherche et la navigation ?</translation>
-+<translation id="144136026008224475">Plus d'extensions &gt;&gt;</translation>
-+<translation id="5486326529110362464">La valeur d'entrée de la clé privée est obligatoire.</translation>
-+<translation id="9039663905644212491">PEAP</translation>
-+<translation id="62780591024586043">Fonctionnalités de localisation expérimentales</translation>
-+<translation id="8584280235376696778">Ou&amp;vrir la vidéo dans un nouvel onglet</translation>
-+<translation id="2845382757467349449">Toujours afficher la barre de favoris</translation>
-+<translation id="2516384155283419848">Reliure</translation>
-+<translation id="3053013834507634016">Utilisation de la clé du certificat</translation>
-+<translation id="4487088045714738411">Clavier belge</translation>
-+<translation id="7511635910912978956"><ph name="NUMBER_FEW"/> heures restantes</translation>
-+<translation id="2152580633399033274">Afficher toutes les images (recommandé)</translation>
-+<translation id="7894567402659809897">Cliquez sur
-+ <ph name="BEGIN_BOLD"/>Démarrer<ph name="END_BOLD"/>,
-+ puis sur
-+ <ph name="BEGIN_BOLD"/>Exécuter<ph name="END_BOLD"/>.
-+ Saisissez
-+ et cliquez sur
-+ <ph name="BEGIN_BOLD"/>OK<ph name="END_BOLD"/>.</translation>
-+<translation id="2934952234745269935">Nom de volume</translation>
-+<translation id="7960533875494434480">Reliure bord court</translation>
-+<translation id="6431347207794742960"><ph name="PRODUCT_NAME"/> va configurer les mises à jour automatiques pour tous les utilisateurs de cet ordinateur.</translation>
-+<translation id="4973698491777102067">Effacer les éléments datant :</translation>
-+<translation id="6074963268421707432">Interdire à tous les sites d'afficher des notifications sur le Bureau</translation>
-+<translation id="8508050303181238566">Appuyez sur <ph name="HOTKEY_NAME"/> pour passer d'un mode de saisie à l'autre.</translation>
-+<translation id="6273404661268779365">Ajouter un nouveau fax</translation>
-+<translation id="1995173078718234136">Recherche de contenu en cours...</translation>
-+<translation id="4735819417216076266">Style d'entrée avec Espace</translation>
-+<translation id="2977095037388048586">Vous avez tenté d'accéder à <ph name="DOMAIN"/>, mais, au lieu de cela, vous communiquez actuellement avec un serveur identifié comme <ph name="DOMAIN2"/>. Cela est peut-être dû à un défaut de configuration du serveur ou à quelque chose de plus grave. Un pirate informatique sur votre réseau cherche peut-être à vous faire visiter une version falsifiée de <ph name="DOMAIN3"/>, donc potentiellement préjudiciable. Nous vous déconseillons vivement de continuer.</translation>
-+<translation id="220138918934036434">Masquer le bouton</translation>
-+<translation id="5374359983950678924">Modifier l'image</translation>
-+<translation id="5158548125608505876">Ne pas synchroniser mes mots de passe</translation>
-+<translation id="2167276631610992935">JavaScript</translation>
-+<translation id="6974306300279582256">Activer les notifications de <ph name="SITE"/></translation>
-+<translation id="492914099844938733">Afficher les incompatibilités</translation>
-+<translation id="5233638681132016545">Nouvel onglet</translation>
-+<translation id="6567688344210276845">Impossible de charger l'icône &quot;<ph name="ICON"/>&quot; d'action de page.</translation>
-+<translation id="5210365745912300556">Fermer l'onglet</translation>
-+<translation id="8628085465172583869">Nom d'hôte du serveur :</translation>
-+<translation id="498765271601821113">Ajouter une carte de paiement</translation>
-+<translation id="7694379099184430148"><ph name="FILENAME"/> : type de fichier inconnu</translation>
-+<translation id="1992397118740194946">Non défini</translation>
-+<translation id="7966826846893205925">Gérer les paramètres de saisie automatique...</translation>
-+<translation id="8556732995053816225">Respecter la &amp;casse</translation>
-+<translation id="3314070176311241517">Autoriser tous les sites à exécuter JavaScript (recommandé)</translation>
-+<translation id="2406911946387278693">Gérer vos périphériques depuis le cloud</translation>
-+<translation id="7419631653042041064">Clavier catalan</translation>
-+<translation id="5710740561465385694">Me demander lorsqu'un site essaie de stocker des données</translation>
-+<translation id="3897092660631435901">Menu</translation>
-+<translation id="7024867552176634416">Sélectionnez le périphérique de stockage amovible à utiliser.</translation>
-+<translation id="8553075262323480129">La traduction a échoué, car nous n'avons pas pu déterminer la langue de la page.</translation>
-+<translation id="5910680277043747137">Vous pouvez créer des profils supplémentaires pour autoriser plusieurs personnes à utiliser et personnaliser Google Chrome.</translation>
-+<translation id="4381849418013903196">Deux-points</translation>
-+<translation id="1103523840287552314">Toujours traduire en <ph name="LANGUAGE"/></translation>
-+<translation id="2263497240924215535">(désactivée)</translation>
-+<translation id="2159087636560291862">Cela signifie que le certificat n'a pas été vérifié par un tiers reconnu par votre ordinateur. N'importe qui peut émettre un certificat en se faisant passer pour un autre site Web. Ce certificat doit donc être vérifié par un tiers approuvé. Sans cette vérification, les informations sur l'identité du certificat sont sans intérêt. Par conséquent, il nous est impossible de vérifier que vous communiquez bien avec <ph name="DOMAIN"/> et non avec un pirate informatique ayant émis son propre certificat en prétendant être <ph name="DOMAIN2"/>. Nous vous déconseillons vivement de continuer.</translation>
-+<translation id="58625595078799656"><ph name="PRODUCT_NAME"/> requiert que vous cryptiez vos données à l'aide de votre mot de passe Google ou de votre propre mot de passe multiterme.</translation>
-+<translation id="8017335670460187064"><ph name="LABEL"/></translation>
-+<translation id="6840184929775541289">N'est pas une autorité de certification</translation>
-+<translation id="6099520380851856040">Date et heure : <ph name="CRASH_TIME"/></translation>
-+<translation id="144518587530125858">Impossible de charger &quot;<ph name="IMAGE_PATH"/>&quot; pour le thème.</translation>
-+<translation id="5355097969896547230">Rechercher à nouveau</translation>
-+<translation id="7925285046818567682">En attente de <ph name="HOST_NAME"/>...</translation>
-+<translation id="2553440850688409052">Masquer ce plug-in</translation>
-+<translation id="3280237271814976245">Enregistrer &amp;sous...</translation>
-+<translation id="8301162128839682420">Ajouter une langue :</translation>
-+<translation id="7658239707568436148">Annuler</translation>
-+<translation id="8695825812785969222">Ouvrir une &amp;adresse...</translation>
-+<translation id="4538417792467843292">Supprimer le mot</translation>
-+<translation id="8412392972487953978">Vous devez saisir deux fois le même mot de passe multiterme.</translation>
-+<translation id="9121814364785106365">Ouvrir dans un onglet épinglé</translation>
-+<translation id="6996264303975215450">Page Web, tout type de contenu</translation>
-+<translation id="3435896845095436175">Activer</translation>
-+<translation id="1891668193654680795">Considérer ce certificat comme fiable pour identifier les développeurs de logiciels.</translation>
-+<translation id="5078638979202084724">Ajouter tous les onglets aux favoris</translation>
-+<translation id="5585118885427931890">Impossible de créer le dossier de favoris.</translation>
-+<translation id="2154710561487035718">Copier l'URL</translation>
-+<translation id="8163672774605900272">Si vous pensez ne pas avoir à utiliser de serveur proxy, procédez comme suit :
-+ <ph name="PLATFORM_TEXT"/></translation>
-+<translation id="5510687173983454382">Définir les utilisateurs autorisés à se connecter à un périphérique et autoriser les sessions de navigation en tant qu'invité</translation>
-+<translation id="3241680850019875542">Sélectionnez le répertoire racine de l'extension à empaqueter. Pour mettre à jour une extension, sélectionnez également le fichier de clé privée à réutiliser.</translation>
-+<translation id="7500424997253660722">Pool restreint :</translation>
-+<translation id="657402800789773160">&amp;Rafraîchir cette page</translation>
-+<translation id="6163363155248589649">&amp;Normal</translation>
-+<translation id="7972714317346275248">PKCS #1 SHA-384 avec chiffrement RSA</translation>
-+<translation id="3020990233660977256">Numéro de série : <ph name="SERIAL_NUMBER"/></translation>
-+<translation id="8426519927982004547">HTTPS/SSL</translation>
-+<translation id="8216781342946147825">Toutes les données de votre ordinateur et des sites Web que vous visitez</translation>
-+<translation id="5548207786079516019">Ceci est une installation secondaire de <ph name="PRODUCT_NAME"/> et ce dernier ne peut pas être défini comme navigateur par défaut.</translation>
-+<translation id="3984413272403535372">Erreur lors de la signature de l'extension</translation>
-+<translation id="8807083958935897582"><ph name="PRODUCT_NAME"/> permet d'effectuer des recherches sur Internet à l'aide du champ polyvalent. Sélectionnez le moteur de recherche à utiliser :</translation>
-+<translation id="6629104427484407292">Sécurité : <ph name="SECURITY"/></translation>
-+<translation id="9208886416788010685">Adobe Reader n'est pas à jour</translation>
-+<translation id="3373604799988099680">Extensions ou applications</translation>
-+<translation id="318408932946428277">Effacer les cookies et autres données de site et de plug-in lorsque je ferme le navigateur</translation>
-+<translation id="314141447227043789">Téléchargement de l'image terminé.</translation>
-+<translation id="8725178340343806893">Favoris</translation>
-+<translation id="5177526793333269655">Afficher les vignettes</translation>
-+<translation id="8926389886865778422">Ne plus afficher ce message</translation>
-+<translation id="6985235333261347343">Agent de récupération de clé Microsoft</translation>
-+<translation id="3605499851022050619">Page de diagnostic de navigation sécurisée</translation>
-+<translation id="4417271111203525803">Adresse ligne 2</translation>
-+<translation id="7617095560120859490">Décrivez-nous le problème recontré. (Champ obligatoire)</translation>
-+<translation id="5618333180342767515">Cela peut prendre quelques minutes.</translation>
-+<translation id="4307992518367153382">Options de base</translation>
-+<translation id="8480417584335382321">Niveau de zoom par défaut :</translation>
-+<translation id="3872166400289564527">Stockage externe</translation>
-+<translation id="5912378097832178659">Modifi&amp;er les moteurs de recherche...</translation>
-+<translation id="8272426682713568063">Cartes de paiement</translation>
-+<translation id="3749289110408117711">Nom du fichier</translation>
-+<translation id="3173397526570909331">Arrêter la synchronisation</translation>
-+<translation id="5538092967727216836">Actualiser le cadre</translation>
-+<translation id="4813345808229079766">Connexion</translation>
-+<translation id="411666854932687641">Mémoire privée</translation>
-+<translation id="119944043368869598">Tout effacer</translation>
-+<translation id="3467848195100883852">Activer la correction orthographique automatique</translation>
-+<translation id="1336254985736398701">Afficher les &amp;infos sur la page</translation>
-+<translation id="7550830279652415241">favoris_<ph name="DATESTAMP"/>.html</translation>
-+<translation id="6828153365543658583">Autoriser uniquement les utilisateurs suivants à se connecter :</translation>
-+<translation id="1652965563555864525">&amp;Muet</translation>
-+<translation id="4200983522494130825">Nouvel ongle&amp;t</translation>
-+<translation id="7979036127916589816">Erreur de synchronisation</translation>
-+<translation id="1029317248976101138">Zoom</translation>
-+<translation id="5455790498993699893"><ph name="ACTIVE_MATCH"/> sur <ph name="TOTAL_MATCHCOUNT"/></translation>
-+<translation id="8890069497175260255">Type de clavier</translation>
-+<translation id="1202290638211552064">Délai d'expiration atteint au niveau de la passerelle ou du serveur proxy en attente d'une réponse d'un serveur en amont.</translation>
-+<translation id="7765158879357617694">Déplacer</translation>
-+<translation id="5731751937436428514">Mode de saisie du vietnamien (VIQR)</translation>
-+<translation id="8412144371993786373">Ajouter la page actuelle aux favoris</translation>
-+<translation id="7615851733760445951">&lt;aucun cookie sélectionné&gt;</translation>
-+<translation id="469553822757430352">Le mot de passe de l'application est incorrect.</translation>
-+<translation id="2493021387995458222">Sélectionner &quot;un mot à la fois&quot;</translation>
-+<translation id="5279600392753459966">Tout bloquer</translation>
-+<translation id="6846298663435243399">Chargement en cours…</translation>
-+<translation id="7392915005464253525">&amp;Rouvrir la fenêtre fermée</translation>
-+<translation id="1144684570366564048">Gérer les exceptions...</translation>
-+<translation id="7400418766976504921">URL</translation>
-+<translation id="1541725072327856736">Katakana demi-chasse</translation>
-+<translation id="7456847797759667638">Ouvrir une adresse</translation>
-+<translation id="1388866984373351434">Données de navigation</translation>
-+<translation id="3754634516926225076">Code PIN incorrect. Veuillez réessayer.</translation>
-+<translation id="7378627244592794276">Non</translation>
-+<translation id="2800537048826676660">Utiliser cette langue pour corriger l'orthographe</translation>
-+<translation id="68541483639528434">Fermer les autres onglets</translation>
-+<translation id="941543339607623937">Clé privée non valide.</translation>
-+<translation id="6499058468232888609">Une erreur réseau s'est produite pendant la communication avec le service de gestion des périphériques.</translation>
-+<translation id="4433862206975946675">Importer les données d'un autre navigateur...</translation>
-+<translation id="4022426551683927403">&amp;Ajouter au dictionnaire</translation>
-+<translation id="2897878306272793870">Voulez-vous vraiment ouvrir <ph name="TAB_COUNT"/> onglets ?</translation>
-+<translation id="312759608736432009">Fabricant du périphérique :</translation>
-+<translation id="362276910939193118">Afficher l'historique complet</translation>
-+<translation id="6079696972035130497">Illimité</translation>
-+<translation id="4365411729367255048">Clavier Neo2 allemand</translation>
-+<translation id="6348657800373377022">Liste déroulante</translation>
-+<translation id="8064671687106936412">Clé :</translation>
-+<translation id="2218515861914035131">Coller en texte brut</translation>
-+<translation id="1725149567830788547">Afficher les &amp;commandes</translation>
-+<translation id="3528033729920178817">Cette page suit votre position géographique.</translation>
-+<translation id="5518584115117143805">Certificat de chiffrement de courrier électronique</translation>
-+<translation id="9203398526606335860">&amp;Profilage activé</translation>
-+<translation id="4307281933914537745">En savoir plus sur la récupération du système</translation>
-+<translation id="2849936225196189499">Essentielle</translation>
-+<translation id="9001035236599590379">Type MIME</translation>
-+<translation id="5612754943696799373">Autoriser le téléchargement ?</translation>
-+<translation id="6353618411602605519">Clavier croate</translation>
-+<translation id="5515810278159179124">Interdire à tous les sites de suivre ma position géographique</translation>
-+<translation id="5999606216064768721">Utiliser la barre de titre du système et les bordures de la fenêtre</translation>
-+<translation id="904752364881701675">En bas à gauche</translation>
-+<translation id="3398951731874728419">Informations sur l'erreur :</translation>
-+<translation id="1464570622807304272">Essayez : saisissez &quot;orchidées&quot;, puis appuyez sur Entrée.</translation>
-+<translation id="8026684114486203427">Pour utiliser Chrome Web Store, vous devez être connecté à un compte Google.</translation>
-+<translation id="8417276187983054885">Configurer <ph name="CLOUD_PRINT_NAME"/></translation>
-+<translation id="3056462238804545033">Petit problème... Nous n'avons pas réussi à vous authentifier. Veuillez vérifier vos identifiants de connexion puis réessayer.</translation>
-+<translation id="5298420986276701358">Pour gérer à distance la configuration de ce périphérique <ph name="PRODUCT_NAME"/> depuis le cloud, connectez-vous avec votre compte Google Apps.</translation>
-+<translation id="2678063897982469759">Réactiver</translation>
-+<translation id="1779766957982586368">Fermer la fenêtre</translation>
-+<translation id="4850886885716139402">Présentation</translation>
-+<translation id="89217462949994770">Vous avez saisi un trop grand nombre de codes PIN incorrects. Veuillez contacter <ph name="CARRIER_ID"/> pour obtenir une nouvelle clé de déverrouillage du code PIN à 8 chiffres.</translation>
-+<translation id="5920618722884262402">Bloquer le contenu inapproprié</translation>
-+<translation id="5120247199412907247">Configuration avancée</translation>
-+<translation id="5922220455727404691">Utiliser SSL 3.0</translation>
-+<translation id="1368352873613152012">Règles de confidentialité liées à la navigation sécurisée</translation>
-+<translation id="5105859138906591953">Vous devez être connecté à votre compte Google pour importer les favoris de la barre d'outils Google dans Google Chrome. Connectez-vous et relancez l'importation.</translation>
-+<translation id="8899851313684471736">Ouvrir le lien dans une nouvelle &amp;fenêtre</translation>
-+<translation id="4110342520124362335">Les cookies de <ph name="DOMAIN"/> ont été bloqués.</translation>
-+<translation id="3303818374450886607">Copies</translation>
-+<translation id="2019718679933488176">&amp;Ouvrir le fichier audio dans un nouvel onglet</translation>
-+<translation id="4138267921960073861">Afficher les noms d'utilisateurs et leur photo sur la page de connexion</translation>
-+<translation id="7465778193084373987">URL de révocation de certificat Netscape</translation>
-+<translation id="5976690834266782200">Ajoute des options de regroupement des onglets dans le menu contextuel des onglets.</translation>
-+<translation id="4755240240651974342">Clavier finnois</translation>
-+<translation id="7049893973755373474">Vérifiez votre connexion Internet. Redémarrez votre routeur, votre modem
-+ ou tout autre périphérique réseau que vous utilisez.</translation>
-+<translation id="7421925624202799674">&amp;Afficher le code source de la page</translation>
-+<translation id="3940082421246752453">Le serveur ne prend pas en charge la version HTTP utilisée dans la demande.</translation>
-+<translation id="661719348160586794">Vos mots de passe enregistrés s'afficheront ici.</translation>
-+<translation id="6686490380836145850">Fermer les onglets sur la droite</translation>
-+<translation id="5608669887400696928"><ph name="NUMBER_DEFAULT"/> heures</translation>
-+<translation id="8844709414456935411"><ph name="PRODUCT_NAME"/>
-+ indique qu'un produit ESET intercepte les connexions sécurisées.
-+ En général, cela ne constitue pas un problème de sécurité car le
-+ logiciel ESET s'exécute souvent sur le même ordinateur. Toutefois, en raison
-+ de certaines incompatibilités avec les connexions sécurisées
-+ <ph name="PRODUCT_NAME"/>,
-+ vous devez configurer les produits ESET de manière à éviter ces
-+ interceptions. Cliquez sur le lien En savoir plus pour obtenir des instructions.</translation>
-+<translation id="3936877246852975078">Les requêtes adressées au serveur ont été temporairement limitées.</translation>
-+<translation id="2600306978737826651">Impossible de télécharger l'image. Gravure annulée.</translation>
-+<translation id="609978099044725181">Activer/désactiver le mode Hanja</translation>
-+<translation id="1829483195200467833">Effacer les paramètres d'ouverture automatique</translation>
-+<translation id="2738771556149464852">Pas après le</translation>
-+<translation id="5774515636230743468">Manifeste :</translation>
-+<translation id="719464814642662924">Visa</translation>
-+<translation id="7474889694310679759">Clavier anglais canadien</translation>
-+<translation id="1817871734039893258">Récupération de fichier Microsoft</translation>
-+<translation id="2423578206845792524">En&amp;registrer l'image sous...</translation>
-+<translation id="6839929833149231406">Zone</translation>
-+<translation id="9068931793451030927">Chemin :</translation>
-+<translation id="283278805979278081">Prendre la photo</translation>
-+<translation id="7320906967354320621">Inactif</translation>
-+<translation id="1407050882688520094">Certains de vos certificats enregistrés identifient ces autorités de certification :</translation>
-+<translation id="4287689875748136217">Impossible d'afficher la page Web, car le serveur n'a envoyé aucune donnée.</translation>
-+<translation id="1634788685286903402">Considérer ce certificat comme fiable pour identifier les utilisateurs de messageries.</translation>
-+<translation id="7052402604161570346">Ce type de fichier peut endommager votre ordinateur. Voulez-vous vraiment télécharger <ph name="FILE_NAME"/> ?</translation>
-+<translation id="8642489171979176277">Importés depuis la barre d'outils Google</translation>
-+<translation id="4142744419835627535">Recherche instantanée et saisie semi-automatique</translation>
-+<translation id="4684427112815847243">Tout synchroniser</translation>
-+<translation id="1125520545229165057">Dvorak (Hsu)</translation>
-+<translation id="8940229512486821554">Exécuter la commande <ph name="EXTENSION_NAME"/> : <ph name="SEARCH_TERMS"/></translation>
-+<translation id="2232876851878324699">Le fichier contenait un certificat, qui n'a pas été importé :</translation>
-+<translation id="7787129790495067395">Vous utilisez actuellement un mot de passe multiterme. Si vous l'oubliez, vous pouvez réinitialiser la synchronisation afin de supprimer vos données des serveurs Google à l'aide de Google Dashboard.</translation>
-+<translation id="2686759344028411998">Impossible de détecter les modules chargés.</translation>
-+<translation id="572525680133754531">Cette fonctionnalité affiche une bordure autour des couches de rendu afin de déboguer et d'étudier leur composition.</translation>
-+<translation id="2011110593081822050">Processus de traitement Web : <ph name="WORKER_NAME"/></translation>
-+<translation id="3294437725009624529">Invité</translation>
-+<translation id="350069200438440499">Nom du fichier :</translation>
-+<translation id="9058204152876341570">Un élément est manquant.</translation>
-+<translation id="8494979374722910010">Échec de la tentative de connexion au serveur.</translation>
-+<translation id="7810202088502699111">Des fenêtres pop-up ont été bloquées sur cette page.</translation>
-+<translation id="8190698733819146287">Personnaliser les langues et la saisie...</translation>
-+<translation id="646727171725540434">Proxy HTTP</translation>
-+<translation id="1006316751839332762">Mot de passe multiterme de chiffrement</translation>
-+<translation id="8795916974678578410">Nouvelle fenêtre</translation>
-+<translation id="2733275712367076659">Certains certificats provenant de ces organisations vous identifient :</translation>
-+<translation id="4801512016965057443">Autoriser l'itinérance des données mobiles</translation>
-+<translation id="2515586267016047495">Alt</translation>
-+<translation id="2046040965693081040">Utiliser les pages actuelles</translation>
-+<translation id="3798449238516105146">Version</translation>
-+<translation id="5764483294734785780">En&amp;registrer le fichier audio sous...</translation>
-+<translation id="5252456968953390977">Itinérance</translation>
-+<translation id="8744641000906923997">Romaji</translation>
-+<translation id="8507996248087185956"><ph name="NUMBER_DEFAULT"/> minutes</translation>
-+<translation id="4845656988780854088">Synchroniser uniquement les paramètres et\ndonnées qui ont changé depuis la dernière connexion\n(requiert votre mot de passe précédent)</translation>
-+<translation id="348620396154188443">Autoriser tous les sites à afficher des notifications sur le Bureau</translation>
-+<translation id="8214489666383623925">Ouvrir le fichier...</translation>
-+<translation id="5376120287135475614">Changer de fenêtre</translation>
-+<translation id="5230160809118287008">Chèvres téléportées</translation>
-+<translation id="1701567960725324452">Si vous arrêtez la synchronisation, les données stockées sur cet ordinateur et dans votre compte Google demeureront à ces deux emplacements. Toutefois, les nouvelles données ou les modifications apportées au contenu existant ne seront pas synchronisées.</translation>
-+<translation id="7761701407923456692">Le certificat du serveur ne correspond pas à l'URL.</translation>
-+<translation id="3885155851504623709">Commune</translation>
-+<translation id="4910171858422458941">Impossible d'activer les plug-ins désactivés par une stratégie d'entreprise.</translation>
-+<translation id="4495419450179050807">Ne pas afficher sur cette page</translation>
-+<translation id="4745800796303246012">Méthodes EAP en Wi-Fi expérimentales</translation>
-+<translation id="1225544122210684390">Disque dur</translation>
-+<translation id="939736085109172342">Nouveau dossier</translation>
-+<translation id="4933484234309072027">intégration sur <ph name="URL"/></translation>
-+<translation id="5554720593229208774">Autorité de certification de messagerie</translation>
-+<translation id="862750493060684461">Cache CSS</translation>
-+<translation id="2832519330402637498">En haut à gauche</translation>
-+<translation id="6749695674681934117">Saisissez le nom du nouveau dossier.</translation>
-+<translation id="6204994989617056362">L'extension de renégociation SSL était introuvable lors de la négociation sécurisée. Avec certains sites, connus pour leur prise en charge de l'extension de renégociation, Google Chrome exige une négociation mieux sécurisée afin de prévenir certaines attaques. L'absence de cette extension suggère que votre connexion a été interceptée et manipulée au cours du transfert.</translation>
-+<translation id="6679492495854441399">Petit problème... Une erreur de communication avec le réseau est survenue lors de la tentative d'inscription de ce périphérique. Veuillez vérifier votre connexion réseau et réessayer.</translation>
-+<translation id="7789962463072032349">pause</translation>
-+<translation id="121827551500866099">Afficher tous les téléchargements...</translation>
-+<translation id="1562633988311880769">Connexion à <ph name="CLOUD_PRINT_NAME"/></translation>
-+<translation id="5949910269212525572">Impossible de résoudre l'adresse DNS du serveur.</translation>
-+<translation id="3115147772012638511">En attente de l'affichage du cache</translation>
-+<translation id="257088987046510401">Thèmes</translation>
-+<translation id="6771079623344431310">Impossible de se connecter au serveur proxy.</translation>
-+<translation id="2200129049109201305">Ignorer la synchronisation des données chiffrées ?</translation>
-+<translation id="1426410128494586442">Oui</translation>
-+<translation id="6725970970008349185">Nombre de suggestions par page</translation>
-+<translation id="6198252989419008588">Modifier le code PIN</translation>
-+<translation id="5749483996735055937">Un problème est survenu lors de la copie de l'image de récupération sur le périphérique.</translation>
-+<translation id="3520476450377425184"><ph name="NUMBER_MANY"/> jours restants</translation>
-+<translation id="7643817847124207232">La connexion Internet a été interrompue.</translation>
-+<translation id="932327136139879170">Début</translation>
-+<translation id="4764675709794295630">« Précédent</translation>
-+<translation id="2560794850818211873">C&amp;opier l'URL de la vidéo</translation>
-+<translation id="6042708169578999844">Vos données sur <ph name="WEBSITE_1"/> et <ph name="WEBSITE_2"/></translation>
-+<translation id="5302048478445481009">Langue</translation>
-+<translation id="5553089923092577885">Mappages des stratégies de certificat</translation>
-+<translation id="5600907569873192868"><ph name="NUMBER_MANY"/> minutes restantes</translation>
-+<translation id="1519704592140256923">Sélectionner la position</translation>
-+<translation id="1275018677838892971">Le site Web à l'adresse <ph name="HOST_NAME"/> contient des éléments provenant de sites signalés comme étant des sites de phishing. Ces derniers incitent les internautes à divulguer leurs informations personnelles en se faisant passer pour des institutions de confiance, telles que des banques.</translation>
-+<translation id="702455272205692181"><ph name="EXTENSION_NAME"/></translation>
-+<translation id="7170041865419449892">Hors de portée</translation>
-+<translation id="908263542783690259">Effacer l'historique de navigation</translation>
-+<translation id="7518003948725431193">Aucune page Web trouvée à l'adresse :<ph name="URL"/></translation>
-+<translation id="745602119385594863">Nouveau moteur de recherche :</translation>
-+<translation id="7484645889979462775">Jamais pour ce site</translation>
-+<translation id="8666066831007952346"><ph name="NUMBER_TWO"/> jours restants</translation>
-+<translation id="9086455579313502267">Impossible d'accéder au réseau.</translation>
-+<translation id="5595485650161345191">Modifier l'adresse</translation>
-+<translation id="2374144379568843525">&amp;Masquer le panneau de la vérification orthographique</translation>
-+<translation id="2694026874607847549">1 cookie</translation>
-+<translation id="4393664266930911253">Activer ces fonctionnalités...</translation>
-+<translation id="6390842777729054533"><ph name="NUMBER_ZERO"/> secondes restantes</translation>
-+<translation id="3909791450649380159">Cou&amp;per</translation>
-+<translation id="2955913368246107853">Fermer la barre de recherche</translation>
-+<translation id="5642508497713047">Signataire de la liste de révocation de certificats</translation>
-+<translation id="813082847718468539">Afficher des informations à propos du site</translation>
-+<translation id="3122464029669770682">UC</translation>
-+<translation id="1684861821302948641">Fermer les pages</translation>
-+<translation id="6092270396854197260">MSPY</translation>
-+<translation id="6802031077390104172"><ph name="USAGE"/> (<ph name="OID"/>)</translation>
-+<translation id="4052120076834320548">Très petite</translation>
-+<translation id="6623138136890659562">Afficher les réseaux privés dans le menu Réseau pour activer la connexion à un VPN</translation>
-+<translation id="8969837897925075737">Vérification de la mise à jour du système...</translation>
-+<translation id="3393716657345709557">L'entrée demandée est introuvable dans le cache.</translation>
-+<translation id="7241389281993241388">Connectez-vous à <ph name="TOKEN_NAME"/> pour importer le certificat client.</translation>
-+<translation id="40334469106837974">Modifier la mise en page</translation>
-+<translation id="4804818685124855865">Se déconnecter</translation>
-+<translation id="2617919205928008385">Espace insuffisant.</translation>
-+<translation id="210445503571712769">Préférences synchronisées</translation>
-+<translation id="1608306110678187802">Imp&amp;rimer le cadre...</translation>
-+<translation id="7427315641433634153">MSCHAP</translation>
-+<translation id="6622980291894852883">Continuer à bloquer les images</translation>
-+<translation id="5937837224523037661">Lorsqu'un site utilise des plug-ins :</translation>
-+<translation id="4988792151665380515">Échec d'exportation de la clé publique</translation>
-+<translation id="6333049849394141510">Choisir les éléments à synchroniser</translation>
-+<translation id="446322110108864323">Paramètres de saisie du Pinyin</translation>
-+<translation id="4948468046837535074">Ouvrir les pages suivantes :</translation>
-+<translation id="5222676887888702881">Déconnexion</translation>
-+<translation id="6978121630131642226">Moteurs de recherche</translation>
-+<translation id="6839225236531462745">Erreur de suppression de certificat</translation>
-+<translation id="6745994589677103306">Ne rien faire</translation>
-+<translation id="855081842937141170">Épingler l'onglet</translation>
-+<translation id="6263541650532042179">réinitialiser la synchronisation</translation>
-+<translation id="6055392876709372977">PKCS #1 SHA-256 avec chiffrement RSA</translation>
-+<translation id="7903984238293908205">Katakana</translation>
-+<translation id="3781488789734864345">Choisir un réseau mobile</translation>
-+<translation id="268053382412112343">&amp;Historique</translation>
-+<translation id="2723893843198727027">Mode développeur :</translation>
-+<translation id="1722567105086139392">Lien</translation>
-+<translation id="2620436844016719705">Système</translation>
-+<translation id="5362741141255528695">Sélectionnez le fichier de clé privée.</translation>
-+<translation id="5292890015345653304">Insérez une carte SD ou une carte mémoire USB.</translation>
-+<translation id="5583370583559395927">Temps restant : <ph name="TIME_REMAINING"/></translation>
-+<translation id="8065982201906486420">Cliquez ici pour exécuter le plug-in <ph name="PLUGIN_NAME"/>.</translation>
-+<translation id="6219717821796422795">Hanyu</translation>
-+<translation id="3725367690636977613">pages</translation>
-+<translation id="2688477613306174402">Configuration en cours</translation>
-+<translation id="1195447618553298278">Erreur inconnue</translation>
-+<translation id="3353284378027041011"><ph name="NUMBER_FEW"/> days ago</translation>
-+<translation id="8811462119186190367">La langue utilisée pour Google Chrome est passée de &quot;<ph name="FROM_LOCALE"/>&quot; à &quot;<ph name="TO_LOCALE"/>&quot; après la synchronisation de vos paramètres.</translation>
-+<translation id="1087119889335281750">&amp;Aucune suggestion orthographique</translation>
-+<translation id="5228309736894624122">Erreur de protocole SSL</translation>
-+<translation id="8216170236829567922">Mode de saisie du thaï (clavier Pattachote)</translation>
-+<translation id="8464132254133862871">Ce compte utilisateur n'est pas compatible avec ce service.</translation>
-+<translation id="6812349420832218321"><ph name="PRODUCT_NAME"/> ne peut pas être exécuté en tant que root.</translation>
-+<translation id="5076340679995252485">C&amp;oller</translation>
-+<translation id="2904348843321044456">Paramètres de contenu...</translation>
-+<translation id="1055216403268280980">Dimensions de l'image</translation>
-+<translation id="1784284518684746740">Sélectionner le fichier à enregistrer sous</translation>
-+<translation id="7032947513385578725">Disque Flash</translation>
-+<translation id="5518442882456325299">Moteur de recherche actuel :</translation>
-+<translation id="14171126816530869">L'identité de <ph name="ORGANIZATION"/> situé à <ph name="LOCALITY"/> a été vérifiée par <ph name="ISSUER"/>.</translation>
-+<translation id="6263082573641595914">Version de l'autorité de certification Microsoft</translation>
-+<translation id="3105917916468784889">Enregistrer une capture d'écran</translation>
-+<translation id="1741763547273950878">Page sur <ph name="SITE"/></translation>
-+<translation id="1587275751631642843">Console &amp;JavaScript</translation>
-+<translation id="8460696843433742627">Réponse reçue incorrecte lors de la tentative de chargement de <ph name="URL"/>.
-+ Cela peut être dû à une opération de maintenance ou à une configuration incorrecte sur le serveur.</translation>
-+<translation id="297870353673992530">Serveur DNS :</translation>
-+<translation id="3222066309010235055">Pré-rendu : <ph name="PRERENDER_CONTENTS_NAME"/></translation>
-+<translation id="6410063390789552572">Impossible d'accéder à la bibliothèque réseau.</translation>
-+<translation id="6880587130513028875">Des images ont été bloquées sur cette page.</translation>
-+<translation id="851263357009351303">Toujours autoriser <ph name="HOST"/> à afficher les images</translation>
-+<translation id="3511307672085573050">Copier l'adr&amp;esse du lien</translation>
-+<translation id="1134009406053225289">Ouvrir dans une fenêtre de navigation privée</translation>
-+<translation id="6655190889273724601">Mode développeur</translation>
-+<translation id="1071917609930274619">Chiffrement des données</translation>
-+<translation id="3473105180351527598">Activer la protection contre le phishing et les logiciels malveillants</translation>
-+<translation id="6151323131516309312">Appuyez sur <ph name="SEARCH_KEY"/> pour rechercher sur <ph name="SITE_NAME"/></translation>
-+<translation id="3753317529742723206">Voulez-vous utiliser <ph name="HANDLER_TITLE"/> (<ph name="HANDLER_HOSTNAME"/>) au lieu de <ph name="REPLACED_HANDLER_TITLE"/> pour gérer les liens <ph name="PROTOCOL"/>:// à partir de maintenant ?</translation>
-+<translation id="6216679966696797604">Démarrer une session en tant qu'invité</translation>
-+<translation id="5456397824015721611">Nombre maximal de caractères chinois dans la mémoire tampon de pré-édition, notamment les entrées de symboles Zhuyin</translation>
-+<translation id="2055443983279698110">Barre de menus GNOME expérimentale disponible</translation>
-+<translation id="2342959293776168129">Effacer l'historique des téléchargements</translation>
-+<translation id="2503522102815150840">Navigateur bloqué...</translation>
-+<translation id="7201354769043018523">Parenthèse drte</translation>
-+<translation id="425878420164891689">Calcul du temps de chargement</translation>
-+<translation id="508794495705880051">Ajouter une carte de paiement...</translation>
-+<translation id="1425975335069981043">Itinérance :</translation>
-+<translation id="1272079795634619415">Arrêter</translation>
-+<translation id="5442787703230926158">Erreur de synchronisation...</translation>
-+<translation id="2462724976360937186">ID de clé de l'autorité de certification</translation>
-+<translation id="6786747875388722282">Extensions</translation>
-+<translation id="3944384147860595744">Imprimez où que vous soyez.</translation>
-+<translation id="2570648609346224037">Un problème est survenu lors du téléchargement de l'image de récupération.</translation>
-+<translation id="4306718255138772973">Cloud Print Proxy</translation>
-+<translation id="9053965862400494292">Une erreur s'est produite lors de la configuration de la synchronisation.</translation>
-+<translation id="8596540852772265699">Fichiers personnalisés</translation>
-+<translation id="7017354871202642555">Impossible de définir le mode une fois la fenêtre créée.</translation>
-+<translation id="3101709781009526431">Date et heure</translation>
-+<translation id="69375245706918574">Personnaliser les préférences de synchronisation</translation>
-+<translation id="833853299050699606">Aucune information disponible sur le forfait</translation>
-+<translation id="1737968601308870607">Signaler un problème</translation>
-+<translation id="4571852245489094179">Importer mes favoris et paramètres</translation>
-+<translation id="99648783926443049">Sélectionnez le <ph name="BEGIN_BOLD"/>menu clé à molette &gt; Paramètres &gt; Options avancées &gt; Modifier les paramètres du proxy<ph name="END_BOLD"/> et vérifiez que vos paramètres sont définis sur &quot;sans proxy&quot; ou &quot;direct&quot;.</translation>
-+<translation id="4421917670248123270">Fermer et annuler les téléchargements</translation>
-+<translation id="5605623530403479164">Autres moteurs de recherche</translation>
-+<translation id="8887243200615092733"><ph name="PRODUCT_NAME"/> peut maintenant synchroniser vos mots de passe. Pour protéger vos données, vous devez confirmer les informations relatives à votre compte.</translation>
-+<translation id="4740663705480958372">Cette fonctionnalité active les API P2P Pepper et P2P JavaScript. L'API est en cours de développement et n'est pas encore opérationnelle.</translation>
-+<translation id="5710435578057952990">L'identité de ce site Web n'a pas été vérifiée.</translation>
-+<translation id="1421046588786494306">Sessions à l'étranger</translation>
-+<translation id="1661245713600520330">Cette page répertorie tous les modules chargés dans le processus principal et les modules enregistrés de manière à être chargés ultérieurement.</translation>
-+<translation id="5451646087589576080">Afficher les &amp;infos sur le cadre</translation>
-+<translation id="3368922792935385530">Connecté</translation>
-+<translation id="3498309188699715599">Paramètres d'entrée en Chewing</translation>
-+<translation id="8486154204771389705">Conserver sur cette page</translation>
-+<translation id="3866443872548686097">Votre support de récupération est prêt. Vous pouvez le retirer du système.</translation>
-+<translation id="6824564591481349393">Copi&amp;er l'adresse e-mail</translation>
-+<translation id="907148966137935206">Interdire à tous les sites d'afficher des fenêtres pop-up (recommandé)</translation>
-+<translation id="5184063094292164363">Console &amp;JavaScript</translation>
-+<translation id="333371639341676808">Empêcher cette page de générer des boîtes de dialogue supplémentaires</translation>
-+<translation id="7632380866023782514">En haut à droite</translation>
-+<translation id="4925520021222027859">Entrez le mot de passe associé à votre application :</translation>
-+<translation id="3494768541638400973">Mode de saisie Google du japonais (pour clavier japonais)</translation>
-+<translation id="5844183150118566785"><ph name="PRODUCT_NAME"/> est à jour (<ph name="VERSION"/>)</translation>
-+<translation id="3118046075435288765">Le serveur a mis fin à la connexion de manière inattendue.</translation>
-+<translation id="8041140688818013446">Il est possible que le serveur hébergeant la page Web soit surchargé ou ait rencontré une erreur. Pour éviter de générer
-+ trop de trafic et d'aggraver la situation,
-+ <ph name="PRODUCT_NAME"/> a temporairement
-+ bloqué l'acceptation des requêtes adressées au serveur.
-+ <ph name="LINE_BREAK"/>
-+ Si vous pensez que ce comportement n'est pas souhaitable, (par exemple, dans le cas où vous déboguez votre propre site Web), vous pouvez
-+ consulter la page <ph name="NET_INTERNALS_PAGE"/>,
-+ sur laquelle vous pourrez trouver plus d'informations ou désactiver cette fonctionnalité.</translation>
-+<translation id="1725068750138367834">Gestionnaire de &amp;fichiers</translation>
-+<translation id="4254921211241441775">Arrêter la synchronisation du compte</translation>
-+<translation id="7791543448312431591">Ajouter</translation>
-+<translation id="8569764466147087991">Sélectionnez le fichier à ouvrir</translation>
-+<translation id="5449451542704866098">Aucun forfait de données</translation>
-+<translation id="307505906468538196">Créer un compte Google</translation>
-+<translation id="2053553514270667976">Code postal</translation>
-+<translation id="48838266408104654">&amp;Gestionnaire de tâches</translation>
-+<translation id="4378154925671717803">Téléphone</translation>
-+<translation id="3694027410380121301">Sélectionner l'onglet précédent</translation>
-+<translation id="6178664161104547336">Sélectionner un certificat</translation>
-+<translation id="1375321115329958930">Mots de passe enregistrés</translation>
-+<translation id="3341703758641437857">Autoriser l'accès aux URL de fichier</translation>
-+<translation id="5702898740348134351">Modifi&amp;er les moteurs de recherche...</translation>
-+<translation id="734303607351427494">Gérer les moteurs de recherche...</translation>
-+<translation id="8326478304147373412">PKCS #7, chaîne de certificats</translation>
-+<translation id="3242765319725186192">Clé pré-partagée :</translation>
-+<translation id="8089798106823170468">Contrôlez et partagez l'accès à vos imprimantes depuis n'importe quel compte Google.</translation>
-+<translation id="5984992849064510607">Ajoute l'option &quot;Utiliser les onglets latéraux&quot; au menu contextuel de la barre d'onglets. Utilisez cette option pour déplacer les onglets du haut de l'écran (affichage par défaut) vers le côté. Particulièrement utile sur les grands écrans.</translation>
-+<translation id="839736845446313156">S'inscrire</translation>
-+<translation id="4668929960204016307">,</translation>
-+<translation id="2409527877874991071">Saisissez un nouveau nom.</translation>
-+<translation id="4240069395079660403"><ph name="PRODUCT_NAME"/> ne peut pas être affiché dans cette langue.</translation>
-+<translation id="747114903913869239">Erreur : impossible de décoder l'extension.</translation>
-+<translation id="5412637665001827670">Clavier bulgare</translation>
-+<translation id="2113921862428609753">Accès aux informations de l'autorité</translation>
-+<translation id="5227536357203429560">Ajouter un réseau privé...</translation>
-+<translation id="732677191631732447">C&amp;opier l'URL du fichier audio</translation>
-+<translation id="7224023051066864079">Masque de sous-réseau :</translation>
-+<translation id="2401813394437822086">Impossible d'accéder à votre compte ?</translation>
-+<translation id="2344262275956902282">Utiliser les touches - et = pour paginer une liste d'entrées</translation>
-+<translation id="3609138628363401169">Le serveur ne prend pas en charge l'extension de renégociation TLS.</translation>
-+<translation id="3369624026883419694">Résolution de l'hôte...</translation>
-+<translation id="8870413625673593573">Récemment fermés</translation>
-+<translation id="9145357542626308749">Le certificat de sécurité du site a été signé avec un algorithme de signature faible.</translation>
-+<translation id="8502803898357295528">Votre mot de passe a été modifié</translation>
-+<translation id="4064488613268730704">Gérer les paramètres de saisie automatique...</translation>
-+<translation id="6830600606572693159">La page Web <ph name="URL"/> n'est pas disponible pour le moment. Cela peut être dû à une surcharge ou à une opération de maintenance.</translation>
-+<translation id="4145797339181155891">Éjecter</translation>
-+<translation id="7886793013438592140">Impossible de lancer le processus de service.</translation>
-+<translation id="8417944620073548444"><ph name="MEGABYTES"/> Mo restants</translation>
-+<translation id="7339898014177206373">Nouvelle fenêtre</translation>
-+<translation id="3026202950002788510">Sélectionnez
-+ <ph name="BEGIN_BOLD"/>
-+ Applications &gt; Préférences système &gt; Réseau &gt; Avancé &gt; Proxys
-+ <ph name="END_BOLD"/>
-+ et désélectionnez les serveurs proxy sélectionnés.</translation>
-+<translation id="7033648024564583278">Gravure en cours d'initialisation...</translation>
-+<translation id="2246340272688122454">Téléchargement de l'image de récupération...</translation>
-+<translation id="7770995925463083016">il y a <ph name="NUMBER_TWO"/> minutes</translation>
-+<translation id="2816269189405906839">Mode de saisie du chinois (cangjie)</translation>
-+<translation id="7087282848513945231">Comté</translation>
-+<translation id="2149951639139208969">Ouvrir l'adresse dans un nouvel onglet</translation>
-+<translation id="175196451752279553">&amp;Rouvrir l'onglet fermé</translation>
-+<translation id="5992618901488170220">Impossible d'afficher la page Web, car votre ordinateur est passé en mode
-+ veille ou hibernation. Dans ce cas, les connexions réseau sont
-+ coupées et les requêtes réseau échouent. L'actualisation de la page
-+ devrait permettre de résoudre ce problème.</translation>
-+<translation id="2655386581175833247">Certificat utilisateur :</translation>
-+<translation id="5039804452771397117">Autoriser</translation>
-+<translation id="5435964418642993308">Appuyer sur Entrée pour revenir en arrière et sur la touche de menu contextuel pour afficher l'historique</translation>
-+<translation id="81686154743329117">ZRM</translation>
-+<translation id="7564146504836211400">Cookies et autres données</translation>
-+<translation id="2266011376676382776">Page(s) ne répondant pas</translation>
-+<translation id="2714313179822741882">Paramètres d'entrée hangûl</translation>
-+<translation id="8658163650946386262">Configurer la synchronisation...</translation>
-+<translation id="3100609564180505575">Modules (<ph name="TOTAL_COUNT"/>). Conflits connus : <ph name="BAD_COUNT"/>, conflits probables : <ph name="SUSPICIOUS_COUNT"/></translation>
-+<translation id="3627671146180677314">Date de renouvellement du certificat Netscape</translation>
-+<translation id="1319824869167805246">Ouvrir tous les favoris dans une nouvelle fenêtre</translation>
-+<translation id="8652487083013326477">bouton radio concernant l'étendue de pages</translation>
-+<translation id="5204967432542742771">Saisissez votre mot de passe</translation>
-+<translation id="4388712255200933062"><ph name="CLOUD_PRINT_NAME"/> est conçu pour rendre l'impression plus intuitive, accessible et utile. <ph name="CLOUD_PRINT_NAME"/> vous permet de rendre vos imprimantes accessibles depuis n'importe quelle application Web ou mobile associée à <ph name="CLOUD_PRINT_NAME"/>.</translation>
-+<translation id="2932611376188126394">Dictionnaire de kanji unique</translation>
-+<translation id="5485754497697573575">Rétablir tous les onglets</translation>
-+<translation id="3371861036502301517">Échec de l'installation de l'extension</translation>
-+<translation id="644038709730536388">En savoir plus sur la manière de se protéger des logiciels malveillants en ligne</translation>
-+<translation id="2155931291251286316">Toujours afficher les fenêtres pop-up de <ph name="HOST"/></translation>
-+<translation id="3445830502289589282">Authentification phase 2 :</translation>
-+<translation id="5650551054760837876">Aucun résultat de recherche trouvé</translation>
-+<translation id="5494362494988149300">Ouvrir une fois le téléchargement &amp;terminé</translation>
-+<translation id="2956763290572484660"><ph name="COOKIES"/> cookies</translation>
-+<translation id="6989836856146457314">Mode de saisie du japonais (pour clavier américain)</translation>
-+<translation id="9187787570099877815">Continuer à bloquer les plug-ins</translation>
-+<translation id="8425492902634685834">Épingler sur la barre des tâches</translation>
-+<translation id="825608351287166772">Les certificats ont une période de validité, comme tous les documents relatifs à votre identité (tel qu'un passeport). Le certificat présenté à votre navigateur n'est pas encore valide ! Lorsqu'un certificat est en dehors de sa période de validité, il n'est pas nécessaire d'assurer la maintenance de certaines informations relatives à son état (s'il a été révoqué ou s'il n'est plus approuvé). Par conséquent, il est impossible de vérifier que le certificat est fiable. Ne poursuivez pas.</translation>
-+<translation id="741630086309232721">Fermer la session d'invité</translation>
-+<translation id="7309459761865060639">Contrôlez vos tâches d'impression et l'état de connexion de vos imprimantes en ligne.</translation>
-+<translation id="4803909571878637176">Désinstallation</translation>
-+<translation id="5209518306177824490">Empreinte SHA-1</translation>
-+<translation id="3300768886937313568">Modifier le code PIN de la carte SIM</translation>
-+<translation id="7447657194129453603">État du réseau :</translation>
-+<translation id="1553538517812678578">sans limite</translation>
-+<translation id="7947315300197525319">(Choisir une autre capture d'écran)</translation>
-+<translation id="3612070600336666959">Désactivation</translation>
-+<translation id="3759461132968374835">Aucune erreur n'a été signalée récemment. Les erreurs n'apparaissent ici que lorsque l'envoi de rapports d'erreur est activé.</translation>
-+<translation id="1516602185768225813">Rouvrir les dernières pages ouvertes</translation>
-+<translation id="189210018541388520">Ouvrir en mode plein écran</translation>
-+<translation id="8795668016723474529">Ajouter une carte de paiement</translation>
-+<translation id="5860033963881614850">Désactivé</translation>
-+<translation id="3956882961292411849">Chargement des informations sur votre forfait Internet mobile, veuillez patienter...</translation>
-+<translation id="689050928053557380">Acheter un forfait de données...</translation>
-+<translation id="4235618124995926194">Inclure cet e-mail :</translation>
-+<translation id="4874539263382920044">Le titre doit comporter au moins un caractère.</translation>
-+<translation id="798525203920325731">Espaces de noms réseau</translation>
-+<translation id="263325223718984101"><ph name="PRODUCT_NAME"/> n'a pas pu terminer l'installation, mais va poursuivre son exécution à partir de son image disque.</translation>
-+<translation id="7025190659207909717">Gestion des services Internet mobiles</translation>
-+<translation id="8265096285667890932">Utiliser les onglets latéraux</translation>
-+<translation id="4250680216510889253">Non</translation>
-+<translation id="3949593566929137881">Saisir le code PIN de la carte SIM</translation>
-+<translation id="6291953229176937411">&amp;Afficher dans le Finder</translation>
-+<translation id="2476990193835943955">Maintenez la touche Ctrl, Alt ou Maj enfoncée&lt;br&gt;pour afficher le raccourci clavier qui lui est associé.</translation>
-+<translation id="9187827965378254003">Vraiment désolé, aucun prototype n'est disponible pour le moment.</translation>
-+<translation id="8933960630081805351">&amp;Afficher dans le Finder</translation>
-+<translation id="3041612393474885105">Informations relatives au certificat</translation>
-+<translation id="7378810950367401542">/</translation>
-+<translation id="4611079913162790275">La synchronisation des mots de passe requiert votre attention.</translation>
-+<translation id="6562758426028728553">Veuillez saisir l'ancien et le nouveau code PIN.</translation>
-+<translation id="614161640521680948">Langue :</translation>
-+<translation id="3665650519256633768">Résultats de recherche</translation>
-+<translation id="3733127536501031542">Serveur SSL avec fonction d'optimisation</translation>
-+<translation id="3614837889828516995">Enregistrer en PDF</translation>
-+<translation id="5745056705311424885">Mémoire USB détectée</translation>
-+<translation id="5895875028328858187">M'avertir lorsque le flux de données est faible ou presque inexistant</translation>
-+<translation id="939598580284253335">Saisir le mot de passe multiterme</translation>
-+<translation id="7917972308273378936">Clavier lituanien</translation>
-+<translation id="8371806639176876412">Les éléments saisis dans le champ polyvalent peuvent être enregistrés.</translation>
-+<translation id="4216499942524365685">Les informations de connexion à votre compte sont obsolètes. Cliquez ici pour saisir à nouveau votre mot de passe.</translation>
-+<translation id="8899388739470541164">Vietnamien</translation>
-+<translation id="4091434297613116013">feuilles de papier</translation>
-+<translation id="7475671414023905704">URL de mot de passe perdu Netscape</translation>
-+<translation id="3335947283844343239">Rouvrir l'onglet fermé</translation>
-+<translation id="4089663545127310568">Effacer les mots de passe enregistrés</translation>
-+<translation id="6500444002471948304">Créer un nouveau dossier...</translation>
-+<translation id="2480626392695177423">Basculer en mode ponctuation pleine chasse ou demi-chasse</translation>
-+<translation id="5830410401012830739">Gérer les paramètres de localisation...</translation>
-+<translation id="8977410484919641907">Synchronisé...</translation>
-+<translation id="2794293857160098038">Options de recherche par défaut</translation>
-+<translation id="3947376313153737208">Aucune sélection</translation>
-+<translation id="1346104802985271895">Mode de saisie du vietnamien (TELEX)</translation>
-+<translation id="5935630983280450497"><ph name="NUMBER_ONE"/> minute restante</translation>
-+<translation id="5889282057229379085">Le nombre maximal d'autorités de certification intermédiaires a été dépassé : <ph name="NUM_INTERMEDIATE_CA"/></translation>
-+<translation id="3180365125572747493">Saisissez un mot de passe pour chiffrer ce fichier de certificat.</translation>
-+<translation id="5496587651328244253">Organiser</translation>
-+<translation id="4821086771593057290">Votre mot de passe a changé. Veuillez réessayer avec votre nouveau mot de passe.</translation>
-+<translation id="7075513071073410194">PKCS #1 MD5 avec chiffrement RSA</translation>
-+<translation id="4378727699507047138">Utiliser le thème classique</translation>
-+<translation id="7124398136655728606">Échap efface toute la mémoire tampon de pré-édition</translation>
-+<translation id="8293206222192510085">Ajouter aux favoris</translation>
-+<translation id="2592884116796016067">Un incident est survenu sur une partie de cette page (HTML WebWorker). Elle risque de ne pas fonctionner correctement.</translation>
-+<translation id="2529133382850673012">Clavier américain</translation>
-+<translation id="4411578466613447185">Signataire de code</translation>
-+<translation id="1354868058853714482">Adobe Reader n'est pas à jour et risque de ne plus être sécurisé.</translation>
-+<translation id="6252594924928912846">Personnaliser les paramètres de synchronisation...</translation>
-+<translation id="8425755597197517046">Co&amp;ller et rechercher</translation>
-+<translation id="1093148655619282731">Détails du certificat sélectionné :</translation>
-+<translation id="5568069709869097550">Impossible de se connecter</translation>
-+<translation id="2743322561779022895">Activation :</translation>
-+<translation id="4181898366589410653">Système de révocation introuvable dans le certificat du serveur</translation>
-+<translation id="8705331520020532516">Numéro de série</translation>
-+<translation id="1665770420914915777">Afficher la page &quot;Nouvel onglet&quot;</translation>
-+<translation id="2629089419211541119">il y a <ph name="NUMBER_ONE"/> heure</translation>
-+<translation id="1691063574428301566">Votre ordinateur redémarrera une fois la mise à jour effectuée.</translation>
-+<translation id="131364520783682672">Verr. maj.</translation>
-+<translation id="6259308910735500867">L'accès au répertoire de l'hôte de communication à distance a été refusé. Essayez avec un autre compte.</translation>
-+<translation id="3415261598051655619">Accessible aux scripts :</translation>
-+<translation id="2335122562899522968">Cette page place des cookies.</translation>
-+<translation id="8461914792118322307">Proxy</translation>
-+<translation id="4089521618207933045">Avec sous-menu</translation>
-+<translation id="1936157145127842922">Afficher dans le dossier</translation>
-+<translation id="6982279413068714821">il y a <ph name="NUMBER_DEFAULT"/> minutes</translation>
-+<translation id="7977590112176369853">&lt;saisir une requête&gt;</translation>
-+<translation id="3449839693241009168">Appuyez sur <ph name="SEARCH_KEY"/> pour envoyer des commandes à <ph name="EXTENSION_NAME"/>.</translation>
-+<translation id="7443484992065838938">Prévisualiser le rapport</translation>
-+<translation id="5714678912774000384">Activer le dernier onglet</translation>
-+<translation id="3799598397265899467">Lorsque je quitte le navigateur</translation>
-+<translation id="2125314715136825419">Continuer sans mettre à jour Adobe Reader (non recommandé)</translation>
-+<translation id="1120026268649657149">Le champ de mot clé doit être vide ou comporter un mot unique</translation>
-+<translation id="542318722822983047">Déplacer le curseur automatiquement au caractère suivant</translation>
-+<translation id="5317780077021120954">Enregistrer</translation>
-+<translation id="9027459031423301635">Ouvrir le lien dans un nouvel ongle&amp;t</translation>
-+<translation id="2251809247798634662">Nouvelle fenêtre de navigation privée</translation>
-+<translation id="358344266898797651">Celtique</translation>
-+<translation id="3625870480639975468">Réinitialiser le zoom</translation>
-+<translation id="5199729219167945352">Prototypes</translation>
-+<translation id="5055518462594137986">Mémoriser mes choix pour tous les liens de ce type</translation>
-+<translation id="246059062092993255">Les plug-ins de cette page ont été bloqués.</translation>
-+<translation id="2870560284913253234">Site</translation>
-+<translation id="6945221475159498467">Sélectionner</translation>
-+<translation id="7724603315864178912">Couper</translation>
-+<translation id="4164507027399414915">Restaurer toutes les miniatures supprimées</translation>
-+<translation id="917051065831856788">Utiliser les onglets latéraux</translation>
-+<translation id="1976150099241323601">Se connecter au dispositif de sécurité</translation>
-+<translation id="6620110761915583480">Enregistrer le fichier</translation>
-+<translation id="4988526792673242964">Pages</translation>
-+<translation id="7543025879977230179">Options de <ph name="PRODUCT_NAME"/></translation>
-+<translation id="2175607476662778685">Barre de lancement rapide</translation>
-+<translation id="6434309073475700221">Annuler</translation>
-+<translation id="1367951781824006909">Choisir un fichier</translation>
-+<translation id="1425127764082410430">&amp;Rechercher <ph name="SEARCH_TERMS"/> avec <ph name="SEARCH_ENGINE"/></translation>
-+<translation id="684265517037058883">(pas encore valide)</translation>
-+<translation id="2027538664690697700">Mettre à jour le plug-in...</translation>
-+<translation id="8205333955675906842">Police Sans-Serif</translation>
-+<translation id="39964277676607559">Impossible de charger le JavaScript &quot;<ph name="RELATIVE_PATH"/>&quot; du script de contenu.</translation>
-+<translation id="4378551569595875038">Connexion...</translation>
-+<translation id="7029809446516969842">Mots de passe</translation>
-+<translation id="8053278772142718589">Fichiers PKCS #12</translation>
-+<translation id="3129020372442395066">Options de saisie automatique de <ph name="PRODUCT_NAME_SHORT"/></translation>
-+<translation id="4114360727879906392">Fenêtre précédente</translation>
-+<translation id="8238649969398088015">Astuce</translation>
-+<translation id="5958418293370246440"><ph name="SAVED_FILES"/> / <ph name="TOTAL_FILES"/> fichiers</translation>
-+<translation id="2350172092385603347">Localisation utilisée, mais les paramètres régionaux par défaut (default_locale) n'ont pas été indiqués dans le manifeste. </translation>
-+<translation id="8221729492052686226">Si vous n'êtes pas à l'origine de cette requête, il s'agit probablement d'une attaque contre votre système. Si vous n'avez pas lancé cette requête de manière intentionnelle, cliquez sur Ne rien faire.</translation>
-+<translation id="5894314466642127212">Votre commentaire a bien été envoyé.</translation>
-+<translation id="894360074127026135">Fonction d'optimisation internationale Netscape </translation>
-+<translation id="6025294537656405544">Taille de police minimale</translation>
-+<translation id="1201402288615127009">Suivant</translation>
-+<translation id="1335588927966684346">Utilitaire :</translation>
-+<translation id="7857823885309308051">Cette opération peut prendre une minute...</translation>
-+<translation id="662870454757950142">Le format du mot de passe est incorrect.</translation>
-+<translation id="370665806235115550">Chargement...</translation>
-+<translation id="1808792122276977615">Ajouter la page...</translation>
-+<translation id="2076269580855484719">Masquer ce plug-in</translation>
-+<translation id="254416073296957292">&amp;Paramètres linguistiques...</translation>
-+<translation id="6652975592920847366">Créer un support de récupération du système d'exploitation</translation>
-+<translation id="52912272896845572">Le fichier de clé privée est incorrect.</translation>
-+<translation id="3232318083971127729">Valeur :</translation>
-+<translation id="8807632654848257479">Stable</translation>
-+<translation id="4209092469652827314">Grande</translation>
-+<translation id="4222982218026733335">Certificat serveur invalide</translation>
-+<translation id="152234381334907219">Jamais enregistrés</translation>
-+<translation id="5600599436595580114">Cette page a été préchargée.</translation>
-+<translation id="8926468725336609312">Google Chrome ne peut pas afficher l'aperçu avant impression lorsque la visionneuse de documents PDF intégrée est désactivée. Pour l'afficher, veuillez accéder à <ph name="BEGIN_LINK"/>chrome://plugins<ph name="END_LINK"/>, activer &quot;Chrome PDF Viewer&quot; et réessayer.</translation>
-+<translation id="8494214181322051417">Nouveau !</translation>
-+<translation id="7762841930144642410"><ph name="BEGIN_BOLD"/>Vous êtes passé en navigation privée<ph name="END_BOLD"/>. Les pages que vous consultez dans cette fenêtre n'apparaîtront ni dans l'historique de votre navigateur, ni dans l'historique des recherches, et ne laisseront aucune trace (comme les cookies) sur votre ordinateur une fois que vous aurez fermé la fenêtre de navigation privée. Tous les fichiers téléchargés et les favoris créés seront toutefois conservés. <ph name="LINE_BREAK"/> <ph name="BEGIN_BOLD"/>Passer en navigation privée n'a aucun effet sur les autres utilisateurs, serveurs ou logiciels. Méfiez-vous :<ph name="END_BOLD"/> <ph name="BEGIN_LIST"/> <ph name="BEGIN_LIST_ITEM"/>Des sites Web qui collectent ou partagent des informations vous concernant<ph name="END_LIST_ITEM"/> <ph name="BEGIN_LIST_ITEM"/>Des fournisseurs d'accès Internet ou des employeurs qui conservent une trace des pages que vous visitez<ph name="END_LIST_ITEM"/> <ph name="BEGIN_LIST_ITEM"/>Des programmes indésirables qui enregistrent vos frappes en échange d'émoticônes gratuites<ph name="END_LIST_ITEM"/> <ph name="BEGIN_LIST_ITEM"/>Des personnes qui pourraient surveiller vos activités<ph name="END_LIST_ITEM"/> <ph name="BEGIN_LIST_ITEM"/>Des personnes qui se tiennent derrière vous<ph name="END_LIST_ITEM"/> <ph name="END_LIST"/> <ph name="BEGIN_LINK"/>En savoir plus sur la navigation privée<ph name="END_LINK"/></translation>
-+<translation id="2386255080630008482">Le certificat du serveur a été révoqué.</translation>
-+<translation id="2135787500304447609">&amp;Reprendre</translation>
-+<translation id="8309505303672555187">Sélectionnez un réseau :</translation>
-+<translation id="6143635259298204954">Impossible d'extraire les fichiers de l'extension. Pour effectuer cette opération en toute sécurité, vous devez disposer d'un chemin d'accès à votre répertoire de profils ne contenant pas de lien symbolique. Aucun chemin de ce type n'existe pour votre profil.</translation>
-+<translation id="1813414402673211292">Effacer les données de navigation</translation>
-+<translation id="4062903950301992112">Si vous êtes conscient que la visite de ce site peut être préjudiciable à votre ordinateur, vous pouvez <ph name="PROCEED_LINK"/>.</translation>
-+<translation id="32330993344203779">Votre périphérique est inscrit pour bénéficier de la gestion d'entreprise.</translation>
-+<translation id="2356762928523809690">Serveur de mise à jour non disponible (erreur : <ph name="ERROR_NUMBER"/>)</translation>
-+<translation id="219008588003277019">Module client natif : <ph name="NEXE_NAME"/></translation>
-+<translation id="5436510242972373446">Rechercher sur <ph name="SITE_NAME"/> :</translation>
-+<translation id="3800764353337460026">Style de symboles</translation>
-+<translation id="6719684875142564568"><ph name="NUMBER_ZERO"/> hours</translation>
-+<translation id="2096368010154057602">Département</translation>
-+<translation id="1036561994998035917">Continuer à utiliser <ph name="ENGINE_NAME"/></translation>
-+<translation id="8730621377337864115">OK</translation>
-+<translation id="665757950158579497">Essayez de désactiver les prédictions d'actions du réseau en procédant comme suit :
-+ Sélectionnez le
-+ <ph name="BEGIN_BOLD"/>
-+ menu clé à molette &gt;
-+ <ph name="SETTINGS_TITLE"/>
-+ &gt;
-+ <ph name="ADVANCED_TITLE"/>
-+ <ph name="END_BOLD"/>
-+ et désélectionnez &quot;<ph name="NO_PREFETCH_DESCRIPTION"/>&quot;.
-+ Si le problème n'est pas résolu, nous vous conseillons de sélectionner de nouveau
-+ cette option pour améliorer les performances.</translation>
-+<translation id="4932733599132424254">Date</translation>
-+<translation id="6267166720438879315">Sélectionnez un certificat pour vous authentifier sur <ph name="HOST_NAME"/>.</translation>
-+<translation id="2422927186524098759">Barre latérale</translation>
-+<translation id="7839809549045544450">La clé publique éphémère Diffie-Hellman associée au serveur est peu sûre.</translation>
-+<translation id="5515806255487262353">Rechercher dans Dictionnaire</translation>
-+<translation id="350048665517711141">Sélectionnez un moteur de recherche</translation>
-+<translation id="2790805296069989825">Clavier russe</translation>
-+<translation id="5708171344853220004">Nom Microsoft principal</translation>
-+<translation id="5464696796438641524">Clavier polonais</translation>
-+<translation id="2080010875307505892">Clavier serbe</translation>
-+<translation id="2953767478223974804"><ph name="NUMBER_ONE"/> minute</translation>
-+<translation id="201192063813189384">Erreur lors de la lecture des données du cache.</translation>
-+<translation id="7851768487828137624">Canary</translation>
-+<translation id="6129938384427316298">Commentaire du certificat Netscape</translation>
-+<translation id="8210608804940886430">Page suivante</translation>
-+<translation id="9065596142905430007"><ph name="PRODUCT_NAME"/> est à jour.</translation>
-+<translation id="1035650339541835006">Paramètres de saisie automatique...</translation>
-+<translation id="6315493146179903667">Tout ramener au premier plan</translation>
-+<translation id="1000498691615767391">Sélectionner le dossier à ouvrir</translation>
-+<translation id="3593152357631900254">Activer le mode Pinyin fuzzy</translation>
-+<translation id="5015344424288992913">Résolution du proxy...</translation>
-+<translation id="8506299468868975633">Le téléchargement de l'image a été interrompu.</translation>
-+<translation id="4724168406730866204">Eten 26</translation>
-+<translation id="308268297242056490">URI</translation>
-+<translation id="4479812471636796472">Clavier Dvorak américain</translation>
-+<translation id="8673026256276578048">Rechercher sur le Web...</translation>
-+<translation id="1437307674059038925">Si vous utilisez un serveur proxy, vérifiez les paramètres associés ou demandez à votre administrateur réseau
-+ si ce serveur fonctionne.</translation>
-+<translation id="149347756975725155">Impossible de charger l'icône de l'extension &quot;<ph name="ICON"/>&quot;.</translation>
-+<translation id="3675321783533846350">Définir un proxy pour se connecter au réseau</translation>
-+<translation id="5451285724299252438">zone de texte concernant l'étendue de pages</translation>
-+<translation id="5669267381087807207">Activation</translation>
-+<translation id="7434823369735508263">Clavier Dvorak britannique</translation>
-+<translation id="1572103024875503863"><ph name="NUMBER_MANY"/> jours</translation>
-+<translation id="2084978867795361905">MS-IME</translation>
-+<translation id="7227669995306390694">Aucun forfait de données <ph name="NETWORK"/></translation>
-+<translation id="3481915276125965083">Les fenêtres pop-up suivantes ont été bloquées sur cette page :</translation>
-+<translation id="7163503212501929773"><ph name="NUMBER_MANY"/> heures restantes</translation>
-+<translation id="7705276765467986571">Impossible de charger le modèle du favori.</translation>
-+<translation id="1196338895211115272">Échec d'exportation de la clé privée</translation>
-+<translation id="5586329397967040209">Utiliser comme page d'accueil</translation>
-+<translation id="629730747756840877">Compte</translation>
-+<translation id="8525306231823319788">Plein écran</translation>
-+<translation id="9054208318010838">Autoriser tous les sites à suivre ma position géographique</translation>
-+<translation id="3058212636943679650">Si la restauration du système d'exploitation de votre ordinateur s'avère nécessaire, une carte SD ou une clé USB de récupération vous sera demandée.</translation>
-+<translation id="2815382244540487333">Les cookies suivants ont été bloqués :</translation>
-+<translation id="8882395288517865445">Inclure les adresses de ma fiche de Carnet d’adresses</translation>
-+<translation id="374530189620960299">Le certificat de sécurité du site n'est pas approuvé !</translation>
-+<translation id="8852407435047342287">Votre liste d'applications, d'extensions et de thèmes installés</translation>
-+<translation id="5647283451836752568">Exécuter tous les plug-ins de cette page</translation>
-+<translation id="8642947597466641025">Augmente la taille du texte</translation>
-+<translation id="5188181431048702787">Accepter et continuer »</translation>
-+<translation id="1293556467332435079">Fichiers
-+</translation>
-+<translation id="2490270303663597841">Appliquer uniquement à cette session de navigation privée</translation>
-+<translation id="1757915090001272240">Latin large</translation>
-+<translation id="8496717697661868878">Exécuter ce plug-in</translation>
-+<translation id="3450660100078934250">MasterCard</translation>
-+<translation id="2916073183900451334">Sur le Web, Tab permet de sélectionner les liens, ainsi que les champs de formulaire.</translation>
-+<translation id="7772127298218883077">À propos de <ph name="PRODUCT_NAME"/></translation>
-+<translation id="2090876986345970080">Paramètres de sécurité du système</translation>
-+<translation id="9219103736887031265">Images</translation>
-+<translation id="5453632173748266363">Cyrillique</translation>
-+<translation id="1008557486741366299">Pas maintenant</translation>
-+<translation id="8415351664471761088">Attendre la fin du téléchargement</translation>
-+<translation id="1545775234664667895">Thème &quot;<ph name="THEME_NAME"/>&quot; installé</translation>
-+<translation id="5329858601952122676">&amp;Supprimer</translation>
-+<translation id="6100736666660498114">Menu Démarrer</translation>
-+<translation id="3994878504415702912">&amp;Zoom</translation>
-+<translation id="9009369504041480176">Transfert en cours (<ph name="PROGRESS_PERCENT"/> %)...</translation>
-+<translation id="8995603266996330174">Géré par <ph name="DOMAIN"/></translation>
-+<translation id="5602600725402519729">&amp;Rafraîchir</translation>
-+<translation id="172612876728038702">Configuration du module de plate-forme sécurisée (TPM) en cours. Veuillez patienter, cela peut prendre quelques minutes.</translation>
-+<translation id="1362165759943288856">Vous avez acheté une quantité illimitée de données le <ph name="DATE"/>.</translation>
-+<translation id="2078019350989722914">Confirmer avant de quitter (<ph name="KEY_EQUIVALENT"/>)</translation>
-+<translation id="7965010376480416255">Mémoire partagée</translation>
-+<translation id="6248988683584659830">Rech. dans les paramètres</translation>
-+<translation id="8323232699731382745">mot de passe d'accès au réseau</translation>
-+<translation id="6588399906604251380">Activer la vérification orthographique</translation>
-+<translation id="7167621057293532233">Types de données</translation>
-+<translation id="7053983685419859001">Bloquer</translation>
-+<translation id="2485056306054380289">Certificat de l'autorité de certification du serveur :</translation>
-+<translation id="6462109140674788769">Clavier grec</translation>
-+<translation id="2727712005121231835">Taille réelle</translation>
-+<translation id="8887733174653581061">Toujours en haut</translation>
-+<translation id="5581211282705227543">Aucun plug-in installé.</translation>
-+<translation id="610886263749567451">Alerte JavaScript</translation>
-+<translation id="5488468185303821006">Autoriser en mode navigation privée</translation>
-+<translation id="6556866813142980365">Rétablir</translation>
-+<translation id="2107287771748948380"><ph name="OBFUSCATED_CC_NUMBER"/>, expire le : <ph name="CC_EXPIRATION_DATE"/></translation>
-+<translation id="6584811624537923135">Confirmer la désinstallation</translation>
-+<translation id="7429235532957570505">Impossible de désactiver les plug-ins ayant été activés par une stratégie d'entreprise.</translation>
-+<translation id="7866522434127619318">Cette fonctionnalité active l'option &quot;Lire en un clic&quot; dans les paramètres de contenu du plug-in.</translation>
-+<translation id="8860923508273563464">Attendre la fin des téléchargements</translation>
-+<translation id="6406506848690869874">Synchronisation</translation>
-+<translation id="5288678174502918605">&amp;Rouvrir l'onglet fermé</translation>
-+<translation id="7238461040709361198">Votre mot de passe de compte Google a changé depuis votre dernière connexion à partir de cet ordinateur.</translation>
-+<translation id="1956050014111002555">Le fichier contenait plusieurs certificats, aucun d'eux n'a été importé :</translation>
-+<translation id="302620147503052030">Afficher le bouton</translation>
-+<translation id="5512074755152723588">La saisie dans le champ polyvalent d'une URL déjà ouverte dans un autre onglet entraîne l'affichage de l'onglet en question, et non l'affichage de l'URL dans l'onglet actuel.</translation>
-+<translation id="9157595877708044936">Configuration en cours...</translation>
-+<translation id="4475552974751346499">Rechercher dans les téléchargements</translation>
-+<translation id="3021256392995617989">Me demander lorsqu'un site tente de suivre ma position géographique (recommandé)</translation>
-+<translation id="5185386675596372454">La nouvelle version de &quot;<ph name="EXTENSION_NAME"/>&quot; a été désactivée, car elle nécessite davantage d'autorisations.</translation>
-+<translation id="4285669636069255873">Clavier phonétique russe</translation>
-+<translation id="4148925816941278100">American Express</translation>
-+<translation id="2320435940785160168">Ce serveur exige un certificat d'authentification et n'a pas accepté celui envoyé par le navigateur.
-+Votre certificat a peut-être expiré ou le serveur n'a pas approuvé l'émetteur.
-+Réessayez avec un autre certificat si vous en avez un.
-+Sinon, vous devrez en obtenir un nouveau d'un autre émetteur.</translation>
-+<translation id="6295228342562451544">Lorsque vous vous connectez à un site Web sécurisé, le serveur hébergeant ce site présente à votre navigateur un &quot;certificat&quot; afin de vérifier l'identité du site. Ce certificat contient des informations d'identité, telles que l'adresse du site Web, laquelle est vérifiée par un tiers approuvé par votre ordinateur. En vérifiant que l'adresse du certificat correspond à l'adresse du site Web, il est possible de s'assurer que vous êtes connecté de façon sécurisée avec le site Web souhaité et non pas avec un tiers (tel qu'un pirate informatique sur votre réseau).</translation>
-+<translation id="6342069812937806050">À l'instant</translation>
-+<translation id="5605716740717446121">Votre carte SIM sera définitivement désactivée si vous ne saisissez pas correctement la clé de déverrouillage du code PIN. Nombre de tentatives restantes : <ph name="TRIES_COUNT"/></translation>
-+<translation id="8836712291807476944"><ph name="SAVED_BYTES"/> / <ph name="TOTAL_BYTES"/> octets, Interrompu</translation>
-+<translation id="5502500733115278303">Importés depuis Firefox</translation>
-+<translation id="569109051430110155">Détection automatique</translation>
-+<translation id="4408599188496843485">&amp;Aide</translation>
-+<translation id="5399158067281117682">Les codes PIN sont différents !</translation>
-+<translation id="8494234776635784157">Contenu Web</translation>
-+<translation id="2681441671465314329">Vider le cache</translation>
-+<translation id="3646789916214779970">Rétablir le thème par défaut</translation>
-+<translation id="1592960452683145077">Le service de communication à distance a démarré correctement. Vous devriez maintenant pouvoir vous connecter à distance à cet ordinateur.</translation>
-+<translation id="1679068421605151609">Outils de développement</translation>
-+<translation id="6648524591329069940">Police Serif</translation>
-+<translation id="6896758677409633944">Copier</translation>
-+<translation id="5260508466980570042">Adresse e-mail ou mot de passe incorrect. Veuillez réessayer.</translation>
-+<translation id="7887998671651498201">Le plug-in suivant ne répond pas : souhaitez-vous interrompre <ph name="PLUGIN_NAME"/> ?</translation>
-+<translation id="173188813625889224">Sens</translation>
-+<translation id="8088823334188264070"><ph name="NUMBER_MANY"/> secondes</translation>
-+<translation id="1337036551624197047">Clavier tchèque</translation>
-+<translation id="4212108296677106246">Voulez-vous que &quot;<ph name="CERTIFICATE_NAME"/>&quot; soit considérée comme une autorité de certification fiable ?</translation>
-+<translation id="2861941300086904918">Gestionnaire de sécurité natif du client</translation>
-+<translation id="6991443949605114807">&lt;p&gt;Lorsque vous exécutez <ph name="PRODUCT_NAME"/> dans un environnement de bureau pris en charge, les paramètres proxy du système sont utilisés. Toutefois, soit votre système n'est pas pris en charge, soit un problème est survenu lors du lancement de votre configuration système.&lt;/p&gt;
-+
-+ &lt;p&gt;Vous avez toujours la possibilité d'effectuer la configuration via la ligne de commande. Pour plus d'informations sur les indicateurs et les variables d'environnement, veuillez vous reporter à &lt;code&gt;man <ph name="PRODUCT_BINARY_NAME"/>&lt;/code&gt;.&lt;/p&gt;</translation>
-+<translation id="9071590393348537582">La page Web à l'adresse <ph name="URL"/> a déclenché trop de redirections. Pour résoudre le problème, effacez les cookies de ce site ou autorisez les cookies tiers. Si le problème persiste, il peut être dû à une mauvaise configuration du serveur et n'être aucunement lié à votre ordinateur.</translation>
-+<translation id="7205869271332034173">SSID :</translation>
-+<translation id="7084579131203911145">Nom du forfait :</translation>
-+<translation id="5815645614496570556">Adresse X.400</translation>
-+<translation id="3551320343578183772">Fermer l'onglet</translation>
-+<translation id="3345886924813989455">Impossible de trouver un navigateur pris en charge.</translation>
-+<translation id="74354239584446316">Le compte associé à la boutique en ligne est le suivant : <ph name="EMAIL_ADDRESS"/>. L'utilisation d'un autre compte pour la synchronisation provoque des erreurs.</translation>
-+<translation id="3712897371525859903">Enregistrer la p&amp;age sous...</translation>
-+<translation id="7926251226597967072"><ph name="PRODUCT_NAME"/> importe actuellement les éléments suivants à partir de <ph name="IMPORT_BROWSER_NAME"/> :</translation>
-+<translation id="2767649238005085901">Appuyez sur Entrée pour avancer et sur la touche de menu contextuel pour afficher l'historique</translation>
-+<translation id="8580634710208701824">Actualiser le cadre</translation>
-+<translation id="1018656279737460067">Annulé</translation>
-+<translation id="7606992457248886637">Autorités</translation>
-+<translation id="707392107419594760">Sélectionnez votre clavier :</translation>
-+<translation id="2007404777272201486">Signaler un problème...</translation>
-+<translation id="2390045462562521613">Ignorer ce réseau</translation>
-+<translation id="3348038390189153836">Nouveau matériel détecté</translation>
-+<translation id="1666788816626221136">Vous disposez de certificats qui n'appartiennent à aucune autre catégorie :</translation>
-+<translation id="4821935166599369261">&amp;Profilage activé</translation>
-+<translation id="1603914832182249871">(Navigation privée)</translation>
-+<translation id="7910768399700579500">&amp;Nouveau dossier</translation>
-+<translation id="7472639616520044048">Types MIME :</translation>
-+<translation id="2307164895203900614">Afficher les pages en arrière-plan (<ph name="NUM_BACKGROUND_APPS"/>)</translation>
-+<translation id="3192947282887913208">Fichiers audio</translation>
-+<translation id="6295535972717341389">Plug-ins</translation>
-+<translation id="8116190140324504026">Plus d'informations...</translation>
-+<translation id="7469894403370665791">Se connecter automatiquement à ce réseau</translation>
-+<translation id="4807098396393229769">Titulaire de la carte</translation>
-+<translation id="4094130554533891764">Elle peut désormais accéder à :</translation>
-+<translation id="4131410914670010031">Noir et blanc</translation>
-+<translation id="3800503346337426623">Ignorer la connexion et naviguer en tant qu'invité</translation>
-+<translation id="2615413226240911668">Toutefois, cette page inclut d'autres ressources qui ne sont pas sécurisées. Ces ressources peuvent être consultées par des tiers pendant leur transfert, et modifiées par un pirate informatique dans le but de changer l'aspect et le comportement de cette page.</translation>
-+<translation id="5880867612172997051">Accès réseau interrompu</translation>
-+<translation id="7842346819602959665">La dernière version de l'extension &quot;<ph name="EXTENSION_NAME"/>&quot; requiert d'autres permissions. Elle a donc été désactivée.</translation>
-+<translation id="3776667127601582921">Dans ce cas, le certificat du serveur ou un certificat d'autorité intermédiaire présenté à votre navigateur n'est pas valide. Cela peut signifier que le certificat est incorrect, qu'il contient des champs non valides ou qu'il n'est pas compatible.</translation>
-+<translation id="2412835451908901523">Veuillez saisir la clé de déverrouillage du code PIN à 8 chiffres fournie par <ph name="CARRIER_ID"/>.</translation>
-+<translation id="6979448128170032817">Exceptions...</translation>
-+<translation id="7584802760054545466">Connexion à <ph name="NETWORK_ID"/></translation>
-+<translation id="208047771235602537">Voulez-vous vraiment quitter <ph name="PRODUCT_NAME"/> alors qu'un téléchargement est en cours ?</translation>
-+<translation id="4060383410180771901">Le site Web ne parvient pas à gérer la demande associée à <ph name="URL"/>.</translation>
-+<translation id="6710213216561001401">Précédent</translation>
-+<translation id="1108600514891325577">&amp;Arrêter</translation>
-+<translation id="6035087343161522833">Lorsque l'option permettant de bloquer l'enregistrement des cookies tiers est activée, la lecture de ces cookies est également bloquée.</translation>
-+<translation id="8619892228487928601"><ph name="CERTIFICATE_NAME"/> : <ph name="ERROR"/></translation>
-+<translation id="1567993339577891801">Console JavaScript</translation>
-+<translation id="1548132948283577726">Les sites pour lesquels vos mots de passe ne seront jamais enregistrés s'afficheront ici.</translation>
-+<translation id="583281660410589416">Inconnu</translation>
-+<translation id="3774278775728862009">Mode de saisie du thaï (clavier TIS-820.2538)</translation>
-+<translation id="9115675100829699941">&amp;Favoris</translation>
-+<translation id="2485422356828889247">Désinstaller</translation>
-+<translation id="2621889926470140926">Voulez-vous vraiment quitter <ph name="PRODUCT_NAME"/> alors que <ph name="DOWNLOAD_COUNT"/> téléchargements sont en cours ?</translation>
-+<translation id="7279701417129455881">Configurer le blocage des cookies...</translation>
-+<translation id="1166359541137214543">ABC</translation>
-+<translation id="5412713837047574330">L'application hébergée par <ph name="HOST_NAME"/> est inaccessible, car vous êtes déconnecté du réseau. Cette page s'affichera dès que la connexion réseau sera rétablie. &lt;br&gt;</translation>
-+<translation id="5528368756083817449">Gestionnaire de favoris</translation>
-+<translation id="7275974018215686543"><ph name="NUMBER_MANY"/> secs ago</translation>
-+<translation id="215753907730220065">Quitter le mode plein écran</translation>
-+<translation id="7849264908733290972">Ouvrir l'&amp;image dans un nouvel onglet</translation>
-+<translation id="1560991001553749272">Favori ajouté !</translation>
-+<translation id="3966072572894326936">Choisir un autre dossier...</translation>
-+<translation id="8766796754185931010">Kotoeri</translation>
-+<translation id="7781829728241885113">Hier</translation>
-+<translation id="2762402405578816341">Synchroniser automatiquement les éléments suivants :</translation>
-+<translation id="1623661092385839831">Votre ordinateur intègre un périphérique de sécurité TPM (module de plate-forme sécurisée) qui permet de mettre en œuvre plusieurs fonctionnalités de sécurité critiques dans Google Chrome OS.</translation>
-+<translation id="3359256513598016054">Contraintes des stratégies de certificat</translation>
-+<translation id="4433914671537236274">Créer un support de récupération</translation>
-+<translation id="4509345063551561634">Emplacement :</translation>
-+<translation id="7596288230018319236">Toutes les pages que vous consultez apparaîtront ici à moins que vous ne les ouvriez dans une fenêtre en navigation privée. Vous pouvez utiliser le bouton Rechercher de cette page pour rechercher dans toutes les pages de votre historique.</translation>
-+<translation id="7434509671034404296">Options pour les développeurs</translation>
-+<translation id="6447842834002726250">Cookies</translation>
-+<translation id="2609371827041010694">Toujours exécuter pour ce site</translation>
-+<translation id="5170568018924773124">Afficher le dossier</translation>
-+<translation id="883848425547221593">Autres favoris</translation>
-+<translation id="6054173164583630569">Clavier français</translation>
-+<translation id="4870177177395420201"><ph name="PRODUCT_NAME"/> ne parvient pas à déterminer ou à définir le navigateur par défaut.</translation>
-+<translation id="8898786835233784856">Sélectionner l'onglet suivant</translation>
-+<translation id="2674170444375937751">Voulez-vous vraiment supprimer ces pages de votre historique ?</translation>
-+<translation id="9111102763498581341">Déverrouiller</translation>
-+<translation id="289695669188700754">ID de clé : <ph name="KEY_ID"/></translation>
-+<translation id="3067198360141518313">Exécuter ce plug-in</translation>
-+<translation id="8767072502252310690">Utilisateurs</translation>
-+<translation id="683526731807555621">Ajouter un moteur</translation>
-+<translation id="6871644448911473373">Répondeur OCSP : <ph name="LOCATION"/></translation>
-+<translation id="8281886186245836920">Ignorer</translation>
-+<translation id="3867944738977021751">Champs de certificat</translation>
-+<translation id="2114224913786726438">Modules (<ph name="TOTAL_COUNT"/>) : aucun conflit détecté.</translation>
-+<translation id="7629827748548208700">Onglet : <ph name="TAB_NAME"/></translation>
-+<translation id="388442998277590542">Impossible de charger la page d'options &quot;<ph name="OPTIONS_PAGE"/>&quot;.</translation>
-+<translation id="8449008133205184768">Coller en adaptant le style</translation>
-+<translation id="9114223350847410618">Veuillez ajouter une autre langue avant de supprimer celle-ci.</translation>
-+<translation id="4408427661507229495">nom du réseau</translation>
-+<translation id="8886960478266132308"><ph name="PRODUCT_NAME"/> synchronise de manière sécurisée vos données avec votre compte Google.</translation>
-+<translation id="8028993641010258682">Taille</translation>
-+<translation id="5031603669928715570">Activer...</translation>
-+<translation id="1383876407941801731">Recherche</translation>
-+<translation id="8398877366907290961">Poursuivre quand même</translation>
-+<translation id="5063180925553000800">Nouveau code PIN :</translation>
-+<translation id="2496540304887968742">La capacité du périphérique doit être d'au moins 4 Go.</translation>
-+<translation id="6974053822202609517">De droite à gauche</translation>
-+<translation id="2370882663124746154">Activer le mode Pinyin double</translation>
-+<translation id="5463856536939868464">Menu contenant des favoris masqués</translation>
-+<translation id="8286227656784970313">Utiliser le dictionnaire système</translation>
-+<translation id="5431084084184068621">Vous avez choisi de chiffrer les données à l'aide de votre mot de passe Google. Vous pouvez modifier vos paramètres de synchronisation à tout moment, si vous changez d'avis.</translation>
-+<translation id="1493263392339817010">Personnaliser les polices...</translation>
-+<translation id="5352033265844765294">Enregistrement des informations de date</translation>
-+<translation id="6449085810994685586">&amp;Vérifier l'orthographe du texte de ce champ</translation>
-+<translation id="3621320549246006887">Ceci est un modèle expérimental qui permet aux enregistrements DNS (utilisant le protocole de sécurité DNSSEC) d'autoriser ou de refuser des certificats HTTPS. Ce message s'affiche lorsque vous activez des fonctionnalités expérimentales via des options de ligne de commande. Vous pouvez supprimer ces options de ligne de commande pour ignorer cette erreur.</translation>
-+<translation id="50960180632766478"><ph name="NUMBER_FEW"/> minutes restantes</translation>
-+<translation id="3174168572213147020">Île</translation>
-+<translation id="748138892655239008">Contraintes de base du certificat</translation>
-+<translation id="457386861538956877">Autres...</translation>
-+<translation id="8063491445163840780">Activer l'onglet 4</translation>
-+<translation id="5966654788342289517">Données personnelles</translation>
-+<translation id="9137013805542155359">Afficher l'original</translation>
-+<translation id="4792385443586519711">Nom de la société</translation>
-+<translation id="6423731501149634044">Définir Adobe Reader comme visionneuse de documents PDF par défaut ?</translation>
-+<translation id="8839907368860424444">Pour gérer les extensions installées, cliquez sur Extensions dans le menu Fenêtre.</translation>
-+<translation id="2461687051570989462">Accédez à vos imprimantes depuis n'importe quel ordinateur ou smartphone. <ph name="BEGIN_LINK"/>En savoir plus<ph name="END_LINK"/></translation>
-+<translation id="7194430665029924274">Me &amp;le rappeler plus tard</translation>
-+<translation id="5790085346892983794">Opération réussie !</translation>
-+<translation id="1901769927849168791">Carte SD détectée.</translation>
-+<translation id="818454486170715660"><ph name="NAME"/> - Propriétaire</translation>
-+<translation id="1358032944105037487">Clavier japonais</translation>
-+<translation id="8201956630388867069">WPA</translation>
-+<translation id="603890000178803545">janv.^févr.^mars^avr.^mai^juin^juil.^août^sept.^oct.^nov.^déc.</translation>
-+<translation id="8302838426652833913">Sélectionnez
-+ <ph name="BEGIN_BOLD"/>
-+ Applications &gt; Préférences système &gt; Réseau &gt; Assistant
-+ <ph name="END_BOLD"/>
-+ pour tester votre connexion.</translation>
-+<translation id="8664389313780386848">&amp;Afficher le code source de la page</translation>
-+<translation id="8970407809569722516">Micrologiciel :</translation>
-+<translation id="1180549724812639004">Créer un profil</translation>
-+<translation id="57646104491463491">Date de modification</translation>
-+<translation id="5992752872167177798">Sandbox seccomp</translation>
-+<translation id="6362853299801475928">Signale&amp;r un problème...</translation>
-+<translation id="3289566588497100676">Entrée de symboles simplifiée</translation>
-+<translation id="6507969014813375884">Chinois simplifié</translation>
-+<translation id="7314244761674113881">Hôte SOCKS</translation>
-+<translation id="5285794783728826432">Considérer ce certificat comme fiable pour identifier les sites Web.</translation>
-+<translation id="4224803122026931301">Exceptions de localisation</translation>
-+<translation id="749452993132003881">Hiragana</translation>
-+<translation id="8226742006292257240">Le mot de passe TPM ci-dessous, généré de façon aléatoire, a été attribué à votre ordinateur :</translation>
-+<translation id="8487693399751278191">Importer mes favoris maintenant...</translation>
-+<translation id="7985242821674907985"><ph name="PRODUCT_NAME"/></translation>
-+<translation id="7484580869648358686">Avertissement : Un problème a été détecté sur cette page.</translation>
-+<translation id="2074739700630368799">Avec Google Chrome OS for business, vous pouvez connecter votre périphérique à Google Apps, ce qui vous permet de le rechercher et de le contrôler depuis le panneau de configuration de Google Apps.</translation>
-+<translation id="4474155171896946103">Ajouter tous les onglets aux favoris...</translation>
-+<translation id="5895187275912066135">Émis le</translation>
-+<translation id="1190844492833803334">Lorsque je ferme le navigateur</translation>
-+<translation id="5646376287012673985">Localisation</translation>
-+<translation id="1110155001042129815">Attendre</translation>
-+<translation id="2607101320794533334">Infos sur la clé publique de l'objet</translation>
-+<translation id="7071586181848220801">Plug-in inconnu</translation>
-+<translation id="3354601307791487577">Connexion en mode invité</translation>
-+<translation id="4419409365248380979">Toujours autoriser <ph name="HOST"/> à paramétrer les cookies</translation>
-+<translation id="2956070106555335453">Résumé</translation>
-+<translation id="917450738466192189">Le certificat du serveur n'est pas valide.</translation>
-+<translation id="2649045351178520408">Chaîne de certificats codés Base 64 ASCII</translation>
-+<translation id="7424526482660971538">Choisir mon propre mot de passe multiterme</translation>
-+<translation id="380271916710942399">Certificat de serveur non répertorié</translation>
-+<translation id="6459488832681039634">Rechercher la sélection</translation>
-+<translation id="2392369802118427583">Activer</translation>
-+<translation id="9040421302519041149">L'accès à ce réseau est protégé.</translation>
-+<translation id="5659593005791499971">E-mail</translation>
-+<translation id="8235325155053717782">Erreur <ph name="ERROR_NUMBER"/> (<ph name="ERROR_NAME"/>) : <ph name="ERROR_TEXT"/></translation>
-+<translation id="6584878029876017575">Signature permanente Microsoft</translation>
-+<translation id="562901740552630300">Sélectionnez
-+ <ph name="BEGIN_BOLD"/>
-+ Démarrer &gt; Panneau de configuration &gt; Réseau et Internet &gt; Centre Réseau et partage &gt; Résolution des problèmes (en bas) &gt; Connexions Internet.
-+ <ph name="END_BOLD"/></translation>
-+<translation id="8816996941061600321">Gestionnaire de &amp;fichiers</translation>
-+<translation id="2773223079752808209">Service client</translation>
-+<translation id="4585473702689066695">Impossible de se connecter au réseau &quot;<ph name="NAME"/>&quot;.</translation>
-+<translation id="4647175434312795566">J'accepte ces termes</translation>
-+<translation id="1084824384139382525">Copier l'adr&amp;esse du lien</translation>
-+<translation id="1221462285898798023">Veuillez démarrer <ph name="PRODUCT_NAME"/> en tant qu'utilisateur normal. Pour l'exécuter en tant que root, vous devez indiquer un autre répertoire de données utilisateur pour stocker les informations du profil.</translation>
-+<translation id="3220586366024592812">Le processus du connecteur <ph name="CLOUD_PRINT_NAME"/> est bloqué. Voulez-vous le redémarrer ?</translation>
-+<translation id="5042992464904238023">Contenu Web</translation>
-+<translation id="6254503684448816922">Clé compromise</translation>
-+<translation id="1181037720776840403">Supprimer</translation>
-+<translation id="4006726980536015530">Si vous fermez <ph name="PRODUCT_NAME"/> maintenant, ces téléchargements seront annulés.</translation>
-+<translation id="4194415033234465088">Dachen 26</translation>
-+<translation id="1664712100580477121">Voulez-vous vraiment graver l'image sur le périphérique suivant :</translation>
-+<translation id="6639554308659482635">Mémoire SQLite</translation>
-+<translation id="8141503649579618569"><ph name="DOWNLOAD_RECEIVED"/>/<ph name="DOWNLOAD_TOTAL"/>, <ph name="TIME_LEFT"/></translation>
-+<translation id="7650701856438921772"><ph name="PRODUCT_NAME"/> est affiché dans cette langue.</translation>
-+<translation id="740624631517654988">Fenêtre pop-up bloquée</translation>
-+<translation id="3738924763801731196"><ph name="OID"/> :</translation>
-+<translation id="6550769511678490130">Ouvrir tous les favoris</translation>
-+<translation id="1847961471583915783">Effacer les cookies et autres données de site et de plug-in lorsque je ferme le navigateur</translation>
-+<translation id="8870318296973696995">Page d'accueil</translation>
-+<translation id="6659594942844771486">Onglet</translation>
-+<translation id="6575134580692778371">Non configuré</translation>
-+<translation id="4624768044135598934">Opération réussie !</translation>
-+<translation id="6014776969142880350">Relancez <ph name="PRODUCT_NAME"/> pour terminer la mise à jour.</translation>
-+<translation id="5582768900447355629">Chiffrer toutes mes données</translation>
-+<translation id="6122365914076864562">Veuillez patienter pendant que nous configurons votre réseau pour mobile.</translation>
-+<translation id="1974043046396539880">Points de distribution de listes de révocation des certificats</translation>
-+<translation id="7049357003967926684">Association</translation>
-+<translation id="8641392906089904981">Appuyez sur Maj+Alt pour changer la disposition du clavier.</translation>
-+<translation id="3024374909719388945">Utiliser l'horloge au format 24 heures</translation>
-+<translation id="1867780286110144690"><ph name="PRODUCT_NAME"/> est prêt à terminer l'installation.</translation>
-+<translation id="5316814419223884568">Lancez votre recherche à partir d'ici</translation>
-+<translation id="8142732521333266922">OK, synchroniser tout</translation>
-+<translation id="965674096648379287">Afin d'être correctement affichée, cette page requiert des données que vous avez précédemment entrées. Vous pouvez de nouveau transmettre ces données, mais, en procédant ainsi, vous devrez répéter chaque action que cette page a effectuée auparavant. Cliquez sur Rafraîchir pour transmettre de nouveau ces données et pour afficher cette page.</translation>
-+<translation id="43742617823094120">Cela signifie que le certificat présenté à votre navigateur a été révoqué par son émetteur. L'intégrité de ce certificat a certainement été compromise, et il ne doit donc pas être approuvé. Ne poursuivez pas.</translation>
-+<translation id="9019654278847959325">Clavier slovaque</translation>
-+<translation id="18139523105317219">Nom de partie EDI</translation>
-+<translation id="6657193944556309583">Vous avez déjà chiffré des données avec un mot de passe multiterme. Saisissez-le ci-dessous.</translation>
-+<translation id="3328801116991980348">Informations sur le site</translation>
-+<translation id="1205605488412590044">Créer un raccourci vers l'application...</translation>
-+<translation id="2065985942032347596">Authentification requise</translation>
-+<translation id="2553340429761841190"><ph name="PRODUCT_NAME"/> n'est pas parvenu à se connecter à <ph name="NETWORK_ID"/>. Sélectionnez un autre réseau ou réessayez.</translation>
-+<translation id="2086712242472027775">Votre compte n'est pas compatible avec <ph name="PRODUCT_NAME"/>. Contactez l'administrateur de votre domaine ou utilisez un compte Google standard pour vous connecter.</translation>
-+<translation id="7222232353993864120">Adresse e-mail</translation>
-+<translation id="2128531968068887769">Client natif</translation>
-+<translation id="7175353351958621980">Chargé depuis :</translation>
-+<translation id="4590074117005971373">Active les balises canvas hautes performances dans un contexte 2D, pour effectuer le rendu via le processeur graphique.</translation>
-+<translation id="7186367841673660872">Cette page en<ph name="ORIGINAL_LANGUAGE"/>a été traduite en<ph name="LANGUAGE_LANGUAGE"/></translation>
-+<translation id="8448695406146523553">Seule une personne en possession de votre mot de passe multiterme peut lire vos données chiffrées. Google ne reçoit ni n'enregistre votre mot de passe multiterme. Si vous oubliez votre mot de passe multiterme, vous devrez</translation>
-+<translation id="6052976518993719690">Autorité de certification SSL</translation>
-+<translation id="1636959874332483835"><ph name="HOST_NAME"/> contient un logiciel malveillant. Votre ordinateur pourrait être infecté par un virus si vous consultez ce site.</translation>
-+<translation id="8050783156231782848">Aucune donnée disponible.</translation>
-+<translation id="1175364870820465910">Im&amp;primer...</translation>
-+<translation id="3866249974567520381">Description</translation>
-+<translation id="2900139581179749587">Voix non reconnue.</translation>
-+<translation id="953692523250483872">Aucun fichier sélectionné</translation>
-+<translation id="2294358108254308676">Souhaitez-vous installer <ph name="PRODUCT_NAME"/> ?</translation>
-+<translation id="6549689063733911810">Activité récente</translation>
-+<translation id="1529968269513889022">de la dernière semaine</translation>
-+<translation id="5542132724887566711">Profil</translation>
-+<translation id="5196117515621749903">Actualiser sans utiliser le cache</translation>
-+<translation id="5552632479093547648">Logiciels malveillants et sites de phishing détectés !</translation>
-+<translation id="4310537301481716192">Onglet fermé !</translation>
-+<translation id="4988273303304146523">il y a <ph name="NUMBER_DEFAULT"/> jours</translation>
-+<translation id="8428213095426709021">Paramètres</translation>
-+<translation id="1588343679702972132">Ce site exige que vous vous identifiiez avec un certificat :</translation>
-+<translation id="7211994749225247711">Supprimer...</translation>
-+<translation id="2819994928625218237">&amp;Aucune suggestion orthographique</translation>
-+<translation id="1065449928621190041">Clavier franco-canadien</translation>
-+<translation id="8327626790128680264">Clavier étendu américain</translation>
-+<translation id="2950186680359523359">Le serveur a mis fin à la connexion sans envoyer de données.</translation>
-+<translation id="9142623379911037913">Autoriser <ph name="SITE"/> à afficher des notifications sur le Bureau ?</translation>
-+<translation id="4196320913210960460">Pour gérer les extensions installées, cliquez sur Extensions dans le menu Outils.</translation>
-+<translation id="3449494395612243720">Erreur de synchronisation, veuillez vous connecter à nouveau.</translation>
-+<translation id="9118804773997839291">La liste suivante fait état des éléments dangereux détectés sur la page. Cliquez sur le lien &quot;Diagnostic&quot; pour obtenir plus d'informations sur un élément particulier.</translation>
-+<translation id="7139724024395191329">Émirat</translation>
-+<translation id="1761265592227862828">Synchroniser tous les paramètres et toutes les données\n(peut prendre un certain temps)</translation>
-+<translation id="7754704193130578113">Toujours demander où enregistrer les fichiers</translation>
-+<translation id="204914487372604757">Créer un raccourci</translation>
-+<translation id="2497284189126895209">Tous les fichiers</translation>
-+<translation id="696036063053180184">Sebeol-sik No-shift</translation>
-+<translation id="452785312504541111">Anglais (pleine chasse)</translation>
-+<translation id="945332329539165145">2D avec canvas et accélération matérielle</translation>
-+<translation id="5220797120063118010">Cette fonctionnalité autorise l'installation d'applications Google Chrome déployées à partir d'un manifeste situé sur une page Web, plutôt qu'avec un fichier crx contenant le manifeste et les icônes.</translation>
-+<translation id="9148126808321036104">Nouvelle connexion</translation>
-+<translation id="2282146716419988068">GPU</translation>
-+<translation id="428771275901304970">Moins de 1 Mo disponible</translation>
-+<translation id="1682548588986054654">Nouvelle fenêtre de navigation privée</translation>
-+<translation id="6833901631330113163">Europe du Sud</translation>
-+<translation id="8691262314411702087">Sélectionner les éléments à synchroniser</translation>
-+<translation id="6065289257230303064">Attributs du répertoire de l'objet du certificat</translation>
-+<translation id="2423017480076849397">Accédez à vos imprimantes et partagez-les en ligne via <ph name="CLOUD_PRINT_NAME"/>.</translation>
-+<translation id="569520194956422927">&amp;Ajouter...</translation>
-+<translation id="4018133169783460046">Afficher <ph name="PRODUCT_NAME"/> dans cette langue</translation>
-+<translation id="5110450810124758964">il y a <ph name="NUMBER_ONE"/> jour</translation>
-+<translation id="3264544094376351444">Police Sans-Serif</translation>
-+<translation id="5586942249556966598">Ne rien faire</translation>
-+<translation id="2820806154655529776"><ph name="NUMBER_ONE"/> seconde</translation>
-+<translation id="1077946062898560804">Configurer les mises à jour automatiques pour tous les utilisateurs</translation>
-+<translation id="3122496702278727796">Échec de la création du répertoire des données</translation>
-+<translation id="4517036173149081027">Fermer et annuler le chargement</translation>
-+<translation id="7150146631451105528"><ph name="DATE"/></translation>
-+<translation id="3166547286524371413">Adresse :</translation>
-+<translation id="4522570452068850558">Détails</translation>
-+<translation id="59659456909144943">Notification : <ph name="NOTIFICATION_NAME"/></translation>
-+<translation id="6731320427842222405">Cette opération peut prendre quelques minutes.</translation>
-+<translation id="4806525999832945986">Géré par <ph name="DOMAIN"/> (<ph name="STATUS"/>)</translation>
-+<translation id="7503191893372251637">Type de certificat Netscape</translation>
-+<translation id="1502960562739459116">Impossible d'afficher certaines parties de ce document PDF. Souhaitez-vous installer Adobe Reader ?</translation>
-+<translation id="4135450933899346655">Vos certificats</translation>
-+<translation id="4731578803613910821">Vos données personnelles sur <ph name="WEBSITE_1"/>, <ph name="WEBSITE_2"/> et <ph name="WEBSITE_3"/></translation>
-+<translation id="7716781361494605745">URL de stratégie de l'autorité de certification Netscape</translation>
-+<translation id="2881966438216424900">Dernier accès :</translation>
-+<translation id="7552203043556919163">Synchroniser les mots de passe</translation>
-+<translation id="630065524203833229">&amp;Quitter</translation>
-+<translation id="4647090755847581616">&amp;Fermer l'onglet</translation>
-+<translation id="2649204054376361687"><ph name="CITY"/>, <ph name="COUNTRY"/></translation>
-+<translation id="7886758531743562066">Le site Web à l'adresse <ph name="HOST_NAME"/> contient des éléments provenant de sites qui semblent héberger des logiciels malveillants. Ces derniers peuvent nuire à votre ordinateur ou agir à votre insu. Le simple fait de visiter un site hébergeant ce type de logiciels peut infecter votre ordinateur.</translation>
-+<translation id="2064746092913005102">Total : <ph name="NUMBER_OF_PAGES"/> <ph name="PAGE_OR_PAGES_LABEL"/> <ph name="TWO_SIDED"/> <ph name="TIMES"/> <ph name="NUMBER_OF_COPIES"/> <ph name="COPIES_LABEL"/> <ph name="EQUAL_SIGN"/> <ph name="NUMBER_OF_SHEETS"/> <ph name="SHEETS_LABEL"/></translation>
-+<translation id="7538227655922918841">Les cookies de plusieurs sites ont été autorisés pour la session uniquement.</translation>
-+<translation id="2385700042425247848">Nom du service :</translation>
-+<translation id="7751005832163144684">Imprimer une page de test</translation>
-+<translation id="3638865692466101147">Aperçu avant impression - <ph name="PREVIEW_TAB_TITLE"/></translation>
-+<translation id="1471300011765310414"><ph name="PRODUCT_NAME"/>
-+ ne peut pas à afficher la page Web, car votre ordinateur n'est pas connecté à Internet.</translation>
-+<translation id="5464632865477611176">Exécuter cette fois</translation>
-+<translation id="4268025649754414643">Chiffrement de la clé</translation>
-+<translation id="7925247922861151263">Échec de la vérification AAA</translation>
-+<translation id="1168020859489941584">Ouverture dans <ph name="TIME_REMAINING"/>...</translation>
-+<translation id="7814458197256864873">&amp;Copier</translation>
-+<translation id="8186706823560132848">Logiciel</translation>
-+<translation id="4692623383562244444">Moteurs de recherche</translation>
-+<translation id="567760371929988174">&amp;Méthodes d'entrée</translation>
-+<translation id="10614374240317010">Jamais enregistrés</translation>
-+<translation id="5116300307302421503">Impossible d'analyser le fichier.</translation>
-+<translation id="2745080116229976798">Subordination qualifiée Microsoft</translation>
-+<translation id="2526590354069164005">Bureau</translation>
-+<translation id="7983301409776629893">Toujours traduire en <ph name="TARGET_LANGUAGE"/> les pages en <ph name="ORIGINAL_LANGUAGE"/></translation>
-+<translation id="4890284164788142455">Thaï</translation>
-+<translation id="4312207540304900419">Activer l'onglet suivant</translation>
-+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> de chargement</translation>
-+<translation id="7648048654005891115">Style de mappage du clavier</translation>
-+<translation id="539295039523818097">Un problème lié à votre microphone s'est produit.</translation>
-+<translation id="4033319557821527966"><ph name="CLOUD_PRINT_NAME"/> vous permet d'accéder aux imprimantes de cet ordinateur, où que vous soyez. Connectez-vous pour l'activer.</translation>
-+<translation id="6970216967273061347">District</translation>
-+<translation id="4479639480957787382">Ethernet</translation>
-+<translation id="6312403991423642364">Erreur de réseau inconnue.</translation>
-+<translation id="751377616343077236">Nom du certificat</translation>
-+<translation id="7154108546743862496">Plus d'informations</translation>
-+<translation id="8637688295594795546">Mise à jour du système disponible. Préparation du téléchargement…</translation>
-+<translation id="5167270755190684957">Galerie des thèmes Google Chrome</translation>
-+<translation id="8382913212082956454">Copi&amp;er l'adresse e-mail</translation>
-+<translation id="7447930227192971403">Activer l'onglet 3</translation>
-+<translation id="2903493209154104877">Adresses</translation>
-+<translation id="2056143100006548702">Plug-in : <ph name="PLUGIN_NAME"/> (<ph name="PLUGIN_VERSION"/>)</translation>
-+<translation id="3479552764303398839">Pas maintenant</translation>
-+<translation id="6445051938772793705">Pays</translation>
-+<translation id="3251759466064201842">&lt;Ne fait pas partie du certificat&gt;</translation>
-+<translation id="4229495110203539533">il y a <ph name="NUMBER_ONE"/> seconde</translation>
-+<translation id="6410257289063177456">Fichiers image</translation>
-+<translation id="6419902127459849040">Europe centrale</translation>
-+<translation id="6707389671160270963">Certificat client SSL</translation>
-+<translation id="6083557600037991373">Pour accélérer l'affichage des pages Web,
-+ <ph name="PRODUCT_NAME"/>
-+ enregistre temporairement les fichiers téléchargés sur le disque. Si
-+ <ph name="PRODUCT_NAME"/>
-+ ne s'arrête pas correctement, ces fichiers peuvent être endommagés, ce qui
-+ génère cette erreur. L'actualisation de la page devrait permettre de résoudre
-+ ce problème ; celui-ci ne se reproduira vraisemblablement plus si l'arrêt s'effectue
-+ correctement.
-+ <ph name="LINE_BREAK"/>
-+ Si le problème persiste, essayez de supprimer le contenu du cache. Cette
-+ erreur peut aussi indiquer que le matériel est sur le point de tomber
-+ en panne.</translation>
-+<translation id="5298219193514155779">Thème créé par</translation>
-+<translation id="7366909168761621528">Données de navigation</translation>
-+<translation id="1047726139967079566">Ajouter cette page aux favoris</translation>
-+<translation id="9020142588544155172">Le serveur a refusé la connexion.</translation>
-+<translation id="6113225828180044308">Module (<ph name="MODULUS_NUM_BITS"/> bits) :\n<ph name="MODULUS_HEX_DUMP"/>\n\nExposant public (<ph name="PUBLIC_EXPONENT_NUM_BITS"/> bits) :\n<ph name="EXPONENT_HEX_DUMP"/></translation>
-+<translation id="2544782972264605588"><ph name="NUMBER_DEFAULT"/> secondes restantes</translation>
-+<translation id="8871696467337989339">Vous utilisez un indicateur de ligne de commande non pris en charge : <ph name="BAD_FLAG"/>. La stabilité et la sécurité en seront affectées.</translation>
-+<translation id="4767443964295394154">Emplacement de téléchargement</translation>
-+<translation id="5031870354684148875">À propos de Google Traduction</translation>
-+<translation id="720658115504386855">Les lettres ne sont pas sensibles à la casse.</translation>
-+<translation id="2454247629720664989">Mot clé</translation>
-+<translation id="3950820424414687140">Connexion</translation>
-+<translation id="4626106357471783850">Redémarrez <ph name="PRODUCT_NAME"/> pour appliquer la mise à jour.</translation>
-+<translation id="1697068104427956555">Sélectionner un carré dans l'image</translation>
-+<translation id="2840798130349147766">Bases de données Web</translation>
-+<translation id="1628736721748648976">Codage</translation>
-+<translation id="1198271701881992799">Mise en route</translation>
-+<translation id="782590969421016895">Utiliser les pages actuelles</translation>
-+<translation id="6521850982405273806">Signaler une erreur</translation>
-+<translation id="736515969993332243">Recherche de réseaux en cours</translation>
-+<translation id="8026334261755873520">Effacer les données de navigation</translation>
-+<translation id="1769104665586091481">Ouvrir le lien dans une nouvelle &amp;fenêtre</translation>
-+<translation id="8503813439785031346">Nom d'utilisateur</translation>
-+<translation id="5319782540886810524">Clavier letton</translation>
-+<translation id="8651585100578802546">Forcer l'actualisation de cette page</translation>
-+<translation id="685714579710025096">Disposition du clavier :</translation>
-+<translation id="1361655923249334273">Non utilisé</translation>
-+<translation id="290555789621781773"><ph name="NUMBER_TWO"/> minutes</translation>
-+<translation id="5434065355175441495">Chiffrement RSA PKCS #1</translation>
-+<translation id="7073704676847768330">Ce n'est probablement pas le site que vous recherchez !</translation>
-+<translation id="8477384620836102176">&amp;Général</translation>
-+<translation id="1074663319790387896">Configurer la synchronisation</translation>
-+<translation id="4302315780171881488">État de connexion :</translation>
-+<translation id="3391392691301057522">Ancien code PIN :</translation>
-+<translation id="1344519653668879001">Désactiver le contrôle des liens hypertexte</translation>
-+<translation id="6463795194797719782">&amp;Modifier</translation>
-+<translation id="4262113024799883061">Chinois</translation>
-+<translation id="4775879719735953715">Navigateur par défaut</translation>
-+<translation id="5575473780076478375">Extension en mode navigation privée :<ph name="EXTENSION_NAME"/></translation>
-+<translation id="4188026131102273494">Mot clé :</translation>
-+<translation id="2930644991850369934">Un problème est survenu lors du téléchargement de l'image de récupération. La connexion réseau a été perdue.</translation>
-+<translation id="3461610253915486539">Votre administrateur a désactivé certaines préférences.</translation>
-+<translation id="5750053751252005701">Forfait de données <ph name="NETWORK"/> épuisé</translation>
-+<translation id="8858939932848080433">Veuillez indiquer à quel niveau vous rencontrez des problèmes avant d'envoyer vos commentaires.</translation>
-+<translation id="1720318856472900922">Authentification du serveur WWW TLS</translation>
-+<translation id="8550022383519221471">Le service de synchronisation n'est pas disponible pour votre domaine.</translation>
-+<translation id="3355823806454867987">Modifier les paramètres du proxy...</translation>
-+<translation id="4780374166989101364">Cette fonctionnalité active les API des extensions expérimentales. Notez que vous ne pouvez pas mettre en ligne des extensions qui font appel aux API expérimentales dans la galerie d'extensions.</translation>
-+<translation id="7227780179130368205">Un logiciel malveillant a été détecté !</translation>
-+<translation id="435243347905038008">Forfait de données <ph name="NETWORK"/> presque épuisé</translation>
-+<translation id="2489428929217601177">des dernières 24 heures</translation>
-+<translation id="7418490403869327287">Une fois activée, la recherche instantanée charge la plupart des pages Web dès que vous saisissez l'URL dans le champ polyvalent, avant même que vous n'appuyiez sur Entrée. Si votre moteur de recherche par défaut est compatible, toute lettre saisie dans ce champ offre de nouveaux résultats et les prédictions intégrées vous guident dans vos recherches.\n\nChaque touche utilisée fait l'objet d'une requête, par conséquent il se peut que les éléments saisies dans le champ polyvalent soient enregistrés par votre moteur de recherche par défaut.\n</translation>
-+<translation id="5149131957118398098"><ph name="NUMBER_ZERO"/> hours left</translation>
-+<translation id="2541913031883863396">poursuivre quand même</translation>
-+<translation id="4278390842282768270">Autorisé</translation>
-+<translation id="2074527029802029717">Retirer l'onglet</translation>
-+<translation id="1533897085022183721">Moins de <ph name="MINUTES"/></translation>
-+<translation id="7503821294401948377">Impossible de charger l'icône &quot;<ph name="ICON"/>&quot; d'action du navigateur.</translation>
-+<translation id="5539694491979265537">Consulter Google Dashboard</translation>
-+<translation id="3942946088478181888">Plus d'informations</translation>
-+<translation id="3722396466546931176">Ajoutez des langues puis faites-les glisser pour les classer dans l'ordre souhaité.</translation>
-+<translation id="7396845648024431313"><ph name="APP_NAME"/> sera lancé au démarrage du système et continuera de s'exécuter en arrière-plan, même toutes les fenêtres de <ph name="PRODUCT_NAME"/> sont fermées.</translation>
-+<translation id="8539727552378197395">Non (HttpOnly)</translation>
-+<translation id="4519351128520996510">Saisir votre mot de passe multiterme pour la synchronisation</translation>
-+<translation id="2391419135980381625">Police standard</translation>
-+<translation id="7893393459573308604"><ph name="ENGINE_NAME"/> (par défaut)</translation>
-+<translation id="5392544185395226057">Cette fonctionnalité active la prise en charge du client natif.</translation>
-+<translation id="5400640815024374115">La puce du module de plate-forme sécurisée (TPM) est désactivée ou inexistante.</translation>
-+<translation id="2151576029659734873">L'index de l'onglet indiqué est incorrect.</translation>
-+<translation id="5150254825601720210">Nom du serveur SSL du certificat Netscape</translation>
-+<translation id="6771503742377376720">Est une autorité de certification</translation>
-+<translation id="8814190375133053267">Wi-Fi</translation>
-+<translation id="2040078585890208937">Connexion à <ph name="NAME"/></translation>
-+<translation id="8410619858754994443">Confirmer le mot de passe :</translation>
-+<translation id="2210840298541351314">Aperçu avant impression</translation>
-+<translation id="3858678421048828670">Clavier italien</translation>
-+<translation id="4938277090904056629">Impossible d'établir une connexion sécurisée à cause de l'antivirus ESET.</translation>
-+<translation id="4521805507184738876">(expiré)</translation>
-+<translation id="111844081046043029">Voulez-vous vraiment quitter cette page ?</translation>
-+<translation id="1951615167417147110">Faire défiler d'une page vers le haut</translation>
-+<translation id="4154664944169082762">Empreintes</translation>
-+<translation id="3202578601642193415">Le plus récent</translation>
-+<translation id="8112886015144590373"><ph name="NUMBER_FEW"/> heures</translation>
-+<translation id="1398853756734560583">Agrandir</translation>
-+<translation id="8988255471271407508">La page Web est introuvable dans le cache. Certaines ressources ne sont restituées fidèlement que si elles sont extraites du cache, notamment les pages générées à partir de données que vous avez envoyées. <ph name="LINE_BREAK"/> Cette erreur peut également être due à un cache endommagé lors d'une fermeture incorrecte. <ph name="LINE_BREAK"/> Si le problème persiste, essayez d'effacer le cache.</translation>
-+<translation id="1195977189444203128">Le plug-in <ph name="PLUGIN_NAME"/> n'est plus à jour.</translation>
-+<translation id="3878562341724547165">Vous avez changé de position. Souhaitez-vous utiliser <ph name="NEW_GOOGLE_URL"/> ?</translation>
-+<translation id="1758018619400202187">EAP-TLS</translation>
-+<translation id="6690744523875189208"><ph name="NUMBER_TWO"/> heures</translation>
-+<translation id="8053390638574070785">Rafraîchir cette page</translation>
-+<translation id="5507756662695126555">Non-répudiation</translation>
-+<translation id="3678156199662914018">Extension : <ph name="EXTENSION_NAME"/></translation>
-+<translation id="9194519262242876737">Active l'API Web audio.</translation>
-+<translation id="3531250013160506608">Zone de saisie de mot de passe</translation>
-+<translation id="8314066201485587418">Effacer les cookies et autres données de site lorsque je quitte le navigateur</translation>
-+<translation id="4094105377635924481">Ajouter l'option de regroupement au menu contextuel des onglets</translation>
-+<translation id="8655295600908251630">Version</translation>
-+<translation id="8250690786522693009">Latin</translation>
-+<translation id="2119721408814495896">Le connecteur <ph name="CLOUD_PRINT_NAME"/> requiert l'installation du pack Microsoft XML Paper Specification Essentials.</translation>
-+<translation id="7624267205732106503">Effacer les cookies et autres données de site lorsque je ferme le navigateur</translation>
-+<translation id="8401363965527883709">Case décochée</translation>
-+<translation id="7771452384635174008">Mise en page</translation>
-+<translation id="6188939051578398125">Saisir un nom ou une adresse</translation>
-+<translation id="8443621894987748190">Choix de l'image du compte</translation>
-+<translation id="10122177803156699">Me montrer</translation>
-+<translation id="5260878308685146029"><ph name="NUMBER_TWO"/> minutes restantes</translation>
-+<translation id="2192505247865591433">De :</translation>
-+<translation id="238391805422906964">Ouvrir un rapport de phishing</translation>
-+<translation id="5921544176073914576">Page de phishing</translation>
-+<translation id="3727187387656390258">Inspecter le pop-up</translation>
-+<translation id="569068482611873351">Importer...</translation>
-+<translation id="6571070086367343653">Modifier la carte de paiement</translation>
-+<translation id="1204242529756846967">Cette langue est utilisée pour corriger l'orthographe.</translation>
-+<translation id="3981760180856053153">Le type d'enregistrement indiqué est incorrect.</translation>
-+<translation id="8464591670878858520">Forfait de données <ph name="NETWORK"/> arrivé à expiration</translation>
-+<translation id="4568660204877256194">Exporter mes favoris...</translation>
-+<translation id="3116361045094675131">Clavier britannique</translation>
-+<translation id="4577070033074325641">Importer des favoris...</translation>
-+<translation id="1641504961675316934"><ph name="CLOUD_PRINT_NAME"/></translation>
-+<translation id="1715941336038158809">Nom d'utilisateur ou mot de passe incorrect</translation>
-+<translation id="1901303067676059328">&amp;Tout sélectionner</translation>
-+<translation id="674375294223700098">Erreur inconnue liée au certificat du serveur.</translation>
-+<translation id="7780428956635859355">Envoyer une capture d'écran enregistrée</translation>
-+<translation id="2850961597638370327">Émis pour : <ph name="NAME"/></translation>
-+<translation id="2168039046890040389">Page précédente</translation>
-+<translation id="1767519210550978135">Hsu</translation>
-+<translation id="2498539833203011245">Réduire</translation>
-+<translation id="2893168226686371498">Navigateur par défaut</translation>
-+<translation id="2435457462613246316">Afficher le mot de passe</translation>
-+<translation id="7988355189918024273">Activer les fonctionnalités d'accessibilité</translation>
-+<translation id="5438653034651341183">Inclure la capture d'écran actuelle :</translation>
-+<translation id="1899708097738826574"><ph name="OPTIONS_TITLE"/> - <ph name="SUBPAGE_TITLE"/></translation>
-+<translation id="1765313842989969521">(cette extension est gérée et ne peut être désinstallée ni désactivée)</translation>
-+<translation id="6983783921975806247">OID enregistré</translation>
-+<translation id="394984172568887996">Importés depuis IE</translation>
-+<translation id="5311260548612583999">Fichier de clé privée (facultatif) :</translation>
-+<translation id="2430043402233747791">Autoriser pour la session uniquement</translation>
-+<translation id="7363290921156020669"><ph name="NUMBER_ZERO"/> mins</translation>
-+<translation id="7568790562536448087">Mise à jour en cours</translation>
-+<translation id="4856408283021169561">Aucun microphone trouvé.</translation>
-+<translation id="8190193592390505034">Connexion à <ph name="PROVIDER_NAME"/></translation>
-+<translation id="6144890426075165477"><ph name="PRODUCT_NAME"/> n'est pas votre navigateur par défaut.</translation>
-+<translation id="823241703361685511">Forfait</translation>
-+<translation id="4068506536726151626">Cette page contient des éléments des sites ci-dessous qui suivent votre position géographique :</translation>
-+<translation id="4721475475128190282">Plusieurs profils</translation>
-+<translation id="4220128509585149162">Plantages</translation>
-+<translation id="8798099450830957504">Par défaut</translation>
-+<translation id="9107059250669762581"><ph name="NUMBER_DEFAULT"/> jours</translation>
-+<translation id="1640283014264083726">PKCS #1 MD4 avec chiffrement RSA</translation>
-+<translation id="872451400847464257">Modifier le moteur de recherche</translation>
-+<translation id="6463061331681402734"><ph name="NUMBER_MANY"/> minutes</translation>
-+<translation id="2466804342846034717">Indiquez le mot de passe approprié ci-dessus, puis saisissez les caractères figurant dans l'image ci-dessous.</translation>
-+<translation id="3881435075661337013">Expiration de <ph name="NETWORK"/> imminente</translation>
-+<translation id="5681833099441553262">Activer l'onglet précédent</translation>
-+<translation id="4792057643643237295">Désactiver l'accès à distance</translation>
-+<translation id="1681614449735360921">Afficher les incompatibilités</translation>
-+<translation id="19094784437781028">Carte de débit Solo</translation>
-+<translation id="2657327428424666237"><ph name="BEGIN_LINK"/>Actualisez<ph name="END_LINK"/> cette page Web ultérieurement.</translation>
-+<translation id="7347751611463936647">Pour utiliser cette extension, saisissez &quot;<ph name="EXTENSION_KEYWORD"/>&quot;, TAB, puis votre commande ou votre recherche.</translation>
-+<translation id="659432221160402784"><ph name="PRODUCT_NAME"/> synchronisera les applications installées, afin que vous puissiez y accéder en vous connectant depuis tout navigateur <ph name="PRODUCT_NAME"/>.</translation>
-+<translation id="892464165639979917">Langues et paramètres du correcteur orthographique...</translation>
-+<translation id="5645845270586517071">Erreur de sécurité</translation>
-+<translation id="2805756323405976993">Applications</translation>
-+<translation id="3651020361689274926">La ressource demandée n'existe plus et aucune adresse de transfert n'est disponible. Il semble que cet état de fait soit permanent.</translation>
-+<translation id="2989786307324390836">Certificat unique binaire codé DER</translation>
-+<translation id="3827774300009121996">&amp;Plein écran</translation>
-+<translation id="3771294271822695279">Fichiers vidéo</translation>
-+<translation id="6704875430222476107"><ph name="PRODUCT_NAME"/> indique que
-+ NetNanny intercepte les connexions sécurisées. En général, cela
-+ ne constitue pas un problème de sécurité, car le logiciel NetNanny s'exécute souvent
-+ sur le même ordinateur. Toutefois, en raison de certaines incompatibilités avec
-+ les connexions sécurisées Google Chrome, vous devez configurer NetNanny
-+ de manière à éviter ces interceptions. Cliquez sur le lien En savoir plus pour obtenir des instructions.</translation>
-+<translation id="3388026114049080752">Vos onglets et activités de navigation</translation>
-+<translation id="7525067979554623046">Créer</translation>
-+<translation id="4711094779914110278">Turc</translation>
-+<translation id="1031460590482534116">Une erreur s'est produite lors de la tentative d'enregistrement du certificat client. Erreur <ph name="ERROR_NUMBER"/> (<ph name="ERROR_NAME"/>)</translation>
-+<translation id="7136984461011502314">Bienvenue dans <ph name="PRODUCT_NAME"/></translation>
-+<translation id="1594030484168838125">Sélectionner</translation>
-+<translation id="204497730941176055">Nom du modèle de certificat Microsoft</translation>
-+<translation id="6705264787989366486">Configuration de l'adresse IP pour <ph name="NAME"/></translation>
-+<translation id="8970721300630048025">Immortalisez votre plus beau sourire et utilisez la photo comme image de compte.</translation>
-+<translation id="4087089424473531098">Extension créée :
-+
-+<ph name="EXTENSION_FILE"/></translation>
-+<translation id="16620462294541761">Mot de passe incorrect. Veuillez réessayer.</translation>
-+<translation id="5017508259293544172">LEAP</translation>
-+<translation id="1394630846966197578">Échec de la connexion aux serveurs de reconnaissance vocale.</translation>
-+<translation id="2498765460639677199">Très grande</translation>
-+<translation id="2378982052244864789">Sélectionner le répertoire de l'extension</translation>
-+<translation id="7861215335140947162">&amp;Téléchargements</translation>
-+<translation id="4778630024246633221">Gestionnaire des certificats</translation>
-+<translation id="6705050455568279082"><ph name="URL"/> souhaite suivre votre position géographique</translation>
-+<translation id="4708849949179781599">Quitter <ph name="PRODUCT_NAME"/></translation>
-+<translation id="2505402373176859469"><ph name="RECEIVED_AMOUNT"/> sur <ph name="TOTAL_SIZE"/></translation>
-+<translation id="6644512095122093795">Proposer d'enregistrer les mots de passe</translation>
-+<translation id="4724450788351008910">Modification de l'affiliation</translation>
-+<translation id="2249605167705922988">par exemple : 1-5, 8, 11-13</translation>
-+<translation id="8691686986795184760">(Activé par une stratégie d'entreprise)</translation>
-+<translation id="1911483096198679472">Qu'est-ce que c'est ?</translation>
-+<translation id="1976323404609382849">Les cookies de plusieurs sites ont été bloqués.</translation>
-+<translation id="2662952950313424742">Serveur DNS spécifié par l'utilisateur et utilisé par Google Chrome, à la place du paramètre système par défaut, pour les résolutions DNS.</translation>
-+<translation id="4176463684765177261">Désactivé</translation>
-+<translation id="2079545284768500474">Annuler</translation>
-+<translation id="114140604515785785">Répertoire racine de l'extension :</translation>
-+<translation id="4788968718241181184">Mode de saisie du vietnamien (TCVN6064)</translation>
-+<translation id="1512064327686280138">Échec de l'activation</translation>
-+<translation id="3254409185687681395">Ajouter cette page aux favoris</translation>
-+<translation id="1384616079544830839">L'identité de ce site Web a été vérifiée par <ph name="ISSUER"/>.</translation>
-+<translation id="8710160868773349942">Adresse e-mail : <ph name="EMAIL_ADDRESSES"/></translation>
-+<translation id="4057991113334098539">Activation...</translation>
-+<translation id="9073281213608662541">PAP</translation>
-+<translation id="1800035677272595847">Sites de phishing</translation>
-+<translation id="8448317557906454022"><ph name="NUMBER_ZERO"/> secs ago</translation>
-+<translation id="402759845255257575">Interdire à tous les sites d'exécuter JavaScript</translation>
-+<translation id="4610637590575890427">Vouliez-vous accéder à <ph name="SITE"/> ?</translation>
-+<translation id="7723779034587221017">La connexion avec le service de configuration a été perdue. Veuillez réinitialiser votre périphérique ou contacter votre représentant de l'assistance technique.</translation>
-+<translation id="3046388203776734202">Paramètres des fenêtres pop-up :</translation>
-+<translation id="3437994698969764647">Tout exporter...</translation>
-+<translation id="8349305172487531364">Barre de favoris</translation>
-+<translation id="1898064240243672867">Stocké dans : <ph name="CERT_LOCATION"/></translation>
-+<translation id="444134486829715816">Développer...</translation>
-+<translation id="1401874662068168819">Gin Yieh</translation>
-+<translation id="7208899522964477531">Rechercher <ph name="SEARCH_TERMS"/> sur <ph name="SITE_NAME"/></translation>
-+<translation id="6255097610484507482">Modifier la carte de paiement</translation>
-+<translation id="5584091888252706332">Au démarrage</translation>
-+<translation id="8960795431111723921">Nous examinons actuellement le problème.</translation>
-+<translation id="2482878487686419369">Notifications</translation>
-+<translation id="8004582292198964060">Navigateur</translation>
-+<translation id="695755122858488207">Case d'option décochée</translation>
-+<translation id="6357135709975569075"><ph name="NUMBER_ZERO"/> days</translation>
-+<translation id="8666678546361132282">Anglais</translation>
-+<translation id="2224551243087462610">Modifier le nom du dossier</translation>
-+<translation id="1358741672408003399">Grammaire et orthographe</translation>
-+<translation id="4910673011243110136">Réseaux privés</translation>
-+<translation id="2527167509808613699">Toutes sortes de connexions</translation>
-+<translation id="9095710730982563314">Exceptions liées aux notifications</translation>
-+<translation id="8072988827236813198">Épingler les onglets</translation>
-+<translation id="1234466194727942574">Barre d'onglets</translation>
-+<translation id="7974087985088771286">Activer l'onglet 6</translation>
-+<translation id="4035758313003622889">Gestionnaire de &amp;tâches</translation>
-+<translation id="6356936121715252359">Paramètres de stockage d'Adobe Flash Player...</translation>
-+<translation id="5885996401168273077">Connexion au réseau</translation>
-+<translation id="7313804056609272439">Mode de saisie du vietnamien (VNI)</translation>
-+<translation id="1768211415369530011">L'application suivante va être lancée si vous acceptez cette requête :\n\n<ph name="APPLICATION"/></translation>
-+<translation id="8793043992023823866">Importation...</translation>
-+<translation id="8106211421800660735">N° de carte</translation>
-+<translation id="2550839177807794974">Gérer les moteurs de recherche...</translation>
-+<translation id="7031711645186424727">Utiliser un moniteur externe</translation>
-+<translation id="6316768948917110108">Gravure de l'image en cours...</translation>
-+<translation id="5089810972385038852">État</translation>
-+<translation id="2872961005593481000">Éteindre</translation>
-+<translation id="8986267729801483565">Enregistrer les fichiers dans le dossier :</translation>
-+<translation id="4322394346347055525">Fermer les autres onglets</translation>
-+<translation id="4411770745820968260">Répertoire de fichiers</translation>
-+<translation id="881799181680267069">Masquer les autres</translation>
-+<translation id="1812631533912615985">Annuler l'épinglage des onglets</translation>
-+<translation id="6042308850641462728">Plus</translation>
-+<translation id="8318945219881683434">Échec de la vérification de la révocation</translation>
-+<translation id="1650709179466243265">Ajouter www. et .com, puis ouvrir la page</translation>
-+<translation id="3524079319150349823">Pour inspecter un pop-up, cliquez avec le bouton droit sur la page ou sur l'icône d'action du navigateur, puis sélectionnez Inspecter le pop-up.</translation>
-+<translation id="994289308992179865">&amp;Répéter</translation>
-+<translation id="7793343764764530903"><ph name="CLOUD_PRINT_NAME"/> est à présent activé. <ph name="PRODUCT_NAME"/> a enregistré les imprimantes installées sur cette machine en les associant à &lt;b&gt;<ph name="EMAIL_ADDRESSES"/>&lt;/b&gt;. Vous pouvez désormais utiliser vos imprimantes depuis n'importe quelle application Web ou mobile associée à <ph name="CLOUD_PRINT_NAME"/>.</translation>
-+<translation id="1703490097606704369">Le serveur de <ph name="HOST_NAME"/>
-+ est introuvable, car la résolution DNS a échoué. DNS est le service Web qui
-+ traduit les noms de site Web en adresses Internet. Cette erreur est
-+ généralement due à l'absence de connexion Internet ou à une configuration
-+ incorrecte du réseau. Cela peut également venir d'un serveur DNS qui ne
-+ répond pas ou d'un pare-feu interdisant l'accès de
-+ <ph name="PRODUCT_NAME"/>
-+ au réseau.</translation>
-+<translation id="8887090188469175989">ZGPY</translation>
-+<translation id="3302709122321372472">Impossible de charger le fichier css &quot;<ph name="RELATIVE_PATH"/>&quot; du script de contenu.</translation>
-+<translation id="305803244554250778">Créer des raccourcis vers des applications aux emplacements suivants :</translation>
-+<translation id="574392208103952083">Moyenne</translation>
-+<translation id="3745810751851099214">Envoyé pour :</translation>
-+<translation id="3937609171782005782">Aider Google à détecter les logiciels malveillants en envoyant des données supplémentaires concernant les sites pour lesquels cet avertissement s'affiche. Ces données seront gérées conformément aux règles définies sur la page <ph name="PRIVACY_PAGE_LINK"/>.</translation>
-+<translation id="8877448029301136595">[répertoire parent]</translation>
-+<translation id="7301360164412453905">Touches de sélection du clavier Hsu</translation>
-+<translation id="8631271110654520730">Copie de l'image de récupération...</translation>
-+<translation id="1963227389609234879">Tout supprimer</translation>
-+<translation id="7779140087128114262">Seule une personne en possession de votre mot de passe multiterme peut lire vos données chiffrées. Google ne reçoit ni n'enregistre votre mot de passe multiterme. Si vous oubliez votre mot de passe multiterme, vous devrez réinitialiser la synchronisation.</translation>
-+<translation id="8027581147000338959">Ouvrir dans une nouvelle fenêtre</translation>
-+<translation id="8019305344918958688">Dommage... Aucune extension n'est installée. :-(</translation>
-+<translation id="7466861475611330213">Style de ponctuation</translation>
-+<translation id="2496180316473517155">Historique de navigation</translation>
-+<translation id="602251597322198729">Ce site tente de télécharger plusieurs fichiers. Voulez-vous autoriser le chargement ?</translation>
-+<translation id="5843685321177053287">Établissement de la liaison avec le service de gestion des périphériques en attente...</translation>
-+<translation id="2052389551707911401"><ph name="NUMBER_MANY"/> heures</translation>
-+<translation id="5411472733320185105">Ne pas utiliser les paramètres du proxy pour les hôtes et domaines suivants :</translation>
-+<translation id="6691936601825168937">&amp;Avancer</translation>
-+<translation id="6566142449942033617">Impossible de charger &quot;<ph name="PLUGIN_PATH"/>&quot; pour le plug-in.</translation>
-+<translation id="7065534935986314333">À propos du système</translation>
-+<translation id="45025857977132537">Utilisation de la clé du certificat : <ph name="USAGES"/></translation>
-+<translation id="6454421252317455908">Mode de saisie du chinois (quick)</translation>
-+<translation id="368789413795732264">Une erreur s'est produite lors de la tentative d'écriture du fichier : <ph name="ERROR_TEXT"/>.</translation>
-+<translation id="1173894706177603556">Renommer</translation>
-+<translation id="5670032673361607750">La synchronisation requiert votre attention.</translation>
-+<translation id="2148716181193084225">Aujourd'hui</translation>
-+<translation id="1002064594444093641">Imp&amp;rimer le cadre...</translation>
-+<translation id="7234674978021619913">Le site <ph name="HOST_NAME"/> a déjà été informé qu'un logiciel malveillant a été détecté sur ses pages. Pour plus d'informations concernant les problèmes rencontrés sur <ph name="HOST_NAME2"/>, consultez notre <ph name="DIAGNOSTIC_PAGE"/> Google.</translation>
-+<translation id="8202390211066742724">Adresse de serveur DNS spécifiée par l'utilisateur.</translation>
-+<translation id="4608500690299898628">&amp;Rechercher...</translation>
-+<translation id="3574305903863751447"><ph name="CITY"/>, <ph name="STATE"/> <ph name="COUNTRY"/></translation>
-+<translation id="8724859055372736596">&amp;Afficher dans le dossier</translation>
-+<translation id="4605399136610325267">Non connecté à Internet.</translation>
-+<translation id="978407797571588532">Sélectionnez
-+ <ph name="BEGIN_BOLD"/>
-+ Démarrer &gt; Panneau de configuration &gt; Connexions réseau &gt; Assistant Nouvelle connexion
-+ <ph name="END_BOLD"/>
-+ pour tester votre connexion.</translation>
-+<translation id="5554489410841842733">Cette icône s'affiche lorsque l'extension peut agir sur la page active.</translation>
-+<translation id="579702532610384533">Reconnexion</translation>
-+<translation id="4862642413395066333">Réponses OCSP de signature</translation>
-+<translation id="5266113311903163739">Erreur d'importation de l'autorité de certification</translation>
-+<translation id="9563164493805065">Gravure de l'image terminée.</translation>
-+<translation id="4756388243121344051">&amp;Historique</translation>
-+<translation id="3789841737615482174">Installer</translation>
-+<translation id="4320697033624943677">Ajouter des utilisateurs</translation>
-+<translation id="9153934054460603056">Enregistrer l'authentification et le mot de passe</translation>
-+<translation id="1455548678241328678">Clavier norvégien</translation>
-+<translation id="2520481907516975884">Basculer en mode chinois/anglais</translation>
-+<translation id="8571890674111243710">Traduction de la page en <ph name="LANGUAGE"/>...</translation>
-+<translation id="4789872672210757069">À propos de &amp;<ph name="PRODUCT_NAME"/></translation>
-+<translation id="4056561919922437609"><ph name="TAB_COUNT"/> onglets</translation>
-+<translation id="4373894838514502496">il y a <ph name="NUMBER_FEW"/> minutes</translation>
-+<translation id="6358450015545214790">Qu'est-ce que c'est ?</translation>
-+<translation id="6264365405983206840">Tout &amp;sélectionner</translation>
-+<translation id="1017280919048282932">&amp;Ajouter au dictionnaire</translation>
-+<translation id="8319414634934645341">Utilisation étendue de la clé</translation>
-+<translation id="4563210852471260509">Le chinois est la langue de saisie initiale</translation>
-+<translation id="6897140037006041989">Agent utilisateur</translation>
-+<translation id="3413122095806433232">Émetteurs de l'autorité de certification : <ph name="LOCATION"/></translation>
-+<translation id="4115153316875436289"><ph name="NUMBER_TWO"/> jours</translation>
-+<translation id="701080569351381435">Code source</translation>
-+<translation id="3286538390144397061">Redémarrer maintenant</translation>
-+<translation id="163309982320328737">La largeur de caractères initiale est Complète</translation>
-+<translation id="5107325588313356747">Pour masquer l'accès à ce programme, vous devez le désinstaller au moyen de \n<ph name="CONTROL_PANEL_APPLET_NAME"/> du Panneau de configuration.\n\nSouhaitez-vous exécuter <ph name="CONTROL_PANEL_APPLET_NAME"/> ?</translation>
-+<translation id="4841055638263130507">Paramètres du microphone</translation>
-+<translation id="6965648386495488594">Port</translation>
-+<translation id="7631887513477658702">&amp;Toujours ouvrir les fichiers de ce type</translation>
-+<translation id="8627795981664801467">Uniquement les connexions sécurisées</translation>
-+<translation id="8680787084697685621">Les informations de connexion au compte sont obsolètes.</translation>
-+<translation id="3228969707346345236">La traduction a échoué, car la page est déjà en <ph name="LANGUAGE"/>.</translation>
-+<translation id="1873879463550486830">Sandbox SUID</translation>
-+<translation id="2190355936436201913">(vide)</translation>
-+<translation id="8515737884867295000">Échec de l'authentification basée sur le certificat</translation>
-+<translation id="5868426874618963178">Envoyer le code source de la page actuelle</translation>
-+<translation id="1269138312169077280">Votre administrateur a désactivé certains paramètres.</translation>
-+<translation id="5818003990515275822">Coréen</translation>
-+<translation id="4182252350869425879">Avertissement : Il s'agit peut-être d'un site de phishing !</translation>
-+<translation id="5458214261780477893">Dvorak</translation>
-+<translation id="5353719617589986386">Étendue de pages incorrecte</translation>
-+<translation id="1164369517022005061"><ph name="NUMBER_DEFAULT"/> heures restantes</translation>
-+<translation id="5943260032016910017">Exceptions liées aux cookies et aux données de site</translation>
-+<translation id="2214283295778284209"><ph name="SITE"/> n'est pas accessible</translation>
-+<translation id="8755376271068075440">P&amp;lus grand</translation>
-+<translation id="8132793192354020517">Connecté à <ph name="NAME"/></translation>
-+<translation id="8187473050234053012">Le certificat de sécurité du site a été révoqué !</translation>
-+<translation id="7444983668544353857">Désactiver <ph name="NETWORKDEVICE"/></translation>
-+<translation id="6003177993629630467"><ph name="PRODUCT_NAME"/> risque de ne pas rester à jour.</translation>
-+<translation id="421577943854572179">intégré sur tout autre site</translation>
-+<translation id="580886651983547002"><ph name="PRODUCT_NAME"/>
-+ ne parvient pas à atteindre le site Web. Cela vient probablement d'un problème de réseau,
-+ mais peut également être dû à un pare-feu ou à un serveur proxy mal configuré.</translation>
-+<translation id="5445557969380904478">À propos de la reconnaissance vocale</translation>
-+<translation id="3093473105505681231">Langues et paramètres du correcteur orthographique...</translation>
-+<translation id="152482086482215392"><ph name="NUMBER_ONE"/> seconde restante</translation>
-+<translation id="529172024324796256">Nom d'utilisateur :</translation>
-+<translation id="3308116878371095290">Le stockage des cookies n'est pas autorisé pour cette page.</translation>
-+<translation id="7521387064766892559">JavaScript</translation>
-+<translation id="7219179957768738017">La connexion utilise <ph name="SSL_VERSION"/>.</translation>
-+<translation id="7014174261166285193">Échec de l'installation</translation>
-+<translation id="1970746430676306437">Afficher les &amp;infos sur la page</translation>
-+<translation id="3199127022143353223">Serveurs</translation>
-+<translation id="2805646850212350655">Système de fichiers de chiffrement Microsoft </translation>
-+<translation id="8053959338015477773">Un plug-in supplémentaire est requis pour afficher certains éléments sur cette page.</translation>
-+<translation id="3541661933757219855">Appuyez sur Ctrl+Alt+/ ou sur Échap pour masquer</translation>
-+<translation id="8813873272012220470">Cette fonctionnalité effectue des vérifications en arrière-plan et vous avertit en cas d'incompatibilité logicielle (modules tiers bloquant le navigateur, par exemple).</translation>
-+<translation id="5020734739305654865">Connexion avec</translation>
-+<translation id="2679385451463308372">Imprimer depuis la boîte de dialogue système…</translation>
-+<translation id="7414887922320653780"><ph name="NUMBER_ONE"/> heure restante</translation>
-+<translation id="121632099317611328">Échec de l'initialisation de l'appareil photo</translation>
-+<translation id="399179161741278232">Importés</translation>
-+<translation id="3829932584934971895">Type de fournisseur :</translation>
-+<translation id="462288279674432182">IP restreinte :</translation>
-+<translation id="3927932062596804919">Refuser</translation>
-+<translation id="3524915994314972210">Démarrage du téléchargement en cours...</translation>
-+<translation id="6484929352454160200">Une nouvelle version de <ph name="PRODUCT_NAME"/> est disponible.</translation>
-+<translation id="3187212781151025377">Clavier hébreu</translation>
-+<translation id="351152300840026870">Police à largeur fixe</translation>
-+<translation id="5827266244928330802">Safari</translation>
-+<translation id="778881183694837592">Les champs obligatoires ne doivent pas rester vides.</translation>
-+<translation id="2371076942591664043">Ouvrir une fois le téléchargement &amp;terminé</translation>
-+<translation id="3920504717067627103">Stratégies de certificat</translation>
-+<translation id="155865706765934889">Pavé tactile</translation>
-+<translation id="7701040980221191251">Aucun</translation>
-+<translation id="5917011688104426363">Activer la barre d'adresse en mode recherche</translation>
-+<translation id="6910239454641394402">Exceptions pour JavaScript</translation>
-+<translation id="2979639724566107830">Ouvrir dans une nouvelle fenêtre</translation>
-+<translation id="3269101346657272573">Veuillez saisir le code PIN.</translation>
-+<translation id="9204065299849069896">Options de saisie automatique...</translation>
-+<translation id="2822854841007275488">Arabe</translation>
-+<translation id="5857090052475505287">Nouveau dossier</translation>
-+<translation id="7450732239874446337">E/S réseau interrompue</translation>
-+<translation id="5178667623289523808">Rechercher le précédent</translation>
-+<translation id="2815448242176260024">Ne jamais enregistrer les mots de passe</translation>
-+<translation id="2989805286512600854">Ouvrir dans un nouvel onglet</translation>
-+<translation id="8687485617085920635">Fenêtre suivante</translation>
-+<translation id="4122118036811378575">&amp;Rechercher le suivant</translation>
-+<translation id="6008256403891681546">JCB</translation>
-+<translation id="2610780100389066815">Signature de liste d'approbation Microsoft</translation>
-+<translation id="8289811203643526145">Gérer les certificats...</translation>
-+<translation id="2788575669734834343">Sélectionnez le fichier de certificat.</translation>
-+<translation id="8404409224170843728">Fabricant :</translation>
-+<translation id="8267453826113867474">Bloquer le contenu inapproprié</translation>
-+<translation id="7959074893852789871">Le fichier contenait plusieurs certificats, dont certains n'ont pas été importés :</translation>
-+<translation id="1213999834285861200">Exceptions pour les images</translation>
-+<translation id="2805707493867224476">Autoriser tous les sites à afficher des fenêtres pop-up</translation>
-+<translation id="3561217442734750519">Vous devez indiquer un chemin valide comme valeur de clé privée.</translation>
-+<translation id="2444609190341826949">Sans mot de passe multiterme, vos mots de passe et autres données chiffrées ne seront pas synchronisés sur cet ordinateur.</translation>
-+<translation id="77221669950527621">Extensions ou applications</translation>
-+<translation id="6650142020817594541">Ce site recommande Google Chrome Frame (déjà installé).</translation>
-+<translation id="6503077044568424649">Les plus visités</translation>
-+<translation id="4625904365165566833">Vous n'êtes pas autorisé à vous connecter. Consultez le propriétaire de cet ordinateur portable.</translation>
-+<translation id="7450633916678972976">Remarque : Lorsque vous cliquez sur &quot;Envoyer&quot;, Google Chrome joint à votre
-+ envoi un journal indiquant votre version de Google Chrome et celle du système
-+ d'exploitation utilisé, ainsi que l'URL associée à votre rapport. Vous pouvez
-+ également joindre une capture d'écran. Ces informations nous
-+ permettent de diagnostiquer les problèmes et d'améliorer les performances de
-+ Google Chrome. Les informations personnelles fournies sciemment dans vos
-+ commentaires ou involontairement dans le journal, l'URL ou la capture
-+ d'écran sont protégées conformément à nos règles de
-+ confidentialité. Si vous ne souhaitez pas indiquer d'URL et/ou de capture
-+ d'écran, décochez les cases &quot;Inclure cette URL&quot; et/ou &quot;Inclure cette capture d'écran&quot;. Vous acceptez que Google utilise vos commentaires pour améliorer ses produits ou services.</translation>
-+<translation id="465365366590259328">Vos modifications seront prises en compte au prochain démarrage de <ph name="PRODUCT_NAME"/>.</translation>
-+<translation id="7168109975831002660">Taille de police minimale</translation>
-+<translation id="7070804685954057874">Entrée directe</translation>
-+<translation id="3265459715026181080">Fermer la fenêtre</translation>
-+<translation id="6074871234879228294">Mode de saisie du japonais (pour clavier japonais)</translation>
-+<translation id="7855296476260297092">Inscription réussie</translation>
-+<translation id="907841381057066561">Échec de création du fichier zip temporaire lors de la création du pack</translation>
-+<translation id="1294298200424241932">Modifier les paramètres de confiance :</translation>
-+<translation id="1384617406392001144">Votre historique de navigation</translation>
-+<translation id="3831099738707437457">&amp;Masquer le panneau de la vérification orthographique</translation>
-+<translation id="1040471547130882189">Plug-in ne répondant pas</translation>
-+<translation id="5473075389972733037">IBM</translation>
-+<translation id="8307664665247532435">Les paramètres seront effacés lors de la prochaine actualisation.</translation>
-+<translation id="790025292736025802"><ph name="URL"/> introuvable</translation>
-+<translation id="895347679606913382">Démarrage...</translation>
-+<translation id="3319048459796106952">Nouvelle fenêtre de nav&amp;igation privée</translation>
-+<translation id="5832669303303483065">Ajouter une adresse postale...</translation>
-+<translation id="3127919023693423797">Authentification en cours...</translation>
-+<translation id="4195643157523330669">Ouvrir dans un nouvel onglet</translation>
-+<translation id="8030169304546394654">Déconnecté</translation>
-+<translation id="4010065515774514159">Action du navigateur</translation>
-+<translation id="4286563808063000730">Le mot de passe multiterme saisi ne peut pas être utilisé, car vous avez déjà chiffré des données avec un mot de passe multiterme. Entrez ci-dessous le mot de passe multiterme actuellement défini pour la synchronisation.</translation>
-+<translation id="1154228249304313899">Ouvrir cette page :</translation>
-+<translation id="9074348188580488499">Voulez-vous vraiment supprimer tous les mots de passe ?</translation>
-+<translation id="6635491740861629599">Sélectionner par domaine</translation>
-+<translation id="3627588569887975815">Ouvrir le lien dans une fenêtre en navi&amp;gation privée</translation>
-+<translation id="5851868085455377790">Émetteur</translation>
-+<translation id="8223496248037436966">Options de saisie automatique</translation>
-+<translation id="1470719357688513792">Les nouveaux paramètres des cookies seront appliqués quand vous aurez actualisé la page.</translation>
-+<translation id="5578327870501192725">Votre connexion à <ph name="DOMAIN"/> est sécurisée par un chiffrement <ph name="BIT_COUNT"/> bits.</translation>
-+<translation id="869884720829132584">Menu Applications</translation>
-+<translation id="7764209408768029281">Outi&amp;ls</translation>
-+<translation id="1139892513581762545">Onglets latéraux</translation>
-+<translation id="7634357567062076565">Reprendre</translation>
-+<translation id="4779083564647765204">Zoom</translation>
-+<translation id="3282430104564575032">Inspecteur de DOM</translation>
-+<translation id="1526560967942511387">Document sans titre</translation>
-+<translation id="1291144580684226670">Police standard</translation>
-+<translation id="3979748722126423326">Activer <ph name="NETWORKDEVICE"/></translation>
-+<translation id="5538307496474303926">Opération en cours...</translation>
-+<translation id="4367133129601245178">C&amp;opier l'URL de l'image</translation>
-+<translation id="7542995811387359312">La saisie automatique des numéros de carte de paiement est désactivée, car la connexion utilisée par ce formulaire n'est pas sécurisée.</translation>
-+<translation id="3494444535872870968">Enregistrer le &amp;cadre sous...</translation>
-+<translation id="987264212798334818">Général</translation>
-+<translation id="7005812687360380971">Défaillance</translation>
-+<translation id="2356070529366658676">Demander</translation>
-+<translation id="5731247495086897348">Coller l'URL et y a&amp;ccéder</translation>
-+<translation id="8467548439852845758">Pour plus de sécurité, <ph name="PRODUCT_NAME"/> va chiffrer vos mots de passe.</translation>
-+<translation id="2524947000814989347">Si vous avez oublié votre mot de passe multiterme, vous devrez arrêter la synchronisation via Google Dashboard.</translation>
-+<translation id="8018154597338652331"><ph name="BURNT_AMOUNT"/> sur <ph name="TOTAL_SIZE"/></translation>
-+<translation id="7635741716790924709">Adresse ligne 1</translation>
-+<translation id="5135533361271311778">Impossible de créer le favori.</translation>
-+<translation id="5271247532544265821">Basculer en mode chinois simplifié/traditionnel</translation>
-+<translation id="2052610617971448509">Votre système Sandbox n'est pas correctement configuré.</translation>
-+<translation id="7384913436093989340">Sélectionnez le <ph name="BEGIN_BOLD"/>menu clé à molette &gt; Préférences &gt; Options avancées &gt; Modifier les paramètres du proxy<ph name="END_BOLD"/> et vérifiez que vos paramètres sont définis sur &quot;sans proxy&quot; ou &quot;direct&quot;.</translation>
-+<translation id="6417515091412812850">Impossible de vérifier si le certificat a été révoqué.</translation>
-+<translation id="7282743297697561153">Stockage des données</translation>
-+<translation id="3363332416643747536"><ph name="DOWNLOAD_RECEIVED"/>/<ph name="DOWNLOAD_TOTAL"/>, Interrompu</translation>
-+<translation id="7347702518873971555">Acheter un forfait</translation>
-+<translation id="5285267187067365830">Installer le plug-in...</translation>
-+<translation id="5334844597069022743">Afficher le code source</translation>
-+<translation id="1166212789817575481">Fermer les onglets sur la droite</translation>
-+<translation id="6472893788822429178">Afficher le bouton &quot;Accueil&quot;</translation>
-+<translation id="4270393598798225102">Version <ph name="NUMBER"/></translation>
-+<translation id="534916491091036097">Parenthèse gche</translation>
-+<translation id="4157869833395312646">Microsoft Server Gated Cryptography</translation>
-+<translation id="8903921497873541725">Zoom avant</translation>
-+<translation id="2195729137168608510">Protection du courrier électronique</translation>
-+<translation id="1425734930786274278">Les cookies suivants ont été bloqués (tous les cookies tiers sont bloqués, sans exception) :</translation>
-+<translation id="6805647936811177813">Connectez-vous à <ph name="TOKEN_NAME"/> pour importer le certificat client de <ph name="HOST_NAME"/></translation>
-+<translation id="3437016096396740659">La batterie est chargée.</translation>
-+<translation id="6916146760805488559">Créer un nouveau profil...</translation>
-+<translation id="1199232041627643649">Maintenez la touche <ph name="KEY_EQUIVALENT"/> enfoncée pour quitter.</translation>
-+<translation id="5428562714029661924">Masquer ce plug-in</translation>
-+<translation id="7907591526440419938">Ouvrir le fichier</translation>
-+<translation id="2568774940984945469">Conteneur de barres d'infos</translation>
-+<translation id="8971063699422889582">Le certificat du serveur a expiré.</translation>
-+<translation id="8281596639154340028">Utiliser <ph name="HANDLER_TITLE"/></translation>
-+<translation id="7134098520442464001">Réduit la taille du texte</translation>
-+<translation id="21133533946938348">Épingler l'onglet</translation>
-+<translation id="1325040735987616223">Mise à jour du système</translation>
-+<translation id="2864069933652346933"><ph name="NUMBER_ZERO"/> days left</translation>
-+<translation id="9090669887503413452">Inclure les informations système</translation>
-+<translation id="3084771660770137092">Google Chrome n'avait pas suffisamment de mémoire ou le processus de la page Web a été arrêté pour une autre raison. Pour continuer, actualisez la page ou ouvrez-en une autre.</translation>
-+<translation id="1114901192629963971">Impossible de valider votre mot de passe sur le réseau actuel. Sélectionnez un autre réseau.</translation>
-+<translation id="5179510805599951267">Cette page n'est pas rédigée en <ph name="ORIGINAL_LANGUAGE"/> ? Signaler l'erreur</translation>
-+<translation id="6430814529589430811">Certificat unique codé Base 64 ASCII</translation>
-+<translation id="5143712164865402236">Activer le mode plein écran</translation>
-+<translation id="8434177709403049435">Codag&amp;e</translation>
-+<translation id="4051923669149193910"><ph name="HANDLER_TITLE"/> est déjà utilisé pour gérer les liens <ph name="PROTOCOL"/>://.</translation>
-+<translation id="2722201176532936492">Touches de sélection</translation>
-+<translation id="385120052649200804">Clavier international américain</translation>
-+<translation id="9012607008263791152">Je comprends que la visite de ce site peut être préjudiciable à mon ordinateur.</translation>
-+<translation id="6640442327198413730">Ressource cache manquante.</translation>
-+<translation id="1441458099223378239">Impossible d'accéder à mon compte</translation>
-+<translation id="5793220536715630615">C&amp;opier l'URL de la vidéo</translation>
-+<translation id="523397668577733901">Vous préférez <ph name="BEGIN_LINK"/>parcourir la galerie<ph name="END_LINK"/> ?</translation>
-+<translation id="2922350208395188000">Impossible de vérifier le certificat du serveur.</translation>
-+<translation id="3778740492972734840">Outils de &amp;développement</translation>
-+<translation id="8335971947739877923">Exporter...</translation>
-+<translation id="5680966941935662618">Paramètres de saisie automatique</translation>
-+<translation id="38385141699319881">Téléchargement de l'image en cours...</translation>
-+<translation id="6004539838376062211">&amp;Options du vérificateur d'orthographe</translation>
-+<translation id="5350198318881239970">Impossible d'ouvrir votre profil correctement.\n\nIl est possible que certaines fonctionnalités ne soient pas disponibles. Vérifiez que ce profil existe et que vous disposez d'une autorisation d'accès à son contenu en lecture et en écriture.</translation>
-+<translation id="4058793769387728514">Vérifier le document maintenant</translation>
-+<translation id="1810107444790159527">Zone de liste</translation>
-+<translation id="3338239663705455570">Clavier slovène</translation>
-+<translation id="1859234291848436338">Sens de l'écriture</translation>
-+<translation id="4567836003335927027">Vos données sur <ph name="WEBSITE_1"/></translation>
-+<translation id="756445078718366910">Ouvrir une fenêtre du navigateur</translation>
-+<translation id="4126154898592630571">Conversion de la date et de l'heure</translation>
-+<translation id="5088534251099454936">PKCS #1 SHA-512 avec chiffrement RSA</translation>
-+<translation id="6392373519963504642">Clavier coréen</translation>
-+<translation id="7887334752153342268">Dupliquer</translation>
-+<translation id="4980691186726139495">Ne pas conserver sur cette page</translation>
-+<translation id="3081523290047420375">Désactiver <ph name="CLOUD_PRINT_NAME"/></translation>
-+<translation id="9207194316435230304">ATOK</translation>
-+<translation id="9026731007018893674">téléchargement</translation>
-+<translation id="7646591409235458998">E-mail :</translation>
-+<translation id="703748601351783580">Ouvrir tous les favoris dans une nouvelle &amp;fenêtre</translation>
-+<translation id="6199775032047436064">Rafraîchir la page actuelle</translation>
-+<translation id="6981982820502123353">Accessibilité</translation>
-+<translation id="112343676265501403">Exceptions pour les plug-ins</translation>
-+<translation id="770273299705142744">Remplissage automatique des formulaires</translation>
-+<translation id="7210998213739223319">Nom d'utilisateur</translation>
-+<translation id="4478664379124702289">Enregistrer le lie&amp;n sous...</translation>
-+<translation id="8725066075913043281">Réessayer</translation>
-+<translation id="8502249598105294518">Personnaliser et configurer <ph name="PRODUCT_NAME"/></translation>
-+<translation id="7392089327262158658">Préférences de saisie automatique <ph name="PRODUCT_NAME_SHORT"/></translation>
-+<translation id="4163521619127344201">Votre position géographique</translation>
-+<translation id="3797008485206955964">Afficher les pages en arrière-plan (<ph name="NUM_BACKGROUND_APPS"/>)</translation>
-+<translation id="8590375307970699841">Configurer les mises à jour automatiques</translation>
-+<translation id="2797524280730715045">il y a <ph name="NUMBER_DEFAULT"/> heures</translation>
-+<translation id="265390580714150011">Valeur du champ</translation>
-+<translation id="9073247318500677671">Les dernières versions d'Unity et GNOME (ainsi que la prochaine version d'Ubuntu, Natty Narwhal) affichent une barre de menus de type OSX sur toute la largeur supérieure de l'écran.</translation>
-+<translation id="3869917919960562512">Index erroné.</translation>
-+<translation id="7031962166228839643">Préparation du module de plate-forme sécurisée (TPM) en cours. Veuillez patienter, l'opération peut prendre quelques minutes.</translation>
-+<translation id="4250377793615429299">Nombre de copies incorrect</translation>
-+<translation id="7180865173735832675">Personnaliser</translation>
-+<translation id="5737306429639033676">Prédire les actions du réseau pour améliorer les performances de chargement des pages</translation>
-+<translation id="8123426182923614874">Données restantes :</translation>
-+<translation id="3707020109030358290">N'est pas une autorité de certification.</translation>
-+<translation id="2115926821277323019">L'URL doit être valide.</translation>
-+<translation id="8986494364107987395">Envoyer automatiquement les statistiques d'utilisation et les rapports d'erreur à Google</translation>
-+<translation id="7070714457904110559">Cette fonctionnalité active la géolocalisation dans les extensions expérimentales. Cela implique l'utilisation des API de localisation du système d'exploitation (si disponibles) et l'envoi de données sur la configuration réseau locale au service de localisation de Google afin de déterminer une position précise.</translation>
-+<translation id="6701535245008341853">Impossible de charger le profil.</translation>
-+<translation id="527605982717517565">Toujours exécuter JavaScript sur <ph name="HOST"/></translation>
-+<translation id="702373420751953740">Version PRL :</translation>
-+<translation id="1307041843857566458">Confirmer la réactivation</translation>
-+<translation id="8314308967132194952">Ajouter une adresse postale...</translation>
-+<translation id="1221024147024329929">PKCS #1 MD2 avec chiffrement RSA</translation>
-+<translation id="853265131227167869">Dim.^Lun.^Mar.^Mer.^Jeu.^Ven.^Sam.</translation>
-+<translation id="3323447499041942178">Zone de saisie</translation>
-+<translation id="580571955903695899">Trier par nom</translation>
-+<translation id="5230516054153933099">Fenêtre</translation>
-+<translation id="7554791636758816595">Nouvel onglet</translation>
-+<translation id="5503844897713343920">Vous tentez d'accéder au site <ph name="DOMAIN"/>, mais le certificat présenté par le serveur a été révoqué par son émetteur. Cela signifie que les informations d'identification présentées par le serveur ne sont pas approuvées. Vous communiquez peut-être avec un pirate informatique. Nous vous déconseillons vivement de continuer.</translation>
-+<translation id="6928853950228839340">Composition hors écran</translation>
-+<translation id="1308727876662951186"><ph name="NUMBER_ZERO"/> mins left</translation>
-+<translation id="7671576867600624">Technologie :</translation>
-+<translation id="1103966635949043187">Accédez à la page d'accueil du site :</translation>
-+<translation id="1951332921786364801">Configurer la communication à distance</translation>
-+<translation id="1086613338090581534">L'émetteur d'un certificat n'ayant pas expiré est tenu d'assurer la maintenance de ce qui s'appelle &quot;une liste de révocation&quot;. Si un certificat est compromis, l'émetteur peut le révoquer en l'ajoutant à la liste de révocation. Ce certificat n'est alors plus approuvé par votre navigateur. Il n'est pas nécessaire d'assurer la maintenance de l'état &quot;révoqué&quot; des certificats expirés. Donc, bien qu'un certificat ait été qualifié de valide pour le site Web que vous visitez actuellement, il est impossible de déterminer s'il a été, depuis, compromis puis révoqué ou s'il est toujours valide. Par conséquent, il n'est pas possible de s'assurer si vous communiquez avec un site Web légitime ou si le certificat a été compromis et se trouve maintenant en la possession d'un pirate informatique avec lequel vous communiquez. Ne poursuivez pas.</translation>
-+<translation id="2645575947416143543">Néanmoins, si vous travaillez dans une entreprise qui génère ses propres certificats, et que vous essayez de vous connecter au site Web interne de l'entreprise avec un certificat de ce type, vous pouvez résoudre ce problème en toute sécurité. Pour ce faire, importez le certificat racine de l'entreprise en tant que &quot;certificat racine&quot;. Par la suite, les certificats émis ou vérifiés par votre entreprise seront approuvés et vous ne verrez plus cette erreur lorsque vous tenterez de vous connecter à nouveau au site Web interne. Contactez le support informatique de votre entreprise pour savoir comment ajouter un nouveau certificat racine sur votre ordinateur.</translation>
-+<translation id="376466258076168640">Définir <ph name="PRODUCT_NAME"/> en tant que navigateur par défaut</translation>
-+<translation id="1056898198331236512">Avertissement</translation>
-+<translation id="8630826211403662855">Préférences de recherche</translation>
-+<translation id="8432745813735585631">Clavier Colemak américain</translation>
-+<translation id="8151639108075998630">Activer la navigation en tant qu'invité</translation>
-+<translation id="2608770217409477136">Utiliser les paramètres par défaut</translation>
-+<translation id="3157931365184549694">Rétablir</translation>
-+<translation id="7426243339717063209">Désinstaller &quot;<ph name="EXTENSION_NAME"/>&quot; ?</translation>
-+<translation id="996250603853062861">Établissement de la connexion sécurisée...</translation>
-+<translation id="6059232451013891645">Dossier :</translation>
-+<translation id="4274292172790327596">Erreur non reconnue</translation>
-+<translation id="760537465793895946">Consultez les conflits connus avec des modules tiers.</translation>
-+<translation id="7042418530779813870">Co&amp;ller et rechercher</translation>
-+<translation id="9110447413660189038">&amp;Remonter</translation>
-+<translation id="375403751935624634">Échec de la traduction en raison d'une erreur de serveur</translation>
-+<translation id="2101225219012730419">Version :</translation>
-+<translation id="1570242578492689919">Polices et codage</translation>
-+<translation id="3082374807674020857"><ph name="PAGE_TITLE"/> - <ph name="PAGE_URL"/></translation>
-+<translation id="8050038245906040378">Signature du code commercial Microsoft</translation>
-+<translation id="3031557471081358569">Sélectionnez les éléments à importer :</translation>
-+<translation id="1368832886055348810">De gauche à droite</translation>
-+<translation id="3031433885594348982">Votre connexion à <ph name="DOMAIN"/> est sécurisée par le biais d'un faible chiffrement.</translation>
-+<translation id="4047345532928475040">sans objet</translation>
-+<translation id="5604324414379907186">Toujours afficher la barre de favoris</translation>
-+<translation id="3220630151624181591">Activer l'onglet 2</translation>
-+<translation id="8898139864468905752">Aperçu des onglets</translation>
-+<translation id="2799223571221894425">Redémarrer</translation>
-+<translation id="5771816112378578655">Configuration en cours...</translation>
-+<translation id="1197979282329025000">Une erreur s'est produite lors de la récupération des fonctions de l'imprimante <ph name="PRINTER_NAME"/>. Cette imprimante n'a pas pu être enregistrée dans <ph name="CLOUD_PRINT_NAME"/>.</translation>
-+<translation id="8820901253980281117">Exceptions pour les fenêtres pop-up</translation>
-+<translation id="1143142264369994168">Signataire du certificat </translation>
-+<translation id="904949795138183864">La page Web <ph name="URL"/> n'existe plus.</translation>
-+<translation id="3228279582454007836">Vous n'avez jamais visité ce site auparavant.</translation>
-+<translation id="2159017110205600596">Personnaliser...</translation>
-+<translation id="5449716055534515760">Fe&amp;rmer la fenêtre</translation>
-+<translation id="2814489978934728345">Arrêter le chargement de cette page</translation>
-+<translation id="2354001756790975382">Autres favoris</translation>
-+<translation id="8561574028787046517"><ph name="PRODUCT_NAME"/> a été mis à jour.</translation>
-+<translation id="5234325087306733083">Mode hors connexion</translation>
-+<translation id="1779392088388639487">Erreur d'importation de fichier PKCS #12</translation>
-+<translation id="166278006618318542">Algorithme de clé publique de l'objet</translation>
-+<translation id="5759272020525228995">Le site Web a rencontré une erreur lors de l'extraction de <ph name="URL"/>.
-+ Cela peut être dû à une opération de maintenance ou à une configuration incorrecte.</translation>
-+<translation id="641480858134062906">Échec du chargement de la page <ph name="URL"/></translation>
-+<translation id="3693415264595406141">Mot de passe :</translation>
-+<translation id="74568296546932365">Conserver <ph name="PAGE_TITLE"/> en tant que moteur de recherche par défaut</translation>
-+<translation id="8602184400052594090">Fichier manifeste absent ou illisible</translation>
-+<translation id="2784949926578158345">La connexion a été réinitialisée.</translation>
-+<translation id="6663792236418322902">Le mot de passe choisi vous sera demandé pour restaurer le fichier. Veillez à le conserver en lieu sûr.</translation>
-+<translation id="4532822216683966758">La vérification de la provenance du certificat DNS est activée, ce qui peut entraîner l'envoi d'informations privées à Google.</translation>
-+<translation id="6321196148033717308">À propos de la reconnaissance vocale</translation>
-+<translation id="3412265149091626468">Aller à la sélection</translation>
-+<translation id="8167737133281862792">Ajouter un certificat</translation>
-+<translation id="2911372483530471524">Espaces de noms PID</translation>
-+<translation id="6093374025603915876">Préférences de saisie automatique</translation>
-+<translation id="8584134039559266300">Activer l'onglet 8</translation>
-+<translation id="5189060859917252173">Le certificat &quot;<ph name="CERTIFICATE_NAME"/>&quot; représente une autorité de certification.</translation>
-+<translation id="3785852283863272759">Envoyer par e-mail l'emplacement de la page</translation>
-+<translation id="2255317897038918278">Enregistrement des informations de date Microsoft</translation>
-+<translation id="3493881266323043047">Validité</translation>
-+<translation id="5979421442488174909">&amp;Traduire en <ph name="LANGUAGE"/></translation>
-+<translation id="7326526699920221209">Batterie : <ph name="PRECENTAGE"/> %</translation>
-+<translation id="952992212772159698">Désactivé</translation>
-+<translation id="8299269255470343364">Japonais</translation>
-+<translation id="5187826826541650604"><ph name="KEY_NAME"/> (<ph name="DEVICE"/>)</translation>
-+<translation id="6429639049555216915">L'application est actuellement inaccessible.</translation>
-+<translation id="2144536955299248197">Lecteur du certificat : <ph name="CERTIFICATE_NAME"/></translation>
-+<translation id="50030952220075532"><ph name="NUMBER_ONE"/> jour restant</translation>
-+<translation id="2885378588091291677">Gestionnaire de tâches</translation>
-+<translation id="5792852254658380406">Gérer les extensions...</translation>
-+<translation id="2359808026110333948">Continuer</translation>
-+<translation id="176759384517330673">Synchronisation avec <ph name="USER_EMAIL_ADDRESS"/> effectuée. Dernière synchronisation : <ph name="LAST_SYNC_TIME"/></translation>
-+<translation id="1618661679583408047">Le certificat de sécurité du site n'est pas encore valide !</translation>
-+<translation id="7039912931802252762">Ouverture de session par carte à puce Microsoft</translation>
-+<translation id="6285074077487067719">Format</translation>
-+<translation id="3065140616557457172">Tapez votre requête ou saisissez une URL pour commencer la navigation : c'est à vous de choisir.</translation>
-+<translation id="5509693895992845810">Enregistrer &amp;sous...</translation>
-+<translation id="5986279928654338866">Le serveur <ph name="DOMAIN"/> requiert un nom d'utilisateur et un mot de passe.</translation>
-+<translation id="521467793286158632">Supprimer tous les mots de passe</translation>
-+<translation id="2491120439723279231">Le certificat du serveur contient des erreurs.</translation>
-+<translation id="4448844063988177157">Recherche de réseaux Wi-Fi...</translation>
-+<translation id="5765780083710877561">Description :</translation>
-+<translation id="338583716107319301">Séparateur</translation>
-+<translation id="2079053412993822885">Si vous supprimez l'un de vos propres certificats, vous ne pouvez plus l'utiliser pour vous identifier.</translation>
-+<translation id="7221869452894271364">Rafraîchir cette page</translation>
-+<translation id="6791443592650989371">État d'activation :</translation>
-+<translation id="4801257000660565496">Créer des raccourcis vers des applications</translation>
-+<translation id="6503256918647795660">Clavier franco-suisse</translation>
-+<translation id="6175314957787328458">GUID de domaine Microsoft</translation>
-+<translation id="8179976553408161302">Entrer</translation>
-+<translation id="8261506727792406068">Supprimer</translation>
-+<translation id="4404805853119650018">Échec de l'enregistrement de cet ordinateur pour l'accès à distance.</translation>
-+<translation id="345693547134384690">Ouvrir l'&amp;image dans un nouvel onglet</translation>
-+<translation id="7422192691352527311">Préférences...</translation>
-+<translation id="354211537509721945">L'administrateur a désactivé les mises à jour.</translation>
-+<translation id="1375198122581997741">À propos de la version</translation>
-+<translation id="7915471803647590281">Veuillez nous indiquer ce qu'il se passe avant d'envoyer votre rapport.</translation>
-+<translation id="5725124651280963564">Connectez-vous à <ph name="TOKEN_NAME"/> afin de générer une clé pour <ph name="HOST_NAME"/>.</translation>
-+<translation id="8418113698656761985">Clavier roumain</translation>
-+<translation id="5976160379964388480">Autres</translation>
-+<translation id="3665842570601375360">Sécurité :</translation>
-+<translation id="1430915738399379752">Imprimer</translation>
-+<translation id="7999087758969799248">Mode de saisie standard</translation>
-+<translation id="2635276683026132559">Signature</translation>
-+<translation id="4835836146030131423">Erreur lors de la connexion</translation>
-+<translation id="7715454002193035316">Pour cette session uniquement</translation>
-+<translation id="2475982808118771221">Une erreur s'est produite.</translation>
-+<translation id="3324684065575061611">(Désactivé par une stratégie d'entreprise)</translation>
-+<translation id="7385854874724088939">Erreur lors de la tentative d'impression. Vérifiez votre imprimante et réessayez.</translation>
-+<translation id="770015031906360009">Grec</translation>
-+<translation id="3834901049798243128">Ignorer les exceptions et bloquer l'enregistrement des cookies tiers</translation>
-+<translation id="8116152017593700047">Cet outil vous permet de sélectionner une capture d'écran enregistrée. Aucune capture d'écran n'est disponible pour le moment. Appuyez simultanément sur Ctrl et sur la touche &quot;Mode Présentation&quot; pour enregistrer une capture d'écran. Vos trois dernières captures apparaissent ici.</translation>
-+<translation id="3454157711543303649">Activation effectuée</translation>
-+<translation id="884923133447025588">Aucun système de révocation trouvé</translation>
-+<translation id="556042886152191864">Bouton</translation>
-+<translation id="1352060938076340443">Interrompu</translation>
-+<translation id="8571226144504132898">Dictionnaire de symboles</translation>
-+<translation id="7229570126336867161">Technologie EvDo requise</translation>
-+<translation id="7582844466922312471">Internet mobile</translation>
-+<translation id="945522503751344254">Envoyer le commentaire</translation>
-+<translation id="4539401194496451708">Associé au profil Chrome <ph name="USER_EMAIL_ADDRESS"/>. Dernière synchronisation : <ph name="LAST_SYNC_TIME"/></translation>
-+<translation id="7369847606959702983">Carte de crédit (autre)</translation>
-+<translation id="6867459744367338172">Langues et saisie</translation>
-+<translation id="7671130400130574146">Utiliser la barre de titre et les bordures de fenêtre du système</translation>
-+<translation id="9170848237812810038">Ann&amp;uler</translation>
-+<translation id="284970761985428403"><ph name="ASCII_NAME"/> (<ph name="UNICODE_NAME"/>)</translation>
-+<translation id="3903912596042358459">Le serveur a refusé d'exécuter la demande.</translation>
-+<translation id="8135557862853121765"><ph name="NUM_KILOBYTES"/> Ko</translation>
-+<translation id="4444364671565852729"><ph name="PRODUCT_NAME"/> a été mis à jour vers la version <ph name="VERSION"/>.</translation>
-+<translation id="5819890516935349394">Navigateur de contenu</translation>
-+<translation id="2731392572903530958">&amp;Rouvrir la fenêtre fermée</translation>
-+<translation id="1254593899333212300">Se connecter directement à Internet</translation>
-+<translation id="6107012941649240045">Émis pour</translation>
-+<translation id="6483805311199035658">Ouverture de <ph name="FILE"/> en cours</translation>
-+<translation id="3576278878016363465">Cibles disponibles pour l'image</translation>
-+<translation id="895541991026785598">Signaler un problème</translation>
-+<translation id="940425055435005472">Taille de police :</translation>
-+<translation id="494286511941020793">Aide pour la configuration de proxy</translation>
-+<translation id="2765217105034171413">Petite</translation>
-+<translation id="1285266685456062655"><ph name="NUMBER_FEW"/> hours ago</translation>
-+<translation id="9154176715500758432">Rester sur cette page</translation>
-+<translation id="5875565123733157100">Type de bug :</translation>
-+<translation id="6988771638657196063">Inclure cette URL :</translation>
-+<translation id="5717920936024713315">Cookies et données de site...</translation>
-+<translation id="3842552989725514455">Police Serif</translation>
-+<translation id="1949795154112250744"><ph name="BEGIN_BOLD"/>Avertissement :<ph name="END_BOLD"/> <ph name="PRODUCT_NAME"/> ne peut pas empêcher les extensions d'enregistrer votre historique de navigation. Pour désactiver cette extension en mode navigation privée, désélectionnez-la.</translation>
-+<translation id="4440967101351338638">ChromiumOs Image Burn</translation>
-+<translation id="1813278315230285598">Services</translation>
-+<translation id="6860097299815761905">Paramètres du proxy...</translation>
-+<translation id="373572798843615002">1 onglet</translation>
-+<translation id="4162393307849942816"><ph name="BEGIN_BOLD"/>Vous naviguez en tant qu'invité<ph name="END_BOLD"/>. Les pages que vous consultez dans cette fenêtre n'apparaîtront pas dans l'historique de votre navigateur ni dans votre historique des recherches. Les autres traces telles que les cookies seront supprimées de l'ordinateur à la fin de votre session. En revanche, les fichiers téléchargés et les favoris créés seront conservés.
-+ <ph name="LINE_BREAK"/>
-+ <ph name="BEGIN_LINK"/>En savoir plus<ph name="END_LINK"/> sur le mode invité</translation>
-+<translation id="827924395145979961">Chargement des pages impossible</translation>
-+<translation id="3092544800441494315">Inclure cette capture d'écran :</translation>
-+<translation id="7714464543167945231">Certificat</translation>
-+<translation id="3616741288025931835">&amp;Effacer les données de navigation...</translation>
-+<translation id="3313622045786997898">Valeur de signature du certificat</translation>
-+<translation id="8535005006684281994">URL de renouvellement du certificat Netscape</translation>
-+<translation id="2440604414813129000">Afficher la s&amp;ource</translation>
-+<translation id="816095449251911490"><ph name="SPEED"/> - <ph name="RECEIVED_AMOUNT"/>, <ph name="TIME_REMAINING"/></translation>
-+<translation id="8200772114523450471">Reprendre</translation>
-+<translation id="6358975074282722691"><ph name="NUMBER_TWO"/> secs ago</translation>
-+<translation id="5423849171846380976">Activé</translation>
-+<translation id="6748105842970712833">Carte SIM désactivée</translation>
-+<translation id="7323391064335160098">Compatibilité avec VPN</translation>
-+<translation id="3929673387302322681">Développement - Instable</translation>
-+<translation id="4251486191409116828">Échec de création du raccourci vers l'application</translation>
-+<translation id="5190835502935405962">Barre de favoris</translation>
-+<translation id="7828272290962178636">Le serveur est en mesure de répondre à la demande.</translation>
-+<translation id="7823073559911777904">Modifier les paramètres du proxy...</translation>
-+<translation id="5438430601586617544">(non empaquetée)</translation>
-+<translation id="6460601847208524483">Rechercher le suivant</translation>
-+<translation id="8433186206711564395">Paramètres réseau</translation>
-+<translation id="4856478137399998590">Votre service Internet mobile est activé et prêt à l'emploi.</translation>
-+<translation id="1676388805288306495">Modifier la police et la langue par défaut des pages Web</translation>
-+<translation id="8969761905474557563">Composition graphique avec accélération matérielle</translation>
-+<translation id="3937640725563832867">Autre nom de l'émetteur du certificat</translation>
-+<translation id="4701488924964507374"><ph name="SENTENCE1"/> <ph name="SENTENCE2"/></translation>
-+<translation id="1163931534039071049">&amp;Afficher le code source du cadre</translation>
-+<translation id="8770196827482281187">Mode de saisie du persan (clavier ISIRI 2901)</translation>
-+<translation id="6423239382391657905">OpenVPN</translation>
-+<translation id="7564847347806291057">Arrêter le processus</translation>
-+<translation id="1607220950420093847">Votre compte a peut-être été supprimé ou désactivé. Veuillez vous déconnecter.</translation>
-+<translation id="5613695965848159202">Authentification anonyme :</translation>
-+<translation id="2233320200890047564">Bases de données indexées</translation>
-+<translation id="7063412606254013905">En savoir plus sur les escroqueries par phishing</translation>
-+<translation id="307767688111441685">Page à l'apparence anormale</translation>
-+<translation id="9076523132036239772">Adresse e-mail ou mot de passe incorrect. Essayez tout d'abord de vous connecter à un réseau.</translation>
-+<translation id="6965978654500191972">Périphérique</translation>
-+<translation id="1242521815104806351">Informations sur la connexion</translation>
-+<translation id="5295309862264981122">Confirmer la navigation</translation>
-+<translation id="1492817554256909552">Nom du point d'accès :</translation>
-+<translation id="5546865291508181392">Rechercher</translation>
-+<translation id="1999115740519098545">Au démarrage</translation>
-+<translation id="2983818520079887040">Paramètres...</translation>
-+<translation id="1465619815762735808">Lire en un clic</translation>
-+<translation id="6941937518557314510">Connectez-vous à <ph name="TOKEN_NAME"/> pour vous authentifier auprès de <ph name="HOST_NAME"/> avec votre certificat.</translation>
-+<translation id="2783600004153937501">Votre administrateur informatique a désactivé certaines options.</translation>
-+<translation id="2099686503067610784">Supprimer le certificat de serveur &quot;<ph name="CERTIFICATE_NAME"/>&quot;?</translation>
-+<translation id="9027603907212475920">Configurer la synchronisation...</translation>
-+<translation id="6873213799448839504">Valider automatiquement une chaîne</translation>
-+<translation id="7238585580608191973">Empreinte SHA-256</translation>
-+<translation id="2501278716633472235">Retour</translation>
-+<translation id="131461803491198646">Réseau domestique, sans itinérance</translation>
-+<translation id="7377249249140280793"><ph name="RELATIVE_DATE"/> - <ph name="FULL_DATE"/></translation>
-+<translation id="5679279978772703611">Gérer les mots de passe enregistrés...</translation>
-+<translation id="4551440281920791563">Sélectionnez
-+ <ph name="BEGIN_BOLD"/>
-+ Menu clé à molette &gt; Options &gt; Options avancées &gt; Modifier les paramètres du proxy &gt; Paramètres réseau
-+ <ph name="END_BOLD"/>
-+ et désélectionnez l'option &quot;Utiliser un serveur proxy pour votre réseau local&quot;.</translation>
-+<translation id="1285320974508926690">Ne jamais traduire ce site</translation>
-+<translation id="8954894007019320973">(suite)</translation>
-+<translation id="3748412725338508953">Trop de redirections</translation>
-+<translation id="5833726373896279253">Ces paramètres ne peuvent être modifiés que par le propriétaire :</translation>
-+<translation id="6858960932090176617">Active la protection XSS Auditor de WebKit (protection contre le Cross-site Scripting), une fonctionnalité qui vous protège de certaines attaques de sites malveillants et offre une sécurité accrue, mais qui n'est pas compatible avec tous les sites Web.</translation>
-+<translation id="6005282720244019462">Clavier latino-américain</translation>
-+<translation id="8831104962952173133">Phishing détecté !</translation>
-+<translation id="5141720258550370428">Voulez-vous utiliser <ph name="HANDLER_TITLE"/> (<ph name="HANDLER_HOSTNAME"/>) pour gérer les liens <ph name="PROTOCOL"/>:// à partir de maintenant ?</translation>
-+<translation id="6751344591405861699"><ph name="WINDOW_TITLE"/> (Navigation privée)</translation>
-+<translation id="6681668084120808868">Prendre une photo</translation>
-+<translation id="780301667611848630">Non merci</translation>
-+<translation id="2812989263793994277">Ne pas afficher les images</translation>
-+<translation id="7190251665563814471">Toujours autoriser ces plug-ins sur <ph name="HOST"/></translation>
-+<translation id="6845383723252244143">Sélectionner un dossier</translation>
-+<translation id="8925458182817574960">&amp;Paramètres</translation>
-+<translation id="6361850914223837199">Informations sur l'erreur :</translation>
-+<translation id="8948393169621400698">Toujours autoriser les plug-ins sur <ph name="HOST"/></translation>
-+<translation id="3865082058368813534">Effacer les données de saisie automatique enregistrées</translation>
-+<translation id="8288345061925649502">Changer de moteur de recherche</translation>
-+<translation id="5436492226391861498">En attente du tunnel proxy...</translation>
-+<translation id="3803991353670408298">Veuillez ajouter un autre mode de saisie avant de supprimer celui-ci.</translation>
-+<translation id="1095623615273566396"><ph name="NUMBER_FEW"/> secondes</translation>
-+<translation id="7006788746334555276">Paramètres de contenu</translation>
-+<translation id="3369521687965833290">Impossible d'extraire les fichiers de l'extension. Pour effectuer cette opération en toute sécurité, vous devez disposer d'un chemin d'accès à votre répertoire de profils commençant par une lettre de lecteur et ne contenant ni jonction, ni point de montage, ni lien symbolique. Aucun chemin de ce type n'existe pour votre profil.</translation>
-+<translation id="337920581046691015"><ph name="PRODUCT_NAME"/> va être installé.</translation>
-+<translation id="6282194474023008486">Code postal</translation>
-+<translation id="7733107687644253241">En bas à droite</translation>
-+<translation id="5139955368427980650">&amp;Ouvrir</translation>
-+<translation id="8136149669168180907"><ph name="DOWNLOADED_AMOUNT"/> téléchargé(s) sur <ph name="TOTAL_SIZE"/></translation>
-+<translation id="7375268158414503514">Commentaires d'ordre général/Autres</translation>
-+<translation id="4643612240819915418">Ou&amp;vrir la vidéo dans un nouvel onglet</translation>
-+<translation id="7997479212858899587">Identité :</translation>
-+<translation id="8300849813060516376">Échec de l'opération OTASP</translation>
-+<translation id="2213819743710253654">Action sur la page</translation>
-+<translation id="1317130519471511503">Modifier des éléments...</translation>
-+<translation id="6391538222494443604">Le répertoire d'extensions est obligatoire.</translation>
-+<translation id="8051695050440594747"><ph name="MEGABYTES"/> Mo disponibles</translation>
-+<translation id="7088615885725309056">Ancien</translation>
-+<translation id="461656879692943278"><ph name="HOST_NAME"/> fournit du contenu provenant de <ph name="ELEMENTS_HOST_NAME"/>, un site connu pour distribuer des logiciels malveillants. Votre ordinateur pourrait être infecté par un virus si vous consultez ce site.</translation>
-+<translation id="1387022316521171484">Ces fonctionnalités expérimentales sont susceptibles d'être modifiées, interrompues ou supprimées à tout moment. Nous ne fournissons aucune garantie quant aux effets de leur activation. Votre navigateur pourrait bien prendre feu. Trêve de plaisanterie, il est possible que votre navigateur supprime toutes vos données ou que votre sécurité et votre vie privée soient compromises de manière inattendue. Nous vous prions d'agir avec précaution.</translation>
-+<translation id="2143778271340628265">Configuration manuelle du proxy</translation>
-+<translation id="5294529402252479912">Mettre à jour Adobe Reader maintenant</translation>
-+<translation id="5263972071113911534"><ph name="NUMBER_MANY"/> days ago</translation>
-+<translation id="7461850476009326849">Désactiver les plug-ins individuels...</translation>
-+<translation id="4097411759948332224">Envoyer une capture d'écran de la page en cours</translation>
-+<translation id="2231990265377706070">Point d'exclamation</translation>
-+<translation id="7199540622786492483"><ph name="PRODUCT_NAME"/> n'est plus à jour, car il n'a pas été relancé depuis quelque temps. La mise à jour disponible sera installée dès que vous le relancerez.</translation>
-+<translation id="8652722422052983852">Petit problème... Tentons de le résoudre.</translation>
-+<translation id="5970231080121144965">Cette fonctionnalité permet d'établir des correspondances entre les sous-chaînes et plusieurs fragments d'URL figurant dans l'historique.</translation>
-+<translation id="4665674675433053715">Page &quot;Nouvel onglet&quot; expérimentale</translation>
-+<translation id="3726527440140411893">Les cookies suivants étaient autorisés lorsque vous avez consulté cette page :</translation>
-+<translation id="3320859581025497771">votre opérateur</translation>
-+<translation id="8828781037212165374">Activer ces fonctionnalités...</translation>
-+<translation id="8562413501751825163">Quitter Firefox avant l'importation</translation>
-+<translation id="3435541101098866721">Ajouter un nouveau téléphone</translation>
-+<translation id="2448046586580826824">Proxy HTTP sécurisé</translation>
-+<translation id="4032534284272647190">Accès à <ph name="URL"/> refusé.</translation>
-+<translation id="4928569512886388887">Finalisation de la mise à jour du système...</translation>
-+<translation id="8258002508340330928">Voulez-vous continuer ?</translation>
-+<translation id="4309420042698375243"><ph name="NUM_KILOBYTES"/> Ko (<ph name="NUM_KILOBYTES_LIVE"/> Ko effectifs)</translation>
-+<translation id="5554573843028719904">Autre réseau Wi-Fi...</translation>
-+<translation id="5034259512732355072">Choisir un autre répertoire...</translation>
-+<translation id="8885905466771744233">L'extension indiquée est déjà associée à une clé privée. Utilisez cette clé ou supprimez-la.</translation>
-+<translation id="7831504847856284956">Ajouter une adresse</translation>
-+<translation id="7505152414826719222">Stockage local</translation>
-+<translation id="2541423446708352368">Afficher tous les téléchargements</translation>
-+<translation id="4381021079159453506">Navigateur de contenu</translation>
-+<translation id="8109246889182548008">Magasin de certificats</translation>
-+<translation id="5030338702439866405">Émis par</translation>
-+<translation id="5280833172404792470">Quitter le mode plein écran (<ph name="ACCELERATOR"/>)</translation>
-+<translation id="2728127805433021124">Le certificat du serveur a été signé avec un algorithme de signature faible.</translation>
-+<translation id="2137808486242513288">Ajouter un utilisateur</translation>
-+<translation id="6193618946302416945">Me proposer de traduire les pages qui sont écrites dans une langue que je ne sais pas lire</translation>
-+<translation id="129553762522093515">Récemment fermés</translation>
-+<translation id="4287167099933143704">Saisir la clé de déverrouillage du code PIN</translation>
-+<translation id="8355915647418390920"><ph name="NUMBER_FEW"/> jours</translation>
-+<translation id="3129140854689651517">Rechercher du texte</translation>
-+<translation id="7221585318879598658">Sans-Serif</translation>
-+<translation id="5558129378926964177">Zoom &amp;avant</translation>
-+<translation id="6451458296329894277">Confirmer le nouvel envoi du formulaire</translation>
-+<translation id="5116333507878097773"><ph name="NUMBER_ONE"/> heure</translation>
-+<translation id="8028152732786498049">Cet élément doit être installé depuis <ph name="CHROME_WEB_STORE"/>.</translation>
-+<translation id="9199258761842902152">Mise en veille ou reprise</translation>
-+<translation id="1851266746056575977">Mettre à jour maintenant</translation>
-+<translation id="7017219178341817193">Ajouter une page</translation>
-+<translation id="1038168778161626396">Chiffrer seulement</translation>
-+<translation id="2756651186786928409">Ne jamais intervertir les touches de modification</translation>
-+<translation id="1217515703261622005">Conversion des numéros spéciaux</translation>
-+<translation id="7179921470347911571">Relancer maintenant</translation>
-+<translation id="3715099868207290855">Synchronisation avec <ph name="USER_EMAIL_ADDRESS"/> effectuée</translation>
-+<translation id="2679312662830811292">il y a <ph name="NUMBER_ONE"/> minute</translation>
-+<translation id="9065203028668620118">Édition</translation>
-+<translation id="4718464510840275738">Préférences synchronisées</translation>
-+<translation id="8788572795284305350"><ph name="NUMBER_ZERO"/> hours ago</translation>
-+<translation id="1177863135347784049">Personnalisé</translation>
-+<translation id="8236028464988198644">Rechercher à partir de la barre d'adresse</translation>
-+<translation id="4881695831933465202">Ouvrir</translation>
-+<translation id="5988520580879236902">Inspecter les vues actives :</translation>
-+<translation id="3593965109698325041">Contraintes de nom du certificat</translation>
-+<translation id="4358697938732213860">Ajouter une adresse</translation>
-+<translation id="8396532978067103567">Mot de passe incorrect.</translation>
-+<translation id="5981759340456370804">Statistiques avancées</translation>
-+<translation id="8160015581537295331">Clavier espagnol</translation>
-+<translation id="3505920073976671674">Sélectionnez votre réseau</translation>
-+<translation id="6644971472240498405"><ph name="NUMBER_ONE"/> jour</translation>
-+<translation id="1782924894173027610">Le serveur de synchronisation est occupé. Veuillez réessayer ultérieurement.</translation>
-+<translation id="6512448926095770873">Quitter cette page</translation>
-+<translation id="5457599981699367932">Naviguer en tant qu'invité</translation>
-+<translation id="3169472444629675720">Discover</translation>
-+<translation id="6294193300318171613">&amp;Toujours afficher la barre de favoris</translation>
-+<translation id="4088820693488683766">Options de recherche</translation>
-+<translation id="3414952576877147120">Taille :</translation>
-+<translation id="9098468523912235228">il y a <ph name="NUMBER_DEFAULT"/> secondes</translation>
-+<translation id="7009102566764819240">La liste suivante fait état des éléments dangereux détectés sur la page. Cliquez sur le lien &quot;Diagnostic&quot; pour obtenir plus d'informations sur une ressource particulière. Si une ressource a été signalée comme site de phishing alors que vous êtes certain de sa fiabilité, cliquez sur le lien &quot;Signaler une erreur&quot;.</translation>
-+<translation id="4923417429809017348">Cette page rédigée dans une langue non identifiée a été traduite en <ph name="LANGUAGE_LANGUAGE"/>.</translation>
-+<translation id="3631337165634322335">Les exceptions ci-dessous s'appliquent uniquement à la session de navigation privée actuelle.</translation>
-+<translation id="676327646545845024">Ne plus afficher la boîte de dialogue pour les liens de ce type</translation>
-+<translation id="494645311413743213"><ph name="NUMBER_TWO"/> secondes restantes</translation>
-+<translation id="1485146213770915382">Insérez <ph name="SEARCH_TERMS_LITERAL"/> dans l'URL où les termes de recherche devraient apparaître.</translation>
-+<translation id="4839303808932127586">En&amp;registrer la vidéo sous...</translation>
-+<translation id="5626134646977739690">Nom :</translation>
-+<translation id="5854409662653665676">Si vous rencontrez des problèmes fréquents avec ce module, vous pouvez tenter d'y remédier en suivant la procédure ci-après :</translation>
-+<translation id="3681007416295224113">Informations relatives au certificat</translation>
-+<translation id="3046084099139788433">Activer l'onglet 7</translation>
-+<translation id="721197778055552897"><ph name="BEGIN_LINK"/>En savoir plus<ph name="END_LINK"/> sur ce problème.</translation>
-+<translation id="1699395855685456105">Version du matériel :</translation>
-+<translation id="212464871579942993">Le site Web à l'adresse <ph name="HOST_NAME"/> contient des éléments provenant de sites qui semblent héberger des logiciels malveillants. Ces derniers peuvent nuire à votre ordinateur ou agir à votre insu. Le simple fait de visiter un site hébergeant ce type de logiciels peut infecter votre ordinateur. Ce site héberge également des informations provenant de sites signalés comme étant des sites de phishing. Ces derniers incitent les internautes à divulguer des informations personnelles en se faisant passer pour des institutions de confiance, telles que des banques.</translation>
-+<translation id="8156020606310233796">Afficher la liste</translation>
-+<translation id="957120528631539888">Désactivez l'affichage des messages de confirmation et le blocage de l'envoi des formulaires.</translation>
-+<translation id="146000042969587795">Ce cadre a été bloqué, car il contient des éléments non sécurisés.</translation>
-+<translation id="8112223930265703044">Tout</translation>
-+<translation id="3968739731834770921">Kana</translation>
-+<translation id="3729920814805072072">Gérer les mots de passe enregistrés...</translation>
-+<translation id="7387829944233909572">Boîte de dialogue &quot;Effacer les données de navigation&quot;</translation>
-+<translation id="8023801379949507775">Mettre à jour les extensions maintenant</translation>
-+<translation id="6549677549082720666">Nouvelle application en arrière-plan installée</translation>
-+<translation id="1983108933174595844">Envoyer une capture d'écran de la page actuelle</translation>
-+<translation id="3298789223962368867">L'URL indiquée est incorrecte.</translation>
-+<translation id="2202898655984161076">Un problème est survenu lors de l'affichage de la liste des imprimantes. Certaines de vos imprimantes ne sont peut-être pas correctement enregistrées dans <ph name="CLOUD_PRINT_NAME"/>.</translation>
-+<translation id="6154697846084421647">Actuellement connecté</translation>
-+<translation id="8241707690549784388">La page que vous recherchez a utilisé des informations que vous avez envoyées. Si vous revenez sur cette page, chaque action précédemment effectuée sera répétée. Souhaitez-vous continuer ?</translation>
-+<translation id="5359419173856026110">Cette fonctionnalité indique la vitesse d'affichage réelle d'une page, en images par seconde, lorsque l'accélération matérielle est active.</translation>
-+<translation id="4104163789986725820">E&amp;xporter...</translation>
-+<translation id="2113479184312716848">&amp;Ouvrir un fichier...</translation>
-+<translation id="8412709057120877195">Configurer le contrôle d'accès pour vos périphériques</translation>
-+<translation id="486595306984036763">Ouvrir un rapport de phishing</translation>
-+<translation id="3140353188828248647">Activer la barre d'adresse</translation>
-+<translation id="4860787810836767172"><ph name="NUMBER_FEW"/> secs ago</translation>
-+<translation id="5565871407246142825">Cartes de paiement</translation>
-+<translation id="2587203970400270934">Code opérateur :</translation>
-+<translation id="3355936511340229503">Erreur de connexion</translation>
-+<translation id="1824910108648426227">Vous avez la possibilité de désactiver ces services.</translation>
-+<translation id="3092040396860056776">Essayer d'afficher la page malgré tout</translation>
-+<translation id="4350711002179453268">Impossible d'établir une connexion sécurisée avec le serveur. Le serveur a peut-être rencontré un problème ou exige un certificat d'authentification du client dont vous ne disposez pas.</translation>
-+<translation id="91731790394942114">Ajouter un nouveau nom</translation>
-+<translation id="5963026469094486319">Obtenir d'autres thèmes</translation>
-+<translation id="2441719842399509963">Rétablir les valeurs par défaut</translation>
-+<translation id="1893137424981664888">Aucun Plug-in installé.</translation>
-+<translation id="3718288130002896473">Action</translation>
-+<translation id="2168725742002792683">Extensions de fichier</translation>
-+<translation id="1753905327828125965">Les plus visités</translation>
-+<translation id="8116972784401310538">&amp;Gestionnaire de favoris</translation>
-+<translation id="1849632043866553433">Caches des applications</translation>
-+<translation id="3591607774768458617">Cette langue est actuellement utilisée par <ph name="PRODUCT_NAME"/>.</translation>
-+<translation id="621638399744152264"><ph name="VALUE"/> %</translation>
-+<translation id="4927301649992043040">Empaqueter l'extension</translation>
-+<translation id="8679658258416378906">Activer l'onglet 5</translation>
-+<translation id="4763816722366148126">Sélectionner le mode de saisie précédent</translation>
-+<translation id="6458308652667395253">Configurer le blocage de JavaScript...</translation>
-+<translation id="8435334418765210033">Réseaux mémorisés</translation>
-+<translation id="6516193643535292276">Impossible de se connecter à Internet.</translation>
-+<translation id="5125751979347152379">URL incorrecte</translation>
-+<translation id="2791364193466153585">Informations sur la sécurité</translation>
-+<translation id="4673916386520338632">Impossible d'installer l'application, car elle est en conflit avec &quot;<ph name="APP_NAME"/>&quot;, qui est déjà installé.</translation>
-+<translation id="2024918351532495204">Votre périphérique n'est pas connecté.</translation>
-+<translation id="6040143037577758943">Fermer</translation>
-+<translation id="5787146423283493983">Accord de la clé</translation>
-+<translation id="1101671447232096497"><ph name="NUMBER_MANY"/> mins ago</translation>
-+<translation id="4265682251887479829">Vous ne trouvez pas ce que vous recherchez ?</translation>
-+<translation id="1804251416207250805">Désactivez l'envoi des pings de contrôle des liens hypertexte.</translation>
-+<translation id="5116628073786783676">En&amp;registrer le fichier audio sous...</translation>
-+<translation id="2557899542277210112">Accédez rapidement à vos favoris en les ajoutant à la barre de favoris.</translation>
-+<translation id="2749881179542288782">Vérifier la grammaire et l'orthographe</translation>
-+<translation id="5105855035535475848">Épingler les onglets</translation>
-+<translation id="6892450194319317066">Sélectionner par type d'application</translation>
-+<translation id="3549436232897695316">assembler</translation>
-+<translation id="5414121716219514204"><ph name="ENGINE_HOST_NAME"/> souhaite devenir votre moteur de recherche.</translation>
-+<translation id="2752805177271551234">Utiliser l'historique d'entrée</translation>
-+<translation id="7268365133021434339">Fermer les onglets</translation>
-+<translation id="4910619056351738551">Voici quelques suggestions :</translation>
-+<translation id="9131598836763251128">Sélectionnez un ou plusieurs fichiers</translation>
-+<translation id="5489059749897101717">Afficher le panneau de la &amp;vérification orthographique</translation>
-+<translation id="3423858849633684918">Veuillez relancer <ph name="PRODUCT_NAME"/>.</translation>
-+<translation id="1232569758102978740">Sans titre</translation>
-+<translation id="1903219944620007795">Pour saisir du texte, sélectionnez une langue et consultez la liste des modes de saisie disponibles.</translation>
-+<translation id="4362187533051781987">Ville</translation>
-+<translation id="9149866541089851383">Modifier...</translation>
-+<translation id="7608619752233383356">Réinitialiser la synchronisation</translation>
-+<translation id="1065245965611933814">Inclure une capture d'écran enregistrée :</translation>
-+<translation id="7876243839304621966">Tout supprimer</translation>
-+<translation id="5663459693447872156">Passer automatiquement en demi-chasse</translation>
-+<translation id="4593021220803146968">&amp;Accéder à <ph name="URL"/></translation>
-+<translation id="1128987120443782698">La capacité de ce périphérique de stockage est de <ph name="DEVICE_CAPACITY"/>. Veuillez insérer une carte SD ou une clé USB d'au moins 4 Go.</translation>
-+<translation id="869257642790614972">Rouvrir le dernier onglet fermé</translation>
-+<translation id="3978267865113951599">(blocage)</translation>
-+<translation id="8412145213513410671">Erreurs (<ph name="CRASH_COUNT"/>)</translation>
-+<translation id="560602183358579978">Traitement de la sélection...</translation>
-+<translation id="7649070708921625228">Aide</translation>
-+<translation id="5994107996638824097">Désolé ! La visionneuse de documents PDF intégrée à Google Chrome, nécessaire à l'affichage de l'aperçu avant impression, n'est pas incluse dans Chromium.</translation>
-+<translation id="976526967778596630">Impossible d'ouvrir <ph name="HOST_NAME"/>, car vous êtes déconnecté du réseau. Cette page s'affichera dès que la connexion réseau sera rétablie. &lt;br&gt;</translation>
-+<translation id="1734072960870006811">Télécopie</translation>
-+<translation id="3095995014811312755">version</translation>
-+<translation id="7052500709156631672">La passerelle ou le serveur proxy a reçu une réponse incorrecte d'un serveur en amont.</translation>
-+<translation id="281133045296806353">Nouvelle fenêtre ouverte dans la session du navigateur</translation>
-+<translation id="7144878232160441200">Réessayer</translation>
-+<translation id="2860002559146138960"><ph name="PRODUCT_NAME"/> peut maintenant synchroniser vos mots de passe. Vos données seront chiffrées avec le mot de passe de votre compte Google ou le mot de passe multiterme de votre choix.</translation>
-+<translation id="3951872452847539732">Les paramètres réseau de votre proxy sont gérés par une extension.</translation>
-+<translation id="6442697326824312960">Retirer l'onglet</translation>
-+<translation id="6382612843547381371">Valable du <ph name="START_DATE_TIME"/> au <ph name="END_DATE_TIME"/></translation>
-+<translation id="6869402422344886127">Case cochée</translation>
-+<translation id="5637380810526272785">Mode de saisie</translation>
-+<translation id="404928562651467259">AVERTISSEMENT</translation>
-+<translation id="7172053773111046550">Clavier estonien</translation>
-+<translation id="497490572025913070">Ajout de bordures aux couches de rendu composées</translation>
-+<translation id="9002707937526687073">Imp&amp;rimer...</translation>
-+<translation id="5953934840931207585">Paramètres de saisie automatique <ph name="PRODUCT_NAME_SHORT"/></translation>
-+<translation id="5556459405103347317">Rafraîchir</translation>
-+<translation id="8000020256436988724">Barre d'outils</translation>
-+<translation id="8326395326942127023">Nom de la base de données :</translation>
-+<translation id="7507930499305566459">Certificat du répondeur d'état</translation>
-+<translation id="2689915906323125315">Utiliser le mot de passe de mon compte Google</translation>
-+<translation id="6440205424473899061">Vos favoris sont maintenant synchronisés avec Google Documents !
-+Pour fusionner et synchroniser vos favoris dans <ph name="PRODUCT_NAME"/> sur un autre ordinateur, procédez de la même manière que précédemment sur l'ordinateur voulu.</translation>
-+<translation id="7727721885715384408">Renommer...</translation>
-+<translation id="2604243255129603442"><ph name="NAME_OF_EXTENSION"/> a été désactivé. Si vous arrêtez la synchronisation des favoris, vous pouvez la réactiver sur la page des extensions, via le menu Outils.</translation>
-+<translation id="2024621544377454980">Affichage des pages impossible</translation>
-+<translation id="7136694880210472378">Utiliser par défaut</translation>
-+<translation id="7681202901521675750">La carte SIM est verrouillée. Veuillez saisir votre code PIN. Nombre de tentatives restantes : <ph name="TRIES_COUNT"/></translation>
-+<translation id="1731346223650886555">Point-virgule</translation>
-+<translation id="158849752021629804">Réseau domestique requis</translation>
-+<translation id="7339763383339757376">PKCS #7, certificat unique</translation>
-+<translation id="7587108133605326224">Langues baltes</translation>
-+<translation id="3991936620356087075">Vous avez saisi un trop grand nombre de clés de verrouillage du code PIN incorrectes. Votre carte SIM est définitivement désactivée.</translation>
-+<translation id="936801553271523408">Données de diagnostic système</translation>
-+<translation id="6389701355360299052">Page Web, contenu HTML uniquement</translation>
-+<translation id="8067791725177197206">Continuer »</translation>
-+<translation id="9009144784540995197">Gérez vos imprimantes.</translation>
-+<translation id="1055006259534905434">(Choisir un problème dans la liste ci-dessous)</translation>
-+<translation id="3021678814754966447">&amp;Afficher le code source du cadre</translation>
-+<translation id="8601206103050338563">Authentification du client WWW TLS</translation>
-+<translation id="1692799361700686467">Les cookies de plusieurs sites sont autorisés.</translation>
-+<translation id="7074488040076962230">Impossible d'afficher la page de la barre latérale &quot;<ph name="SIDEBAR_PAGE"/>&quot;.</translation>
-+<translation id="529232389703829405">Vous avez acheté <ph name="DATA_AMOUNT"/> de données le <ph name="DATE"/>.</translation>
-+<translation id="5271549068863921519">Enregistrer le mot de passe</translation>
-+<translation id="4345587454538109430">Configurer...</translation>
-+<translation id="8148264977957212129">Mode de saisie du pinyin</translation>
-+<translation id="5787378733537687553">Intervertir les touches Ctrl et Alt de gauche</translation>
-+<translation id="7772032839648071052">Confirmer le mot de passe multiterme</translation>
-+<translation id="6857811139397017780">Activer <ph name="NETWORKSERVICE"/></translation>
-+<translation id="3251855518428926750">Ajouter...</translation>
-+<translation id="4120075327926916474">Voulez-vous que Google Chrome enregistre ces informations de carte de paiement pour le remplissage de formulaires Web ?</translation>
-+<translation id="6929555043669117778">Continuer à bloquer les fenêtres pop-up</translation>
-+<translation id="5864471791310927901">Échec de la vérification DHCP</translation>
-+<translation id="3508920295779105875">Choisir un autre dossier...</translation>
-+<translation id="2503458975635466059">Le profil semble être utilisé par le processus <ph name="PROCESS_ID"/> sur l'hôte <ph name="HOST_NAME"/>. Si vous êtes certain qu'aucun autre processus n'utilise ce profil, supprimez le fichier <ph name="LOCK_FILE"/> et relancez <ph name="PRODUCT_NAME"/>.</translation>
-+<translation id="2987775926667433828">Chinois traditionnel</translation>
-+<translation id="6684737638449364721">Effacer les données de navigation...</translation>
-+<translation id="3954582159466790312">Ré&amp;activer le son</translation>
-+<translation id="1110772031432362678">Aucun réseau trouvé.</translation>
-+<translation id="3936390757709632190">&amp;Ouvrir le fichier audio dans un nouvel onglet</translation>
-+<translation id="7297622089831776169">&amp;Méthodes d'entrée</translation>
-+<translation id="5731698828607291678">Onglets ou fenêtres</translation>
-+<translation id="1152775729948968688">Toutefois, cette page inclut d'autres ressources qui ne sont pas sécurisées. Ces ressources peuvent être consultées par des tiers pendant leur transfert, et modifiées par un pirate informatique dans le but de changer le comportement de cette page.</translation>
-+<translation id="604124094241169006">Automatique</translation>
-+<translation id="862542460444371744">&amp;Extensions</translation>
-+<translation id="8045462269890919536">Roumain</translation>
-+<translation id="6320286250305104236">Paramètres du réseau...</translation>
-+<translation id="2927657246008729253">Changer...</translation>
-+<translation id="7978412674231730200">Clé privée</translation>
-+<translation id="464745974361668466">Format :</translation>
-+<translation id="5308380583665731573">Se connecter</translation>
-+<translation id="9111395131601239814"><ph name="NETWORKDEVICE"/> : <ph name="STATUS"/></translation>
-+<translation id="9049981332609050619">Vous avez tenté de contacter <ph name="DOMAIN"/>, mais le certificat présenté par le serveur est incorrect.</translation>
-+<translation id="4414232939543644979">Nouvelle fenêtre de nav&amp;igation privée</translation>
-+<translation id="1693754753824026215">La page à l'adresse <ph name="SITE"/> indique :</translation>
-+<translation id="7148804936871729015">Le serveur associé à <ph name="URL"/> n'a pas répondu à temps. Cela peut être dû à une surcharge.</translation>
-+<translation id="5950967683057767490">L2TP/IPSec + Clé pré-partagée</translation>
-+<translation id="8108473539339615591">XSS Auditor</translation>
-+<translation id="1902576642799138955">Durée de validité</translation>
-+<translation id="4910021444507283344">WebGL</translation>
-+<translation id="6692173217867674490">Mot de passe multiterme erroné</translation>
-+<translation id="5550431144454300634">Corriger automatiquement la saisie</translation>
-+<translation id="3308006649705061278">Unité d'organisation</translation>
-+<translation id="8912362522468806198">Compte Google</translation>
-+<translation id="4443536555189480885">&amp;Aide</translation>
-+<translation id="340485819826776184">Utiliser un service de prédiction afin de compléter les recherches et les URL saisies dans la barre d'adresse</translation>
-+<translation id="4074900173531346617">Certificat du signataire de courrier électronique</translation>
-+<translation id="6165508094623778733">En savoir plus</translation>
-+<translation id="9052208328806230490">Vous avez enregistré vos imprimantes sur <ph name="CLOUD_PRINT_NAME"/> via le compte <ph name="EMAIL"/>.</translation>
-+<translation id="822618367988303761">il y a <ph name="NUMBER_TWO"/> jours</translation>
-+<translation id="7928333295097642153"><ph name="HOUR"/>:<ph name="MINUTE"/> restantes</translation>
-+<translation id="7568593326407688803">Cette page est en<ph name="ORIGINAL_LANGUAGE"/>Voulez-vous la traduire ?</translation>
-+<translation id="563969276220951735">Saisie automatique des formulaires</translation>
-+<translation id="6870130893560916279">Clavier ukrainien</translation>
-+<translation id="8629974950076222828">Ouvrir tous les favoris dans une fenêtre de navigation privée</translation>
-+<translation id="3126026824346185272">Ctrl</translation>
-+<translation id="4745438305783437565"><ph name="NUMBER_FEW"/> minutes</translation>
-+<translation id="2649911884196340328">Le certificat de sécurité du serveur contient des erreurs !</translation>
-+<translation id="6666647326143344290">avec votre compte Google</translation>
-+<translation id="3828029223314399057">Rechercher dans les favoris</translation>
-+<translation id="4885705234041587624">MSCHAPv2</translation>
-+<translation id="5614190747811328134">Avertissement utilisateur</translation>
-+<translation id="8906421963862390172">&amp;Options du vérificateur d'orthographe</translation>
-+<translation id="9046895021617826162">Échec de la connexion</translation>
-+<translation id="1492188167929010410">Identifiant de l'erreur <ph name="CRASH_ID"/></translation>
-+<translation id="1963692530539281474"><ph name="NUMBER_DEFAULT"/> jours restants</translation>
-+<translation id="4470270245053809099">Émis par : <ph name="NAME"/></translation>
-+<translation id="5365539031341696497">Mode de saisie du thaï (clavier Kesmanee)</translation>
-+<translation id="2403091441537561402">Passerelle :</translation>
-+<translation id="6337234675334993532">Chiffrement</translation>
-+<translation id="668171684555832681">Autre...</translation>
-+<translation id="1932098463447129402">Pas avant le</translation>
-+<translation id="7845920762538502375"><ph name="PRODUCT_NAME"/> n'a pas pu synchroniser vos données, car la connexion avec le serveur de synchronisation n'a pas pu être établie. Nouvel essai...</translation>
-+<translation id="2192664328428693215">Me demander lorsqu'un site souhaite afficher des notifications sur le Bureau (recommandé)</translation>
-+<translation id="6708242697268981054">Source :</translation>
-+<translation id="4786993863723020412">Erreur de lecture du cache</translation>
-+<translation id="6630452975878488444">Raccourci de sélection</translation>
-+<translation id="8709969075297564489">Vérifier la révocation du certificat serveur</translation>
-+<translation id="8698171900303917290">Vous rencontrez des problèmes lors de l'installation ?</translation>
-+<translation id="830868413617744215">Bêta</translation>
-+<translation id="5925147183566400388">Pointeur de la déclaration CPS (Certification Practice Statement)</translation>
-+<translation id="1497270430858433901">Le <ph name="DATE"/>, vous avez reçu <ph name="DATA_AMOUNT"/> à utiliser librement.</translation>
-+<translation id="8150167929304790980">Nom complet</translation>
-+<translation id="636850387210749493">Inscription d'entreprise</translation>
-+<translation id="1947424002851288782">Clavier allemand</translation>
-+<translation id="932508678520956232">Impossible de lancer l'impression.</translation>
-+<translation id="4861833787540810454">&amp;Lire</translation>
-+<translation id="2552545117464357659">Récent</translation>
-+<translation id="7269802741830436641">Cette page Web présente une boucle de redirection.</translation>
-+<translation id="4180788401304023883">Supprimer le certificat &quot;<ph name="CERTIFICATE_NAME"/>&quot; émis par l'autorité de certification ?</translation>
-+<translation id="5869522115854928033">Mots de passe enregistrés</translation>
-+<translation id="2089090684895656482">Moins</translation>
-+<translation id="1709220265083931213">Options avancées</translation>
-+<translation id="5748266869826978907">Vérifiez vos paramètres DNS. Contactez votre administrateur réseau si vous n'êtes pas sûr de vous.</translation>
-+<translation id="4771973620359291008">Une erreur inconnue s'est produite.</translation>
-+<translation id="5509914365760201064">Émetteur : <ph name="CERTIFICATE_AUTHORITY"/></translation>
-+<translation id="7073385929680664879">Passer d'un mode de saisie à l'autre</translation>
-+<translation id="6898699227549475383">Organisation (O)</translation>
-+<translation id="4333854382783149454">PKCS #1 SHA-1 avec chiffrement RSA</translation>
-+<translation id="762904068808419792">Entrez la requête de recherche ici.</translation>
-+<translation id="8615618338313291042">Application en mode navigation privée : <ph name="APP_NAME"/></translation>
-+<translation id="978146274692397928">La largeur de ponctuation initiale est Complète</translation>
-+<translation id="8959027566438633317">Installer <ph name="EXTENSION_NAME"/> ?</translation>
-+<translation id="8155798677707647270">Installation d'une nouvelle version...</translation>
-+<translation id="6886871292305414135">Ouvrir le lien dans un nouvel ongle&amp;t</translation>
-+<translation id="1639192739400715787">Pour accéder aux paramètres de sécurité, saisissez le code PIN de la carte SIM.</translation>
-+<translation id="7961015016161918242">Jamais</translation>
-+<translation id="3950924596163729246">Impossible d'accéder au réseau.</translation>
-+<translation id="2835170189407361413">Effacer le formulaire</translation>
-+<translation id="4631110328717267096">Échec de la mise à jour du système</translation>
-+<translation id="3695919544155087829">Saisissez le mot de passe utilisé pour chiffrer ce fichier de certificat.</translation>
-+<translation id="2230051135190148440">CHAP</translation>
-+<translation id="6308937455967653460">Enregistrer le lie&amp;n sous...</translation>
-+<translation id="5421136146218899937">Effacer les données de navigation...</translation>
-+<translation id="5783059781478674569">Options de reconnaissance vocale</translation>
-+<translation id="5441100684135434593">Réseau câblé</translation>
-+<translation id="3285322247471302225">Nouvel ongle&amp;t</translation>
-+<translation id="3943582379552582368">R&amp;etour</translation>
-+<translation id="7607002721634913082">Téléchargement suspendu</translation>
-+<translation id="480990236307250886">Ouvrir la page d'accueil</translation>
-+<translation id="8286036467436129157">Connexion</translation>
-+<translation id="5999940714422617743">L'installation de <ph name="EXTENSION_NAME"/> est terminée.</translation>
-+<translation id="1122198203221319518">&amp;Outils</translation>
-+<translation id="5757539081890243754">Page d'accueil</translation>
-+<translation id="2760009672169282879">Clavier phonétique bulgare</translation>
-+<translation id="6608140561353073361">Cookies et données de site...</translation>
-+<translation id="8007030362289124303">Batterie faible</translation>
-+<translation id="4513946894732546136">Commentaires</translation>
-+<translation id="1135328998467923690">Package incorrect : &quot;<ph name="ERROR_CODE"/>&quot;.</translation>
-+<translation id="5906719743126878045"><ph name="NUMBER_TWO"/> heures restantes</translation>
-+<translation id="1753682364559456262">Configurer les paramètres de blocage des images...</translation>
-+<translation id="6550675742724504774">Options</translation>
-+<translation id="8959208747503200525"><ph name="NUMBER_TWO"/> hours ago</translation>
-+<translation id="431076611119798497">&amp;Détails</translation>
-+<translation id="737801893573836157">Masquer la barre de titre du système et utiliser les bordures</translation>
-+<translation id="5352235189388345738">Elle peut accéder aux éléments suivants :</translation>
-+<translation id="5040262127954254034">Confidentialité</translation>
-+<translation id="7666868073052500132">Objets : <ph name="USAGES"/></translation>
-+<translation id="6985345720668445131">Paramètres d'entrée du japonais</translation>
-+<translation id="3258281577757096226">Sebeol-sik Final</translation>
-+<translation id="6906268095242253962">Veuillez vous connecter à Internet pour continuer.</translation>
-+<translation id="1908748899139377733">Afficher les &amp;infos sur le cadre</translation>
-+<translation id="803771048473350947">Fichier</translation>
-+<translation id="6206311232642889873">Cop&amp;ier l'image</translation>
-+<translation id="5158983316805876233">Utiliser le même proxy pour tous les protocoles</translation>
-+<translation id="7108338896283013870">Masquer</translation>
-+<translation id="3366404380928138336">Requête de protocole externe</translation>
-+<translation id="5300589172476337783">Afficher</translation>
-+<translation id="3160041952246459240">Certains de vos certificats enregistrés identifient ces serveurs :</translation>
-+<translation id="566920818739465183">Vous avez visité ce site pour la première fois le <ph name="VISIT_DATE"/>.</translation>
-+<translation id="2961695502793809356">Cliquer pour avancer, maintenir pour voir l'historique</translation>
-+<translation id="4092878864607680421">La dernière version de l'application &quot;<ph name="APP_NAME"/>&quot; requiert d'autres autorisations. Elle a donc été désactivée.</translation>
-+<translation id="8421864404045570940"><ph name="NUMBER_DEFAULT"/> secondes</translation>
-+<translation id="5828228029189342317">Vous avez choisi d'ouvrir automatiquement certains types de fichiers après leur téléchargement.</translation>
-+<translation id="1416836038590872660">EAP-MD5</translation>
-+<translation id="176587472219019965">&amp;Nouvelle fenêtre</translation>
-+<translation id="2788135150614412178">+</translation>
-+<translation id="4055738107007928968">Vous avez essayé d'accéder au site <ph name="DOMAIN"/>, mais le serveur a présenté un certificat signé avec un algorithme de signature faible. Il se peut que les informations d'identification fournies par le serveur aient été falsifiées. Le serveur n'est peut-être pas celui auquel vous souhaitez accéder (il peut s'agir d'une tentative de piratage). Nous nous déconseillons vivement de continuer.</translation>
-+<translation id="5308689395849655368">L'envoi de rapports d'erreur est désactivé.</translation>
-+<translation id="8372369524088641025">Clé WEP incorrecte</translation>
-+<translation id="8689341121182997459">Date d'expiration :</translation>
-+<translation id="899403249577094719">URL de base du certificat Netscape</translation>
-+<translation id="2737363922397526254">Réduire...</translation>
-+<translation id="4880827082731008257">Rechercher dans l'historique</translation>
-+<translation id="8661290697478713397">Ouvrir le lien dans la fenêtre de navi&amp;gation privée</translation>
-+<translation id="4197700912384709145"><ph name="NUMBER_ZERO"/> secondes</translation>
-+<translation id="7454780465968211330">Historique avancé pour le champ polyvalent</translation>
-+<translation id="2158448795143567596">Active l'utilisation de graphismes 3D dans les éléments canvas via l'API WebGL.</translation>
-+<translation id="1702534956030472451">Occident</translation>
-+<translation id="6636709850131805001">État non reconnu</translation>
-+<translation id="6095984072944024315">−</translation>
-+<translation id="9141716082071217089">Impossible de vérifier si le certificat du serveur a été révoqué.</translation>
-+<translation id="4304224509867189079">Se connecter</translation>
-+<translation id="5332624210073556029">Fuseau horaire :</translation>
-+<translation id="4799797264838369263">Cette option est soumise à une stratégie d'entreprise. Contactez votre administrateur pour plus d'informations.</translation>
-+<translation id="4492190037599258964">Résultats de recherche pour &quot;<ph name="SEARCH_STRING"/>&quot;</translation>
-+<translation id="3573179567135747900">Revenir à &quot;<ph name="FROM_LOCALE"/>&quot; (redémarrage requis)</translation>
-+<translation id="2238123906478057869"><ph name="PRODUCT_NAME"/> va exécuter les tâches suivantes :</translation>
-+<translation id="4042471398575101546">Ajouter la page</translation>
-+<translation id="8848709220963126773">Changement de mode via la touche Maj</translation>
-+<translation id="4871865824885782245">Options de date et d'heure...</translation>
-+<translation id="8828933418460119530">Nom DNS</translation>
-+<translation id="988159990683914416">Build de développement</translation>
-+<translation id="8026354464835030469"><ph name="BURNT_AMOUNT"/> sur ...</translation>
-+<translation id="4114470632216071239">Verrouiller la carte SIM (code PIN obligatoire pour utiliser les données mobiles)</translation>
-+<translation id="2183426022964444701">Sélectionnez le répertoire racine de l'extension.</translation>
-+<translation id="2517143724531502372">Les cookies de <ph name="DOMAIN"/> sont autorisés uniquement pour cette session.</translation>
-+<translation id="9018524897810991865">Confirmer les préférences de synchronisation</translation>
-+<translation id="4719905780348837473">RSN</translation>
-+<translation id="5212108862377457573">Ajuster la conversion en fonction de l'entrée précédente</translation>
-+<translation id="5398353896536222911">Afficher le panneau de la &amp;vérification orthographique</translation>
-+<translation id="5811533512835101223">(Revenir à la capture d'écran d'origine)</translation>
-+<translation id="5131817835990480221">Mettre à jour &amp;<ph name="PRODUCT_NAME"/></translation>
-+<translation id="939519157834106403">SSID</translation>
-+<translation id="3705722231355495246">-</translation>
-+<translation id="2635102990349508383">Les informations de connexion au compte n'ont pas encore été saisies.</translation>
-+<translation id="6902055721023340732">URL de configuration automatique</translation>
-+<translation id="4268574628540273656">URL :</translation>
-+<translation id="7481312909269577407">Avancer</translation>
-+<translation id="3759876923365568382"><ph name="NUMBER_FEW"/> jours restants</translation>
-+<translation id="295228163843771014">Vous avez choisi de ne pas synchroniser les mots de passe. Vous pouvez à tout moment modifier vos paramètres de synchronisation, si vous changez d'avis.</translation>
-+<translation id="5972826969634861500">Lancer <ph name="PRODUCT_NAME"/></translation>
-+<translation id="7828702903116529889"><ph name="PRODUCT_NAME"/>
-+ ne parvient pas à accéder au réseau.
-+ <ph name="LINE_BREAK"/>
-+ Il est possible que votre pare-feu ou votre antivirus considère
-+ <ph name="PRODUCT_NAME"/>
-+ comme un intrus dans votre ordinateur et qu'il bloque ses tentatives de connexion à Internet.</translation>
-+<translation id="878069093594050299">Ce certificat a été vérifié pour les utilisations suivantes :</translation>
-+<translation id="5852112051279473187">Petit problème ! Une erreur est survenue lors de l'inscription de ce périphérique. Veuillez réessayer ou contacter votre représentant de l'assistance technique.</translation>
-+<translation id="1664314758578115406">Ajouter aux favoris</translation>
-+<translation id="7088418943933034707">Gérer les certificats...</translation>
-+<translation id="8482183012530311851">Analyse du périphérique...</translation>
-+<translation id="3127589841327267804">PYJJ</translation>
-+<translation id="8808478386290700967">Web Store</translation>
-+<translation id="1732215134274276513">Annuler l'épinglage des onglets</translation>
-+<translation id="4084682180776658562">Favori</translation>
-+<translation id="8859057652521303089">Sélectionnez votre langue :</translation>
-+<translation id="3030138564564344289">Réessayer le téléchargement</translation>
-+<translation id="8525552230188318924">Configurer la synchronisation des mots de passe</translation>
-+<translation id="4381091992796011497">Nom d'utilisateur :</translation>
-+<translation id="5830720307094128296">Enregistrer la p&amp;age sous...</translation>
-+<translation id="8114439576766120195">Vos données sur tous les sites Web</translation>
-+<translation id="4668954208278016290">Un problème est survenu lors de l'extraction de l'image sur l'ordinateur.</translation>
-+<translation id="5822838715583768518">Lancer l'application</translation>
-+<translation id="3942974664341190312">Dubeol-sik</translation>
-+<translation id="8477241577829954800">Remplacé</translation>
-+<translation id="6735304988756581115">Afficher les cookies et autres données de site...</translation>
-+<translation id="3048564749795856202">Si vous pensez avoir cerné les risques, vous pouvez <ph name="PROCEED_LINK"/>.</translation>
-+<translation id="2433507940547922241">Apparence</translation>
-+<translation id="839072384475670817">Créer des raccourci&amp;s vers des applications...</translation>
-+<translation id="1478632774608054702">Exécuter le flash PPAPI dans le processus du moteur de rendu</translation>
-+<translation id="6756161853376828318">Définir <ph name="PRODUCT_NAME"/> en tant que navigateur par défaut</translation>
-+<translation id="9112614144067920641">Veuillez choisir un nouveau code PIN.</translation>
-+<translation id="2061855250933714566"><ph name="ENCODING_CATEGORY"/> (<ph name="ENCODING_NAME"/>)</translation>
-+<translation id="7138678301420049075">Autre</translation>
-+<translation id="9147392381910171771">&amp;Options</translation>
-+<translation id="1803557475693955505">Impossible de charger la page d'arrière-plan &quot;<ph name="BACKGROUND_PAGE"/>&quot;.</translation>
-+<translation id="5818334088068591797">À quel niveau rencontrez-vous des problèmes ? (Champ obligatoire)</translation>
-+<translation id="6264485186158353794">Retour à la sécurité</translation>
-+<translation id="5130080518784460891">Eten</translation>
-+<translation id="5847724078457510387">Ce site répertorie tous ses certificats valides dans le système DNS. Un certificat non répertorié a cependant été utilisé par le serveur.</translation>
-+<translation id="1394853081832053657">Options de reconnaissance vocale</translation>
-+<translation id="5037676449506322593">Tout sélectionner</translation>
-+<translation id="2785530881066938471">Impossible de charger le fichier &quot;<ph name="RELATIVE_PATH"/>&quot; pour le script de contenu, car ce fichier n'est pas codé en UTF-8.</translation>
-+<translation id="3807747707162121253">&amp;Annuler</translation>
-+<translation id="3306897190788753224">Désactiver temporairement la personnalisation des conversions, les suggestions basées sur l'historique et le dictionnaire utilisateur</translation>
-+<translation id="2574102660421949343">Les cookies de <ph name="DOMAIN"/> sont autorisés.</translation>
-+<translation id="77999321721642562">Au fil du temps, la zone ci-dessous affichera les huit sites que vous avez le plus visités.</translation>
-+<translation id="1503894213707460512">Le plug-in <ph name="PLUGIN_NAME"/> a besoin de votre autorisation pour s'exécuter.</translation>
-+<translation id="471800408830181311">Échec de création de clé privée</translation>
-+<translation id="1273291576878293349">Ouvrir tous les favoris dans une fenêtre de navigation privée</translation>
-+<translation id="1639058970766796751">Placer dans la file d'attente</translation>
-+<translation id="1177437665183591855">Erreur de certificat serveur inconnue</translation>
-+<translation id="8467473010914675605">Mode de saisie du coréen</translation>
-+<translation id="3819800052061700452">&amp;Plein écran</translation>
-+<translation id="5419540894229653647"><ph name="ERROR_DESCRIPTION_TEXT"/>
-+ <ph name="LINE_BREAK"/>
-+ Vous pouvez essayer de diagnostiquer le problème en procédant comme suit :
-+ <ph name="LINE_BREAK"/>
-+ <ph name="PLATFORM_TEXT"/></translation>
-+<translation id="3533943170037501541">Bienvenue sur votre page d'accueil !</translation>
-+<translation id="2333340435262918287">Vos modifications seront prises en compte au prochain démarrage de <ph name="PRODUCT_NAME"/>.</translation>
-+<translation id="5906065664303289925">Adresse du matériel :</translation>
-+<translation id="3178000186192127858">Lecture seule</translation>
-+<translation id="2187895286714876935">Erreur d'importation du certificat serveur</translation>
-+<translation id="5460896875189097758">Données stockées localement</translation>
-+<translation id="4618990963915449444">Tous les fichiers de <ph name="DEVICE_NAME"/> vont être effacés.</translation>
-+<translation id="614998064310228828">Modèle du périphérique :</translation>
-+<translation id="1581962803218266616">Afficher dans le Finder</translation>
-+<translation id="6096326118418049043">Nom X.500</translation>
-+<translation id="6086259540486894113">Vous devez sélectionner au moins un type de données à synchroniser.</translation>
-+<translation id="923467487918828349">Tout afficher</translation>
-+<translation id="5101042277149003567">Ouvrir tous les favoris</translation>
-+<translation id="4298972503445160211">Clavier danois</translation>
-+<translation id="6621440228032089700">Cette fonctionnalité permet de réaliser un rendu hors écran de la texture, au lieu d'un affichage direct.</translation>
-+<translation id="3488065109653206955">Partiellement activé</translation>
-+<translation id="1481244281142949601">Votre système Sandbox est correctement configuré.</translation>
-+<translation id="4849517651082200438">Ne pas installer</translation>
-+<translation id="8602882075393902833">Activer la recherche instantanée pour accélérer la recherche et la navigation</translation>
-+<translation id="6349678711452810642">Utiliser par défaut</translation>
-+<translation id="6263284346895336537">Non essentielle</translation>
-+<translation id="6409731863280057959">Fenêtres pop-up</translation>
-+<translation id="3459774175445953971">Dernière modification :</translation>
-+<translation id="73289266812733869">Désélectionné</translation>
-+<translation id="3435738964857648380">Sécurité</translation>
-+<translation id="9112987648460918699">Rechercher...</translation>
-+<translation id="2231233239095101917">Le script de la page utilisait trop de mémoire. Rafraîchissez la page pour réactiver le script.</translation>
-+<translation id="870805141700401153">Signature du code individuel Microsoft</translation>
-+<translation id="5119173345047096771">Mozilla Firefox</translation>
-+<translation id="9020278534503090146">Page Web inaccessible</translation>
-+<translation id="4768698601728450387">Recadrer l'image</translation>
-+<translation id="6245028464673554252">Si vous fermez <ph name="PRODUCT_NAME"/> maintenant, le téléchargement sera annulé.</translation>
-+<translation id="3943857333388298514">Coller</translation>
-+<translation id="385051799172605136">Retour</translation>
-+<translation id="1742300158964248589">Impossible de graver l'image.</translation>
-+<translation id="2670965183549957348">Mode de saisie du Chewing</translation>
-+<translation id="5095208057601539847">Province</translation>
-+<translation id="4085298594534903246">JavaScript a été bloqué sur cette page.</translation>
-+<translation id="5630492933376732170">Remarque : Lorsque vous cliquez sur &quot;Envoyer&quot;, Google Chrome OS
-+ joint à votre envoi un journal des événements système de
-+ votre périphérique. Ces informations nous permettent de diagnostiquer les
-+ problèmes, de comprendre comment vous interagissez avec votre
-+ périphérique et d'améliorer les performances de ce dernier. Les
-+ informations personnelles fournies sciemment dans vos commentaires ou
-+ involontairement dans les journaux système et la capture d'écran sont
-+ protégées conformément à nos <ph name="BEGIN_LINK"/>Règles de confidentialité<ph name="END_LINK"/>.
-+ Si vous ne souhaitez pas envoyer de journaux système, décochez la case
-+ &quot;Inclure les informations système&quot;.</translation>
-+<translation id="4341977339441987045">Interdire à tous les sites de stocker des données</translation>
-+<translation id="806812017500012252">Trier par nom</translation>
-+<translation id="3781751432212184938">Afficher un aperçu des onglets...</translation>
-+<translation id="2960316970329790041">Annuler l'importation</translation>
-+<translation id="3835522725882634757">Ce serveur envoie des données que <ph name="PRODUCT_NAME"/> ne comprend pas. Veuillez <ph name="BEGIN_LINK"/>signaler un bug<ph name="END_LINK"/> et inclure la <ph name="BEGIN2_LINK"/>liste des raw<ph name="END2_LINK"/>.</translation>
-+<translation id="5361734574074701223">Calcul de la durée restante</translation>
-+<translation id="6937152069980083337">Mode de saisie Google du japonais (pour clavier américain)</translation>
-+<translation id="1731911755844941020">Envoi de la requête...</translation>
-+<translation id="8371695176452482769">Parlez maintenant</translation>
-+<translation id="2988488679308982380">Impossible d'installer le package : &quot;<ph name="ERROR_CODE"/>&quot;</translation>
-+<translation id="2904079386864173492">Modèle :</translation>
-+<translation id="3447644283769633681">Bloquer tous les cookies tiers</translation>
-+<translation id="8917047707340793412">Remplacer par <ph name="ENGINE_NAME"/></translation>
-+<translation id="6129953537138746214">Espace</translation>
-+<translation id="3704331259350077894">Arrêt du fonctionnement</translation>
-+<translation id="5801568494490449797">Préférences</translation>
-+<translation id="1038842779957582377">Nom inconnu</translation>
-+<translation id="5327248766486351172">Nom</translation>
-+<translation id="5553784454066145694">Choisir un nouveau code PIN</translation>
-+<translation id="8989148748219918422"><ph name="ORGANIZATION"/> [<ph name="COUNTRY"/>]</translation>
-+<translation id="4664482161435122549">Erreur d'exportation de fichier PKCS #12</translation>
-+<translation id="2445081178310039857">Le répertoire racine de l'extension doit être indiqué.</translation>
-+<translation id="8251578425305135684">Miniature supprimée</translation>
-+<translation id="6163522313638838258">Tout développer...</translation>
-+<translation id="3037605927509011580">Aie aie aie</translation>
-+<translation id="5803531701633845775">Choisir les expressions en arrière-plan, sans déplacer le pointeur</translation>
-+<translation id="1918141783557917887">Plu&amp;s petit</translation>
-+<translation id="6996550240668667907">Afficher le clavier en superposition</translation>
-+<translation id="4065006016613364460">C&amp;opier l'URL de l'image</translation>
-+<translation id="6965382102122355670">OK</translation>
-+<translation id="8000066093800657092">Aucun réseau détecté</translation>
-+<translation id="4481249487722541506">Charger l'extension non empaquetée...</translation>
-+<translation id="8180239481735238521">page</translation>
-+<translation id="8321738493186308836">Active l'interface utilisateur et le code de support pour le processus du service de communication à distance, de même que le plug-in client. Avertissement : ce service n'est actuellement disponible que pour les tests de développeurs. Si vous ne faites pas partie de l'équipe de développement et ne figurez pas sur la liste blanche, aucun élément de l'interface utilisateur activée ne fonctionnera.</translation>
-+<translation id="2963783323012015985">Clavier turc</translation>
-+<translation id="2149973817440762519">Modifier le favori</translation>
-+<translation id="5431318178759467895">Couleur</translation>
-+<translation id="7064842770504520784">Personnaliser les paramètres de synchronisation...</translation>
-+<translation id="2784407158394623927">Activation de votre service Internet mobile</translation>
-+<translation id="3679848754951088761"><ph name="SOURCE_ORIGIN"/></translation>
-+<translation id="6920989436227028121">Ouvrir dans un onglet standard</translation>
-+<translation id="4057041477816018958"><ph name="SPEED"/> - <ph name="RECEIVED_AMOUNT"/></translation>
-+<translation id="2050339315714019657">Portrait</translation>
-+<translation id="6978839998405419496"><ph name="NUMBER_ZERO"/> days ago</translation>
-+<translation id="6139139147415955203">Active un service en arrière-plan qui connecte le service <ph name="CLOUD_PRINT_NAME"/> aux éventuelles imprimantes installées sur cet ordinateur. Une fois ce labo activé, vous pouvez lancer <ph name="CLOUD_PRINT_NAME"/> en vous connectant à votre compte Google via Options/Préférences dans la section Options avancées.</translation>
-+<translation id="5112577000029535889">Outils de &amp;développement</translation>
-+<translation id="2301382460326681002">Le répertoire racine de l'extension est incorrect.</translation>
-+<translation id="7839192898639727867">ID de clé de l'objet du certificat</translation>
-+<translation id="4759238208242260848">Téléchargements</translation>
-+<translation id="2879560882721503072">Le stockage du certificat client généré par <ph name="ISSUER"/> a réussi.</translation>
-+<translation id="1275718070701477396">Sélectionnée</translation>
-+<translation id="1178581264944972037">Suspendre</translation>
-+<translation id="6492313032770352219">Taille sur le disque :</translation>
-+<translation id="5233231016133573565">ID du processus</translation>
-+<translation id="5941711191222866238">Réduire</translation>
-+<translation id="4121428309786185360">Expire le</translation>
-+<translation id="2049137146490122801">Votre administrateur a désactivé l'accès aux fichiers locaux sur votre ordinateur.</translation>
-+<translation id="1146498888431277930">Erreur de connexion SSL</translation>
-+<translation id="8041089156583427627">Envoyer</translation>
-+<translation id="6394627529324717982">Virgule</translation>
-+<translation id="253434972992662860">&amp;Pause</translation>
-+<translation id="335985608243443814">Parcourir...</translation>
-+<translation id="7802488492289385605">Mode de saisie Google du japonais (pour clavier Dvorak américain)</translation>
-+<translation id="7452120598248906474">Police à largeur fixe</translation>
-+<translation id="3129687551880844787">Stockage de session</translation>
-+<translation id="7427348830195639090">Page en arrière-plan : <ph name="BACKGROUND_PAGE_URL"/></translation>
-+<translation id="5898154795085152510">Le serveur a renvoyé un certificat client incorrect. Erreur <ph name="ERROR_NUMBER"/> (<ph name="ERROR_NAME"/>)</translation>
-+<translation id="2704184184447774363">Signature de document Microsoft</translation>
-+<translation id="5677928146339483299">Bloqué</translation>
-+<translation id="1474842329983231719">Gérer les paramètres d'impression...</translation>
-+<translation id="2455981314101692989">Cette page Web a désactivé la saisie automatique dans ce formulaire.</translation>
-+<translation id="1646136617204068573">Clavier hongrois</translation>
-+<translation id="5988840637546770870">Les versions en développement permettent de tester de nouvelles idées, mais elles peuvent s'avérer très instables. Nous vous prions d'agir avec précaution.</translation>
-+<translation id="3569713929051927529">Ajouter un dossier...</translation>
-+<translation id="4032664149172368180">Mode de saisie du japonais (pour clavier Dvorak américain)</translation>
-+<translation id="3748706263662799310">Signaler un bug</translation>
-+<translation id="7167486101654761064">&amp;Toujours ouvrir les fichiers de ce type</translation>
-+<translation id="4283623729247862189">Disque optique</translation>
-+<translation id="5826507051599432481">Nom commun</translation>
-+<translation id="8914326144705007149">Très grande</translation>
-+<translation id="4215444178533108414">Modification terminée</translation>
-+<translation id="5154702632169343078">Objet</translation>
-+<translation id="2273562597641264981">Opérateur :</translation>
-+<translation id="122082903575839559">Algorithme de signature du certificat</translation>
-+<translation id="2181257377760181418">Cette fonctionnalité permet d'afficher un onglet d'aperçu avant de lancer une impression.</translation>
-+<translation id="7240120331469437312">Autre nom de l'objet du certificat</translation>
-+<translation id="6900113680982781280">Activer la saisie automatique pour remplir les formulaires Web d'un simple clic</translation>
-+<translation id="1131850611586448366">Le site Web à l'adresse <ph name="HOST_NAME"/> a été signalé comme étant un site de phishing. Ces sites tentent d'amener les internautes à divulguer leurs informations personnelles en se faisant passer pour des institutions de confiance, telles que des banques.</translation>
-+<translation id="5413218268059792983">Rechercher directement sur <ph name="SEARCH_ENGINE"/></translation>
-+<translation id="1161575384898972166">Connectez-vous à <ph name="TOKEN_NAME"/> pour exporter le certificat client.</translation>
-+<translation id="1718559768876751602">Créer un compte Google maintenant</translation>
-+<translation id="1884319566525838835">État Sandbox</translation>
-+<translation id="2770465223704140727">Retirer de la liste</translation>
-+<translation id="3590587280253938212">rapide</translation>
-+<translation id="6053401458108962351">&amp;Effacer les données de navigation…</translation>
-+<translation id="2339641773402824483">Vérification des mises à jour...</translation>
-+<translation id="9111742992492686570">Télécharger les mises à jour de sécurité essentielles</translation>
-+<translation id="8636666366616799973">Package incorrect. Détails : &quot;<ph name="ERROR_MESSAGE"/>&quot;.</translation>
-+<translation id="2045969484888636535">Continuer à bloquer les cookies</translation>
-+<translation id="7353601530677266744">Ligne de commande</translation>
-+<translation id="2766006623206032690">Coller l'URL et y a&amp;ccéder</translation>
-+<translation id="4394049700291259645">Désactiver</translation>
-+<translation id="969892804517981540">Build officiel</translation>
-+<translation id="445923051607553918">Se connecter à un réseau Wi-Fi</translation>
-+<translation id="100242374795662595">Périphérique inconnu</translation>
-+<translation id="9087725134750123268">Supprimer les cookies et autres données de site</translation>
-+<translation id="5050255233730056751">URL saisies</translation>
-+<translation id="3349155901412833452">Utiliser les touches , et . pour paginer une liste d'entrées</translation>
-+<translation id="6872947427305732831">Vider la mémoire</translation>
-+<translation id="2742870351467570537">Supprimer les éléments sélectionnés</translation>
-+<translation id="7561196759112975576">Toujours</translation>
-+<translation id="2116673936380190819">de moins d'une heure</translation>
-+<translation id="5765491088802881382">Aucun réseau n'est disponible.</translation>
-+<translation id="1971538228422220140">Supprimer les cookies et autres données de site et de plug-in</translation>
-+<translation id="883487340845134897">Intervertir les touches Rechercher et Ctrl gauche</translation>
-+<translation id="5692957461404855190">Faites glisser trois doigts sur la surface de votre trackpad pour afficher un aperçu de tous vos onglets. Cliquez sur une vignette pour la sélectionner. Idéal en mode plein écran.</translation>
-+<translation id="1375215959205954975">Nouveau ! Configurer la synchronisation des mots de passe</translation>
-+<translation id="5183088099396036950">Échec de la tentative de connexion au serveur</translation>
-+<translation id="4469842253116033348">Désactiver les notifications de <ph name="SITE"/></translation>
-+<translation id="7999229196265990314">Les fichiers suivants ont été créés :
-+
-+Extension : <ph name="EXTENSION_FILE"/>
-+Fichier de clé : <ph name="KEY_FILE"/>
-+
-+Conservez votre fichier de clé en lieu sûr. Vous en aurez besoin lors de la création de nouvelles versions de l'extension.</translation>
-+<translation id="1846078536247420691">&amp;Oui</translation>
-+<translation id="3036649622769666520">Ouvrir les fichiers</translation>
-+<translation id="2966459079597787514">Clavier suédois</translation>
-+<translation id="7685049629764448582">Mémoire JavaScript </translation>
-+<translation id="6398765197997659313">Quitter le mode plein écran</translation>
-+<translation id="6059652578941944813">Hiérarchie des certificats</translation>
-+<translation id="4886690096315032939">Afficher l'onglet existant si l'URL associée est demandée dans un autre</translation>
-+<translation id="5729712731028706266">&amp;Afficher</translation>
-+<translation id="774576312655125744">Vos données personnelles sur <ph name="WEBSITE_1"/>, <ph name="WEBSITE_2"/> et sur <ph name="NUMBER_OF_OTHER_WEBSITES"/> autres sites Web</translation>
-+<translation id="6359806961507272919">SMS de <ph name="PHONE_NUMBER"/></translation>
-+<translation id="4508765956121923607">Afficher la s&amp;ource</translation>
-+<translation id="5975083100439434680">Zoom arrière</translation>
-+<translation id="8080048886850452639">C&amp;opier l'URL du fichier audio</translation>
-+<translation id="2817109084437064140">Importer et associer au périphérique...</translation>
-+<translation id="3331321258768829690">(<ph name="UTCOFFSET"/>) <ph name="LONGTZNAME"/> (<ph name="EXEMPLARCITY"/>)</translation>
-+<translation id="619398760000422129">Plug-ins (par ex. Adobe Flash Player, QuickTime, etc.)</translation>
-+<translation id="5849869942539715694">Empaqueter l'extension...</translation>
-+<translation id="7339785458027436441">Vérifier l'orthographe lors de la frappe</translation>
-+<translation id="8308427013383895095">Échec de la traduction en raison d'un problème de connexion réseau</translation>
-+<translation id="1801298019027379214">Code PIN incorrect. Veuillez réessayer. Nombre de tentatives restantes : <ph name="TRIES_COUNT"/></translation>
-+<translation id="1384721974622518101">Vous pouvez effectuer une recherche directement à partir du champ ci-dessus.</translation>
-+<translation id="992543612453727859">Ajouter les expressions au premier plan</translation>
-+<translation id="3857773447683694438">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</translation>
-+<translation id="1244147615850840081">Opérateur</translation>
-+<translation id="8203365863660628138">Confirmer l'installation</translation>
-+<translation id="406259880812417922">(Mot clé : <ph name="KEYWORD"/>)</translation>
-+<translation id="309628958563171656">Sensibilité :</translation>
-+</translationbundle>
-diff --git a/tools/grit/grit/testdata/generated_resources_iw.xtb b/tools/grit/grit/testdata/generated_resources_iw.xtb
-new file mode 100644
-index 0000000000..86b55334c0
---- /dev/null
-+++ b/tools/grit/grit/testdata/generated_resources_iw.xtb
-@@ -0,0 +1,4 @@
-+<?xml version="1.0" ?>
-+<!DOCTYPE translationbundle>
-+<translationbundle lang="iw">
-+</translationbundle>
-diff --git a/tools/grit/grit/testdata/generated_resources_no.xtb b/tools/grit/grit/testdata/generated_resources_no.xtb
-new file mode 100644
-index 0000000000..913638ba4e
---- /dev/null
-+++ b/tools/grit/grit/testdata/generated_resources_no.xtb
-@@ -0,0 +1,4 @@
-+<?xml version="1.0" ?>
-+<!DOCTYPE translationbundle>
-+<translationbundle lang="no">
-+</translationbundle>
-diff --git a/tools/grit/grit/testdata/grit_part.grdp b/tools/grit/grit/testdata/grit_part.grdp
-new file mode 100644
-index 0000000000..c8e9d92692
---- /dev/null
-+++ b/tools/grit/grit/testdata/grit_part.grdp
-@@ -0,0 +1,5 @@
-+<?xml version="1.0" encoding="UTF-8"?>
-+<grit-part>
-+ <!-- Important for test purposes that this file not exist. -->
-+ <structure type="chrome_scaled_image" name="IDR_DOES_NOT_EXIST" file="does-not-exist.png" />
-+</grit-part>
-diff --git a/tools/grit/grit/testdata/header.html b/tools/grit/grit/testdata/header.html
-new file mode 100644
-index 0000000000..8e9d10ec50
---- /dev/null
-+++ b/tools/grit/grit/testdata/header.html
-@@ -0,0 +1,39 @@
-+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-+<html><head><title>[$~TITLE~$]</title>
-+<meta http-equiv="cache-control" content="no-cache">
-+<meta http-equiv="content-type" content="text/html; charset=utf-8">
-+<meta http-equiv="pragma" content="no-cache">
-+<meta http-equiv="expires" content="-1">
-+[EXTRA_META]
-+<style>
-+BODY,TD,DIV,.P,A { FONT-FAMILY: arial,sans-serif}
-+DIV,TD { COLOR: #000}
-+.f { COLOR: #6f6f6f}
-+.fl:link { COLOR: #6f6f6f}
-+A:link, .w, A.w:link, .w A:link { COLOR: #00c}
-+A:visited { COLOR: #551a8b}
-+.fl:visited { COLOR: #551a8b}
-+A:active, .fl:active { COLOR: #f00}
-+.h { COLOR: #3399CC}
-+.i { COLOR: #a90a08}
-+.i:link { COLOR: #a90a08}
-+.a, .a:link, .a:visited { COLOR: #008000}
-+DIV.n { MARGIN-TOP: 1ex}
-+.n A { FONT-SIZE: 10pt; COLOR: #000}
-+.n .i { FONT-WEIGHT: bold; FONT-SIZE: 10pt}
-+.q A:visited { COLOR: #00c}
-+.q A:link { COLOR: #00c}
-+.q A:active { COLOR: #00c}
-+.q { COLOR: #00c}
-+.b { FONT-WEIGHT: bold; FONT-SIZE: 12pt; COLOR: #00c}
-+.ch { CURSOR: hand}
-+.e { MARGIN-TOP: 0.75em; MARGIN-BOTTOM: 0.75em}
-+.g { MARGIN-TOP: 1em; MARGIN-BOTTOM: 1em}
-+.f { MARGIN-TOP: 0.5em; MARGIN-BOTTOM: 0.25em}
-+.s { HEIGHT: 10px }
-+.c:active { COLOR: #ff0000}
-+.c:visited { COLOR: #551a8b}
-+.c:link { COLOR: #7777cc}
-+.c { COLOR: #7777cc }
-+</style>
-+</head>
-\ No newline at end of file
-diff --git a/tools/grit/grit/testdata/homepage.html b/tools/grit/grit/testdata/homepage.html
-new file mode 100644
-index 0000000000..cce4f2cf35
---- /dev/null
-+++ b/tools/grit/grit/testdata/homepage.html
-@@ -0,0 +1,37 @@
-+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-+<html><head><title>Google Desktop Search</title>
-+<meta http-equiv="content-type" content="text/html; charset=utf-8">
-+<meta http-equiv="cache-control" content="no-cache">
-+<meta http-equiv="pragma" content="no-cache">
-+<meta http-equiv="expires" content="-1">
-+<style>
-+BODY,TD,A,P {FONT-FAMILY: arial,sans-serif}
-+.q {COLOR: #0000cc}
-+</style>
-+<script>
-+<!--
-+function sf(){document.f.q.focus();}
-+// -->
-+</script>
-+</head>
-+<BODY text=#000000 vLink=#551a8b aLink=#ff0000 link=#0000cc bgColor=#ffffff onload=sf()>
-+<center>
-+<TABLE cellSpacing=0 cellPadding=0 border=0>
-+<tr><td><a href="[$~HOMEPAGE~$]"><IMG border=0 height=110 alt="Google Desktop Search" src="hp_logo.gif" width=276></a></td></tr></table><BR>
-+<FORM name=f method=GET action='[$~SEARCHURL~$]'>
-+<TABLE cellSpacing=0 cellPadding=4 border=0>
-+<tr>
-+<TD class=q noWrap><FONT size=-1>
-+[$~LINKS~$]
-+</font></td>
-+</tr></table>
-+<table cellspacing=0 cellpadding=0>
-+<tr vAlign=top>
-+<td width=25%>&nbsp;</td>
-+<td align=center><input maxlength=256 size=62 name=q value="[DISP_QUERY]"><br><input type=submit value="Search Desktop" name=btnG><INPUT type=submit value="Search the Web" name="redir" accesskey=w></td>
-+<td align=left valign=top nowrap width=25%><font size=-2>&nbsp;&nbsp;<A href="[$~PREFERENCES~$]">Desktop&nbsp;Preferences</a></font></td>
-+</tr></table></FORM>
-+<p><FONT color=#224499><B>Search your own computer.</B></font></p>
-+<span style='width:29em'>[$~MESSAGE~$]</span><br>
-+<br><FONT size=-1>[$~SETHOMEPAGE~$][$~BOTTOMLINE~$]</font></p>
-+<p><FONT size=-2>&copy;2005 Google - Searching [NUM_ITEMS] items</font></p></center></body></html>
-\ No newline at end of file
-diff --git a/tools/grit/grit/testdata/hover.html b/tools/grit/grit/testdata/hover.html
-new file mode 100644
-index 0000000000..b8f9ce0200
---- /dev/null
-+++ b/tools/grit/grit/testdata/hover.html
-@@ -0,0 +1,177 @@
-+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-+<html><head>
-+<meta http-equiv="content-type" content="text/html; charset=utf-8">
-+<meta http-equiv="cache-control" content="no-cache">
-+<meta http-equiv="pragma" content="no-cache">
-+<meta http-equiv="expires" content="-1">
-+<style>
-+BODY { FONT-SIZE: 8pt; FONT-FAMILY: verdana,arial,san-serif }
-+P { FONT-SIZE: 8pt; FONT-FAMILY: verdana,arial,san-serif }
-+TD { FONT-SIZE: 8pt; FONT-FAMILY: verdana,arial,san-serif }
-+A { FONT-SIZE: 8pt; FONT-FAMILY: verdana,arial,san-serif }
-+DIV { FONT-SIZE: 8pt; TEXT-DECORATION: none }
-+A:hover { COLOR: #ffffff }
-+.border { BORDER-RIGHT: 0px; BORDER-TOP: 0px; BORDER-LEFT: 0px; BORDER-BOTTOM: 0px }
-+</style>
-+
-+<!-- menu experiment start -->
-+
-+<style>
-+<!--
-+.menu1 {
-+cursor:default;
-+position:absolute;
-+left: 10;
-+top: 0;
-+text-align: left;
-+font-family: Arial, Helvetica, sans-serif;
-+font-size: 8pt;
-+background-color: menu;
-+visibility: hidden;
-+padding-top: 2px;
-+padding-bottom: 2px;
-+border: 1 solid;
-+border-color: #888888;
-+z-index: 100;
-+}
-+.menuitems {
-+padding-left: 5px;
-+padding-right: 5px;
-+}
-+-->
-+</style>
-+<SCRIPT LANGUAGE="JavaScript1.2">
-+<!--
-+var menustyle = "menu1";
-+
-+function showmenu() {
-+ // var rightedge = document.body.clientWidth-event.clientX;
-+ // var bottomedge = document.body.clientHeight-event.clientY;
-+ // if (rightedge < rcmenu.offsetWidth)
-+ // rcmenu.style.left = document.body.scrollLeft + event.clientX - rcmenu.offsetWidth;
-+ // else
-+ // rcmenu.style.left = document.body.scrollLeft + event.clientX;
-+ // if (bottomedge < rcmenu.offsetHeight)
-+ // rcmenu.style.top = document.body.scrollTop + event.clientY - rcmenu.offsetHeight;
-+ // else
-+ // rcmenu.style.top = document.body.scrollTop + event.clientY;
-+
-+ rcmenu.style.visibility = "visible";
-+ // rcmenu.style.zindex = 0;
-+ // document.all('rcmenu').style.zindex = 20;
-+ document.onkeydown=ck;
-+ return false;
-+}
-+
-+function hidemenu() {
-+ rcmenu.style.visibility = "hidden";
-+}
-+
-+function ck(e){
-+ evt=document.all?window.event:e;
-+ k=document.all?window.event.keyCode:e.keyCode;
-+
-+ if(k==27 /*<Esc>*/) {
-+ hidemenu();
-+ }
-+}
-+
-+function menumouseover() {
-+ if (event.srcElement.className == "menuitems") {
-+ event.srcElement.style.backgroundColor = "highlight";
-+ event.srcElement.style.color = "white";
-+ }
-+}
-+
-+function menumouseout() {
-+ if (event.srcElement.className == "menuitems") {
-+ event.srcElement.style.backgroundColor = "";
-+ event.srcElement.style.color = "black";
-+ window.status = "";
-+ }
-+}
-+
-+function menuselect() {
-+ if (event.srcElement.className == "menuitems") {
-+ if (event.srcElement.getAttribute("target") != null)
-+ window.open(event.srcElement.url, event.srcElement.getAttribute("target"));
-+ else if (event.srcElement.url.length)
-+ window.location = event.srcElement.url;
-+ }
-+}
-+// -->
-+</script>
-+
-+<!-- menu experiment end -->
-+
-+</head>
-+<BODY bottomMargin=0 bgColor=#3300cc leftMargin=0 topMargin=0 rightMargin=0 marginwidth="0" marginheight="0" border=0 style="border-width:0;" scroll=no>
-+
-+<!-- <br> -->
-+
-+<!-- menu experiment start -->
-+
-+<div id="rcmenu" class="skin0" onMouseover="menumouseover()" onMouseout="menumouseout()" onClick="menuselect();">
-+<span class="menuitems" url="[$~SETDISP1~$]">Sidebar</span>
-+<span class="menuitems" url="[$~SETDISP4~$]">Minibar</span>
-+<span class="menuitems" url="[$~HIDE2~$]">Close</span>
-+</div>
-+
-+<script language="JavaScript1.2">
-+if (document.all && window.print) {
-+ rcmenu.className = menustyle;
-+ document.oncontextmenu = showmenu;
-+ document.body.onclick = hidemenu;
-+}
-+</script>
-+
-+<!-- menu experiment end -->
-+
-+<script>
-+function hide() {
-+ return 1;
-+ // return confirm("Are you sure you want to hide the minibar?\nYou can show it again in Google Desktop Search Preferences. ");
-+}
-+function clear() {
-+ document.getElementById('q').value='';
-+}
-+</script>
-+
-+<TABLE cellSpacing=0 cellPadding=0 bgColor=#3300cc border=0><TBODY>
-+<tr><TD vAlign=top>
-+
-+<form method=get action="[$~SEARCHURL~$]" [$~SEARCH_TARGET~$] name=f1 ID="f1" onsubmit="window.setTimeout('clear()', 500)">
-+<input type=hidden name=src value=3>
-+<input type=hidden name=redir value='' ID="redir">
-+
-+<TABLE cellSpacing=0 cellPadding=0 bgColor=#3300cc border=0><TBODY>
-+
-+<tr>
-+<!-- border-top: #414a4f 0px solid; -->
-+<!-- z-index:2; z-order:2; -->
-+<TD vAlign=top>&nbsp;<INPUT name=q style="position:relative; height=19px;" class=border size=12>&nbsp;</td>
-+
-+<TD TABINDEX="2" onkeydown='if(event.keyCode!=16&&event.keyCode!=9)onclick()' onclick="f1.submit();q.value=''" class=ch onmouseover="this.bgColor='6666FF'" style="BORDER-RIGHT: #000000 1px solid; BORDER-TOP: #6666cc 1px solid; BORDER-LEFT: #6666cc 1px solid; BORDER-BOTTOM: #000000 1px solid" onmouseout="this.bgColor='#000099'" vAlign=center align=middle bgColor=#000099><IMG src="logo.gif" align=middle></td>
-+
-+<TD width=2><IMG height=1 width=1></td>
-+
-+<TD TABINDEX="3" onkeydown='if(event.keyCode!=16&&event.keyCode!=9)onclick()' onclick="redir.value='google'; f1.submit(); redir.value='';q.value=''" class=ch onmouseover="this.bgColor='6666FF'" style="BORDER-RIGHT: #000000 1px solid; BORDER-TOP: #6666cc 1px solid; BORDER-LEFT: #6666cc 1px solid; BORDER-BOTTOM: #000000 1px solid" onmouseout="this.bgColor='#000099'" vAlign=center align=middle bgColor=#000099><IMG src="gfavicon.ico" align=middle></td></tr>
-+</TBODY></table>
-+</td>
-+
-+<TD width=5><IMG height=1 width=1></td>
-+
-+<TD vAlign=top>
-+
-+<TABLE cellSpacing=0 cellPadding=1 bgColor=#000099><TBODY>
-+<tr><TD TABINDEX="4" onkeydown='if(event.keyCode!=16&&event.keyCode!=9)onclick()' valign=top onclick="location.href='[$~SETDISP1~$]';" class=ch onmouseover="this.bgColor='6666FF'" style="BORDER-RIGHT: #000000 1px solid; BORDER-TOP: #6666cc 1px solid; BORDER-LEFT: #6666cc 1px solid; BORDER-BOTTOM: #000000 1px solid" onmouseout="this.bgColor='#000099'" vAlign=top noWrap bgColor=#000099><IMG src="down.gif"></td></tr></TBODY></table>
-+
-+</td>
-+
-+<TD width=1><IMG height=1 width=1></td>
-+
-+<TD vAlign=top><TABLE cellSpacing=0 cellPadding=1><TBODY>
-+<tr><TD TABINDEX="5" onkeydown='if(event.keyCode!=16&&event.keyCode!=9)onclick()' valign=top onclick="if (hide())location.href='[$~HIDE2~$]';" class=ch onmouseover="this.bgColor='6666FF'" style="BORDER-RIGHT: #000000 1px solid; BORDER-TOP: #6666cc 1px solid; BORDER-LEFT: #6666cc 1px solid; BORDER-BOTTOM: #000000 1px solid" onmouseout="this.bgColor='#000099'" vAlign=top noWrap bgColor=#000099><IMG src="close.gif"></td></tr></TBODY></table></td></tr></TBODY></table>
-+
-+</form>
-+</body></html>
-diff --git a/tools/grit/grit/testdata/include_test.html b/tools/grit/grit/testdata/include_test.html
-new file mode 100644
-index 0000000000..e08f2e2e8a
---- /dev/null
-+++ b/tools/grit/grit/testdata/include_test.html
-@@ -0,0 +1,31 @@
-+<include src="included_sample.html">
-+<if expr="True">
-+should be kept
-+</if>
-+in the middle...
-+<if expr="False">
-+should be removed
-+</if>
-+
-+<if expr="False">
-+should be removed
-+ <if expr="True">
-+ should be removed because outer expr is False
-+ </if>
-+should be removed
-+</if>
-+
-+<if expr="True">
-+ <if expr="True">
-+ <if expr="True">
-+ nested true should be kept
-+ </if>
-+ <if expr="False">
-+ should be removed
-+ </if>
-+ </if>
-+ <if expr="True">
-+ silbing true should be kept
-+ </if>
-+</if>
-+at the end...
-diff --git a/tools/grit/grit/testdata/included_sample.html b/tools/grit/grit/testdata/included_sample.html
-new file mode 100644
-index 0000000000..7150ffcbea
---- /dev/null
-+++ b/tools/grit/grit/testdata/included_sample.html
-@@ -0,0 +1 @@
-+Hello Include!
-diff --git a/tools/grit/grit/testdata/indexing_speed.html b/tools/grit/grit/testdata/indexing_speed.html
-new file mode 100644
-index 0000000000..db1787b0e2
---- /dev/null
-+++ b/tools/grit/grit/testdata/indexing_speed.html
-@@ -0,0 +1,58 @@
-+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-+<html><head><title>Google Desktop Search Index Speed</title>
-+<meta http-equiv=content-type content="text/html; charset=utf-8">
-+<style>
-+BODY {
-+ MARGIN-LEFT: 3em; MARGIN-RIGHT: 3em; FONT-FAMILY: arial,sans-serif
-+}
-+</style>
-+</head>
-+<BODY text=#000000 vLink=#551a8b aLink=#ff0000 link=#0000cc bgColor=#ffffff>
-+<TABLE cellSpacing=2 cellPadding=0 width="100%" border=0>
-+ <tr>
-+ <TD vAlign=top width="1%"><A href="[$~HOMEPAGE~$]">
-+ <IMG alt="Go to Google Desktop Search" src="/logo3.gif" border=0></A></td>
-+ <td>&nbsp;</td>
-+ <TD noWrap>
-+ <TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
-+ <tr>
-+ <TD bgColor=#3399CC><IMG height=1 alt="" width=1></td>
-+ </tr>
-+ </table>
-+ <TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
-+ <tr>
-+ <TD noWrap bgColor=#efefef><B>&nbsp;Index Speed</B></td>
-+ <TD noWrap align=right bgColor=#efefef><FONT size=-1><A href="/customize.html">Index Speed
-+ Help</A> | <A href="[$~ABOUT~$]"> About Google Desktop Search</A></font></td>
-+ </tr></table></td></tr></table>
-+<FONT size=-1>
-+<p>
-+To make your emails, files, and previously viewed web pages searchable, Google Desktop Search
-+needs to index them. This indexing process is currently occuring in the background
-+and your computer performance is minimally impacted.
-+<p>
-+You have the option of speeding up this process.
-+<p><B><FONT color=#FF0000>Warning:</font></B> Speeding up indexing will cause your computer
-+to become unusable for many minutes, depending on how many items need to be indexed. FAST INDEXING IS NOT
-+RECOMMENDED.
-+<BR>&nbsp;<BR>
-+<FORM action="[$~SETINDEXSPEED~$]" method=GET>
-+<input name=url value="[PREVPAGE]" type=hidden>
-+<input type=radio name=FAST value="0" [FAST0-CHECKED] id=f0><label for=f0>Use background indexing (recommended)</label><br>
-+<input type=radio name=FAST value="1" [FAST1-CHECKED] id=f1><label for=f1>Use fast indexing</label><br><br>
-+<input type=submit value="Set Indexing Speed">
-+</FORM>
-+<BR>
-+
-+<p>&nbsp;<BR>
-+<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
-+<TR bgColor=#3399CC>
-+ <TD align=middle height=1><IMG height=1 alt="" width=1></td></tr>
-+</table>
-+
-+<TABLE cellSpacing=0 cellPadding=0 width="100%" align=center bgColor=#efefef border=0>
-+<tr>
-+ <TD align=middle height=20><FONT size=-1><A href="[$~HOMEPAGE~$]">Google Desktop Search&nbsp;Home</A> - <a href="[$~STATUS~$]">Status</a> - <A href="[$~ABOUT~$]">About Google Desktop Search</A> - [$~BUILDNUMBER~$] - &copy;2005 Google </font> </td></tr>
-+</table><BR>
-+</body>
-+</html>
-\ No newline at end of file
-diff --git a/tools/grit/grit/testdata/install_prefs.html b/tools/grit/grit/testdata/install_prefs.html
-new file mode 100644
-index 0000000000..eca0b56de5
---- /dev/null
-+++ b/tools/grit/grit/testdata/install_prefs.html
-@@ -0,0 +1,92 @@
-+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-+<html><head><title>Google Desktop Search: Initial Preferences</title>
-+<meta http-equiv="cache-control" content="no-cache">
-+<meta http-equiv="content-type" content="text/html; charset=utf-8">
-+<meta http-equiv="pragma" content="no-cache">
-+<meta http-equiv="expires" content="-1">
-+<style>
-+BODY { FONT-FAMILY: arial,sans-serif }
-+.c:active { COLOR: #FF0000 }
-+.c:visited { COLOR: #7777CC }
-+.c:link { COLOR: #7777CC }
-+</style>
-+<script>
-+<!--
-+override = 1;
-+function ee() {if (override==1) {(new Image()).src="[COMPLETING]";}}
-+// -->
-+</script>
-+</head><body leftmargin=30 rightmargin=30 onresize="stw()" onunload="ee()">
-+<form onsubmit='override=0;return true;' action='[STEP2]' name=f method=post>
-+<img src="logo3.gif" border=0>
-+<div id=c1 style="width:600px">
-+<br><font color=#00218a><b>To continue, please set these initial preferences:</b></font><br><br>
-+<table border=0 id=t1 width=100%>
-+<tr>
-+ <td valign=top><input name=AIM id=chat type=checkbox checked></td>
-+ <td>&nbsp;</td><td><label for=chat><font size=-1><B>Enable search over Instant Messenger chats</b><br>
-+ <font size=-1>Google Desktop Search will store your chats and make them searchable.
-+</font></label></td></tr>
-+<tr height=1><td height=10px></td></tr>
-+<tr>
-+ <td valign=top><input name=HTTPS id=https type=checkbox checked></td>
-+ <td>&nbsp;</td><td><label for=https><font size=-1><b>Enable search over secure web pages (HTTPS)</b>
-+ <br><font size=-1>Google Desktop Search will store secure web pages that you view and make them
-+ searchable.</font></label> </td></tr>
-+<tr height=1px><td height=10px></td></tr>
-+
-+<tr>
-+ <td valign=top><input name=SEARCHBOX id=SEARCHBOX type=checkbox checked
-+ onclick="handleSBClick(this)"></td>
-+ <td>&nbsp;</td><td><label for=searchbox><font size=-1><b>Display search box</b></label>
-+ <br><table border=0 cellpadding=0><tr><td valign=top>
-+
-+<input type="radio" name="SBDISPLAY" id="DISPLAYDB" [DB-CHECKED] value="DISPLAYDB"></td><td>
-+<label for=DISPLAYDB><font size=-1>Deskbar - A search box in your taskbar</font></label></td></tr>
-+<tr><td></td></tr>
-+<tr><td></td><td><img src="deskbar.gif" alt="Deskbar" width="268" height="34"></td></tr>
-+<tr><td height=2></td></tr>
-+<tr><td valign=top>
-+
-+<input type="radio" name="SBDISPLAY" id="DISPLAYMB" [MB-CHECKED] VALUE="DISPLAYMB"></td><td>
-+<label for=DISPLAYMB><font size=-1>Floating Deskbar - A search box that you can put anywhere on your desktop</font></label></td></tr>
-+<tr><td></td></tr>
-+<tr><td></td><td><img src="minibar.gif" width="137" height="27"></td></tr>
-+<tr><td height=2></td></tr>
-+
-+</table>
-+</td></tr>
-+
-+<tr>
-+ <td valign=top><input name=SENDDATA id=usage type=checkbox checked></td>
-+ <td>&nbsp;</td><td><label for=usage><font size=-1><b>Help us improve Google Desktop Search by sending usage data and crash reports</b></label>
-+</font></td></tr>
-+<tr height=8px><td colspan=3 height=8px></td></tr>
-+<tr><td colspan=3><font size=-1>You can change these and other preferences at any time.</font></td></tr>
-+</table></div>
-+<p><input type=submit value="Set Preferences and Continue" id=s><br>
-+</form>
-+</center>
-+[SCRIPT]
-+<script>
-+<!--
-+function handleSBClick(checkbox) {
-+ document.getElementById("DISPLAYDB").disabled = !checkbox.checked;
-+ document.getElementById("DISPLAYMB").disabled = !checkbox.checked;
-+}
-+function stw() {
-+if (document.all && document.body.clientWidth < 600) {
-+ var w = document.body.clientWidth-35;
-+ if (w < 10) { w = 10; }
-+ w = w + 'px';
-+ document.getElementById('c1').style.width=w;
-+ return false;
-+}
-+document.getElementById('c1').style.width='600px';
-+}
-+stw();
-+document.f.s.focus();
-+// -->
-+</script>
-+<img SRC="http://www.google.com" WIDTH="0" HEIGHT="0" ALIGN="right"></img>
-+</body></html>
-\ No newline at end of file
-diff --git a/tools/grit/grit/testdata/install_prefs2.html b/tools/grit/grit/testdata/install_prefs2.html
-new file mode 100644
-index 0000000000..18380397c2
---- /dev/null
-+++ b/tools/grit/grit/testdata/install_prefs2.html
-@@ -0,0 +1,52 @@
-+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-+<html><head><title>Indexing has Started</title>
-+<meta http-equiv="cache-control" content="no-cache">
-+<meta http-equiv="content-type" content="text/html; charset=utf-8">
-+<meta http-equiv="pragma" content="no-cache">
-+<meta http-equiv="expires" content="-1">
-+<style>
-+BODY { FONT-FAMILY: arial,sans-serif }
-+}
-+</style>
-+<script>
-+<!--
-+override = 1;
-+function ee() {if (override==1) {(new Image()).src="[COMPLETING]";}}
-+// -->
-+</script>
-+</head><body leftmargin=30 rightmargin=30 onresize="stw()" onunload="ee()">
-+<form onsubmit='override=0;return true;' action="[STEP3]" name=f>
-+<img src="/logo3.gif" border=0><br><br>
-+<div id=c1 style="width:575px">
-+<table border=0 id=t1 width=100%><tr><td>
-+<font color=#00218a><b>One-time indexing has started.</b></font><br><br>
-+<font size=-1>An index is being prepared on your computer to allow you
-+to search your information as fast as you can search the web.<br><br>
-+<li>This is a one-time process that may take several hours.
-+<li>You may continue to use your computer as usual and it is safe to shut down your computer.
-+<li>Indexing will be performed only when your computer is idle.
-+</ul>
-+</font>
-+<p><input type=submit value="Go to the Desktop Search homepage" name=s><br>
-+</center>
-+</td></tr></table>
-+</form>
-+</div>
-+<script>
-+<!--
-+function stw() {
-+if (document.all && document.body.clientWidth < 575) {
-+ var w = document.body.clientWidth-35;
-+ if (w < 10) { w = 10; }
-+ w = w + 'px';
-+ document.getElementById('c1').style.width=w;
-+ return false;
-+}
-+document.getElementById('c1').style.width='575px';
-+}
-+stw();
-+// -->
-+document.f.s.focus();
-+</script>
-+[SCRIPT]
-+</body></html>
-\ No newline at end of file
-diff --git a/tools/grit/grit/testdata/klonk-alternate-skeleton.rc b/tools/grit/grit/testdata/klonk-alternate-skeleton.rc
-new file mode 100644
-index 0000000000000000000000000000000000000000..5f2c82a55469ddaab246c095826ad9e6743c0015
-GIT binary patch
-literal 1088
-zcmbW0Pfr3t48`AhKZV)z#sGrI5!gjnU?DC>IT2z!82=r_L=!)}zjnmk5b$6oGo9&7
-z+t=4lu9UG-Ujxl_t%b{59ih$9PSBn!lW7`iGrKMm&Q10vTRK5!yRJHlRN`fcWril@
-zv|?uHM))d_NBa7`nW9TQ&PZ3tsax6ojav@U&9TYdHduz6k{G4GFTfpX_hk&`!z0F`
-z!gJ>6WBh&UO&i_oS+VOvUfcD9JR=y&;3OxP2%KT$#JB9W=UtgQpDT@>(E^#^A;oG%
-z4omjIK7rLXcRgmyS+%u_Gl2`MhOxMB{GGM&5o6cXF<vdhEe5Mu-+3OQZF~Ht$8Yl5
-z&=^M*j(xG~y3(sxz{#AtW^kPoyR!dZ9)}Sd$_6;Qjx#V<A+O@5j%7~Al)9jj*71v4
-z<zn{ZUuJA?73tB}iB6fJ)6H}8)1l|&XFq3N%P!P%;Wv{#b&7SVweIxDUCbEh>E~=G
-z`!#F5=z%_bq95y7+aIx?IdcSN`A)xX^vZjCS7lnS#=iZ)E7W%eX8!kr-#RDO36^!o
-Qqn&wY8qX0d7T}2V4SbP+*8l(j
-
-literal 0
-HcmV?d00001
-
-diff --git a/tools/grit/grit/testdata/klonk.ico b/tools/grit/grit/testdata/klonk.ico
-new file mode 100644
-index 0000000000000000000000000000000000000000..d371b214dc366249870efccc5da278d023aa91f1
-GIT binary patch
-literal 766
-zcmeH_F%H5o5CqSFL>Ve71Sxq1;TtsMEB*!Fv6LbmDQFSUL1mXjO7OC0gpl|G?B1ML
-zXS=a1V(2`di0U>FnQ~o{oUDnF5j(}bxAgSuhE8lMu~rkI8Ju%mb%Im^Xd<+ZwEgwd
-zFTg+WQOj5lfvO*4mbEA^bCj9KHh65vjx^zvsKW{eA8|Ah`w&r;%!`QgmEiG3hXCcC
-L+@V2V6oA7MJIRBx
-
-literal 0
-HcmV?d00001
-
-diff --git a/tools/grit/grit/testdata/klonk.rc b/tools/grit/grit/testdata/klonk.rc
-new file mode 100644
-index 0000000000000000000000000000000000000000..35652c4e6dd7cf7b7f62f637e191acf66f487235
-GIT binary patch
-literal 9824
-zcmeI2+j1L48poSUd<zdSObP?FE=Nu{E-aL6%Z`X;t1MXwwb^nxw&R#MQm|was(2h8
-z0*VVRxZyQ;25z?&e*drad1j>1!LS!j6*Z-q>7MTIeClrf{=b{yW=KLKoQA`29(tkA
-z?@<`g*P*W;F2X@LqqP?P!IgxQa2&e)&gmcUJfiQMr{-PocF21|OVCckGsY~31#sNt
-zeuJJaU(OhLWaHAYxy#{kNExfq8uQ5J2xc`jLo2kyURV$HuoL#fZm7|_&ii)Q3SZFE
-z;@$|W^lb4S@e23#yIdxsED4)%Ix5vi$fg&b@^yerB!M>k-sfJ2-!(XtBx>~E;y0>;
-zywqpO@eUBz4c2yv49m3k+_Z88eb3Rg>+A-4?GCk8rmyLEuD7;k@iyBQuQz|u4r}P|
-z1pk!hKgO!w#>STMq~-8ViH-G#etL?RCgF{OzaBBS8aA-k=%+1wau1JP!(#WbwJk2e
-z{FW=3II|6mUA$wTS=-Ei$KrzUMVn6ea?kwXHeRp*%qrtH8Cm5n-|(IYVUu<pe(r=N
-zzO@*)I&s84Ull`c5XBVjPVmJ8W*uVn!oE+xdXM3B1?=zfi}cBtkC36HBDof6y#96&
-zDNK-*c<mwsaiN$Tt;G8iy#LgqQ-aMX7AOxWcPO4D;cMihSg+XijJE^e#f+h-em)#K
-zU}i#pm$ov9MjtR<GnAE-XHJcd#M&7}G3rSx$}4^5MSA<RMTcOD8qE;QGcM((Z-!r=
-z@>HA@wRN;~7h6y+xyz_&R~;+XxM^eZ-_q~|%%b86_{3Asa-8FBk+Z7c-kJgN>UjHR
-zv*J6C_vNv`hUxGkXMvL0T0vKhVQf$p6QjfeUR}fgl_wW2W!gk%O?<jZPZ}19O{d7^
-z*finVDx2ru9D3dIaKoU~fb#-41E46PT;&oc4LDIw7tD-Ohf;>IO<b0BC*h%aN($z?
-zm)50Lg3jeb@}4KgpHn7``|w@I(iDZ;#6d+vaXpT`D6f;D{i-%|`usUfYCfinmyGTN
-zIW7V>a`tbcYLDwE{AY?>BR88vkIj3pcp9ftwy~b;A8i-;T|_p=$nY5yWU!`jTE^ib
-ze*F+mE-Vf$<AuvpIC5FVr`t!>>e;=5g*fg0^w_NUeElxZA2EAWiGRui^1Zl<=<)P1
-zF&Y;=yo$%KVIA>VGwa=@)kgQbrt31jq~WuvvL2VO`$<s`-l~FW4S%T*JzWty@3kqC
-zpB4rFU-(`|ov-8B%D+84yQpbJq|Cy#a=VYFm5(Lg9joHhbBjy*SqUH5^H#VWD)#mP
-zmDd8gX|wiIT+{3pP+PpWiFV4=ZF*H_#xD)})(!p!_EWXI5x?KFnQQblnWI&vvb<)-
-zFIrzJTT2IfU>zNqGSmHCaU;Y2q0yQ$JF7mTwL~ub{sOMb^Vh8GFZ(K1F-x>#wroJR
-z&tF1@??TN-{BD^Hb<bj)tU9hU-SUgid^Mw80(r42u2^L$1ARm5q2(uK*A(fk5cewP
-z9Zr$-B@Y%=OVA@~R*aezo@z;A8C69Z##=4Z+%_6(qSKmXx%;{Kv$<M>gJ;mLeTx&a
-ztSZO1p-!t5NvMLINn_JEi1N%h$mrKfeZ%Sxtv*(<o;FujMW(#py@aoK$>Sq%E`|5`
-zMQa!2rJ*fu!l%|$%^a7pE^XVFE$AM-((pNcct~BK8YqR1Sd~Aqmi*&@D)rQ&bN`YW
-zMPvC%+;<TLnyH+o+P!PzGEPTvj<#1#Q&p3I;<v-i%S09-uHQ3$KQw!lbu5_YDT~KE
-zq3F><0?G_uSf2bldXz_x`Rp$tXUa07m0#5g^O>n*TJE4PW#|}5_jztxOjO*+fAM}<
-zk=Lii5sD#879SKTSIp++>5AlgXumxIv246U-XKqCe;}^ATDIP+P{%8`Yynw2Uilpc
-z$xha}X;{ahB+#YVajq(xJ|2|kCBqoURxZc-PC<V34wS`l@7lObCdzS5sL5l@zQ+BG
-z;+Tl3tUl7t#}1OyYFBw_V3AMzKfW@m<J*t$@OdlXBE$+_TOoq!`H*`aipPX9y8N3z
-zJLpP#o#HyZq-`Au=XaT7{)rj2n4zkrdkJOKOvhNvbdE_@DQ#r;l~PX2VN1f=r#R=S
-z`e>WGR&NeH+c%h>-Yw>z7_{+>=5WWql;yg~F}<jhong+@E{wQv`%$Z$n`LNxVSLVu
-zqX`bJ2rtN9gE2WJxg8d*6Uugv=9gd**I(1S$3)lvXuIe$9VB*sDZi`wUr{S<ASs*o
-zEyw#FTC@PgtLUAvrjGSZrVFRipYc2<9~H+>V-&+XR>jna$uG|ylUKW<KRZ>)Rw*m^
-z_oOjp@vHny>%lMrW)jt@&DG$}J`tOC!twxncz`|R{U9wplS^%1SD7h)zLPS$9KxSJ
-z^(luqF00#DmestFZxDq%2fL5@KE>#H<EVwdOuH`m{4TpYASY`FCbM&`$abwl+vH7a
-za;>I|)#R(g69An@YFD|(_t^K?Y(=LYvGR$s)LKbvaYc(JPp$Xb2G?a>eC9KE-cEhZ
-zHSZ3+_C$Rze-w`BSsn7ZgI%TJO=9FfdDBy)V;pqaYpnOHjNdZ)cZhIWOV;71NPE_b
-z5ZwYd@EV=tI))^?mN>3>KBO~=3-s|NvQu_bO!m`Xy&s`1RS8A9bec9lO`@(ym$)sX
-zMVVq?wjta)kvTJp%Bk>bSh}4@HcmwuW}T<$ta~!gT03ja*d|hI1w9*Uk>}TwPvL12
-z=Q{J$UgQ8RXmu+(2GDd)J#{>6mrEh;W{57|8=6JgB)U>?#`vQXEaBEZgsP}6H0c~I
-zlTn_wQLB~3>U1IQ2y}Rh=cM|##66Rnd!p7F(K=LbM6B`LtO3?OS3Ko>03~gD6g5tu
-zOSRooa>4*SqvO;gSO;d)IuFc4e&rSY3#4arR~e}tmqXie5w!0rzg2#y{KWm2%CD85
-zD?e8LTlv1?UR>st9pKlDtGM^mfuA&df=7MIT`QQ#k8mnxoriygx5#|&^UZ&6F?Nx!
-z2jMH^+zTJm>H?vU#6L!6XLz9~{RHheL_xo4SVUcx*(c|e8ZfVRzJC37^PM7DoUXW9
-zRu0v_b;|ztF`73W!u5N4HWX!l^<O!vStkE0N0PgK{5wU`>ZH1;i+3m{&0Ya4gg*c`
-C>9bG(
-
-literal 0
-HcmV?d00001
-
-diff --git a/tools/grit/grit/testdata/ko_oem_enable_bug.html b/tools/grit/grit/testdata/ko_oem_enable_bug.html
-new file mode 100644
-index 0000000000..f2c199cc15
---- /dev/null
-+++ b/tools/grit/grit/testdata/ko_oem_enable_bug.html
-@@ -0,0 +1 @@
-+<IMG style="VERTICAL-ALIGN: middle" height=16 alt=아웃룩 src="/email.gif" width=16>
-\ No newline at end of file
-diff --git a/tools/grit/grit/testdata/ko_oem_non_admin_bug.html b/tools/grit/grit/testdata/ko_oem_non_admin_bug.html
-new file mode 100644
-index 0000000000..b9e8a1f288
---- /dev/null
-+++ b/tools/grit/grit/testdata/ko_oem_non_admin_bug.html
-@@ -0,0 +1 @@
-+<INPUT id=s type=submit value="&nbsp;&nbsp;확인&nbsp;&nbsp;">
-\ No newline at end of file
-diff --git a/tools/grit/grit/testdata/mini.html b/tools/grit/grit/testdata/mini.html
-new file mode 100644
-index 0000000000..8ac0a231a0
---- /dev/null
-+++ b/tools/grit/grit/testdata/mini.html
-@@ -0,0 +1,36 @@
-+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-+<html><head>
-+<meta http-equiv=content-type content="text/html; charset=windows-1252">
-+<meta http-equiv="cache-control" content="no-cache">
-+<meta http-equiv="pragma" content="no-cache">
-+<meta http-equiv="expires" content="-1">
-+<style>
-+BODY { FONT-SIZE: 8pt; FONT-FAMILY: verdana,arial,san-serif }
-+P { FONT-SIZE: 8pt; FONT-FAMILY: verdana,arial,san-serif }
-+TD { FONT-SIZE: 8pt; FONT-FAMILY: verdana,arial,san-serif }
-+A { FONT-SIZE: 8pt; FONT-FAMILY: verdana,arial,san-serif }
-+DIV { FONT-SIZE: 8pt; TEXT-DECORATION: none }
-+A:hover { COLOR: #ffffff }
-+.border { BORDER-RIGHT: 0px; BORDER-TOP: 0px; BORDER-LEFT: 0px; BORDER-BOTTOM: 0px }
-+</style>
-+</head>
-+
-+<BODY bottomMargin=0 bgColor=#3300cc leftMargin=0 topMargin=0 rightMargin=0 marginwidth="0" marginheight="0">
-+
-+<TABLE cellSpacing=0 cellPadding=0 bgColor=#3300cc border=0><TBODY>
-+<tr><TD vAlign=top>
-+
-+<TABLE cellSpacing=0 cellPadding=0 bgColor=#3300cc border=0><TBODY>
-+
-+<tr>
-+<TD vAlign=top>&nbsp;<INPUT style="position:relative; height=17px;" class=border size=10>&nbsp;</td>
-+
-+<TD class=ch onmouseover="this.bgColor='6666FF'" style="BORDER-RIGHT: #000000 1px solid; BORDER-TOP: #6666cc 1px solid; BORDER-LEFT: #6666cc 1px solid; BORDER-BOTTOM: #000000 1px solid" onmouseout="this.bgColor='#000099'" vAlign=center align=middle bgColor=#000099><img height=1 width=1><IMG src="logo.gif" align=middle><img height=1 width=1></td>
-+
-+</TBODY></table>
-+</td>
-+
-+<TD width=2><IMG height=1 width=1></td>
-+
-+<TD vAlign=top><TABLE cellSpacing=0 cellPadding=1><TBODY>
-+<tr><TD class=ch onmouseover="this.bgColor='6666FF'" style="BORDER-RIGHT: #000000 1px solid; BORDER-TOP: #6666cc 1px solid; BORDER-LEFT: #6666cc 1px solid; BORDER-BOTTOM: #000000 1px solid" onmouseout="this.bgColor='#000099'" vAlign=top noWrap bgColor=#000099><IMG src="mini_close.gif"></td></tr></TBODY></table></td></tr></TBODY></table></body></html>
-diff --git a/tools/grit/grit/testdata/oem_enable.html b/tools/grit/grit/testdata/oem_enable.html
-new file mode 100644
-index 0000000000..db6b85eca6
---- /dev/null
-+++ b/tools/grit/grit/testdata/oem_enable.html
-@@ -0,0 +1,106 @@
-+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-+<html><head><title>Google Desktop Search Download</title>
-+<meta http-equiv=content-type content="text/html; charset=utf-8">
-+<style>BODY {
-+ FONT-FAMILY: arial,sans-serif
-+}
-+TD {
-+ FONT-FAMILY: arial,sans-serif
-+}
-+DIV {
-+ FONT-FAMILY: arial,sans-serif
-+}
-+.p {
-+ FONT-FAMILY: arial,sans-serif
-+}
-+A {
-+ FONT-FAMILY: arial,sans-serif
-+}
-+DIV {
-+ COLOR: #000
-+}
-+TD {
-+ COLOR: #000
-+}
-+A:link {
-+ COLOR: #00c
-+}
-+A:visited {
-+ COLOR: #551a8b
-+}
-+</style>
-+
-+<meta content="mshtml 6.00.2800.1476" name=generator></head>
-+<body>
-+<center>
-+<TABLE cellSpacing=0 cellPadding=0 border=0>
-+ <TBODY>
-+ <TR vAlign=center>
-+ <td>
-+ <DIV align=center><IMG height=55 alt="Google Desktop Search"
-+ src="/logo3.gif" width=150 border=0 search=""
-+ desktop=""></DIV></td>
-+ <td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
-+ <td><FONT size=+1><B>Search your own
-+computer.</B></font></td></tr></TBODY></table><BR>
-+<TABLE cellSpacing=0 cellPadding=0 width=630 border=0>
-+ <TBODY>
-+ <tr>
-+ <TD vAlign=top width="53%"><FONT size=-1>
-+ <LI>Find your email, files, web history and chats instantly <NOBR>
-+ <LI>View web pages you've seen, even when you're not online</NOBR>
-+ <LI>Search as easily as you do on Google
-+ <p><B>Google Desktop Search finds:</B></p></font>
-+ <TABLE cellSpacing=1 cellPadding=0 width=325 border=0 valign="center">
-+ <TBODY>
-+ <TR vAlign=center>
-+ <TD colSpan=3><FONT size=-1><IMG style="VERTICAL-ALIGN: middle"
-+ height=16 alt=Outlook src="/email.gif"
-+ width=16>&nbsp;&nbsp;Email from Outlook, Outlook Express, &amp;
-+ Thunderbird</font></td></tr>
-+ <tr>
-+ <TD noWrap colSpan=3><FONT size=-1><IMG
-+ style="VERTICAL-ALIGN: middle" height=16 alt="Internet Explorer"
-+ src="/html.gif" width=16>&nbsp;&nbsp;Web history
-+ from IE/Firefox/Mozilla/Netscape</font></td></tr>
-+ <tr>
-+ <TD noWrap colSpan=3><FONT size=-1><IMG
-+ style="VERTICAL-ALIGN: middle" height=16 alt=Text
-+ src="/file.gif" width=16>&nbsp;&nbsp;Files in Word,
-+ Excel, Powerpoint, PDF, &amp; media formats</font></td></tr>
-+ <tr>
-+ <TD vAlign=top colSpan=3><FONT size=-1><IMG
-+ style="VERTICAL-ALIGN: middle" height=16 alt="AOL IM"
-+ src="/aim.gif" width=16>&nbsp;&nbsp;Chats from AOL
-+ Instant Messenger</font></td></tr>
-+ <tr>
-+ <TD noWrap><FONT size=-1>&nbsp;</font></td></tr></TBODY></table><FONT
-+ size=-1>&nbsp;</font><FONT size=-1><A
-+ href="http://desktop.google.com/about.html">About Desktop
-+ Search</A>&nbsp;&nbsp; <A
-+ href="http://desktop.google.com/screenshots.html">Screenshots</A>&nbsp;&nbsp;
-+ <A href="http://desktop.google.com/support">Help</A>&nbsp;&nbsp; <A
-+ href="http://desktop.google.com/feedback.html">Contact
-+ Us</A><BR></font></LI></td>
-+ <td>&nbsp;&nbsp;&nbsp;</td>
-+ <TD vAlign=top width="53%">
-+ <TABLE cellPadding=2 width="100%" align=center>
-+ <TBODY>
-+ <tr>
-+ <TD
-+ style="BORDER-RIGHT: rgb(204,204,204) 1px solid; BORDER-TOP: rgb(204,204,204) 1px solid; BORDER-LEFT: rgb(204,204,204) 1px solid; BORDER-BOTTOM: rgb(204,204,204) 1px solid"
-+ width="100%" bgColor=#e7eff7 blah2="#fff8dd" blah="#e7eaf7"><BR>
-+ <center><FONT size=-1>By using, you agree to our <A
-+ href="http://desktop.google.com/eula.html"><BR>Terms &amp;
-+ Conditions</A> and <A
-+ href="http://desktop.google.com/privacypolicy.html">Privacy
-+ Policy</A></font></center>
-+ <p></p>
-+ <FORM action='[STEP2]'>
-+ <P align=center><INPUT style="PADDING-RIGHT: 3px; PADDING-LEFT: 3px; FONT-WEIGHT: bold; FONT-SIZE: 17px; PADDING-BOTTOM: 4px; PADDING-TOP: 4px" type=submit value="Agree and Start Using" name=Submit>
-+ </p></FORM><FONT size=-2>* Automatically starts when you turn on
-+ your computer</font> </td></tr></TBODY></table>
-+ <p></p></td></tr></TBODY></table></center>
-+<p></p>
-+<center><FONT color=#666666 size=-2>©2005 Google</font>
-+<p></p></center></body></html>
-diff --git a/tools/grit/grit/testdata/oem_non_admin.html b/tools/grit/grit/testdata/oem_non_admin.html
-new file mode 100644
-index 0000000000..8b7ca13e21
---- /dev/null
-+++ b/tools/grit/grit/testdata/oem_non_admin.html
-@@ -0,0 +1,39 @@
-+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-+<html><head><title>Google Desktop Search Preferences</title>
-+<meta http-equiv=cache-control content=no-cache>
-+<meta http-equiv=content-type content="text/html; charset=utf-8">
-+<meta http-equiv=pragma content=no-cache>
-+<meta http-equiv=expires content=-1>
-+<style>BODY {
-+ FONT-FAMILY: arial,sans-serif
-+}
-+.c:active {
-+ COLOR: #ff0000
-+}
-+.c:visited {
-+ COLOR: #7777cc
-+}
-+.c:link {
-+ COLOR: #7777cc
-+}
-+</style>
-+
-+<script>
-+<!--
-+override = 1;
-+function ee() {if (override==1) {(new Image()).src="/doneinstallprefs&s=3286011577";}}
-+// -->
-+</script>
-+
-+<meta content="mshtml 6.00.2800.1476" name=generator></head>
-+<BODY onresize=stw() leftMargin=30 rightMargin=30 onunload=ee()>
-+<FORM name=f onsubmit=javascript:window.close();><IMG
-+src="/logo3.gif" border=0>
-+<DIV id=c1 style="WIDTH: 600px">
-+<p><BR><FONT color=#00218a><B>We're sorry, but you need administrator access to
-+enable Desktop Search.</B></font></p><FONT size=-1>
-+<p>To install or run Google Desktop Search you need administrator access on this
-+computer. Please try installing again once you have administrator
-+access.</p></font></DIV>
-+<p><INPUT id=s type=submit value=&nbsp;&nbsp;OK&nbsp;&nbsp;> <BR></p>
-+<center></center></FORM></body></html>
-diff --git a/tools/grit/grit/testdata/onebox.html b/tools/grit/grit/testdata/onebox.html
-new file mode 100644
-index 0000000000..c24ff043a5
---- /dev/null
-+++ b/tools/grit/grit/testdata/onebox.html
-@@ -0,0 +1,21 @@
-+<html><head><title>Google Desktop Search Results</title>
-+<style><!--
-+body,td,div,.p,a{font-family:arial,sans-serif }
-+body{ background-color: transparent }
-+div,td{color:#000}
-+.f,.fl:link{color:#6f6f6f}
-+a:link,.w,a.w:link,.w a:link{color:#00c}
-+a:visited,.fl:visited{color:#551a8b}
-+a:active,.fl:active{color:#f00}
-+.t a:link,.t a:active,.t a:visited,.t{color:#000}
-+//-->
-+</style>
-+</head>
-+<body>
-+<table cellspacing=0 cellpadding=1 border=0 ID="Google Desktop Search">
-+<tr><td colspan=2><nobr><a href="http://[WEBSERVER][$~QUERY~$]" target=_parent>[NUMRESULTS] [RESULT-STRING] stored on your computer</a><font size=-1>&nbsp;-&nbsp;<a href="[HIDENOW]" style="color:#7777cc;" target=_parent>Hide</a>&nbsp;-&nbsp;<a href="http://desktop.google.com/integration.html" style="color:#7777cc;" target=_parent>About</a></font></nobr></td></tr>
-+<tr><td valign=top width=40><img height=27 style="margin-top:2px;" src="http://[WEBSERVER]/onebox.gif"></td>
-+<td valign=top width="99%"><font size=-1>[RESULTS]</font></td></tr>
-+</table>
-+</body>
-+</html>
-diff --git a/tools/grit/grit/testdata/oneclick.html b/tools/grit/grit/testdata/oneclick.html
-new file mode 100644
-index 0000000000..32dc6459dd
---- /dev/null
-+++ b/tools/grit/grit/testdata/oneclick.html
-@@ -0,0 +1,34 @@
-+[HEADER]
-+
-+
-+<TABLE cellSpacing=4 cellPadding=0 width="100%" border=0>
-+<tr>
-+ <TD vAlign=top align=left width=50%>
-+ [EMAIL_TOP_CHROME]
-+
-+ <p class=f>
-+ <TABLE cellSpacing=6 cellPadding=0 width="100%" border=0>
-+ [EMAIL]
-+ </table>
-+ </td>
-+
-+
-+ <TD width=1 align=middle bgColor=#cfcfcf><IMG height=1 width=1></td>
-+ <TD width=50% vAlign=top align=left>
-+ [FREQ_TOP_CHROME]
-+ <p class=f>
-+ <TABLE cellSpacing=6 cellPadding=0 width="100%" border=0 ID="Table1">
-+ [$~FREQ~$]
-+ </table>
-+ <p class=g>
-+ [RECENT_TOP_CHROME]
-+ <TABLE cellSpacing=6 cellPadding=0 width="100%" border=0 ID="Table2">
-+ [$~RECENT~$]
-+ </table>
-+ </td>
-+ </tr>
-+</table>
-+<center><BR>
-+
-+
-+[FOOTER]
-diff --git a/tools/grit/grit/testdata/password.html b/tools/grit/grit/testdata/password.html
-new file mode 100644
-index 0000000000..16007a1ac0
---- /dev/null
-+++ b/tools/grit/grit/testdata/password.html
-@@ -0,0 +1,37 @@
-+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-+<html><head><title>Password Required</title>
-+<meta http-equiv="content-type" content="text/html; charset=utf-8">
-+<meta http-equiv="cache-control" content="no-cache">
-+<meta http-equiv="pragma" content="no-cache">
-+<meta http-equiv="expires" content="-1">
-+<style>
-+BODY,TD,A,P {FONT-FAMILY: arial,sans-serif}
-+.q {COLOR: #0000cc}
-+</style>
-+<script>
-+<!--
-+function sf(){document.f.q.focus();}
-+// -->
-+</script>
-+</head>
-+<body text=#000000 vLink=#551a8b aLink=#ff0000 link=#0000cc bgColor=#ffffff onload=sf()>
-+<center>
-+<table cellSpacing=0 cellPadding=0 border=0>
-+<tr><td><a href="[$~HOMEPAGE~$]"><IMG border=0 height=110 alt="Google Desktop Search" src="hp_logo.gif" width=276></a></td></tr></table><BR>
-+<form name=f method=GET action='/password'>
-+<table cellSpacing=0 cellPadding=4 border=0>
-+<tr><td class=q noWrap><font size=-1>
-+ <table cellSpacing=0 cellPadding=0>
-+ <tr vAlign=top>
-+ <td align=middle>Password required:&nbsp;&nbsp;<input maxLength=80 size=30 type=password name=pw value="">
-+ <script>
-+ document.f.q.focus();
-+ </script>
-+ &nbsp;<input type=submit value="Submit" name=submit>
-+ </td></tr>
-+ </table>
-+ </form>
-+</td></tr>
-+</table>
-+<br><font size=-1>[$~BOTTOMLINE~$]</font></p>
-+<p><font size=-2>&copy;2005 Google</font></p></center></body></html>
-\ No newline at end of file
-diff --git a/tools/grit/grit/testdata/preferences.html b/tools/grit/grit/testdata/preferences.html
-new file mode 100644
-index 0000000000..b37412436b
---- /dev/null
-+++ b/tools/grit/grit/testdata/preferences.html
-@@ -0,0 +1,234 @@
-+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-+<html><head><title>Google Desktop Search Preferences</title>
-+<meta http-equiv=content-type content="text/html; charset=utf-8">
-+<style>
-+body {
-+ margin-left: 2em; margin-right: 2em;
-+ font-family: arial,sans-serif;
-+ color:#000; background-color:#fff;
-+}
-+a:active { color:#f00 }
-+a:visited { color:#551a8b }
-+a:link { color:#00c }
-+a.c:active { color: #ff0000 }
-+a.c:visited { color: #7777cc }
-+a.c:link { color: #7777cc }
-+.b { font-weight: bold }
-+.shaded-header { background-color: #e8f4f7; border-top: 1px solid #39c;
-+margin: 0px; padding: 0px }
-+.shaded-subheader { background-color: #e8f4f7; margin: 12px 0px 0px 0px;
-+ padding: 0px }
-+.plain-subheader { background-color: #fff; margin: 12px 0px 0px 0px;
-+ padding: 0px }
-+.header-element { margin: 0px; padding: 2px}
-+.expand { width: 98% }
-+.s { font-size: smaller }
-+.prefgroup { border: 2px solid #e8f4f7; width: 100% }
-+.phead { font-weight: bold; font-size: smaller; vertical-align: top;
-+text-transform: capitalize; border-bottom: 2px solid #e8f4f7; margin: 0px;
-+padding: 8px}
-+.pbody { border-bottom: 2px solid #e8f4f7; margin: 0px;
-+padding: 8px}
-+.pref-last { border-bottom: 0px }
-+.example { color: gray; font-family: monospace; }
-+</style>
-+<script>
-+<!--
-+function validate() {}
-+function fnOnClickAll() {for (var i = 0; i < document.langform.lr.length; i++) {
-+document.langform.lr[i].checked = false;}}
-+function fnOnClickSome() {
-+var count = 0;for (var i = 0; i < document.langform.lr.length; i++) {
-+if (document.langform.lr[i].checked) {count++;}}
-+document.langform.lang[0].checked = (count <= 0);
-+document.langform.lang[1].checked = (count > 0);}
-+// -->
-+</script>
-+</head>
-+<body onload="checkOffice()">
-+<form name=prefs action="[$~SETPREFS~$]" method=post><input name=url
-+value="[PREVPAGE]" type=hidden>
-+<table cellspacing=2 cellpadding=0 width="100%" border=0>
-+<tr>
-+<td valign=top width="1%"><a href="[$~HOMEPAGE~$]">
-+<img alt="Go to Google Desktop Search" src="logo3.gif" border=0></a></td>
-+<td>&nbsp;</td>
-+<td nowrap>
-+
-+<table class="shaded-header"><tr>
-+<td class="header-element b expand">Preferences</td>
-+<td class="header-element s">
-+<a href="http://desktop.google.com/preferences.html">Preferences&nbsp;Help</a>
-+</td>
-+</tr></table>
-+
-+</tr></table>
-+
-+<table class="shaded-subheader"><tr>
-+<td class="header-element expand s">
-+<span class="b">Save</span> your preferences when finished.</td>
-+<td class="header-element"><input type=submit value="Save Preferences"
-+name=submit2></td>
-+</tr></table>
-+
-+[STATUS-MESSAGE]
-+<table class="plain-subheader"><tr>
-+<td class="header-element expand"><span class="b">Preferences</span><span
-+class="s"> (changes apply to Google Desktop Search application)</span></td>
-+</tr></table>
-+
-+<table class="prefgroup" cellpadding=0 cellspacing=0>
-+
-+<!-- -->
-+<tr>
-+<td class="phead">Search types</td>
-+<td class="pbody"><div class="s">Index the following items so that you can
-+search for them:<br />&nbsp;</div>
-+<div>
-+ <table border=0>
-+ <tr>
-+ <td width=150 nowrap valign=top><span class="s">
-+ <input type=checkbox [CHECK-EMAIL] name=EMAIL id=h3><label for=h3>
-+ Email</label><br>
-+ <input type=checkbox [CHECK-AIM] name=AIM id=h5><label for=h5> Chats
-+ (AOL/MSN IM)</label><br>
-+ <input type=checkbox onclick='if(!this.checked){h12.checked=0;h12.disabled=1;}
-+ else {h12.disabled=0;}' [CHECK-WEB] name=WEB id=h11><label for=h11> Web
-+ history</label>
-+
-+ </span></td>
-+ <td width=120 nowrap valign=top><span class="s">
-+ <script>
-+<!--
-+function checkOffice() { var w = document.getElementById("h7");
-+var e = document.getElementById("h8"); var o = document.getElementById("h10");
-+if (!(w.checked || e.checked)) { o.checked=0;o.disabled=1;} else {o.disabled=0;} }
-+// -->
-+ </script>
-+ <input type=checkbox [CHECK-DOC] name=DOC id=h7 onclick='checkOffice()'>
-+ <label for=h7> Word</label><br>
-+ <input type=checkbox [CHECK-XLS] name=XLS id=h8 onclick='checkOffice()'>
-+ <label for=h8> Excel</label><br>
-+ <input type=checkbox [CHECK-PPT] name=PPT id=h9>
-+ <label for=h9> PowerPoint</label><br>
-+ </span></td><td nowrap valign=top><span class="s">
-+ <input type=checkbox [CHECK-PDF] name=PDF id=hpdf>
-+ <label for=hpdf> PDF</label><br>
-+ <input type=checkbox [CHECK-TXT] name=TXT id=h6>
-+ <label for=h6> Text, media, and other files</label><br>
-+ </tr>
-+ <tr><td nowrap valign=top colspan=3><span class="s"><br />
-+ <input type=checkbox [CHECK-SECUREOFFICE] name=SECUREOFFICE id=h10>
-+ <label for=h10> Password-protected Office documents (Word, Excel)</label><br />
-+ <input type=checkbox [DISABLED-HTTPS] [CHECK-HTTPS] name=HTTPS id=h12><label
-+ for=h12> Secure pages (HTTPS) in web history</label></span></td></tr>
-+</table>
-+</div></td></tr>
-+</div>
-+</td>
-+</tr>
-+
-+<!-- -->
-+<tr>
-+<td class="phead">Plug-ins</td>
-+<td class="pbody"><div class="s"
-+style="display:[ADDIN-DISPLAYSTYLE]">Index these additional items:<p>
-+[ADDIN-DO]
-+[ADDIN-OPTIONS]</div><div class="s">
-+To install plug-ins to index other items, visit the
-+<a href="http://desktop.google.com/plugins.html">Plug-ins Download page</a>.</div>
-+</tr>
-+
-+<!-- -->
-+<tr>
-+<td class="phead">Don't search these items</td>
-+<td class="pbody"><div class="s">
-+<label for=FORBIDDEN>Do not search web sites with the following URLs or files
-+with the following paths. Put each entry on a separate line. Examples:</label><br>
-+<span class="example">c:\Documents and Settings\username\Private Stuff</span><br>
-+<span class="example">http://www.domain.com/</span><br>
-+<div>&nbsp;</div>
-+<div><TEXTAREA rows=3 cols=65 name=FORBIDDEN id=FORBIDDEN>[FORBIDDEN]
-+</TEXTAREA></div>
-+</tr>
-+
-+<!-- -->
-+<tr>
-+<td class="phead pref">Search Box Display</td>
-+<td class="pbody pref" valign=top>
-+
-+<table border=0 cellpadding=0><tr><td valign=top>
-+
-+<input type="radio" name="SBDISPLAY" id="DISPLAYDB" [CHECK-DISPLAYDB] value="DISPLAYDB"></td><td>
-+<label for=DISPLAYDB><font size=-1>Deskbar - A search box in your taskbar</font></label></td></tr>
-+<tr><td></td></tr>
-+<tr><td></td><td><img src="deskbar.gif" alt="Deskbar" width="268" height="34"></td></tr>
-+<tr><td height=2></td></tr>
-+<tr><td valign=top>
-+
-+<input type="radio" name="SBDISPLAY" id="DISPLAYMB" [CHECK-DISPLAYMB] VALUE="DISPLAYMB"></td><td>
-+<label for=DISPLAYMB><font size=-1>Floating Deskbar - A search box you can put anywhere on your desktop</font></label></td></tr>
-+<tr><td></td></tr>
-+<tr><td></td><td><img src="minibar.gif" width="137" height="27"></td></tr>
-+<tr><td height=2></td></tr>
-+<tr><td valign=top>
-+
-+<input type=radio name="SBDISPLAY" id="DISPLAYNONE" [CHECK-DISPLAYNONE] VALUE="DISPLAYNONE"></td><td valign=top>
-+<label for=DISPLAYNONE><font size=-1> None</font></label>
-+</td></tr>
-+</table>
-+
-+</td></tr>
-+
-+<!-- -->
-+<tr>
-+<td class="phead pref">Number of Results</td>
-+<td class="pbody pref"><label for=num><span class="s">
-+Display <select name=num id="num">
-+<option [CHECK-NUM-10]>10
-+<option [CHECK-NUM-20]>20
-+<option [CHECK-NUM-30]>30
-+<option [CHECK-NUM-50]>50
-+<option [CHECK-NUM-100]>100</select>
-+ results per page</span></label>
-+</td>
-+</tr>
-+
-+<!-- -->
-+<tr>
-+<td class="phead">Google integration</td>
-+<td class="pbody">
-+<table border=0 cellpadding=0>
-+<tr><td><input type=CHECKBOX name=ONEBOX [CHECK-ONEBOX] id=onebox></td>
-+<td><label for=onebox>
-+ <span class="s">Show Desktop Search results on Google Web Search result pages.
-+ </span></label></td></tr>
-+ <tr><td></td><td>
-+ <span class="s">Your personal results are private from Google.</span>
-+ </td></tr></table>
-+</td>
-+</tr>
-+
-+<!-- -->
-+<tr>
-+<td class="phead pref-last">Help us improve</td>
-+<td class="pbody pref-last">
-+<input type=CHECKBOX name=SENDDATA id="SENDDATA" [CHECK-SENDDATA]><label for=
-+SENDDATA> <span class="s">Send non-personal usage data and crash reports to
-+Google to help improve Desktop Search.</span></label>
-+</td>
-+</tr>
-+
-+</table>
-+
-+<table class="shaded-subheader"><tr>
-+<td class="header-element expand s"><span class="b">Save</span> your preferences
-+when finished.</td>
-+<td class="header-element"><input type=submit value="Save Preferences"
-+name=submit2></td>
-+</tr></table>
-+
-+<p><div align=center>[$~BOTTOMLINE~$]</div>
-+<br><center><span class="s">&copy;2005 Google</span></center>
-+</form></body></html>
-\ No newline at end of file
-diff --git a/tools/grit/grit/testdata/preprocess_test.html b/tools/grit/grit/testdata/preprocess_test.html
-new file mode 100644
-index 0000000000..13ece9a9f6
---- /dev/null
-+++ b/tools/grit/grit/testdata/preprocess_test.html
-@@ -0,0 +1,7 @@
-+<if expr="True">
-+should be kept
-+</if>
-+in the middle...
-+<if expr="False">
-+should be removed
-+</if>
-diff --git a/tools/grit/grit/testdata/privacy.html b/tools/grit/grit/testdata/privacy.html
-new file mode 100644
-index 0000000000..1d45f4a539
---- /dev/null
-+++ b/tools/grit/grit/testdata/privacy.html
-@@ -0,0 +1,35 @@
-+[!]
-+title Privacy and Google Desktop Search
-+template
-+privacy_bottomline
-+hp_image
-+
-+<TABLE CELLSPACING=0 CELLPADDING=5 WIDTH="98%" BORDER=0>
-+<TR VALIGN=TOP>
-+<td>
-+<h4>Privacy and Google Desktop Search</h4>
-+
-+<p><FONT SIZE=-1>Google is committed to making search on your desktop as easy
-+as searching the web. We recognize that privacy is an important issue,
-+so we designed and built Google Desktop Search with respect for your privacy.
-+<p>
-+So that you can easily search your computer, the Google Desktop Search application indexes
-+and stores versions of your files and other computer activity,
-+such as email, chats, and web history. These versions may also be mixed
-+with your Web search results to produce
-+results pages for you that integrate relevant content from your computer and
-+information from the Web.
-+<p>
-+Your computer's content is not made accessible to Google or anyone else without your explicit permission.
-+
-+<p>You can read the
-+<A HREF='http://desktop.google.com/privacypolicy.html?hl=[LANG_CODE]'>Privacy Policy</A>
-+and <A HREF='http://desktop.google.com/privacyfaq.html?hl=[LANG_CODE]'>Privacy FAQ</A> online.
-+</font>
-+</td></tr></table>
-+
-+<center><br>
-+<TABLE CELLSPACING=0 CELLPADDING=0 WIDTH="100%" BORDER=0>
-+<TR BGCOLOR=#3399CC><TD ALIGN=MIDDLE HEIGHT=1><IMG HEIGHT=1 ALT="" WIDTH=1></td></tr></table>
-+<FONT SIZE=-1>[$~PRIVACY_BOTTOMLINE~$] - &copy;2005 Google </font>
-+</center>
-\ No newline at end of file
-diff --git a/tools/grit/grit/testdata/quit_apps.html b/tools/grit/grit/testdata/quit_apps.html
-new file mode 100644
-index 0000000000..a501b0e2bf
---- /dev/null
-+++ b/tools/grit/grit/testdata/quit_apps.html
-@@ -0,0 +1,49 @@
-+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-+<html><head><title>Google Desktop Search Preferences</title>
-+<meta http-equiv=cache-control content=no-cache>
-+<meta http-equiv=content-type content="text/html; charset=utf-8">
-+<meta http-equiv=pragma content=no-cache>
-+<meta http-equiv=expires content=-1>
-+<style>BODY {
-+ FONT-FAMILY: arial,sans-serif
-+}
-+.c:active {
-+ COLOR: #ff0000
-+}
-+.c:visited {
-+ COLOR: #7777cc
-+}
-+.c:link {
-+ COLOR: #7777cc
-+}
-+</style>
-+
-+<script>
-+<!--
-+// -->
-+</script>
-+
-+<meta content="mshtml 6.00.2800.1476" name=generator></head>
-+<BODY onresize=stw() leftMargin=30 rightMargin=30>
-+<FORM name=f action='[NEXTSTEP]' method=post><IMG src="/logo3.gif"
-+border=0>
-+<DIV id=c1 style="WIDTH: 600px">
-+<p><BR><FONT color=#00218a><B>To start using Google Desktop Search, we may need to close the following programs if they are running:</B></font></p>
-+<FONT size=-1><p>You can start these programs once Google Desktop Search is running.</p></font>
-+
-+<LI><FONT size=-1>AOL Instant Messenger</font>
-+<LI><FONT size=-1>Firefox</font>
-+<LI><FONT size=-1>Internet Explorer</font>
-+<LI><FONT size=-1>Microsoft Excel</font>
-+<LI><FONT size=-1>Microsoft Outlook </font>
-+<LI><FONT size=-1>Microsoft Word </font>
-+<LI><FONT size=-1>Mozilla</font>
-+<LI><FONT size=-1>Mozilla Thunderbird</font>
-+<LI><FONT size=-1>Netscape</font>
-+<LI><FONT size=-1>Opera</font>
-+<LI><FONT size=-1>Other web browsers</font>
-+<FONT size=-1>
-+<p>This will take only a few seconds to complete. </p></font></LI></DIV>
-+<p><INPUT id=s type=submit name="quit" value="&nbsp;&nbsp;OK.&nbsp;&nbsp;Close&nbsp;these&nbsp;applications&nbsp;&nbsp;">
-+ <INPUT id=s type=submit name="redir" value="&nbsp;&nbsp;Cancel.&nbsp;I'll&nbsp;run&nbsp;this&nbsp;later&nbsp;&nbsp;"><BR></p>
-+<center></center></FORM></body></html>
-diff --git a/tools/grit/grit/testdata/recrawl.html b/tools/grit/grit/testdata/recrawl.html
-new file mode 100644
-index 0000000000..0401e7c2b0
---- /dev/null
-+++ b/tools/grit/grit/testdata/recrawl.html
-@@ -0,0 +1,30 @@
-+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-+<html><head><title>Refresh index</title>
-+<meta http-equiv="content-type" content="text/html; charset=utf-8">
-+<meta http-equiv="cache-control" content="no-cache">
-+<meta http-equiv="pragma" content="no-cache">
-+<meta http-equiv="expires" content="-1">
-+<style>
-+BODY,TD,A,P {FONT-FAMILY: arial,sans-serif}
-+.q {COLOR: #0000cc}
-+</style>
-+</head>
-+<body text="#000000" vLink="#551a8b" aLink="#ff0000" link="#0000cc" bgColor="#ffffff">
-+<center>
-+<table cellSpacing="0" cellPadding="0" border="0">
-+<tr>
-+<td><a href="[$~HOMEPAGE~$]"><img border="0" height="110" alt="Google Desktop Search" src="hp_logo.gif" width="276"></a>
-+</td>
-+</tr>
-+</table>
-+<br>
-+<center>Google Desktop Search is now recrawling your drive to index new files.</center>
-+<center>Note that new files are indexed automatically, and this step is generally not needed.</center>
-+<center>Click <a href="[$~HOMEPAGE~$]">here</a> to continue.</center>
-+</td></tr></table>
-+<br>
-+<font size="-1">[$~BOTTOMLINE~$]</font>
-+<p><font size="-2">&copy;2005 Google</font></p>
-+</center>
-+</body>
-+</html>
-\ No newline at end of file
-diff --git a/tools/grit/grit/testdata/resource_ids b/tools/grit/grit/testdata/resource_ids
-new file mode 100644
-index 0000000000..d5d440d57f
---- /dev/null
-+++ b/tools/grit/grit/testdata/resource_ids
-@@ -0,0 +1,13 @@
-+{
-+ "SRCDIR": ".",
-+ "test.grd": {
-+ "messages": [100, 10000],
-+ },
-+ "substitute_no_ids.grd": {
-+ "messages": [10000, 20000],
-+ },
-+ "<(FOO)/file.grd": {
-+ },
-+ "<(SHARED_INTERMEDIATE_DIR)/devtools/devtools.grd": {
-+ },
-+}
-diff --git a/tools/grit/grit/testdata/script.html b/tools/grit/grit/testdata/script.html
-new file mode 100644
-index 0000000000..f177d9c30e
---- /dev/null
-+++ b/tools/grit/grit/testdata/script.html
-@@ -0,0 +1,38 @@
-+<script>
-+function run(n,cut){
-+ var out = "", str = "abcdefghijklmnopqrstuvwxyz 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ,./:;'\"()*!?-_@[]{}#%`+=|\\>";
-+ n.innerHTML = 'aa';
-+
-+ var base = n.scrollWidth;
-+ for(var i=0;i<str.length;i++) {
-+ n.innerHTML = 'a'+str.charAt(i)+'a';
-+ out += str.charAt(i) + (n.scrollWidth-base) +";";
-+
-+ if(cut && !i && (n.scrollWidth-base == cut)) {
-+ return '\x02'+"0;";
-+ }
-+ }
-+ // extra cases for literals
-+ n.innerHTML = 'a&lt;a';
-+ out += '<' + (n.scrollWidth-base) +";";
-+ n.innerHTML = 'a&amp;a';
-+ out += '&' + (n.scrollWidth-base) +";";
-+
-+ var base_height = n.scrollHeight;
-+ n.innerHTML += '<br>a';
-+ out += '\x01' + (n.scrollHeight-base_height) +";";
-+
-+ return out;
-+}
-+
-+function TEST_WIDTH() {
-+ var n = document.getElementById('test');
-+ var out = run(n[$~CUT~$]);
-+ if (out.length>4){
-+ n.style.fontWeight='bold';
-+ out += run(n);
-+ }
-+ n.outerHTML = "";
-+ (new Image()).src="[$~SETWIDTH~$]?src=[COMPONENT]&data="+escape(out).replace(/\+/g,"%2B");
-+}
-+</script>
-diff --git a/tools/grit/grit/testdata/searchbox.html b/tools/grit/grit/testdata/searchbox.html
-new file mode 100644
-index 0000000000..9eccba99a5
---- /dev/null
-+++ b/tools/grit/grit/testdata/searchbox.html
-@@ -0,0 +1,22 @@
-+<body bgcolor=#ffffff topmargin=2 marginheight=2>
-+<table border=0 cellpadding=0 cellspacing=0 width=1%>
-+<tr>
-+<td valign=top><a href='[$~HOMEPAGE~$]'><img width=150 height=55 src="/logo3.gif" alt="Go to Google Desktop Search" border=0 vspace=12></a></td>
-+<td>&nbsp;&nbsp;</td>
-+<td valign=top>
-+<table cellpadding=0 cellspacing=0 border=0><tr><td colspan=2 height=14 valign=bottom>
-+<table border=0 cellpadding=4 cellspacing=0>
-+<tr><td class=q><font size=-1>
-+[$~LINKS~$]
-+</tr>
-+</table>
-+</td>
-+</tr>
-+<tr><td nowrap><form name=gs method=GET action='[$~SEARCHURL~$]'><input type=text name=q size=41 maxlength=2048 value="[DISP_QUERY]"><input type=hidden name=ie value="UTF-8">
-+<font size=-1>[$~FLAGS~$]<input type=submit name="btnG" value="Search Desktop"><span id=hf></span></font></td>
-+<td><font size=-2>&nbsp;&nbsp;<a href='[$~PREFERENCES~$]'>Desktop&nbsp;Preferences</a><br>&nbsp;&nbsp;<a [DELETE_EXTRA] href=[DELETE_PAGE]><nobr>[DELETE_NAME]</nobr></a></font></td>
-+</tr></table>
-+<table cellpadding=0 cellspacing=0 border=0>
-+<tr><td><font size=-1>&nbsp;</font></td></tr>
-+</table>
-+</td></tr></form></table>
-\ No newline at end of file
-diff --git a/tools/grit/grit/testdata/sidebar_h.html b/tools/grit/grit/testdata/sidebar_h.html
-new file mode 100644
-index 0000000000..e103e8f8db
---- /dev/null
-+++ b/tools/grit/grit/testdata/sidebar_h.html
-@@ -0,0 +1,82 @@
-+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-+<html><head>
-+<meta http-equiv="content-type" content="text/html; charset=utf-8">
-+<meta http-equiv="cache-control" content="no-cache">
-+<meta http-equiv="pragma" content="no-cache">
-+<meta http-equiv="expires" content="-1">
-+<style>
-+BODY,TD,DIV,A,.p { FONT-FAMILY: arial,sans-serif; SCROLL: no}
-+DIV,TD {COLOR: #000}
-+.f, .fl:link {COLOR: #6f6f6f}
-+A:link { COLOR: #00c}
-+A:visited { COLOR: #551a8b}
-+A:active { COLOR: #f00}
-+.fl:active { COLOR: #f00}
-+.h { COLOR: #3399CC}
-+.a, .a:link {COLOR: #008000}
-+.b { FONT-WEIGHT: bold; FONT-SIZE: 12pt; COLOR: #00c}
-+.g { MARGIN-TOP: .5em; MARGIN-BOTTOM: .5em}
-+.f { MARGIN-TOP: 0.5em; MARGIN-BOTTOM: 0.25em}
-+.c:active, .c:visited, .c:link { COLOR: #6f6f6f}
-+.ch {CURSOR: hand}
-+</style>
-+</head>
-+<BODY onload="TEST_WIDTH();" bottomMargin=0 leftMargin=2 topMargin=0 rightMargin=2 marginwidth=0 marginheight=0 SCROLL=NO bgcolor=#E0E0E0 style="border-style:solid; border-width:0;" oncontextmenu="return false;">
-+
-+<script>
-+function hide() {
-+ return 1;
-+ // return confirm("Are you sure you want to hide the sidebar?\nYou can show it again in Google Desktop Search Preferences. ");
-+}
-+</script>
-+
-+
-+<TABLE border=0 cellPadding=0 cellSpacing=0 width="100%"><tr>
-+<TD WIDTH="19%" VALIGN=TOP>
-+
-+<form method=get action="[$~SEARCHURL~$]">
-+<input type=hidden name=src value=4>
-+
-+ <table cellspacing=0 cellpadding=0 width='1%'>
-+ <tr><td nowrap align=center valign=middle><nobr><img width=16 height=16 src=logo.gif>&nbsp;<b><i><font color=#6F6F6F>Google Desktop Search</font></i></b><IMG id=ctl src="[CONTROL_IMAGE]" border=0 usemap="#control"></nobr></td></tr>
-+ <tr><td nowrap align=center valign=middle><nobr><input TABINDEX="1" style="font-size:10px; width:'100%';" name="q" id="q"></nobr></td></tr>
-+ <tr><td nowrap align=center valign=middle><nobr><input TABINDEX="2" style="font-size:10px" type=submit value="Local search" name=btnG> <input TABINDEX="3" style="font-size:9px" type=submit value="Web search" name=redir></nobr></td></tr>
-+<MAP name="control">
-+<area TABINDEX="4" onkeydown='if(event.keyCode==13)location.href="movesidebar?side=1"' title="Move sidebar to Top" shape="rect" coords="9,0,22,8" href="/movesidebar?side=1" onmouseover="ctl.src='control1.gif'" onmouseout="ctl.src='[CONTROL_IMAGE]'">
-+<area TABINDEX="5" onkeydown='if(event.keyCode==13)location.href="movesidebar?side=3"' title="Move sidebar to Bottom" shape="rect" coords="9,9,22,17" href="/movesidebar?side=3" onmouseover="ctl.src='control3.gif'" onmouseout="ctl.src='[CONTROL_IMAGE]'">
-+<area TABINDEX="6" onkeydown='if(event.keyCode==13)location.href="movesidebar?side=0"' title="Move sidebar to Left" shape="rect" coords="0,2,8,15" href="/movesidebar?side=0" onmouseover="ctl.src='control0.gif'" onmouseout="ctl.src='[CONTROL_IMAGE]'">
-+<area TABINDEX="7" onkeydown='if(event.keyCode==13)location.href="movesidebar?side=2"' title="Move sidebar to Right" shape="rect" coords="23,2,31,15" href="/movesidebar?side=2" onmouseover="ctl.src='control2.gif'" onmouseout="ctl.src='[CONTROL_IMAGE]'">
-+</MAP>
-+ </table>
-+</form>
-+
-+</td>
-+<TD WIDTH="27%" VALIGN=TOP>
-+
-+[HEADER_SECTION1]
-+[CONTENT_INBOX]
-+
-+</td>
-+<TD WIDTH="27%" VALIGN=top>
-+
-+[HEADER_SECTION2]
-+[CONTENT_HIST]
-+
-+</td>
-+<TD WIDTH="27%" VALIGN=top>
-+
-+[CONTENT_NEWS]
-+[CONTENT_OTHER]
-+
-+</td>
-+<TD WIDTH="1%" VALIGN=top>
-+
-+<a TABINDEX="8" onkeydown='if(event.keyCode!=16&&event.keyCode!=9)onclick()' onclick='return hide()' href='[$~HIDE1~$]' class=ch><img width=12 height=11 style='vertical-align:top;' src=/hide.gif valign=top border=0></a>
-+
-+</td>
-+</tr></table>
-+
-+<font size=-1><span style="visibility:hidden" id='test'>t</span></font>
-+
-+[SCRIPT]
-+</body></html>
-diff --git a/tools/grit/grit/testdata/sidebar_v.html b/tools/grit/grit/testdata/sidebar_v.html
-new file mode 100644
-index 0000000000..e040d8ec59
---- /dev/null
-+++ b/tools/grit/grit/testdata/sidebar_v.html
-@@ -0,0 +1,267 @@
-+<html><head>
-+<title>Google Desktop Search Sidebar</title>
-+<meta http-equiv="content-type" content="text/html; charset=utf-8">
-+<meta http-equiv="cache-control" content="no-cache">
-+<meta http-equiv="pragma" content="no-cache">
-+<meta http-equiv="expires" content="-1">
-+<style>
-+BODY,TD,P,A {FONT-FAMILY: verdana,arial,sans-serif;font-size:8pt; color:#fff}
-+a:link, a:visited, a:hover, b, q b { color: #ffffff}
-+a:active { color: #ff0000}
-+a:link,a:active,a:visited,div { text-decoration: none;font-size:8pt }
-+.g{margin-top: 1em;}
-+.gg{margin-top: .4em;}
-+.norepeat { background-repeat: no-repeat }
-+.indent{margin-top: 0px; margin-bottom: 0px;margin-left:3px;}
-+.c:link, .c:visited, .c:active { color: #959595;font-size:8pt }
-+.ch{cursor:pointer;cursor:hand}
-+.gap{margin-top: 10px; margin-bottom: 10px;}
-+.off {display:none}
-+.on {display:on}
-+.but { border-top: 1px solid #73787E;border-bottom: 1px solid #000000;border-right: 1px solid #000000;border-left: 1px solid #73787E;margin-top: 0px; margin-bottom: 0px; cursor:pointer;cursor:hand}
-+</style>
-+<script>
-+
-+function toggle(i) {
-+ var v = document.getElementById(i);
-+ var vi = document.getElementById(i+'icon');
-+ var c = (v['className'] == 'on');
-+ if (c) {
-+ v['className'] = 'off';
-+ vi.src='up.gif';
-+ }
-+ else {
-+ v['className'] = 'on';
-+ vi.src='down.gif';
-+ }
-+ (new Image()).src="[$~TOGGLE~$]?setting="+i+"&mode="+v['className']+"&rnd="+Math.random();
-+
-+ if (!c && (v['oclass'] == 'off')) {
-+ location.href = location.href;
-+ }
-+
-+ return true;
-+}
-+function hide() {
-+ // return confirm("Are you sure you want to hide the sidebar?\nYou can show it again in Google Desktop Search Preferences.");
-+ return 1;
-+}
-+</script>
-+
-+<!-- menu experiment start -->
-+
-+<style>
-+<!--
-+.menu1 {
-+cursor:default;
-+position:absolute;
-+text-align: left;
-+font-family: Arial, Helvetica, sans-serif;
-+font-size: 8pt;
-+font-color: #000000;
-+color: #000000;
-+background-color: menu;
-+visibility: hidden;
-+padding-top: 2px;
-+padding-bottom: 2px;
-+border: 1 solid;
-+border-color: #888888;
-+z-index: 100;
-+}
-+.menuitems {
-+padding-left: 5px;
-+padding-right: 5px;
-+}
-+-->
-+</style>
-+<SCRIPT LANGUAGE="JavaScript1.2">
-+<!--
-+var menustyle = "menu1";
-+
-+function showmenu() {
-+ var rightedge = document.body.clientWidth-event.clientX;
-+ var bottomedge = document.body.clientHeight-event.clientY;
-+ // if (rightedge < rcmenu.offsetWidth)
-+ // rcmenu.style.left = document.body.scrollLeft + event.clientX - rcmenu.offsetWidth;
-+ // else
-+ // rcmenu.style.left = document.body.scrollLeft + event.clientX;
-+
-+ // if (rcmenu.style.left < 0) rcmenu.style.left = 0;
-+ rcmenu.style.left = 0;
-+
-+ if (bottomedge < rcmenu.offsetHeight)
-+ rcmenu.style.top = document.body.scrollTop + event.clientY - rcmenu.offsetHeight;
-+ else
-+ rcmenu.style.top = document.body.scrollTop + event.clientY;
-+
-+ if (rcmenu.style.top < 0) rcmenu.style.top = 0;
-+
-+ rcmenu.style.visibility = "visible";
-+ // rcmenu.style.zindex = 0;
-+ // document.all('rcmenu').style.zindex = 20;
-+ document.onkeydown=ck;
-+ return false;
-+}
-+
-+function hidemenu() {
-+ rcmenu.style.visibility = "hidden";
-+}
-+
-+function ck(e){
-+ evt=document.all?window.event:e;
-+ k=document.all?window.event.keyCode:e.keyCode;
-+
-+ if(k==27 /*<Esc>*/) {
-+ hidemenu();
-+ }
-+}
-+
-+function menumouseover() {
-+ if (event.srcElement.className == "menuitems") {
-+ event.srcElement.style.backgroundColor = "highlight";
-+ event.srcElement.style.color = "white";
-+ }
-+}
-+
-+function menumouseout() {
-+ if (event.srcElement.className == "menuitems") {
-+ event.srcElement.style.backgroundColor = "";
-+ event.srcElement.style.color = "black";
-+ window.status = "";
-+ }
-+}
-+
-+function menuselect() {
-+ if (event.srcElement.className == "menuitems") {
-+ if (event.srcElement.getAttribute("target") != null)
-+ window.open(event.srcElement.url, event.srcElement.getAttribute("target"));
-+ else if (event.srcElement.url.length)
-+ window.location = event.srcElement.url;
-+ }
-+}
-+// -->
-+</script>
-+
-+<!-- menu experiment end -->
-+
-+</head>
-+
-+<body onload="TEST_WIDTH();" bottommargin=0 leftmargin=0 marginheight=0 marginwidth=0 rightmargin=0 topmargin=0 style="background-color:'#384146'; background-repeat: repeat-y; border-style:solid; border-width:0;" background="greyback.jpg" scroll=NO oncontextmenu="return false;">
-+
-+<!-- menu experiment start -->
-+
-+<div id="rcmenu" class="skin0" onMouseover="menumouseover()" onMouseout="menumouseout()" onClick="menuselect();">
-+<div class="menuitems" url="[$~SETDISP4~$]">Switch to minibar</div>
-+<div class="menuitems" url="[$~SETDISP2~$]">Switch to hoverbar</div>
-+<div class="menuitems" url="[$~HIDE1~$]">Close sidebar</div>
-+<div class="menuitems" url="">No change</div>
-+</div>
-+
-+<script language="JavaScript1.2">
-+if (document.all && window.print) {
-+ rcmenu.className = menustyle;
-+ document.oncontextmenu = showmenu;
-+ document.body.onclick = hidemenu;
-+}
-+</script>
-+
-+<!-- menu experiment end -->
-+
-+<div id="oneliner" style="visibility:hidden; position:absolute; left:0px; top:0px;"></div>
-+<script>
-+var h = document.getElementById("oneliner").offsetHeight*2;
-+document.write("<style type='text/css'>.truncme { overflow:hidden;height: " +h+"px; }</style>");
-+</script>
-+
-+<table cellpadding=0 cellspacing=0 border=0 width='100%'>
-+<form method=get action="[$~SEARCHURL~$]" id=f1>
-+<input type=hidden name=src value=5>
-+<input type=hidden name=redir value=''>
-+<tr>
-+ <td width='1%'><IMG id=ctl src="[CONTROL_IMAGE]" border=0 usemap="#control"></td>
-+ <td width='97%'><input TABINDEX="1" NAME="q" style="width:'100%'; FONT-FAMILY: verdana,arial,sans-serif;font-size:8pt"></td>
-+ <td width='1%'><table cellpadding=2 cellspacing=0><tr><td> </td><td TABINDEX="8" onkeydown='if(event.keyCode!=16&&event.keyCode!=9)onclick()' onmouseover="this.bgColor='4C535B'" onmouseout="this.bgColor='#414A4F'" class=but bgcolor=414A4F valign=top onclick="location.href='[$~SETDISP2~$]';"><img src="mini_mini.gif"></td></tr></table></td>
-+ <td width='1%'><table cellpadding=2 cellspacing=0><tr><td TABINDEX="9" onkeydown='if(event.keyCode!=16&&event.keyCode!=9)onclick()' onmouseover="this.bgColor='4C535B'" onmouseout="this.bgColor='#414A4F'" class=but bgcolor=414A4F valign=top onclick="if (hide())location.href='[$~HIDE1~$]';"><img src="mini_close.gif"></td></tr></table></td>
-+</tr>
-+<MAP name="control">
-+<area TABINDEX="4" onkeydown='if(event.keyCode==13)location.href="movesidebar?side=1"' title="Move sidebar to Top" shape="rect" coords="9,0,22,8" href="/movesidebar?side=1" onmouseover="ctl.src='control1.gif'" onmouseout="ctl.src='[CONTROL_IMAGE]'">
-+<area TABINDEX="5" onkeydown='if(event.keyCode==13)location.href="movesidebar?side=3"' title="Move sidebar to Bottom" shape="rect" coords="9,9,22,17" href="/movesidebar?side=3" onmouseover="ctl.src='control3.gif'" onmouseout="ctl.src='[CONTROL_IMAGE]'">
-+<area TABINDEX="6" onkeydown='if(event.keyCode==13)location.href="movesidebar?side=0"' title="Move sidebar to Left" shape="rect" coords="0,2,8,15" href="/movesidebar?side=0" onmouseover="ctl.src='control0.gif'" onmouseout="ctl.src='[CONTROL_IMAGE]'">
-+<area TABINDEX="7" onkeydown='if(event.keyCode==13)location.href="movesidebar?side=2"' title="Move sidebar to Right" shape="rect" coords="23,2,31,15" href="/movesidebar?side=2" onmouseover="ctl.src='control2.gif'" onmouseout="ctl.src='[CONTROL_IMAGE]'">
-+</MAP>
-+</table>
-+
-+<center>
-+<table cellpadding=2 cellspacing=3>
-+<tr>
-+ <td TABINDEX="2" onkeydown='if(event.keyCode!=16&&event.keyCode!=9)onclick()' onclick="f1.submit()" onmouseover="this.bgColor='4C535B'" onmouseout="this.bgColor='#414A4F'" class=ch nowrap bgcolor=414A4F valign=top style="border-top: 1px solid #73787E;border-bottom: 1px solid #252C30;border-right: 1px solid #252C30;border-left: 1px solid #73787E;"><img src="logo.gif" align="texttop"> <font color=ffffff>Google Desktop Search&nbsp;</td>
-+ <td TABINDEX="3" onkeydown='if(event.keyCode!=16&&event.keyCode!=9)onclick()' onclick="redir.value='google'; f1.submit(); redir.value='';" onmouseover="this.bgColor='4C535B'" onmouseout="this.bgColor='#414A4F'" class=ch bgcolor=414A4F nowrap valign=top style="border-top: 1px solid #73787E;border-bottom: 1px solid #252C30;border-right: 1px solid #252C30;border-left: 1px solid #73787E;">&nbsp;<font color=ffffff>Web&nbsp;</td>
-+</tr>
-+</form>
-+</table>
-+</center>
-+
-+<p class=gg>
-+<table width=100% cellpadding=0 cellspacing=0><tr><td bgcolor=4E555C><img width=1 height=1></td></tr></table>
-+<table class=ch cellpadding=0 cellspacing=0 style="background-color:'#424B50'; background-repeat: repeat-y;" background="section.jpg" width=100% height=18><tr onDblClick='return toggle("news");' onclick='return toggle("news");' onmouseover="this.bgColor='#465055'" onmouseout="this.bgColor=''"><td width=16 align=right><img id=newsicon src="[$~NEWS_MODE~$]" border=0></td><td valign=middle>&nbsp;<font color=ffffff>News</td></tr></table>
-+<table width=100% cellpadding=0 cellspacing=0><tr><td bgcolor=313B40><img width=1 height=1></td></tr></table>
-+
-+<span id="news" class=[$~NEWS_CLASS~$] oclass=[$~NEWS_CLASS~$]>
-+[CONTENT_NEWS]
-+<p class=g>
-+</span>
-+
-+<table width=100% cellpadding=0 cellspacing=0><tr><td bgcolor=4E555C><img width=1 height=1></td></tr></table>
-+<table class=ch cellpadding=0 cellspacing=0 style="background-color:'#424B50'; background-repeat: repeat-y;" background="section.jpg" width=100% height=18><tr onDblClick='return toggle("inbox");' onclick='return toggle("inbox");' onmouseover="this.bgColor='#465055'" onmouseout="this.bgColor=''"><td width=16 align=right><img id=inboxicon src="[$~INBOX_MODE~$]" border=0></td><td valign=middle>&nbsp;<font color=ffffff>Email</td></tr></table>
-+<table width=100% cellpadding=0 cellspacing=0><tr><td bgcolor=313B40><img width=1 height=1></td></tr></table>
-+
-+<span id=inbox class=[$~INBOX_CLASS~$] oclass=[$~INBOX_CLASS~$]>
-+[CONTENT_INBOX]
-+<p class=g>
-+</span>
-+
-+<table width=100% cellpadding=0 cellspacing=0><tr><td bgcolor=4E555C><img width=1 height=1></td></tr></table>
-+<table class=ch cellpadding=0 cellspacing=0 style="background-color:'#424B50'; background-repeat: repeat-y;" background="section.jpg" width=100% height=18><tr onDblClick='return toggle("hist");' onclick='return toggle("hist");' onmouseover="this.bgColor='#465055'" onmouseout="this.bgColor=''"><td width=16 align=right><img id=histicon src="[$~HIST_MODE~$]" border=0></td><td valign=middle>&nbsp;<font color=ffffff>Related History</td></tr></table>
-+<table width=100% cellpadding=0 cellspacing=0><tr><td bgcolor=313B40><img width=1 height=1></td></tr></table>
-+
-+<span id="hist" class=[$~HIST_CLASS~$] oclass=[$~HIST_CLASS~$]>
-+[CONTENT_HIST]
-+<p class=g>
-+</span>
-+
-+
-+<table width=100% cellpadding=0 cellspacing=0><tr><td bgcolor=4E555C><img width=1 height=1></td></tr></table>
-+<table class=ch cellpadding=0 cellspacing=0 style="background-color:'#424B50'; background-repeat: repeat-y;" background="section.jpg" width=100% height=18><tr onDblClick='return toggle("recent");' onclick='return toggle("recent");' onmouseover="this.bgColor='#465055'" onmouseout="this.bgColor=''"><td width=16 align=right><img id=recenticon src="[$~RECENT_MODE~$]" border=0></td><td valign=middle>&nbsp;<font color=ffffff>Recent</td></tr></table>
-+<table width=100% cellpadding=0 cellspacing=0><tr><td bgcolor=313B40><img width=1 height=1></td></tr></table>
-+
-+<span id="recent" class=[$~RECENT_CLASS~$] oclass=[$~RECENT_CLASS~$]>
-+[CONTENT_RECENT]
-+<p class=g>
-+</span>
-+
-+
-+<table width=100% cellpadding=0 cellspacing=0><tr><td bgcolor=4E555C><img width=1 height=1></td></tr></table>
-+<table class=ch cellpadding=0 cellspacing=0 style="background-color:'#424B50'; background-repeat: repeat-y;" background="section.jpg" width=100% height=18><tr onDblClick='return toggle("popular");' onclick='return toggle("popular");' onmouseover="this.bgColor='#465055'" onmouseout="this.bgColor=''"><td width=16 align=right><img id=popularicon src="[$~POPULAR_MODE~$]" border=0></td><td valign=middle>&nbsp;<font color=ffffff>Frequently Visited</td></tr></table>
-+<table width=100% cellpadding=0 cellspacing=0><tr><td bgcolor=313B40><img width=1 height=1></td></tr></table>
-+
-+<span id="popular" class=[$~POPULAR_CLASS~$] oclass=[$~POPULAR_CLASS~$]>
-+[CONTENT_POPULAR]
-+<p class=g>
-+</span>
-+
-+
-+<table width=100% cellpadding=0 cellspacing=0><tr><td bgcolor=4E555C><img width=1 height=1></td></tr></table>
-+<table class=ch cellpadding=0 cellspacing=0 style="background-color:'#424B50'; background-repeat: repeat-y;" background="section.jpg" width=100% height=18><tr onDblClick='return toggle("quib_debug");' onclick='return toggle("quib_debug");' onmouseover="this.bgColor='#465055'" onmouseout="this.bgColor=''"><td width=16 align=right><img id=quib_debugicon src="[$~QUIB_DEBUG_MODE~$]" border=0></td><td valign=middle>&nbsp;<font color=ffffff>Implicit Query Debug</td></tr></table>
-+<table width=100% cellpadding=0 cellspacing=0><tr><td bgcolor=313B40><img width=1 height=1></td></tr></table>
-+
-+<span id="quib_debug" class=[$~QUIB_DEBUG_CLASS~$] oclass=[$~QUIB_DEBUG_CLASS~$]>
-+[CONTENT_QUIB_DEBUG]
-+</span>
-+
-+<span style="visibility:hidden" id='test'>t</span>
-+
-+[CONTENT_OTHER]
-+
-+[SCRIPT]
-+</body>
-+</html>
-diff --git a/tools/grit/grit/testdata/simple-input.xml b/tools/grit/grit/testdata/simple-input.xml
-new file mode 100644
-index 0000000000..92827fa4b5
---- /dev/null
-+++ b/tools/grit/grit/testdata/simple-input.xml
-@@ -0,0 +1,52 @@
-+<?xml version="1.0" encoding="UTF-8"?>
-+<grit base_dir="." latest_public_release="2" current_release="3" source_lang_id="en-US">
-+ <release seq="2">
-+ <messages>
-+ <message name="IDS_OLD_MESSAGE" translateable="true">Hello earthlings!</message>
-+ </messages>
-+ </release>
-+ <release seq="3">
-+ <includes>
-+ <include name="ID_EDIT_BOX_ICON" type="icon" translateable="false" file="images/edit_box.ico" />
-+ <include name="ID_LOGO" type="gif" translateable="true" file="images/logo.gif"/>
-+ </includes>
-+ <messages>
-+ <message name="IDS_BTN_GO" desc="Button text" meaning="verb">Go!</message>
-+ <message name="IDS_GREETING" desc="Printed to greet the currently logged in user">
-+ Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, how are you doing today?
-+ </message>
-+ </messages>
-+ <structures>
-+ <structure type="menu" name="IDM_FOO" file="rc_files/menus.rc" />
-+ <structure type="dialog" name="IDD_BLAT" file="rc_files/dialogs.rc" />
-+ <structure type="tr_html" name="IDR_HTML_TEMPLATE" file="templates/homepage.html" />
-+ <structure type="dialog" name="IDD_NARROW_DIALOG" file="rc_files/dialogs.rc">
-+ <skeleton expr="lang == 'fr-FR'" variant_of_revision="3">
-+ <![CDATA[IDD_DIALOG1 DIALOGEX 0, 0, 186, 90
-+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION |
-+ WS_SYSMENU
-+CAPTION "TRANSLATEABLEPLACEHOLDER1"
-+FONT 8, "MS Shell Dlg", 400, 0, 0x1
-+BEGIN
-+ DEFPUSHBUTTON "TRANSLATEABLEPLACEHOLDER2",IDOK,129,7,50,14
-+ PUSHBUTTON "TRANSLATEABLEPLACEHOLDER3",IDCANCEL,129,24,50,14
-+ LTEXT "TRANSLATEABLEPLACEHOLDER4",IDC_STATIC,23,31,40,8
-+END]]>
-+ </skeleton>
-+ </structure>
-+ <structure type="version" name="VS_VERSION_INFO" file="rc_files/version.rc"/>
-+ </structures>
-+ </release>
-+ <translations>
-+ <file path="figs_nl_translations.xml" />
-+ <file path="cjk_translations.xml" />
-+ </translations>
-+ <outputs>
-+ <output filename="resource.h" type="rc_header" />
-+ <output filename="resource_en.rc" type="rc_all" lang="en-US" />
-+ <output filename="resource_fr.rc" type="rc_all" lang="fr-FR" />
-+ <output filename="resource_it.rc" type="rc_translateable" lang="it-IT" />
-+ <output filename="resource_zh_cn.rc" type="rc_translateable" lang="zh-CN" />
-+ <output filename="nontranslateable.rc" type="rc_nontranslateable" />
-+ </outputs>
-+</grit>
-diff --git a/tools/grit/grit/testdata/simple.html b/tools/grit/grit/testdata/simple.html
-new file mode 100644
-index 0000000000..4392d23e98
---- /dev/null
-+++ b/tools/grit/grit/testdata/simple.html
-@@ -0,0 +1,3 @@
-+<p>
-+ Hello!
-+</p>
-\ No newline at end of file
-diff --git a/tools/grit/grit/testdata/source.rc b/tools/grit/grit/testdata/source.rc
-new file mode 100644
-index 0000000000..fbc72284e9
---- /dev/null
-+++ b/tools/grit/grit/testdata/source.rc
-@@ -0,0 +1,57 @@
-+IDC_KLONKMENU MENU
-+BEGIN
-+ POPUP "&File"
-+ BEGIN
-+ MENUITEM "E&xit", IDM_EXIT
-+ MENUITEM "This be ""Klonk"" me like", ID_FILE_THISBE
-+ POPUP "gonk"
-+ BEGIN
-+ MENUITEM "Klonk && is [good]", ID_GONK_KLONKIS
-+ END
-+ END
-+ POPUP "&Help"
-+ BEGIN
-+ MENUITEM "&About ...", IDM_ABOUT
-+ END
-+END
-+
-+IDD_ABOUTBOX DIALOGEX 22, 17, 230, 75
-+STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
-+CAPTION "About"
-+FONT 8, "System", 0, 0, 0x0
-+BEGIN
-+ ICON IDI_KLONK,IDC_MYICON,14,9,20,20
-+ LTEXT "klonk Version ""yibbee"" 1.0",IDC_STATIC,49,10,119,8,
-+ SS_NOPREFIX
-+ LTEXT "Copyright (C) 2005",IDC_STATIC,49,20,119,8
-+ DEFPUSHBUTTON "OK",IDOK,195,6,30,11,WS_GROUP
-+ CONTROL "Jack ""Black"" Daniels",IDC_RADIO1,"Button",
-+ BS_AUTORADIOBUTTON,46,51,84,10
-+END
-+
-+IDD_DIFFERENT_LENGTH_IN_TRANSL DIALOGEX 22, 17, 230, 75
-+STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
-+CAPTION "Bingobobbi"
-+FONT 8, "System", 0, 0, 0x0
-+BEGIN
-+ LTEXT "Howdie dodie!",IDC_STATIC,49,10,119,8,SS_NOPREFIX
-+ LTEXT "Yo froodie!",IDC_STATIC,49,20,119,8
-+END
-+
-+STRINGTABLE
-+BEGIN
-+ IDS_SIMPLE "One"
-+ IDS_PLACEHOLDER "%s birds"
-+ IDS_PLACEHOLDERS "%d of %d"
-+ IDS_REORDERED_PLACEHOLDERS "$1 of $2"
-+ // Won't be in translations list because it has changed
-+ IDS_CHANGED "This was the old version"
-+ IDS_TWIN_1 "Hello"
-+ IDS_TWIN_2 "Hello"
-+ IDS_NOT_TRANSLATEABLE ":"
-+ IDS_LONGER_TRANSLATED "Removed document $1"
-+ // Won't appear in the list of translations because it's not in the .grd file
-+ IDS_NO_LONGER_USED "Not used"
-+ IDS_DIFFERENT_TWIN_1 "Howdie"
-+ IDS_DIFFERENT_TWIN_2 "Howdie"
-+END
-diff --git a/tools/grit/grit/testdata/special_100_percent/a.png b/tools/grit/grit/testdata/special_100_percent/a.png
-new file mode 100644
-index 0000000000000000000000000000000000000000..5d5089038ca71172e95db9e7aae1e1fa5cebd505
-GIT binary patch
-literal 159
-zcmeAS@N?(olHy`uVBq!ia0vp^oFL4>0wld=oSO}#(mY)pLnNjq|2Y3)zGGzYPN&L+
-zMSC}CcCfp=Dtxv4%6W%G#Q=|R|L;6pCCLUWO)Z<5eoL%TkDTw=s4X!^d(Qa<2khAN
-zZPy!XToBAic1Ss}vcWiD27B3&`Zj^H6CO>7R1{ToQ;=ggdEYbV=IISvfHpFCy85}S
-Ib4q9e0O9jEh5!Hn
-
-literal 0
-HcmV?d00001
-
-diff --git a/tools/grit/grit/testdata/status.html b/tools/grit/grit/testdata/status.html
-new file mode 100644
-index 0000000000..6b997b9369
---- /dev/null
-+++ b/tools/grit/grit/testdata/status.html
-@@ -0,0 +1,44 @@
-+[HEADER]
-+<table cellspacing=0 cellPadding=0 width="100%" border=0>
-+<tr bgcolor=#3399cc><td align=middle height=1><img height=1 width=1></td></tr>
-+</table>
-+<table cellspacing=0 cellPadding=1 width="100%" bgcolor=#e8f4f7 border=0>
-+<tr><td height=20><font size=+1 color=#000000>&nbsp;<b>Desktop Search Status</b></font></td></tr>
-+</table>
-+<br>
-+<center>
-+[$~MESSAGE~$]
-+<table cellspacing=0 cellPadding=6 width=500 border=0>
-+<tr>
-+ <td>&nbsp;</td>
-+ <td align=right nowrap><i><font size=-1>Number of items</font></i></td>
-+ <td align=right nowrap><i><font size=-1>Time of newest item</font></i></td>
-+</tr>
-+<tr>
-+ <td width=1% nowrap><img style="vertical-align:middle" width=16 height=16 src=favicon.ico>&nbsp; Total searchable items</td>
-+ <td align=right><b>[TOTAL_COUNT]</b></td>
-+ <td align=right><b>[TOTAL_TIME]</b></td>
-+</tr>
-+<tr>
-+ <td><font size=-1>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img style="vertical-align:middle" src=email.gif width=16 height=16>&nbsp; Emails</font></td>
-+ <td align=right><font size=-1>[EMAIL_COUNT]</font></td>
-+ <td align=right><font size=-1>[EMAIL_TIME]</font></td>
-+</tr>
-+<tr>
-+ <td><font size=-1>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img style="vertical-align:middle" src="16x16_chat.gif" width=16 height=16>&nbsp; Chats</font></td>
-+ <td align=right><font size=-1>[IM_COUNT]</font></td>
-+ <td align=right><font size=-1>[IM_TIME]</font></td>
-+</tr>
-+<tr>
-+ <td><font size=-1>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img style="vertical-align:middle" src=html.gif width=16 height=16>&nbsp; Web history</font></td>
-+ <td align=right><font size=-1>[WEB_COUNT]</font></td>
-+ <td align=right><font size=-1>[WEB_TIME]</font></td>
-+</tr>
-+<tr>
-+ <td><font size=-1>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img style="vertical-align:middle" src=file.gif width=16 height=16>&nbsp; Files</font></td>
-+ <td align=right><font size=-1>[FILE_COUNT]</font></td>
-+ <td align=right><font size=-1>[FILE_TIME]</font></td>
-+</tr>
-+</table>
-+</center>
-+[FOOTER]
-\ No newline at end of file
-diff --git a/tools/grit/grit/testdata/structure_variables.html b/tools/grit/grit/testdata/structure_variables.html
-new file mode 100644
-index 0000000000..2a15de8072
---- /dev/null
-+++ b/tools/grit/grit/testdata/structure_variables.html
-@@ -0,0 +1,4 @@
-+<h1>[GREETING]!</h1>
-+Some cool things are [THINGS].
-+Did you know that [EQUATION]?
-+<include src="[filename].html">
-diff --git a/tools/grit/grit/testdata/substitute.grd b/tools/grit/grit/testdata/substitute.grd
-new file mode 100644
-index 0000000000..95dcc56e1d
---- /dev/null
-+++ b/tools/grit/grit/testdata/substitute.grd
-@@ -0,0 +1,31 @@
-+<?xml version="1.0" encoding="UTF-8"?> <!-- -*- XML -*- -->
-+<grit
-+ base_dir="."
-+ source_lang_id="en"
-+ tc_project="GoogleDesktopWindowsClient"
-+ latest_public_release="0"
-+ current_release="1"
-+ enc_check="möl">
-+ <outputs>
-+ <output filename="resource.h" type="rc_header" />
-+ <output filename="en_generated_resources.rc" type="rc_all" lang="en" />
-+ <output filename="sv_generated_resources.rc" type="rc_all" lang="sv" />
-+ </outputs>
-+ <translations>
-+ <file path="substitute.xmb" lang="sv" />
-+ </translations>
-+ <release seq="1" allow_pseudo="false">
-+ <messages first_id="8192">
-+ <message name="IDS_COPYRIGHT_GOOGLE_LONG" sub_variable="true" desc="Gadget copyright notice. Needs to be updated every year.">
-+ Copyright 2008 Google Inc. All Rights Reserved.
-+ </message>
-+ <message name="IDS_NEWS_PANEL_COPYRIGHT">
-+ Google Desktop News gadget
-+[IDS_COPYRIGHT_GOOGLE_LONG]
-+View news that is personalized based on the articles you read.
-+
-+For example, if you read lots of sports news, you'll see more sports articles. If you read technology news less often, you'll see fewer of those articles.
-+ </message>
-+ </messages>
-+ </release>
-+</grit>
-diff --git a/tools/grit/grit/testdata/substitute.xmb b/tools/grit/grit/testdata/substitute.xmb
-new file mode 100644
-index 0000000000..e592069c8b
---- /dev/null
-+++ b/tools/grit/grit/testdata/substitute.xmb
-@@ -0,0 +1,10 @@
-+<?xml version="1.0" encoding="UTF-8"?>
-+<!DOCTYPE translationbundle SYSTEM "/home/build/nonconf/google3/i18n/translationbundle.dtd">
-+<translationbundle lang="sv">
-+<translation id="7239109800378180620">© 2008 Google Inc. Med ensamrätt.</translation>
-+<translation id="6212022020330010625">Google Desktop News gadget
-+<ph name="IDS_COPYRIGHT_GOOGLE_LONG_1"/>
-+Se nyheter som är anpassade till dig, baserat på de artiklar du läser.
-+
-+Om du t.ex. läser massor av sportnyheter kommer du att se fler sportartiklar. Om du inte läser tekniknyheter lika ofta ser du färre av dessa artiklar.</translation>
-+</translationbundle>
-diff --git a/tools/grit/grit/testdata/substitute_no_ids.grd b/tools/grit/grit/testdata/substitute_no_ids.grd
-new file mode 100644
-index 0000000000..d569d1cacd
---- /dev/null
-+++ b/tools/grit/grit/testdata/substitute_no_ids.grd
-@@ -0,0 +1,31 @@
-+<?xml version="1.0" encoding="UTF-8"?> <!-- -*- XML -*- -->
-+<grit
-+ base_dir="."
-+ source_lang_id="en"
-+ tc_project="GoogleDesktopWindowsClient"
-+ latest_public_release="0"
-+ current_release="1"
-+ enc_check="möl">
-+ <outputs>
-+ <output filename="resource.h" type="rc_header" />
-+ <output filename="en_generated_resources.rc" type="rc_all" lang="en" />
-+ <output filename="sv_generated_resources.rc" type="rc_all" lang="sv" />
-+ </outputs>
-+ <translations>
-+ <file path="substitute.xmb" lang="sv" />
-+ </translations>
-+ <release seq="1" allow_pseudo="false">
-+ <messages>
-+ <message name="IDS_COPYRIGHT_GOOGLE_LONG" sub_variable="true" desc="Gadget copyright notice. Needs to be updated every year.">
-+ Copyright 2008 Google Inc. All Rights Reserved.
-+ </message>
-+ <message name="IDS_NEWS_PANEL_COPYRIGHT">
-+ Google Desktop News gadget
-+[IDS_COPYRIGHT_GOOGLE_LONG]
-+View news that is personalized based on the articles you read.
-+
-+For example, if you read lots of sports news, you'll see more sports articles. If you read technology news less often, you'll see fewer of those articles.
-+ </message>
-+ </messages>
-+ </release>
-+</grit>
-diff --git a/tools/grit/grit/testdata/substitute_tmpl.grd b/tools/grit/grit/testdata/substitute_tmpl.grd
-new file mode 100644
-index 0000000000..be7b601707
---- /dev/null
-+++ b/tools/grit/grit/testdata/substitute_tmpl.grd
-@@ -0,0 +1,31 @@
-+<?xml version="1.0" encoding="UTF-8"?> <!-- -*- XML -*- -->
-+<grit
-+ base_dir="."
-+ source_lang_id="en"
-+ tc_project="GoogleDesktopWindowsClient"
-+ latest_public_release="0"
-+ current_release="1"
-+ enc_check="möl">
-+ <outputs>
-+ <output filename="resource.h" type="rc_header" />
-+ <output filename="en_${name}_resources.rc" type="rc_all" lang="en" />
-+ <output filename="sv_${name}_resources.rc" type="rc_all" lang="sv" />
-+ </outputs>
-+ <translations>
-+ <file path="substitute.xmb" lang="sv" />
-+ </translations>
-+ <release seq="1" allow_pseudo="false">
-+ <messages first_id="8192">
-+ <message name="IDS_COPYRIGHT_GOOGLE_LONG" sub_variable="true" desc="Gadget copyright notice. Needs to be updated every year.">
-+ Copyright 2008 Google Inc. All Rights Reserved.
-+ </message>
-+ <message name="IDS_NEWS_PANEL_COPYRIGHT">
-+ Google Desktop News gadget
-+[IDS_COPYRIGHT_GOOGLE_LONG]
-+View news that is personalized based on the articles you read.
-+
-+For example, if you read lots of sports news, you'll see more sports articles. If you read technology news less often, you'll see fewer of those articles.
-+ </message>
-+ </messages>
-+ </release>
-+</grit>
-diff --git a/tools/grit/grit/testdata/test_css.css b/tools/grit/grit/testdata/test_css.css
-new file mode 100644
-index 0000000000..55d5dd1770
---- /dev/null
-+++ b/tools/grit/grit/testdata/test_css.css
-@@ -0,0 +1 @@
-+This is a test!
-diff --git a/tools/grit/grit/testdata/test_html.html b/tools/grit/grit/testdata/test_html.html
-new file mode 100644
-index 0000000000..55d5dd1770
---- /dev/null
-+++ b/tools/grit/grit/testdata/test_html.html
-@@ -0,0 +1 @@
-+This is a test!
-diff --git a/tools/grit/grit/testdata/test_js.js b/tools/grit/grit/testdata/test_js.js
-new file mode 100644
-index 0000000000..55d5dd1770
---- /dev/null
-+++ b/tools/grit/grit/testdata/test_js.js
-@@ -0,0 +1 @@
-+This is a test!
-diff --git a/tools/grit/grit/testdata/test_svg.svg b/tools/grit/grit/testdata/test_svg.svg
-new file mode 100644
-index 0000000000..55d5dd1770
---- /dev/null
-+++ b/tools/grit/grit/testdata/test_svg.svg
-@@ -0,0 +1 @@
-+This is a test!
-diff --git a/tools/grit/grit/testdata/test_text.txt b/tools/grit/grit/testdata/test_text.txt
-new file mode 100644
-index 0000000000..55d5dd1770
---- /dev/null
-+++ b/tools/grit/grit/testdata/test_text.txt
-@@ -0,0 +1 @@
-+This is a test!
-diff --git a/tools/grit/grit/testdata/time_related.html b/tools/grit/grit/testdata/time_related.html
-new file mode 100644
-index 0000000000..ee64b1665e
---- /dev/null
-+++ b/tools/grit/grit/testdata/time_related.html
-@@ -0,0 +1,11 @@
-+[HEADER]
-+[CHROME]
-+[NAV_PRE_POST]
-+[$~MESSAGE~$]<br>
-+<table border=0 cellpadding=2 cellspacing=0 width='100%'>
-+[CONTENTS]
-+</table><br>
-+
-+[NAV_PRE_POST]
-+[FOOTER]
-+
-diff --git a/tools/grit/grit/testdata/toolbar_about.html b/tools/grit/grit/testdata/toolbar_about.html
-new file mode 100644
-index 0000000000..bb4b0eb355
---- /dev/null
-+++ b/tools/grit/grit/testdata/toolbar_about.html
-@@ -0,0 +1,138 @@
-+<html id=dlgAbout STYLE="width: 25.8em; height: 17em" [GRITDIR]>
-+<head>
-+<meta http-equiv="content-type" content="text/html; charset=utf-8">
-+<title>About Google Toolbar</title>
-+<style>
-+.button {
-+ width: 7em;
-+ height: 2.2em;
-+ color: buttontext;
-+ font-family: MS Sans Serif;
-+ font-size:8pt;
-+ cursor: hand;
-+}
-+</style>
-+
-+<script> <!--
-+ function HandleError(message, url, line) {
-+ var L_Dialog_ErrorMessage = "An error has occured in this dialog.";
-+ var L_ErrorNumber_Text = "Error: ";
-+ var str = L_Dialog_ErrorMessage + "\n\n"
-+ + L_ErrorNumber_Text + line + "\n"
-+ + message;
-+ alert (str);
-+ window.close();
-+ return true;
-+ }
-+
-+ function OnKeyPress(nCode) {
-+ if (nCode == 27) {
-+ window.close();
-+ return;
-+ }
-+ }
-+
-+ function OnLoad() {
-+ if ((null != window.dialogArguments) && (window.dialogArguments.indexOf("&") == -1) && (window.dialogArguments.indexOf("<") == -1)) {
-+ version.innerHTML = window.dialogArguments;
-+ } else {
-+ version.innerText = "Version: Unknown";
-+ }
-+ }
-+
-+ window.onerror = HandleError;
-+ // -->
-+</script>
-+
-+</head>
-+
-+
-+<body bgcolor="#FFFFFF" onload="OnLoad()" onkeydown="OnKeyPress(event.keyCode)" onkeypress="OnKeyPress(event.keyCode)" scroll=no>
-+
-+<table border=0>
-+
-+ <tr height=5>
-+ <td width=5></td>
-+ <td></td>
-+ <td></td>
-+ <td></td>
-+ <td width=5></td>
-+ </tr>
-+
-+ <tr>
-+ <td></td>
-+ <td colspan=3>
-+
-+
-+<table border="0" cellpadding="0" cellspacing="0" valign="top">
-+ <tr>
-+ <td valign="top" height="47" width="155">
-+ <div align="center"><img src="title_toolbar.gif" width="275" height="59" alt="Google Toolbar"></div>
-+ </td>
-+ <td valign="middle" height="47" width="713">
-+ <hr size=1 color=25479D></td></tr>
-+</table>
-+
-+
-+ </td>
-+ <!--
-+ <TD colspan=2>
-+ <span style="COLOR: black; FONT: 18pt Tahoma, MS Shell Dlg"><b>
-+ Google Toolbar&trade;</b>
-+ </span>
-+ </TD>
-+ -->
-+ <td valign="middle">
-+ </td>
-+ </tr>
-+
-+ <tr>
-+ <td></td>
-+ <td align=center><img src="googly.gif"></td>
-+ <td colspan=2 align=left>
-+ <span style="WIDTH: 25em; height:6em COLOR: black; FONT: 8pt Tahoma, MS Shell Dlg">
-+ <span id=version></span><br>
-+ </span>
-+ </td>
-+ <td></td>
-+ </tr>
-+
-+ <tr height=50>
-+ <td></td>
-+ <td></td>
-+ <td colspan=2 align=left>
-+ <span style="WIDTH: 25em; height:6em COLOR: black; FONT: 8pt Tahoma, MS Shell Dlg">
-+ <!--$/translate-->
-+ <i>De parvis grandis acervus erit</i>
-+ <!--$translate-->
-+ </span>
-+ </td>
-+ <td></td>
-+ </tr>
-+
-+ <tr height=40>
-+ <td></td>
-+ <td></td>
-+ <td></td>
-+ <td></td>
-+ <td></td>
-+ </tr>
-+
-+ <tr>
-+ <td></td>
-+ <td width=80></td>
-+ <td>
-+ <!--$/translate-->
-+ <span style="WIDTH: 20em; COLOR: black; FONT: 8pt Tahoma, MS Shell Dlg" id="copyright">&copy; 2006 Google</span>
-+ <!--$translate-->
-+ </td>
-+ <td id=ok-button align=right><button tabindex=1 type=submit align=right id="okButton" class=button onClick="window.close();" >OK</button>
-+ </td>
-+ <td></td>
-+ </tr>
-+
-+</table>
-+</span>
-+
-+</body>
-+</html>
-diff --git a/tools/grit/grit/testdata/tools/grit/resource_ids b/tools/grit/grit/testdata/tools/grit/resource_ids
-new file mode 100644
-index 0000000000..8a2b608df1
---- /dev/null
-+++ b/tools/grit/grit/testdata/tools/grit/resource_ids
-@@ -0,0 +1,176 @@
-+# Copyright (c) 2011 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+#
-+# This file is used to assign starting resource ids for resources and strings
-+# used by Chromium. This is done to ensure that resource ids are unique
-+# across all the grd files. If you are adding a new grd file, please add
-+# a new entry to this file.
-+#
-+# The first entry in the file, SRCDIR, is special: It is a relative path from
-+# this file to the base of your checkout.
-+#
-+# http://msdn.microsoft.com/en-us/library/t2zechd4(VS.71).aspx says that the
-+# range for IDR_ is 1 to 28,671 and the range for IDS_ is 1 to 32,767 and
-+# common convention starts practical use of IDs at 100 or 101.
-+{
-+ "SRCDIR": "../..",
-+
-+ "chrome/browser/browser_resources.grd": {
-+ "includes": [500],
-+ },
-+ "chrome/browser/resources/component_extension_resources.grd": {
-+ "includes": [1000],
-+ },
-+ "chrome/browser/resources/net_internals_resources.grd": {
-+ "includes": [1500],
-+ },
-+ "chrome/browser/resources/shared_resources.grd": {
-+ "includes": [2000],
-+ },
-+ "chrome/common/common_resources.grd": {
-+ "includes": [2500],
-+ },
-+ "chrome/default_plugin/default_plugin_resources.grd": {
-+ "includes": [3000],
-+ },
-+ "chrome/renderer/renderer_resources.grd": {
-+ "includes": [3500],
-+ },
-+ "net/base/net_resources.grd": {
-+ "includes": [4000],
-+ },
-+ "webkit/glue/webkit_resources.grd": {
-+ "includes": [4500],
-+ },
-+ "webkit/tools/test_shell/test_shell_resources.grd": {
-+ "includes": [5000],
-+ },
-+ "ui/resources/ui_resources.grd": {
-+ "includes": [5500],
-+ },
-+ "chrome/app/theme/theme_resources.grd": {
-+ "includes": [6000],
-+ },
-+ "chrome_frame/resources/chrome_frame_resources.grd": {
-+ "includes": [6500],
-+ },
-+ # WebKit.grd can be in two different places depending on whether we are
-+ # in a chromium checkout or a webkit-only checkout.
-+ "third_party/WebKit/Source/WebKit/chromium/WebKit.grd": {
-+ "includes": [7000],
-+ },
-+ "WebKit.grd": {
-+ "includes": [7000],
-+ },
-+
-+ "ui/base/strings/app_locale_settings.grd": {
-+ "META": {"join": 2},
-+ "messages": [7500],
-+ },
-+ "chrome/app/resources/locale_settings.grd": {
-+ "includes": [8000],
-+ "messages": [8500],
-+ },
-+ # These each start with the same resource id because we only use one
-+ # file for each build (cros, linux, mac, or win).
-+ "chrome/app/resources/locale_settings_cros.grd": {
-+ "messages": [9000],
-+ },
-+ "chrome/app/resources/locale_settings_linux.grd": {
-+ "messages": [9000],
-+ },
-+ "chrome/app/resources/locale_settings_mac.grd": {
-+ "messages": [9000],
-+ },
-+ "chrome/app/resources/locale_settings_win.grd": {
-+ "messages": [9000],
-+ },
-+
-+ "ui/base/strings/ui_strings.grd": {
-+ "META": {"join": 4},
-+ "messages": [9500],
-+ },
-+ # Chromium strings and Google Chrome strings must start at the same id.
-+ # We only use one file depending on whether we're building Chromium or
-+ # Google Chrome.
-+ "chrome/app/chromium_strings.grd": {
-+ "messages": [10000],
-+ },
-+ "chrome/app/google_chrome_strings.grd": {
-+ "messages": [10000],
-+ },
-+ # Leave lots of space for generated_resources since it has most of our
-+ # strings.
-+ "chrome/app/generated_resources.grd": {
-+ "META": {"join": 2},
-+ "structures": [10500],
-+ "messages": [11000],
-+ },
-+ # The chrome frame dialogs are also in generated_resources.grd so they
-+ # get included by the translation console. We make sure that the ids
-+ # for structures here are the same as for generated_resources.grd.
-+ "chrome_frame/resources/chrome_frame_dialogs.grd": {
-+ "structures": [10500],
-+ "includes": [10750],
-+ },
-+ "webkit/glue/inspector_strings.grd": {
-+ "messages": [16000],
-+ },
-+ "webkit/glue/webkit_strings.grd": {
-+ "messages": [16500],
-+ },
-+
-+ "chrome_frame/resources/chrome_frame_resources.grd": {
-+ "includes": [17500],
-+ "structures": [18000],
-+ },
-+
-+ "ui/gfx/gfx_resources.grd": {
-+ "includes": [18500],
-+ },
-+
-+ "chrome/app/policy/policy_templates.grd": {
-+ "structures": [19000],
-+ "messages": [19010],
-+ },
-+
-+ "chrome/browser/autofill/autofill_resources.grd": {
-+ "messages": [19500],
-+ },
-+ "chrome/browser/resources/sync_internals_resources.grd": {
-+ "includes": [20000],
-+ },
-+ # This file is generated during the build.
-+ "<(SHARED_INTERMEDIATE_DIR)/devtools/devtools_resources.grd": {
-+ "includes": [20500],
-+ },
-+ # All standard and large theme resources should have the same IDs.
-+ "chrome/app/theme/theme_resources_standard.grd": {
-+ "includes": [21000],
-+ },
-+ "chrome/app/theme/theme_resources_large.grd": {
-+ "includes": [21000],
-+ },
-+ # This file is generated during the build.
-+ "chrome/browser/debugger/frontend/devtools_frontend_resources.grd": {
-+ "META": {"join": 2},
-+ "includes": [21500],
-+ },
-+ "cloud_print/virtual_driver/win/install/virtual_driver_setup_resources.grd": {
-+ "messages": [22500],
-+ },
-+ "chrome/browser/resources/quota_internals_resources.grd": {
-+ "includes": [23000],
-+ },
-+ "chrome/browser/resources/workers_resources.grd": {
-+ "includes": [23500],
-+ },
-+ # All standard and large theme resources should have the same IDs.
-+ "ui/resources/ui_resources_standard.grd": {
-+ "includes": [24000],
-+ },
-+ "ui/resources/ui_resources_large.grd": {
-+ "includes": [24000],
-+ },
-+}
-diff --git a/tools/grit/grit/testdata/transl.rc b/tools/grit/grit/testdata/transl.rc
-new file mode 100644
-index 0000000000..2f2595db3f
---- /dev/null
-+++ b/tools/grit/grit/testdata/transl.rc
-@@ -0,0 +1,56 @@
-+IDC_KLONKMENU MENU
-+BEGIN
-+ POPUP "&Skra"
-+ BEGIN
-+ MENUITEM "&Haetta", IDM_EXIT
-+ MENUITEM "Thetta er ""Klonk"" sem eg fyla", ID_FILE_THISBE
-+ POPUP "gonkurinn"
-+ BEGIN
-+ MENUITEM "Klonk && er [good]", ID_GONK_KLONKIS
-+ END
-+ END
-+ POPUP "&Hjalp"
-+ BEGIN
-+ MENUITEM "&Um...", IDM_ABOUT
-+ END
-+END
-+
-+IDD_ABOUTBOX DIALOGEX 22, 17, 230, 75
-+STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
-+CAPTION "Um Klonk"
-+FONT 8, "System", 0, 0, 0x0
-+BEGIN
-+ ICON IDI_KLONK,IDC_MYICON,14,9,20,20
-+ LTEXT "klonk utgafa ""jibbi"" 1.0",IDC_STATIC,49,10,119,8,
-+ SS_NOPREFIX
-+ LTEXT "Hofundarrettur (C) 2005",IDC_STATIC,49,20,119,8
-+ DEFPUSHBUTTON "I lagi",IDOK,195,6,30,11,WS_GROUP
-+ CONTROL "Jack ""Black"" Daniels",IDC_RADIO1,"Button",
-+ BS_AUTORADIOBUTTON,46,51,84,10
-+END
-+
-+IDD_DIFFERENT_LENGTH_IN_TRANSL DIALOGEX 22, 17, 230, 75
-+STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
-+CAPTION "Bingobobbi"
-+FONT 8, "System", 0, 0, 0x0
-+BEGIN
-+ LTEXT "Howdie dodie!",IDC_STATIC,49,10,119,8,SS_NOPREFIX
-+END
-+
-+STRINGTABLE
-+BEGIN
-+ IDS_SIMPLE "Ein"
-+ IDS_PLACEHOLDER "%s Vogeln"
-+ IDS_PLACEHOLDERS "%d von %d"
-+ // Shouldn't be part of translations list because the translation is
-+ // reordered so placeholder fixup fails
-+ IDS_REORDERED_PLACEHOLDERS "$2 auf $1"
-+ IDS_CHANGED "Dass war die alte Version"
-+ IDS_TWIN_1 "Hallo"
-+ IDS_TWIN_2 "Hallo"
-+ IDS_NOT_TRANSLATEABLE ":"
-+ IDS_LONGER_TRANSLATED "Dokument $1 ist entfernt worden"
-+ IDS_NO_LONGER_USED "Nicht verwendet"
-+ IDS_DIFFERENT_TWIN_1 "Howdie"
-+ IDS_DIFFERENT_TWIN_2 "Hallo sagt man"
-+END
-diff --git a/tools/grit/grit/testdata/versions.html b/tools/grit/grit/testdata/versions.html
-new file mode 100644
-index 0000000000..d1f40d8d72
---- /dev/null
-+++ b/tools/grit/grit/testdata/versions.html
-@@ -0,0 +1,7 @@
-+[HEADER]
-+
-+[TOP_CHROME]
-+[CONTENTS]
-+
-+[NEXT_PREV]
-+[FOOTER]
-diff --git a/tools/grit/grit/testdata/whitelist.txt b/tools/grit/grit/testdata/whitelist.txt
-new file mode 100644
-index 0000000000..5b3aca40b5
---- /dev/null
-+++ b/tools/grit/grit/testdata/whitelist.txt
-@@ -0,0 +1,4 @@
-+IDS_MESSAGE_WHITELISTED
-+IDR_STRUCTURE_WHITELISTED
-+IDR_STRUCTURE_IN_TRUE_IF_WHITELISTED
-+IDR_INCLUDE_WHITELISTED
-diff --git a/tools/grit/grit/testdata/whitelist_resources.grd b/tools/grit/grit/testdata/whitelist_resources.grd
-new file mode 100644
-index 0000000000..9925688ff5
---- /dev/null
-+++ b/tools/grit/grit/testdata/whitelist_resources.grd
-@@ -0,0 +1,54 @@
-+<?xml version="1.0" encoding="UTF-8"?>
-+<grit latest_public_release="0"
-+ current_release="1"
-+ output_all_resource_defines="false">
-+ <outputs>
-+ <output filename="whitelist_test_resources.h" type="rc_header">
-+ <emit emit_type='prepend'></emit>
-+ </output>
-+ <output filename="whitelist_test_resources_map.cc"
-+ type="resource_file_map_source" />
-+ <output filename="whitelist_test_resources_map.h"
-+ type="resource_map_header" />
-+ <output filename="whitelist_test_resources.pak" type="data_package" />
-+ </outputs>
-+ <translations>
-+ <file path="substitute.xmb" lang="sv" />
-+ </translations>
-+ <release seq="1">
-+ <structures>
-+ <structure name="IDR_STRUCTURE_WHITELISTED" file="browser.html"
-+ type="chrome_html" >
-+ </structure>
-+ <structure name="IDR_STRUCTURE_NOT_WHITELISTED" file="deleted.html"
-+ type="chrome_html" >
-+ </structure>
-+ <if expr="True">
-+ <structure name="IDR_STRUCTURE_IN_TRUE_IF_WHITELISTED"
-+ file="details.html"
-+ type="chrome_html" >
-+ </structure>
-+ <structure name="IDR_STRUCTURE_IN_TRUE_IF_NOT_WHITELISTED"
-+ file="error.html"
-+ type="chrome_html" >
-+ </structure>
-+ </if>
-+ <if expr="False">
-+ <structure name="IDR_STRUCTURE_IN_FALSE_IF_WHITELISTED"
-+ file="status.html"
-+ type="chrome_html" >
-+ </structure>
-+ <structure name="IDR_STRUCTURE_IN_FALSE_IF_NOT_WHITELISTED"
-+ file="simple.html"
-+ type="chrome_html" >
-+ </structure>
-+ </if>
-+ </structures>
-+ <includes>
-+ <include name="IDR_INCLUDE_WHITELISTED" file="klonk.ico"
-+ type="BINDATA" />
-+ <include name="IDR_INCLUDE_NOT_WHITELISTED" file="klonk.rc"
-+ type="BINDATA" />
-+ </includes>
-+ </release>
-+</grit>
-diff --git a/tools/grit/grit/testdata/whitelist_strings.grd b/tools/grit/grit/testdata/whitelist_strings.grd
-new file mode 100644
-index 0000000000..df80f5fd32
---- /dev/null
-+++ b/tools/grit/grit/testdata/whitelist_strings.grd
-@@ -0,0 +1,23 @@
-+<?xml version="1.0" encoding="UTF-8"?>
-+<grit latest_public_release="0"
-+ current_release="1"
-+ output_all_resource_defines="false">
-+ <outputs >
-+ <output filename="whitelist_test_resources.h" type="rc_header">
-+ <emit emit_type='prepend'></emit>
-+ </output>
-+ <output filename="en_whitelist_test_strings.rc" type="rc_all" lang="en" />
-+ </outputs>
-+ <release seq="1">
-+ <messages>
-+ <message name="IDS_MESSAGE_WHITELISTED"
-+ desc="A message in the whiltelist file.">
-+ Whitelisted.
-+ </message>
-+ <message name="IDS_MESSAGE_NOT_WHITELISTED"
-+ desc="A message that isn't in the whiltelist file.">
-+ Not whitelisted.
-+ </message>
-+ </messages>
-+ </release>
-+</grit>
-diff --git a/tools/grit/grit/tool/__init__.py b/tools/grit/grit/tool/__init__.py
-new file mode 100644
-index 0000000000..cc455b36e7
---- /dev/null
-+++ b/tools/grit/grit/tool/__init__.py
-@@ -0,0 +1,8 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Package grit.tool
-+'''
-+
-+pass
-diff --git a/tools/grit/grit/tool/android2grd.py b/tools/grit/grit/tool/android2grd.py
-new file mode 100644
-index 0000000000..005297bafe
---- /dev/null
-+++ b/tools/grit/grit/tool/android2grd.py
-@@ -0,0 +1,484 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+"""The 'grit android2grd' tool."""
-+
-+from __future__ import print_function
-+
-+import getopt
-+import os.path
-+import sys
-+from xml.dom import Node
-+import xml.dom.minidom
-+
-+import six
-+from six import StringIO
-+
-+import grit.node.empty
-+from grit.node import node_io
-+from grit.node import message
-+
-+from grit.tool import interface
-+
-+from grit import grd_reader
-+from grit import lazy_re
-+from grit import tclib
-+
-+
-+# The name of a string in strings.xml
-+_STRING_NAME = lazy_re.compile(r'[a-z0-9_]+\Z')
-+
-+# A string's character limit in strings.xml
-+_CHAR_LIMIT = lazy_re.compile(r'\[CHAR-LIMIT=(\d+)\]')
-+
-+# Finds String.Format() style format specifiers such as "%-5.2f".
-+_FORMAT_SPECIFIER = lazy_re.compile(
-+ r'%'
-+ r'([1-9][0-9]*\$|<)?' # argument_index
-+ r'([-#+ 0,(]*)' # flags
-+ r'([0-9]+)?' # width
-+ r'(\.[0-9]+)?' # precision
-+ r'([bBhHsScCdoxXeEfgGaAtT%n])') # conversion
-+
-+
-+class Android2Grd(interface.Tool):
-+ """Tool for converting Android string.xml files into chrome Grd files.
-+
-+Usage: grit [global options] android2grd [OPTIONS] STRINGS_XML
-+
-+The Android2Grd tool will convert an Android strings.xml file (whose path is
-+specified by STRINGS_XML) and create a chrome style grd file containing the
-+relevant information.
-+
-+Because grd documents are much richer than strings.xml documents we supplement
-+the information required by grds using OPTIONS with sensible defaults.
-+
-+OPTIONS may be any of the following:
-+
-+ --name FILENAME Specify the base FILENAME. This should be without
-+ any file type suffix. By default
-+ "chrome_android_strings" will be used.
-+
-+ --languages LANGUAGES Comma separated list of ISO language codes (e.g.
-+ en-US, en-GB, ru, zh-CN). These codes will be used
-+ to determine the names of resource and translations
-+ files that will be declared by the output grd file.
-+
-+ --grd-dir GRD_DIR Specify where the resultant grd file
-+ (FILENAME.grd) should be output. By default this
-+ will be the present working directory.
-+
-+ --header-dir HEADER_DIR Specify the location of the directory where grit
-+ generated C++ headers (whose name will be
-+ FILENAME.h) will be placed. Use an empty string to
-+ disable rc generation. Default: empty.
-+
-+ --rc-dir RC_DIR Specify the directory where resource files will
-+ be located relative to grit build's output
-+ directory. Use an empty string to disable rc
-+ generation. Default: empty.
-+
-+ --xml-dir XML_DIR Specify where to place localized strings.xml files
-+ relative to grit build's output directory. For each
-+ language xx a values-xx/strings.xml file will be
-+ generated. Use an empty string to disable
-+ strings.xml generation. Default: '.'.
-+
-+ --xtb-dir XTB_DIR Specify where the xtb files containing translations
-+ will be located relative to the grd file. Default:
-+ '.'.
-+"""
-+
-+ _NAME_FLAG = 'name'
-+ _LANGUAGES_FLAG = 'languages'
-+ _GRD_DIR_FLAG = 'grd-dir'
-+ _RC_DIR_FLAG = 'rc-dir'
-+ _HEADER_DIR_FLAG = 'header-dir'
-+ _XTB_DIR_FLAG = 'xtb-dir'
-+ _XML_DIR_FLAG = 'xml-dir'
-+
-+ def __init__(self):
-+ self.name = 'chrome_android_strings'
-+ self.languages = []
-+ self.grd_dir = '.'
-+ self.rc_dir = None
-+ self.xtb_dir = '.'
-+ self.xml_res_dir = '.'
-+ self.header_dir = None
-+
-+ def ShortDescription(self):
-+ """Returns a short description of the Android2Grd tool.
-+
-+ Overridden from grit.interface.Tool
-+
-+ Returns:
-+ A string containing a short description of the android2grd tool.
-+ """
-+ return 'Converts Android string.xml files into Chrome grd files.'
-+
-+ def ParseOptions(self, args):
-+ """Set this objects and return all non-option arguments."""
-+ flags = [
-+ Android2Grd._NAME_FLAG,
-+ Android2Grd._LANGUAGES_FLAG,
-+ Android2Grd._GRD_DIR_FLAG,
-+ Android2Grd._RC_DIR_FLAG,
-+ Android2Grd._HEADER_DIR_FLAG,
-+ Android2Grd._XTB_DIR_FLAG,
-+ Android2Grd._XML_DIR_FLAG, ]
-+ (opts, args) = getopt.getopt(
-+ args, None, ['%s=' % o for o in flags] + ['help'])
-+
-+ for key, val in opts:
-+ # Get rid of the preceding hypens.
-+ k = key[2:]
-+ if k == Android2Grd._NAME_FLAG:
-+ self.name = val
-+ elif k == Android2Grd._LANGUAGES_FLAG:
-+ self.languages = val.split(',')
-+ elif k == Android2Grd._GRD_DIR_FLAG:
-+ self.grd_dir = val
-+ elif k == Android2Grd._RC_DIR_FLAG:
-+ self.rc_dir = val
-+ elif k == Android2Grd._HEADER_DIR_FLAG:
-+ self.header_dir = val
-+ elif k == Android2Grd._XTB_DIR_FLAG:
-+ self.xtb_dir = val
-+ elif k == Android2Grd._XML_DIR_FLAG:
-+ self.xml_res_dir = val
-+ elif k == 'help':
-+ self.ShowUsage()
-+ sys.exit(0)
-+ return args
-+
-+ def Run(self, opts, args):
-+ """Runs the Android2Grd tool.
-+
-+ Inherited from grit.interface.Tool.
-+
-+ Args:
-+ opts: List of string arguments that should be parsed.
-+ args: String containing the path of the strings.xml file to be converted.
-+ """
-+ args = self.ParseOptions(args)
-+ if len(args) != 1:
-+ print('Tool requires one argument, the path to the Android '
-+ 'strings.xml resource file to be converted.')
-+ return 2
-+ self.SetOptions(opts)
-+
-+ android_path = args[0]
-+
-+ # Read and parse the Android strings.xml file.
-+ with open(android_path) as android_file:
-+ android_dom = xml.dom.minidom.parse(android_file)
-+
-+ # Do the hard work -- convert the Android dom to grd file contents.
-+ grd_dom = self.AndroidDomToGrdDom(android_dom)
-+ grd_string = six.text_type(grd_dom)
-+
-+ # Write the grd string to a file in grd_dir.
-+ grd_filename = self.name + '.grd'
-+ grd_path = os.path.join(self.grd_dir, grd_filename)
-+ with open(grd_path, 'w') as grd_file:
-+ grd_file.write(grd_string)
-+
-+ def AndroidDomToGrdDom(self, android_dom):
-+ """Converts a strings.xml DOM into a DOM representing the contents of
-+ a grd file.
-+
-+ Args:
-+ android_dom: A xml.dom.Document containing the contents of the Android
-+ string.xml document.
-+ Returns:
-+ The DOM for the grd xml document produced by converting the Android DOM.
-+ """
-+
-+ # Start with a basic skeleton for the .grd file.
-+ root = grd_reader.Parse(StringIO(
-+ '''<?xml version="1.0" encoding="UTF-8"?>
-+ <grit base_dir="." latest_public_release="0"
-+ current_release="1" source_lang_id="en">
-+ <outputs />
-+ <translations />
-+ <release allow_pseudo="false" seq="1">
-+ <messages fallback_to_english="true" />
-+ </release>
-+ </grit>'''), dir='.')
-+ outputs = root.children[0]
-+ translations = root.children[1]
-+ messages = root.children[2].children[0]
-+ assert (isinstance(messages, grit.node.empty.MessagesNode) and
-+ isinstance(translations, grit.node.empty.TranslationsNode) and
-+ isinstance(outputs, grit.node.empty.OutputsNode))
-+
-+ if self.header_dir:
-+ cpp_header = self.__CreateCppHeaderOutputNode(outputs, self.header_dir)
-+ for lang in self.languages:
-+ # Create an output element for each language.
-+ if self.rc_dir:
-+ self.__CreateRcOutputNode(outputs, lang, self.rc_dir)
-+ if self.xml_res_dir:
-+ self.__CreateAndroidXmlOutputNode(outputs, lang, self.xml_res_dir)
-+ if lang != 'en':
-+ self.__CreateFileNode(translations, lang)
-+ # Convert all the strings.xml strings into grd messages.
-+ self.__CreateMessageNodes(messages, android_dom.documentElement)
-+
-+ return root
-+
-+ def __CreateMessageNodes(self, messages, resources):
-+ """Creates the <message> elements and adds them as children of <messages>.
-+
-+ Args:
-+ messages: the <messages> element in the strings.xml dom.
-+ resources: the <resources> element in the grd dom.
-+ """
-+ # <string> elements contain the definition of the resource.
-+ # The description of a <string> element is contained within the comment
-+ # node element immediately preceeding the string element in question.
-+ description = ''
-+ for child in resources.childNodes:
-+ if child.nodeType == Node.COMMENT_NODE:
-+ # Remove leading/trailing whitespace; collapse consecutive whitespaces.
-+ description = ' '.join(child.data.split())
-+ elif child.nodeType == Node.ELEMENT_NODE:
-+ if child.tagName != 'string':
-+ print('Warning: ignoring unknown tag <%s>' % child.tagName)
-+ else:
-+ translatable = self.IsTranslatable(child)
-+ raw_name = child.getAttribute('name')
-+ if not _STRING_NAME.match(raw_name):
-+ print('Error: illegal string name: %s' % raw_name)
-+ grd_name = 'IDS_' + raw_name.upper()
-+ # Transform the <string> node contents into a tclib.Message, taking
-+ # care to handle whitespace transformations and escaped characters,
-+ # and coverting <xliff:g> placeholders into <ph> placeholders.
-+ msg = self.CreateTclibMessage(child)
-+ msg_node = self.__CreateMessageNode(messages, grd_name, description,
-+ msg, translatable)
-+ messages.AddChild(msg_node)
-+ # Reset the description once a message has been parsed.
-+ description = ''
-+
-+ def CreateTclibMessage(self, android_string):
-+ """Transforms a <string/> element from strings.xml into a tclib.Message.
-+
-+ Interprets whitespace, quotes, and escaped characters in the android_string
-+ according to Android's formatting and styling rules for strings. Also
-+ converts <xliff:g> placeholders into <ph> placeholders, e.g.:
-+
-+ <xliff:g id="website" example="google.com">%s</xliff:g>
-+ becomes
-+ <ph name="website"><ex>google.com</ex>%s</ph>
-+
-+ Returns:
-+ The tclib.Message.
-+ """
-+ msg = tclib.Message()
-+ current_text = '' # Accumulated text that hasn't yet been added to msg.
-+ nodes = android_string.childNodes
-+
-+ for i, node in enumerate(nodes):
-+ # Handle text nodes.
-+ if node.nodeType in (Node.TEXT_NODE, Node.CDATA_SECTION_NODE):
-+ current_text += node.data
-+
-+ # Handle <xliff:g> and other tags.
-+ elif node.nodeType == Node.ELEMENT_NODE:
-+ if node.tagName == 'xliff:g':
-+ assert node.hasAttribute('id'), 'missing id: ' + node.data()
-+ placeholder_id = node.getAttribute('id')
-+ placeholder_text = self.__FormatPlaceholderText(node)
-+ placeholder_example = node.getAttribute('example')
-+ if not placeholder_example:
-+ print('Info: placeholder does not contain an example: %s' %
-+ node.toxml())
-+ placeholder_example = placeholder_id.upper()
-+ msg.AppendPlaceholder(tclib.Placeholder(placeholder_id,
-+ placeholder_text, placeholder_example))
-+ else:
-+ print('Warning: removing tag <%s> which must be inside a '
-+ 'placeholder: %s' % (node.tagName, node.toxml()))
-+ msg.AppendText(self.__FormatPlaceholderText(node))
-+
-+ # Handle other nodes.
-+ elif node.nodeType != Node.COMMENT_NODE:
-+ assert False, 'Unknown node type: %s' % node.nodeType
-+
-+ is_last_node = (i == len(nodes) - 1)
-+ if (current_text and
-+ (is_last_node or nodes[i + 1].nodeType == Node.ELEMENT_NODE)):
-+ # For messages containing just text and comments (no xml tags) Android
-+ # strips leading and trailing whitespace. We mimic that behavior.
-+ if not msg.GetContent() and is_last_node:
-+ current_text = current_text.strip()
-+ msg.AppendText(self.__FormatAndroidString(current_text))
-+ current_text = ''
-+
-+ return msg
-+
-+ def __FormatAndroidString(self, android_string, inside_placeholder=False):
-+ r"""Returns android_string formatted for a .grd file.
-+
-+ * Collapses consecutive whitespaces, except when inside double-quotes.
-+ * Replaces \\, \n, \t, \", \' with \, newline, tab, ", '.
-+ """
-+ backslash_map = {'\\' : '\\', 'n' : '\n', 't' : '\t', '"' : '"', "'" : "'"}
-+ is_quoted_section = False # True when we're inside double quotes.
-+ is_backslash_sequence = False # True after seeing an unescaped backslash.
-+ prev_char = ''
-+ output = []
-+ for c in android_string:
-+ if is_backslash_sequence:
-+ # Unescape \\, \n, \t, \", and \'.
-+ assert c in backslash_map, 'Illegal escape sequence: \\%s' % c
-+ output.append(backslash_map[c])
-+ is_backslash_sequence = False
-+ elif c == '\\':
-+ is_backslash_sequence = True
-+ elif c.isspace() and not is_quoted_section:
-+ # Turn whitespace into ' ' and collapse consecutive whitespaces.
-+ if not prev_char.isspace():
-+ output.append(' ')
-+ elif c == '"':
-+ is_quoted_section = not is_quoted_section
-+ else:
-+ output.append(c)
-+ prev_char = c
-+ output = ''.join(output)
-+
-+ if is_quoted_section:
-+ print('Warning: unbalanced quotes in string: %s' % android_string)
-+
-+ if is_backslash_sequence:
-+ print('Warning: trailing backslash in string: %s' % android_string)
-+
-+ # Check for format specifiers outside of placeholder tags.
-+ if not inside_placeholder:
-+ format_specifier = _FORMAT_SPECIFIER.search(output)
-+ if format_specifier:
-+ print('Warning: format specifiers are not inside a placeholder '
-+ '<xliff:g/> tag: %s' % output)
-+
-+ return output
-+
-+ def __FormatPlaceholderText(self, placeholder_node):
-+ """Returns the text inside of an <xliff:g> placeholder node."""
-+ text = []
-+ for childNode in placeholder_node.childNodes:
-+ if childNode.nodeType in (Node.TEXT_NODE, Node.CDATA_SECTION_NODE):
-+ text.append(childNode.data)
-+ elif childNode.nodeType != Node.COMMENT_NODE:
-+ assert False, 'Unknown node type in ' + placeholder_node.toxml()
-+ return self.__FormatAndroidString(''.join(text), inside_placeholder=True)
-+
-+ def __CreateMessageNode(self, messages_node, grd_name, description, msg,
-+ translatable):
-+ """Creates and initializes a <message> element.
-+
-+ Message elements correspond to Android <string> elements in that they
-+ declare a string resource along with a programmatic id.
-+ """
-+ if not description:
-+ print('Warning: no description for %s' % grd_name)
-+ # Check that we actually fit within the character limit we've specified.
-+ match = _CHAR_LIMIT.search(description)
-+ if match:
-+ char_limit = int(match.group(1))
-+ msg_content = msg.GetRealContent()
-+ if len(msg_content) > char_limit:
-+ print('Warning: char-limit for %s is %d, but length is %d: %s' %
-+ (grd_name, char_limit, len(msg_content), msg_content))
-+ return message.MessageNode.Construct(parent=messages_node,
-+ name=grd_name,
-+ message=msg,
-+ desc=description,
-+ translateable=translatable)
-+
-+ def __CreateFileNode(self, translations_node, lang):
-+ """Creates and initializes the <file> elements.
-+
-+ File elements provide information on the location of translation files
-+ (xtbs)
-+ """
-+ xtb_file = os.path.normpath(os.path.join(
-+ self.xtb_dir, '%s_%s.xtb' % (self.name, lang)))
-+ fnode = node_io.FileNode()
-+ fnode.StartParsing(u'file', translations_node)
-+ fnode.HandleAttribute('path', xtb_file)
-+ fnode.HandleAttribute('lang', lang)
-+ fnode.EndParsing()
-+ translations_node.AddChild(fnode)
-+ return fnode
-+
-+ def __CreateCppHeaderOutputNode(self, outputs_node, header_dir):
-+ """Creates the <output> element corresponding to the generated c header."""
-+ header_file_name = os.path.join(header_dir, self.name + '.h')
-+ header_node = node_io.OutputNode()
-+ header_node.StartParsing(u'output', outputs_node)
-+ header_node.HandleAttribute('filename', header_file_name)
-+ header_node.HandleAttribute('type', 'rc_header')
-+ emit_node = node_io.EmitNode()
-+ emit_node.StartParsing(u'emit', header_node)
-+ emit_node.HandleAttribute('emit_type', 'prepend')
-+ emit_node.EndParsing()
-+ header_node.AddChild(emit_node)
-+ header_node.EndParsing()
-+ outputs_node.AddChild(header_node)
-+ return header_node
-+
-+ def __CreateRcOutputNode(self, outputs_node, lang, rc_dir):
-+ """Creates the <output> element corresponding to various rc file output."""
-+ rc_file_name = self.name + '_' + lang + ".rc"
-+ rc_path = os.path.join(rc_dir, rc_file_name)
-+ node = node_io.OutputNode()
-+ node.StartParsing(u'output', outputs_node)
-+ node.HandleAttribute('filename', rc_path)
-+ node.HandleAttribute('lang', lang)
-+ node.HandleAttribute('type', 'rc_all')
-+ node.EndParsing()
-+ outputs_node.AddChild(node)
-+ return node
-+
-+ def __CreateAndroidXmlOutputNode(self, outputs_node, locale, xml_res_dir):
-+ """Creates the <output> element corresponding to various rc file output."""
-+ # Need to check to see if the locale has a region, e.g. the GB in en-GB.
-+ # When a locale has a region Android expects the region to be prefixed
-+ # with an 'r'. For example for en-GB Android expects a values-en-rGB
-+ # directory. Also, Android expects nb, tl, in, iw, ji as the language
-+ # codes for Norwegian, Tagalog/Filipino, Indonesian, Hebrew, and Yiddish:
-+ # http://developer.android.com/reference/java/util/Locale.html
-+ if locale == 'es-419':
-+ android_locale = 'es-rUS'
-+ else:
-+ android_lang, dash, region = locale.partition('-')
-+ lang_map = {'no': 'nb', 'fil': 'tl', 'id': 'in', 'he': 'iw', 'yi': 'ji'}
-+ android_lang = lang_map.get(android_lang, android_lang)
-+ android_locale = android_lang + ('-r' + region if region else '')
-+ values = 'values-' + android_locale if android_locale != 'en' else 'values'
-+ xml_path = os.path.normpath(os.path.join(
-+ xml_res_dir, values, 'strings.xml'))
-+
-+ node = node_io.OutputNode()
-+ node.StartParsing(u'output', outputs_node)
-+ node.HandleAttribute('filename', xml_path)
-+ node.HandleAttribute('lang', locale)
-+ node.HandleAttribute('type', 'android')
-+ node.EndParsing()
-+ outputs_node.AddChild(node)
-+ return node
-+
-+ def IsTranslatable(self, android_string):
-+ """Determines if a <string> element is a candidate for translation.
-+
-+ A <string> element is by default translatable unless otherwise marked.
-+ """
-+ if android_string.hasAttribute('translatable'):
-+ value = android_string.getAttribute('translatable').lower()
-+ if value not in ('true', 'false'):
-+ print('Warning: translatable attribute has invalid value: %s' % value)
-+ return value == 'true'
-+ else:
-+ return True
-diff --git a/tools/grit/grit/tool/android2grd_unittest.py b/tools/grit/grit/tool/android2grd_unittest.py
-new file mode 100644
-index 0000000000..a6934a707c
---- /dev/null
-+++ b/tools/grit/grit/tool/android2grd_unittest.py
-@@ -0,0 +1,181 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for grit.tool.android2grd'''
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+import unittest
-+import xml.dom.minidom
-+
-+from grit import util
-+from grit.node import empty
-+from grit.node import message
-+from grit.node import misc
-+from grit.node import node_io
-+from grit.tool import android2grd
-+
-+
-+class Android2GrdUnittest(unittest.TestCase):
-+
-+ def __Parse(self, xml_string):
-+ return xml.dom.minidom.parseString(xml_string).childNodes[0]
-+
-+ def testCreateTclibMessage(self):
-+ tool = android2grd.Android2Grd()
-+ msg = tool.CreateTclibMessage(self.__Parse(r'''
-+ <string name="simple">A simple string</string>'''))
-+ self.assertEqual(msg.GetRealContent(), 'A simple string')
-+ msg = tool.CreateTclibMessage(self.__Parse(r'''
-+ <string name="outer_whitespace">
-+ Strip leading/trailing whitespace
-+ </string>'''))
-+ self.assertEqual(msg.GetRealContent(), 'Strip leading/trailing whitespace')
-+ msg = tool.CreateTclibMessage(self.__Parse(r'''
-+ <string name="inner_whitespace">Fold multiple spaces</string>'''))
-+ self.assertEqual(msg.GetRealContent(), 'Fold multiple spaces')
-+ msg = tool.CreateTclibMessage(self.__Parse(r'''
-+ <string name="escaped_spaces">Retain \n escaped\t spaces</string>'''))
-+ self.assertEqual(msg.GetRealContent(), 'Retain \n escaped\t spaces')
-+ msg = tool.CreateTclibMessage(self.__Parse(r'''
-+ <string name="quotes"> " Quotes preserve
-+ whitespace" but only for "enclosed elements "
-+ </string>'''))
-+ self.assertEqual(msg.GetRealContent(), ''' Quotes preserve
-+ whitespace but only for enclosed elements ''')
-+ msg = tool.CreateTclibMessage(self.__Parse(
-+ r'''<string name="escaped_characters">Escaped characters: \"\'\\\t\n'''
-+ '</string>'))
-+ self.assertEqual(msg.GetRealContent(), '''Escaped characters: "'\\\t\n''')
-+ msg = tool.CreateTclibMessage(self.__Parse(
-+ '<string xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" '
-+ 'name="placeholders">'
-+ 'Open <xliff:g id="FILENAME" example="internet.html">%s</xliff:g>?'
-+ '</string>'))
-+ self.assertEqual(msg.GetRealContent(), 'Open %s?')
-+ self.assertEqual(len(msg.GetPlaceholders()), 1)
-+ self.assertEqual(msg.GetPlaceholders()[0].presentation, 'FILENAME')
-+ self.assertEqual(msg.GetPlaceholders()[0].original, '%s')
-+ self.assertEqual(msg.GetPlaceholders()[0].example, 'internet.html')
-+ msg = tool.CreateTclibMessage(self.__Parse(r'''
-+ <string name="comment">Contains a <!-- ignore this --> comment
-+ </string>'''))
-+ self.assertEqual(msg.GetRealContent(), 'Contains a comment')
-+
-+ def testIsTranslatable(self):
-+ tool = android2grd.Android2Grd()
-+ string_el = self.__Parse('<string>Hi</string>')
-+ self.assertTrue(tool.IsTranslatable(string_el))
-+ string_el = self.__Parse(
-+ '<string translatable="true">Hi</string>')
-+ self.assertTrue(tool.IsTranslatable(string_el))
-+ string_el = self.__Parse(
-+ '<string translatable="false">Hi</string>')
-+ self.assertFalse(tool.IsTranslatable(string_el))
-+
-+ def __ParseAndroidXml(self, options = []):
-+ tool = android2grd.Android2Grd()
-+
-+ tool.ParseOptions(options)
-+
-+ android_path = util.PathFromRoot('grit/testdata/android.xml')
-+ with open(android_path) as android_file:
-+ android_dom = xml.dom.minidom.parse(android_file)
-+
-+ grd = tool.AndroidDomToGrdDom(android_dom)
-+ self.assertTrue(isinstance(grd, misc.GritNode))
-+
-+ return grd
-+
-+ def testAndroidDomToGrdDom(self):
-+ grd = self.__ParseAndroidXml(['--languages', 'en-US,en-GB,ru'])
-+
-+ # Check that the structure of the GritNode is as expected.
-+ messages = grd.GetChildrenOfType(message.MessageNode)
-+ translations = grd.GetChildrenOfType(empty.TranslationsNode)
-+ files = grd.GetChildrenOfType(node_io.FileNode)
-+
-+ self.assertEqual(len(translations), 1)
-+ self.assertEqual(len(files), 3)
-+ self.assertEqual(len(messages), 5)
-+
-+ # Check that a message node is constructed correctly.
-+ msg = [x for x in messages if x.GetTextualIds()[0] == 'IDS_PLACEHOLDERS']
-+ self.assertTrue(msg)
-+ msg = msg[0]
-+
-+ self.assertTrue(msg.IsTranslateable())
-+ self.assertEqual(msg.attrs["desc"], "A string with placeholder.")
-+
-+ def testTranslatableAttribute(self):
-+ grd = self.__ParseAndroidXml([])
-+ messages = grd.GetChildrenOfType(message.MessageNode)
-+ msgs = [x for x in messages if x.GetTextualIds()[0] == 'IDS_CONSTANT']
-+ self.assertTrue(msgs)
-+ self.assertFalse(msgs[0].IsTranslateable())
-+
-+ def testTranslations(self):
-+ grd = self.__ParseAndroidXml(['--languages', 'en-US,en-GB,ru,id'])
-+
-+ files = grd.GetChildrenOfType(node_io.FileNode)
-+ us_file = [x for x in files if x.attrs['lang'] == 'en-US']
-+ self.assertTrue(us_file)
-+ self.assertEqual(us_file[0].GetInputPath(),
-+ 'chrome_android_strings_en-US.xtb')
-+
-+ id_file = [x for x in files if x.attrs['lang'] == 'id']
-+ self.assertTrue(id_file)
-+ self.assertEqual(id_file[0].GetInputPath(),
-+ 'chrome_android_strings_id.xtb')
-+
-+ def testOutputs(self):
-+ grd = self.__ParseAndroidXml(['--languages', 'en-US,ru,id',
-+ '--rc-dir', 'rc/dir',
-+ '--header-dir', 'header/dir',
-+ '--xtb-dir', 'xtb/dir',
-+ '--xml-dir', 'xml/dir'])
-+
-+ outputs = grd.GetChildrenOfType(node_io.OutputNode)
-+ self.assertEqual(len(outputs), 7)
-+
-+ header_outputs = [x for x in outputs if x.GetType() == 'rc_header']
-+ rc_outputs = [x for x in outputs if x.GetType() == 'rc_all']
-+ xml_outputs = [x for x in outputs if x.GetType() == 'android']
-+
-+ self.assertEqual(len(header_outputs), 1)
-+ self.assertEqual(len(rc_outputs), 3)
-+ self.assertEqual(len(xml_outputs), 3)
-+
-+ # The header node should have an "<emit>" child and the proper filename.
-+ self.assertTrue(header_outputs[0].GetChildrenOfType(node_io.EmitNode))
-+ self.assertEqual(util.normpath(header_outputs[0].GetFilename()),
-+ util.normpath('header/dir/chrome_android_strings.h'))
-+
-+ id_rc = [x for x in rc_outputs if x.GetLanguage() == 'id']
-+ id_xml = [x for x in xml_outputs if x.GetLanguage() == 'id']
-+ self.assertTrue(id_rc)
-+ self.assertTrue(id_xml)
-+ self.assertEqual(util.normpath(id_rc[0].GetFilename()),
-+ util.normpath('rc/dir/chrome_android_strings_id.rc'))
-+ self.assertEqual(util.normpath(id_xml[0].GetFilename()),
-+ util.normpath('xml/dir/values-in/strings.xml'))
-+
-+ us_rc = [x for x in rc_outputs if x.GetLanguage() == 'en-US']
-+ us_xml = [x for x in xml_outputs if x.GetLanguage() == 'en-US']
-+ self.assertTrue(us_rc)
-+ self.assertTrue(us_xml)
-+ self.assertEqual(util.normpath(us_rc[0].GetFilename()),
-+ util.normpath('rc/dir/chrome_android_strings_en-US.rc'))
-+ self.assertEqual(util.normpath(us_xml[0].GetFilename()),
-+ util.normpath('xml/dir/values-en-rUS/strings.xml'))
-+
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/tool/build.py b/tools/grit/grit/tool/build.py
-new file mode 100644
-index 0000000000..204592bf0d
---- /dev/null
-+++ b/tools/grit/grit/tool/build.py
-@@ -0,0 +1,556 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''The 'grit build' tool.
-+'''
-+
-+from __future__ import print_function
-+
-+import codecs
-+import filecmp
-+import getopt
-+import gzip
-+import os
-+import shutil
-+import sys
-+
-+import six
-+
-+from grit import grd_reader
-+from grit import shortcuts
-+from grit import util
-+from grit.format import minifier
-+from grit.node import brotli_util
-+from grit.node import include
-+from grit.node import message
-+from grit.node import structure
-+from grit.tool import interface
-+
-+
-+# It would be cleaner to have each module register itself, but that would
-+# require importing all of them on every run of GRIT.
-+'''Map from <output> node types to modules under grit.format.'''
-+_format_modules = {
-+ 'android': 'android_xml',
-+ 'c_format': 'c_format',
-+ 'chrome_messages_json': 'chrome_messages_json',
-+ 'chrome_messages_json_gzip': 'chrome_messages_json',
-+ 'data_package': 'data_pack',
-+ 'policy_templates': 'policy_templates_json',
-+ 'rc_all': 'rc',
-+ 'rc_header': 'rc_header',
-+ 'rc_nontranslateable': 'rc',
-+ 'rc_translateable': 'rc',
-+ 'resource_file_map_source': 'resource_map',
-+ 'resource_map_header': 'resource_map',
-+ 'resource_map_source': 'resource_map',
-+}
-+
-+def GetFormatter(type):
-+ modulename = 'grit.format.' + _format_modules[type]
-+ __import__(modulename)
-+ module = sys.modules[modulename]
-+ try:
-+ return module.Format
-+ except AttributeError:
-+ return module.GetFormatter(type)
-+
-+
-+class RcBuilder(interface.Tool):
-+ '''A tool that builds RC files and resource header files for compilation.
-+
-+Usage: grit build [-o OUTPUTDIR] [-D NAME[=VAL]]*
-+
-+All output options for this tool are specified in the input file (see
-+'grit help' for details on how to specify the input file - it is a global
-+option).
-+
-+Options:
-+
-+ -a FILE Assert that the given file is an output. There can be
-+ multiple "-a" flags listed for multiple outputs. If a "-a"
-+ or "--assert-file-list" argument is present, then the list
-+ of asserted files must match the output files or the tool
-+ will fail. The use-case is for the build system to maintain
-+ separate lists of output files and to catch errors if the
-+ build system's list and the grit list are out-of-sync.
-+
-+ --assert-file-list Provide a file listing multiple asserted output files.
-+ There is one file name per line. This acts like specifying
-+ each file with "-a" on the command line, but without the
-+ possibility of running into OS line-length limits for very
-+ long lists.
-+
-+ -o OUTPUTDIR Specify what directory output paths are relative to.
-+ Defaults to the current directory.
-+
-+ -p FILE Specify a file containing a pre-determined mapping from
-+ resource names to resource ids which will be used to assign
-+ resource ids to those resources. Resources not found in this
-+ file will be assigned ids normally. The motivation is to run
-+ your app's startup and have it dump the resources it loads,
-+ and then pass these via this flag. This will pack startup
-+ resources together, thus reducing paging while all other
-+ resources are unperturbed. The file should have the format:
-+ RESOURCE_ONE_NAME 123
-+ RESOURCE_TWO_NAME 124
-+
-+ -D NAME[=VAL] Specify a C-preprocessor-like define NAME with optional
-+ value VAL (defaults to 1) which will be used to control
-+ conditional inclusion of resources.
-+
-+ -E NAME=VALUE Set environment variable NAME to VALUE (within grit).
-+
-+ -f FIRSTIDSFILE Path to a python file that specifies the first id of
-+ value to use for resources. A non-empty value here will
-+ override the value specified in the <grit> node's
-+ first_ids_file.
-+
-+ -w WHITELISTFILE Path to a file containing the string names of the
-+ resources to include. Anything not listed is dropped.
-+
-+ -t PLATFORM Specifies the platform the build is targeting; defaults
-+ to the value of sys.platform. The value provided via this
-+ flag should match what sys.platform would report for your
-+ target platform; see grit.node.base.EvaluateCondition.
-+
-+ --whitelist-support
-+ Generate code to support extracting a resource whitelist
-+ from executables.
-+
-+ --write-only-new flag
-+ If flag is non-0, write output files to a temporary file
-+ first, and copy it to the real output only if the new file
-+ is different from the old file. This allows some build
-+ systems to realize that dependent build steps might be
-+ unnecessary, at the cost of comparing the output data at
-+ grit time.
-+
-+ --depend-on-stamp
-+ If specified along with --depfile and --depdir, the depfile
-+ generated will depend on a stampfile instead of the first
-+ output in the input .grd file.
-+
-+ --js-minifier A command to run the Javascript minifier. If not set then
-+ Javascript won't be minified. The command should read the
-+ original Javascript from standard input, and output the
-+ minified Javascript to standard output. A non-zero exit
-+ status will be taken as indicating failure.
-+
-+ --css-minifier A command to run the CSS minifier. If not set then CSS won't
-+ be minified. The command should read the original CSS from
-+ standard input, and output the minified CSS to standard
-+ output. A non-zero exit status will be taken as indicating
-+ failure.
-+
-+ --brotli The full path to the brotli executable generated by
-+ third_party/brotli/BUILD.gn, required if any entries use
-+ compress="brotli".
-+
-+Conditional inclusion of resources only affects the output of files which
-+control which resources get linked into a binary, e.g. it affects .rc files
-+meant for compilation but it does not affect resource header files (that define
-+IDs). This helps ensure that values of IDs stay the same, that all messages
-+are exported to translation interchange files (e.g. XMB files), etc.
-+'''
-+
-+ def ShortDescription(self):
-+ return 'A tool that builds RC files for compilation.'
-+
-+ def Run(self, opts, args):
-+ brotli_util.SetBrotliCommand(None)
-+ os.environ['cwd'] = os.getcwd()
-+ self.output_directory = '.'
-+ first_ids_file = None
-+ predetermined_ids_file = None
-+ whitelist_filenames = []
-+ assert_output_files = []
-+ target_platform = None
-+ depfile = None
-+ depdir = None
-+ whitelist_support = False
-+ write_only_new = False
-+ depend_on_stamp = False
-+ js_minifier = None
-+ css_minifier = None
-+ replace_ellipsis = True
-+ (own_opts, args) = getopt.getopt(
-+ args, 'a:p:o:D:E:f:w:t:',
-+ ('depdir=', 'depfile=', 'assert-file-list=', 'help',
-+ 'output-all-resource-defines', 'no-output-all-resource-defines',
-+ 'no-replace-ellipsis', 'depend-on-stamp', 'js-minifier=',
-+ 'css-minifier=', 'write-only-new=', 'whitelist-support', 'brotli='))
-+ for (key, val) in own_opts:
-+ if key == '-a':
-+ assert_output_files.append(val)
-+ elif key == '--assert-file-list':
-+ with open(val) as f:
-+ assert_output_files += f.read().splitlines()
-+ elif key == '-o':
-+ self.output_directory = val
-+ elif key == '-D':
-+ name, val = util.ParseDefine(val)
-+ self.defines[name] = val
-+ elif key == '-E':
-+ (env_name, env_value) = val.split('=', 1)
-+ os.environ[env_name] = env_value
-+ elif key == '-f':
-+ # TODO(joi@chromium.org): Remove this override once change
-+ # lands in WebKit.grd to specify the first_ids_file in the
-+ # .grd itself.
-+ first_ids_file = val
-+ elif key == '-w':
-+ whitelist_filenames.append(val)
-+ elif key == '--no-replace-ellipsis':
-+ replace_ellipsis = False
-+ elif key == '-p':
-+ predetermined_ids_file = val
-+ elif key == '-t':
-+ target_platform = val
-+ elif key == '--depdir':
-+ depdir = val
-+ elif key == '--depfile':
-+ depfile = val
-+ elif key == '--write-only-new':
-+ write_only_new = val != '0'
-+ elif key == '--depend-on-stamp':
-+ depend_on_stamp = True
-+ elif key == '--js-minifier':
-+ js_minifier = val
-+ elif key == '--css-minifier':
-+ css_minifier = val
-+ elif key == '--whitelist-support':
-+ whitelist_support = True
-+ elif key == '--brotli':
-+ brotli_util.SetBrotliCommand([os.path.abspath(val)])
-+ elif key == '--help':
-+ self.ShowUsage()
-+ sys.exit(0)
-+
-+ if len(args):
-+ print('This tool takes no tool-specific arguments.')
-+ return 2
-+ self.SetOptions(opts)
-+ self.VerboseOut('Output directory: %s (absolute path: %s)\n' %
-+ (self.output_directory,
-+ os.path.abspath(self.output_directory)))
-+
-+ if whitelist_filenames:
-+ self.whitelist_names = set()
-+ for whitelist_filename in whitelist_filenames:
-+ self.VerboseOut('Using whitelist: %s\n' % whitelist_filename);
-+ whitelist_contents = util.ReadFile(whitelist_filename, 'utf-8')
-+ self.whitelist_names.update(whitelist_contents.strip().split('\n'))
-+
-+ if js_minifier:
-+ minifier.SetJsMinifier(js_minifier)
-+
-+ if css_minifier:
-+ minifier.SetCssMinifier(css_minifier)
-+
-+ self.write_only_new = write_only_new
-+
-+ self.res = grd_reader.Parse(opts.input,
-+ debug=opts.extra_verbose,
-+ first_ids_file=first_ids_file,
-+ predetermined_ids_file=predetermined_ids_file,
-+ defines=self.defines,
-+ target_platform=target_platform)
-+
-+ # Set an output context so that conditionals can use defines during the
-+ # gathering stage; we use a dummy language here since we are not outputting
-+ # a specific language.
-+ self.res.SetOutputLanguage('en')
-+ self.res.SetWhitelistSupportEnabled(whitelist_support)
-+ self.res.RunGatherers()
-+
-+ # Replace ... with the single-character version. http://crbug.com/621772
-+ if replace_ellipsis:
-+ for node in self.res:
-+ if isinstance(node, message.MessageNode):
-+ node.SetReplaceEllipsis(True)
-+
-+ self.Process()
-+
-+ if assert_output_files:
-+ if not self.CheckAssertedOutputFiles(assert_output_files):
-+ return 2
-+
-+ if depfile and depdir:
-+ self.GenerateDepfile(depfile, depdir, first_ids_file, depend_on_stamp)
-+
-+ return 0
-+
-+ def __init__(self, defines=None):
-+ # Default file-creation function is codecs.open(). Only done to allow
-+ # overriding by unit test.
-+ self.fo_create = codecs.open
-+
-+ # key/value pairs of C-preprocessor like defines that are used for
-+ # conditional output of resources
-+ self.defines = defines or {}
-+
-+ # self.res is a fully-populated resource tree if Run()
-+ # has been called, otherwise None.
-+ self.res = None
-+
-+ # The set of names that are whitelisted to actually be included in the
-+ # output.
-+ self.whitelist_names = None
-+
-+ # Whether to compare outputs to their old contents before writing.
-+ self.write_only_new = False
-+
-+ @staticmethod
-+ def AddWhitelistTags(start_node, whitelist_names):
-+ # Walk the tree of nodes added attributes for the nodes that shouldn't
-+ # be written into the target files (skip markers).
-+ for node in start_node:
-+ # Same trick data_pack.py uses to see what nodes actually result in
-+ # real items.
-+ if (isinstance(node, include.IncludeNode) or
-+ isinstance(node, message.MessageNode) or
-+ isinstance(node, structure.StructureNode)):
-+ text_ids = node.GetTextualIds()
-+ # Mark the item to be skipped if it wasn't in the whitelist.
-+ if text_ids and text_ids[0] not in whitelist_names:
-+ node.SetWhitelistMarkedAsSkip(True)
-+
-+ @staticmethod
-+ def ProcessNode(node, output_node, outfile):
-+ '''Processes a node in-order, calling its formatter before and after
-+ recursing to its children.
-+
-+ Args:
-+ node: grit.node.base.Node subclass
-+ output_node: grit.node.io.OutputNode
-+ outfile: open filehandle
-+ '''
-+ base_dir = util.dirname(output_node.GetOutputFilename())
-+
-+ formatter = GetFormatter(output_node.GetType())
-+ formatted = formatter(node, output_node.GetLanguage(), output_dir=base_dir)
-+ # NB: Formatters may be generators or return lists. The writelines API
-+ # accepts iterables as a shortcut to calling write directly. That means
-+ # you can pass strings (iteration yields characters), but not bytes (as
-+ # iteration yields integers). Python 2 worked due to its quirks with
-+ # bytes/string implementation, but Python 3 fails. It's also a bit more
-+ # inefficient to call write once per character/byte. Handle all of this
-+ # ourselves by calling write directly on strings/bytes before falling back
-+ # to writelines.
-+ if isinstance(formatted, (six.string_types, six.binary_type)):
-+ outfile.write(formatted)
-+ else:
-+ outfile.writelines(formatted)
-+ if output_node.GetType() == 'data_package':
-+ with open(output_node.GetOutputFilename() + '.info', 'w') as infofile:
-+ if node.info:
-+ # We terminate with a newline so that when these files are
-+ # concatenated later we consistently terminate with a newline so
-+ # consumers can account for terminating newlines.
-+ infofile.writelines(['\n'.join(node.info), '\n'])
-+
-+ @staticmethod
-+ def _EncodingForOutputType(output_type):
-+ # Microsoft's RC compiler can only deal with single-byte or double-byte
-+ # files (no UTF-8), so we make all RC files UTF-16 to support all
-+ # character sets.
-+ if output_type in ('rc_header', 'resource_file_map_source',
-+ 'resource_map_header', 'resource_map_source'):
-+ return 'cp1252'
-+ if output_type in ('android', 'c_format', 'plist', 'plist_strings', 'doc',
-+ 'json', 'android_policy', 'chrome_messages_json',
-+ 'chrome_messages_json_gzip', 'policy_templates'):
-+ return 'utf_8'
-+ # TODO(gfeher) modify here to set utf-8 encoding for admx/adml
-+ return 'utf_16'
-+
-+ def Process(self):
-+ for output in self.res.GetOutputFiles():
-+ output.output_filename = os.path.abspath(os.path.join(
-+ self.output_directory, output.GetOutputFilename()))
-+
-+ # If there are whitelisted names, tag the tree once up front, this way
-+ # while looping through the actual output, it is just an attribute check.
-+ if self.whitelist_names:
-+ self.AddWhitelistTags(self.res, self.whitelist_names)
-+
-+ for output in self.res.GetOutputFiles():
-+ self.VerboseOut('Creating %s...' % output.GetOutputFilename())
-+
-+ # Set the context, for conditional inclusion of resources
-+ self.res.SetOutputLanguage(output.GetLanguage())
-+ self.res.SetOutputContext(output.GetContext())
-+ self.res.SetFallbackToDefaultLayout(output.GetFallbackToDefaultLayout())
-+ self.res.SetDefines(self.defines)
-+
-+ # Assign IDs only once to ensure that all outputs use the same IDs.
-+ if self.res.GetIdMap() is None:
-+ self.res.InitializeIds()
-+
-+ # Make the output directory if it doesn't exist.
-+ self.MakeDirectoriesTo(output.GetOutputFilename())
-+
-+ # Write the results to a temporary file and only overwrite the original
-+ # if the file changed. This avoids unnecessary rebuilds.
-+ out_filename = output.GetOutputFilename()
-+ tmp_filename = out_filename + '.tmp'
-+ tmpfile = self.fo_create(tmp_filename, 'wb')
-+
-+ output_type = output.GetType()
-+ if output_type != 'data_package':
-+ encoding = self._EncodingForOutputType(output_type)
-+ tmpfile = util.WrapOutputStream(tmpfile, encoding)
-+
-+ # Iterate in-order through entire resource tree, calling formatters on
-+ # the entry into a node and on exit out of it.
-+ with tmpfile:
-+ self.ProcessNode(self.res, output, tmpfile)
-+
-+ if output_type == 'chrome_messages_json_gzip':
-+ gz_filename = tmp_filename + '.gz'
-+ with open(tmp_filename, 'rb') as tmpfile, open(gz_filename, 'wb') as f:
-+ with gzip.GzipFile(filename='', mode='wb', fileobj=f, mtime=0) as fgz:
-+ shutil.copyfileobj(tmpfile, fgz)
-+ os.remove(tmp_filename)
-+ tmp_filename = gz_filename
-+
-+ # Now copy from the temp file back to the real output, but on Windows,
-+ # only if the real output doesn't exist or the contents of the file
-+ # changed. This prevents identical headers from being written and .cc
-+ # files from recompiling (which is painful on Windows).
-+ if not os.path.exists(out_filename):
-+ os.rename(tmp_filename, out_filename)
-+ else:
-+ # CHROMIUM SPECIFIC CHANGE.
-+ # This clashes with gyp + vstudio, which expect the output timestamp
-+ # to change on a rebuild, even if nothing has changed, so only do
-+ # it when opted in.
-+ if not self.write_only_new:
-+ write_file = True
-+ else:
-+ files_match = filecmp.cmp(out_filename, tmp_filename)
-+ write_file = not files_match
-+ if write_file:
-+ shutil.copy2(tmp_filename, out_filename)
-+ os.remove(tmp_filename)
-+
-+ self.VerboseOut(' done.\n')
-+
-+ # Print warnings if there are any duplicate shortcuts.
-+ warnings = shortcuts.GenerateDuplicateShortcutsWarnings(
-+ self.res.UberClique(), self.res.GetTcProject())
-+ if warnings:
-+ print('\n'.join(warnings))
-+
-+ # Print out any fallback warnings, and missing translation errors, and
-+ # exit with an error code if there are missing translations in a non-pseudo
-+ # and non-official build.
-+ warnings = (self.res.UberClique().MissingTranslationsReport().
-+ encode('ascii', 'replace'))
-+ if warnings:
-+ self.VerboseOut(warnings)
-+ if self.res.UberClique().HasMissingTranslations():
-+ print(self.res.UberClique().missing_translations_)
-+ sys.exit(-1)
-+
-+
-+ def CheckAssertedOutputFiles(self, assert_output_files):
-+ '''Checks that the asserted output files are specified in the given list.
-+
-+ Returns true if the asserted files are present. If they are not, returns
-+ False and prints the failure.
-+ '''
-+ # Compare the absolute path names, sorted.
-+ asserted = sorted([os.path.abspath(i) for i in assert_output_files])
-+ actual = sorted([
-+ os.path.abspath(os.path.join(self.output_directory,
-+ i.GetOutputFilename()))
-+ for i in self.res.GetOutputFiles()])
-+
-+ if asserted != actual:
-+ missing = list(set(asserted) - set(actual))
-+ extra = list(set(actual) - set(asserted))
-+ error = '''Asserted file list does not match.
-+
-+Expected output files:
-+%s
-+Actual output files:
-+%s
-+Missing output files:
-+%s
-+Extra output files:
-+%s
-+'''
-+ print(error % ('\n'.join(asserted), '\n'.join(actual), '\n'.join(missing),
-+ ' \n'.join(extra)))
-+ return False
-+ return True
-+
-+
-+ def GenerateDepfile(self, depfile, depdir, first_ids_file, depend_on_stamp):
-+ '''Generate a depfile that contains the imlicit dependencies of the input
-+ grd. The depfile will be in the same format as a makefile, and will contain
-+ references to files relative to |depdir|. It will be put in |depfile|.
-+
-+ For example, supposing we have three files in a directory src/
-+
-+ src/
-+ blah.grd <- depends on input{1,2}.xtb
-+ input1.xtb
-+ input2.xtb
-+
-+ and we run
-+
-+ grit -i blah.grd -o ../out/gen \
-+ --depdir ../out \
-+ --depfile ../out/gen/blah.rd.d
-+
-+ from the directory src/ we will generate a depfile ../out/gen/blah.grd.d
-+ that has the contents
-+
-+ gen/blah.h: ../src/input1.xtb ../src/input2.xtb
-+
-+ Where "gen/blah.h" is the first output (Ninja expects the .d file to list
-+ the first output in cases where there is more than one). If the flag
-+ --depend-on-stamp is specified, "gen/blah.rd.d.stamp" will be used that is
-+ 'touched' whenever a new depfile is generated.
-+
-+ Note that all paths in the depfile are relative to ../out, the depdir.
-+ '''
-+ depfile = os.path.abspath(depfile)
-+ depdir = os.path.abspath(depdir)
-+ infiles = self.res.GetInputFiles()
-+
-+ # We want to trigger a rebuild if the first ids change.
-+ if first_ids_file is not None:
-+ infiles.append(first_ids_file)
-+
-+ if (depend_on_stamp):
-+ output_file = depfile + ".stamp"
-+ # Touch the stamp file before generating the depfile.
-+ with open(output_file, 'a'):
-+ os.utime(output_file, None)
-+ else:
-+ # Get the first output file relative to the depdir.
-+ outputs = self.res.GetOutputFiles()
-+ output_file = os.path.join(self.output_directory,
-+ outputs[0].GetOutputFilename())
-+
-+ output_file = os.path.relpath(output_file, depdir)
-+ # The path prefix to prepend to dependencies in the depfile.
-+ prefix = os.path.relpath(os.getcwd(), depdir)
-+ deps_text = ' '.join([os.path.join(prefix, i) for i in infiles])
-+
-+ depfile_contents = output_file + ': ' + deps_text
-+ self.MakeDirectoriesTo(depfile)
-+ outfile = self.fo_create(depfile, 'w', encoding='utf-8')
-+ outfile.write(depfile_contents)
-+
-+ @staticmethod
-+ def MakeDirectoriesTo(file):
-+ '''Creates directories necessary to contain |file|.'''
-+ dir = os.path.split(file)[0]
-+ if not os.path.exists(dir):
-+ os.makedirs(dir)
-diff --git a/tools/grit/grit/tool/build_unittest.py b/tools/grit/grit/tool/build_unittest.py
-new file mode 100644
-index 0000000000..c4a2f2752b
---- /dev/null
-+++ b/tools/grit/grit/tool/build_unittest.py
-@@ -0,0 +1,341 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for the 'grit build' tool.
-+'''
-+
-+from __future__ import print_function
-+
-+import codecs
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+import unittest
-+
-+from grit import util
-+from grit.tool import build
-+
-+
-+class BuildUnittest(unittest.TestCase):
-+
-+ # IDs should not change based on whitelisting.
-+ # Android WebView currently relies on this.
-+ EXPECTED_ID_MAP = {
-+ 'IDS_MESSAGE_WHITELISTED': 6889,
-+ 'IDR_STRUCTURE_WHITELISTED': 11546,
-+ 'IDR_STRUCTURE_IN_TRUE_IF_WHITELISTED': 11548,
-+ 'IDR_INCLUDE_WHITELISTED': 15601,
-+ }
-+
-+ def testFindTranslationsWithSubstitutions(self):
-+ # This is a regression test; we had a bug where GRIT would fail to find
-+ # messages with substitutions e.g. "Hello [IDS_USER]" where IDS_USER is
-+ # another <message>.
-+ output_dir = util.TempDir({})
-+ builder = build.RcBuilder()
-+ class DummyOpts(object):
-+ def __init__(self):
-+ self.input = util.PathFromRoot('grit/testdata/substitute.grd')
-+ self.verbose = False
-+ self.extra_verbose = False
-+ builder.Run(DummyOpts(), ['-o', output_dir.GetPath()])
-+ output_dir.CleanUp()
-+
-+ def testGenerateDepFile(self):
-+ output_dir = util.TempDir({})
-+ builder = build.RcBuilder()
-+ class DummyOpts(object):
-+ def __init__(self):
-+ self.input = util.PathFromRoot('grit/testdata/depfile.grd')
-+ self.verbose = False
-+ self.extra_verbose = False
-+ expected_dep_file = output_dir.GetPath('substitute.grd.d')
-+ builder.Run(DummyOpts(), ['-o', output_dir.GetPath(),
-+ '--depdir', output_dir.GetPath(),
-+ '--depfile', expected_dep_file])
-+
-+ self.failUnless(os.path.isfile(expected_dep_file))
-+ with open(expected_dep_file) as f:
-+ line = f.readline()
-+ (dep_output_file, deps_string) = line.split(': ')
-+ deps = deps_string.split(' ')
-+
-+ self.failUnlessEqual("default_100_percent.pak", dep_output_file)
-+ self.failUnlessEqual(deps, [
-+ util.PathFromRoot('grit/testdata/default_100_percent/a.png'),
-+ util.PathFromRoot('grit/testdata/grit_part.grdp'),
-+ util.PathFromRoot('grit/testdata/special_100_percent/a.png'),
-+ ])
-+ output_dir.CleanUp()
-+
-+ def testGenerateDepFileWithResourceIds(self):
-+ output_dir = util.TempDir({})
-+ builder = build.RcBuilder()
-+ class DummyOpts(object):
-+ def __init__(self):
-+ self.input = util.PathFromRoot('grit/testdata/substitute_no_ids.grd')
-+ self.verbose = False
-+ self.extra_verbose = False
-+ expected_dep_file = output_dir.GetPath('substitute_no_ids.grd.d')
-+ builder.Run(DummyOpts(),
-+ ['-f', util.PathFromRoot('grit/testdata/resource_ids'),
-+ '-o', output_dir.GetPath(),
-+ '--depdir', output_dir.GetPath(),
-+ '--depfile', expected_dep_file])
-+
-+ self.failUnless(os.path.isfile(expected_dep_file))
-+ with open(expected_dep_file) as f:
-+ line = f.readline()
-+ (dep_output_file, deps_string) = line.split(': ')
-+ deps = deps_string.split(' ')
-+
-+ self.failUnlessEqual("resource.h", dep_output_file)
-+ self.failUnlessEqual(2, len(deps))
-+ self.failUnlessEqual(deps[0],
-+ util.PathFromRoot('grit/testdata/substitute.xmb'))
-+ self.failUnlessEqual(deps[1],
-+ util.PathFromRoot('grit/testdata/resource_ids'))
-+ output_dir.CleanUp()
-+
-+ def testAssertOutputs(self):
-+ output_dir = util.TempDir({})
-+ class DummyOpts(object):
-+ def __init__(self):
-+ self.input = util.PathFromRoot('grit/testdata/substitute.grd')
-+ self.verbose = False
-+ self.extra_verbose = False
-+
-+ # Incomplete output file list should fail.
-+ builder_fail = build.RcBuilder()
-+ self.failUnlessEqual(2,
-+ builder_fail.Run(DummyOpts(), [
-+ '-o', output_dir.GetPath(),
-+ '-a', os.path.abspath(
-+ output_dir.GetPath('en_generated_resources.rc'))]))
-+
-+ # Complete output file list should succeed.
-+ builder_ok = build.RcBuilder()
-+ self.failUnlessEqual(0,
-+ builder_ok.Run(DummyOpts(), [
-+ '-o', output_dir.GetPath(),
-+ '-a', os.path.abspath(
-+ output_dir.GetPath('en_generated_resources.rc')),
-+ '-a', os.path.abspath(
-+ output_dir.GetPath('sv_generated_resources.rc')),
-+ '-a', os.path.abspath(output_dir.GetPath('resource.h'))]))
-+ output_dir.CleanUp()
-+
-+ def testAssertTemplateOutputs(self):
-+ output_dir = util.TempDir({})
-+ class DummyOpts(object):
-+ def __init__(self):
-+ self.input = util.PathFromRoot('grit/testdata/substitute_tmpl.grd')
-+ self.verbose = False
-+ self.extra_verbose = False
-+
-+ # Incomplete output file list should fail.
-+ builder_fail = build.RcBuilder()
-+ self.failUnlessEqual(2,
-+ builder_fail.Run(DummyOpts(), [
-+ '-o', output_dir.GetPath(),
-+ '-E', 'name=foo',
-+ '-a', os.path.abspath(output_dir.GetPath('en_foo_resources.rc'))]))
-+
-+ # Complete output file list should succeed.
-+ builder_ok = build.RcBuilder()
-+ self.failUnlessEqual(0,
-+ builder_ok.Run(DummyOpts(), [
-+ '-o', output_dir.GetPath(),
-+ '-E', 'name=foo',
-+ '-a', os.path.abspath(output_dir.GetPath('en_foo_resources.rc')),
-+ '-a', os.path.abspath(output_dir.GetPath('sv_foo_resources.rc')),
-+ '-a', os.path.abspath(output_dir.GetPath('resource.h'))]))
-+ output_dir.CleanUp()
-+
-+ def _verifyWhitelistedOutput(self,
-+ filename,
-+ whitelisted_ids,
-+ non_whitelisted_ids,
-+ encoding='utf8'):
-+ self.failUnless(os.path.exists(filename))
-+ whitelisted_ids_found = []
-+ non_whitelisted_ids_found = []
-+ with codecs.open(filename, encoding=encoding) as f:
-+ for line in f.readlines():
-+ for whitelisted_id in whitelisted_ids:
-+ if whitelisted_id in line:
-+ whitelisted_ids_found.append(whitelisted_id)
-+ if filename.endswith('.h'):
-+ numeric_id = int(line.split()[2])
-+ expected_numeric_id = self.EXPECTED_ID_MAP.get(whitelisted_id)
-+ self.assertEqual(
-+ expected_numeric_id, numeric_id,
-+ 'Numeric ID for {} was {} should be {}'.format(
-+ whitelisted_id, numeric_id, expected_numeric_id))
-+ for non_whitelisted_id in non_whitelisted_ids:
-+ if non_whitelisted_id in line:
-+ non_whitelisted_ids_found.append(non_whitelisted_id)
-+ self.longMessage = True
-+ self.assertEqual(whitelisted_ids,
-+ whitelisted_ids_found,
-+ '\nin file {}'.format(os.path.basename(filename)))
-+ non_whitelisted_msg = ('Non-Whitelisted IDs {} found in {}'
-+ .format(non_whitelisted_ids_found, os.path.basename(filename)))
-+ self.assertFalse(non_whitelisted_ids_found, non_whitelisted_msg)
-+
-+ def testWhitelistStrings(self):
-+ output_dir = util.TempDir({})
-+ builder = build.RcBuilder()
-+ class DummyOpts(object):
-+ def __init__(self):
-+ self.input = util.PathFromRoot('grit/testdata/whitelist_strings.grd')
-+ self.verbose = False
-+ self.extra_verbose = False
-+ whitelist_file = util.PathFromRoot('grit/testdata/whitelist.txt')
-+ builder.Run(DummyOpts(), ['-o', output_dir.GetPath(),
-+ '-w', whitelist_file])
-+ header = output_dir.GetPath('whitelist_test_resources.h')
-+ rc = output_dir.GetPath('en_whitelist_test_strings.rc')
-+
-+ whitelisted_ids = ['IDS_MESSAGE_WHITELISTED']
-+ non_whitelisted_ids = ['IDS_MESSAGE_NOT_WHITELISTED']
-+ self._verifyWhitelistedOutput(
-+ header,
-+ whitelisted_ids,
-+ non_whitelisted_ids,
-+ )
-+ self._verifyWhitelistedOutput(
-+ rc,
-+ whitelisted_ids,
-+ non_whitelisted_ids,
-+ encoding='utf16'
-+ )
-+ output_dir.CleanUp()
-+
-+ def testWhitelistResources(self):
-+ output_dir = util.TempDir({})
-+ builder = build.RcBuilder()
-+ class DummyOpts(object):
-+ def __init__(self):
-+ self.input = util.PathFromRoot('grit/testdata/whitelist_resources.grd')
-+ self.verbose = False
-+ self.extra_verbose = False
-+ whitelist_file = util.PathFromRoot('grit/testdata/whitelist.txt')
-+ builder.Run(DummyOpts(), ['-o', output_dir.GetPath(),
-+ '-w', whitelist_file])
-+ header = output_dir.GetPath('whitelist_test_resources.h')
-+ map_cc = output_dir.GetPath('whitelist_test_resources_map.cc')
-+ map_h = output_dir.GetPath('whitelist_test_resources_map.h')
-+ pak = output_dir.GetPath('whitelist_test_resources.pak')
-+
-+ # Ensure the resource map header and .pak files exist, but don't verify
-+ # their content.
-+ self.failUnless(os.path.exists(map_h))
-+ self.failUnless(os.path.exists(pak))
-+
-+ whitelisted_ids = [
-+ 'IDR_STRUCTURE_WHITELISTED',
-+ 'IDR_STRUCTURE_IN_TRUE_IF_WHITELISTED',
-+ 'IDR_INCLUDE_WHITELISTED',
-+ ]
-+ non_whitelisted_ids = [
-+ 'IDR_STRUCTURE_NOT_WHITELISTED',
-+ 'IDR_STRUCTURE_IN_TRUE_IF_NOT_WHITELISTED',
-+ 'IDR_STRUCTURE_IN_FALSE_IF_WHITELISTED',
-+ 'IDR_STRUCTURE_IN_FALSE_IF_NOT_WHITELISTED',
-+ 'IDR_INCLUDE_NOT_WHITELISTED',
-+ ]
-+ for output_file in (header, map_cc):
-+ self._verifyWhitelistedOutput(
-+ output_file,
-+ whitelisted_ids,
-+ non_whitelisted_ids,
-+ )
-+ output_dir.CleanUp()
-+
-+ def testWriteOnlyNew(self):
-+ output_dir = util.TempDir({})
-+ builder = build.RcBuilder()
-+ class DummyOpts(object):
-+ def __init__(self):
-+ self.input = util.PathFromRoot('grit/testdata/substitute.grd')
-+ self.verbose = False
-+ self.extra_verbose = False
-+ UNCHANGED = 10
-+ header = output_dir.GetPath('resource.h')
-+
-+ builder.Run(DummyOpts(), ['-o', output_dir.GetPath()])
-+ self.failUnless(os.path.exists(header))
-+ first_mtime = os.stat(header).st_mtime
-+
-+ os.utime(header, (UNCHANGED, UNCHANGED))
-+ builder.Run(DummyOpts(),
-+ ['-o', output_dir.GetPath(), '--write-only-new', '0'])
-+ self.failUnless(os.path.exists(header))
-+ second_mtime = os.stat(header).st_mtime
-+
-+ os.utime(header, (UNCHANGED, UNCHANGED))
-+ builder.Run(DummyOpts(),
-+ ['-o', output_dir.GetPath(), '--write-only-new', '1'])
-+ self.failUnless(os.path.exists(header))
-+ third_mtime = os.stat(header).st_mtime
-+
-+ self.assertTrue(abs(second_mtime - UNCHANGED) > 5)
-+ self.assertTrue(abs(third_mtime - UNCHANGED) < 5)
-+ output_dir.CleanUp()
-+
-+ def testGenerateDepFileWithDependOnStamp(self):
-+ output_dir = util.TempDir({})
-+ builder = build.RcBuilder()
-+ class DummyOpts(object):
-+ def __init__(self):
-+ self.input = util.PathFromRoot('grit/testdata/substitute.grd')
-+ self.verbose = False
-+ self.extra_verbose = False
-+ expected_dep_file_name = 'substitute.grd.d'
-+ expected_stamp_file_name = expected_dep_file_name + '.stamp'
-+ expected_dep_file = output_dir.GetPath(expected_dep_file_name)
-+ expected_stamp_file = output_dir.GetPath(expected_stamp_file_name)
-+ if os.path.isfile(expected_stamp_file):
-+ os.remove(expected_stamp_file)
-+ builder.Run(DummyOpts(), ['-o', output_dir.GetPath(),
-+ '--depdir', output_dir.GetPath(),
-+ '--depfile', expected_dep_file,
-+ '--depend-on-stamp'])
-+ self.failUnless(os.path.isfile(expected_stamp_file))
-+ first_mtime = os.stat(expected_stamp_file).st_mtime
-+
-+ # Reset mtime to very old.
-+ OLDTIME = 10
-+ os.utime(expected_stamp_file, (OLDTIME, OLDTIME))
-+
-+ builder.Run(DummyOpts(), ['-o', output_dir.GetPath(),
-+ '--depdir', output_dir.GetPath(),
-+ '--depfile', expected_dep_file,
-+ '--depend-on-stamp'])
-+ self.failUnless(os.path.isfile(expected_stamp_file))
-+ second_mtime = os.stat(expected_stamp_file).st_mtime
-+
-+ # Some OS have a 2s stat resolution window, so can't do a direct comparison.
-+ self.assertTrue((second_mtime - OLDTIME) > 5)
-+ self.assertTrue(abs(second_mtime - first_mtime) < 5)
-+
-+ self.failUnless(os.path.isfile(expected_dep_file))
-+ with open(expected_dep_file) as f:
-+ line = f.readline()
-+ (dep_output_file, deps_string) = line.split(': ')
-+ deps = deps_string.split(' ')
-+
-+ self.failUnlessEqual(expected_stamp_file_name, dep_output_file)
-+ self.failUnlessEqual(deps, [
-+ util.PathFromRoot('grit/testdata/substitute.xmb'),
-+ ])
-+ output_dir.CleanUp()
-+
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/tool/buildinfo.py b/tools/grit/grit/tool/buildinfo.py
-new file mode 100644
-index 0000000000..7f8d1a3b04
---- /dev/null
-+++ b/tools/grit/grit/tool/buildinfo.py
-@@ -0,0 +1,78 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+"""Output the list of files to be generated by GRIT from an input.
-+"""
-+
-+from __future__ import print_function
-+
-+import getopt
-+import os
-+import sys
-+
-+from grit import grd_reader
-+from grit.node import structure
-+from grit.tool import interface
-+
-+class DetermineBuildInfo(interface.Tool):
-+ """Determine what files will be read and output by GRIT.
-+Outputs the list of generated files and inputs used to stdout.
-+
-+Usage: grit buildinfo [-o DIR]
-+
-+The output directory is used for display only.
-+"""
-+
-+ def __init__(self):
-+ pass
-+
-+ def ShortDescription(self):
-+ """Describes this tool for the usage message."""
-+ return ('Determine what files will be needed and\n'
-+ 'output by GRIT with a given input.')
-+
-+ def Run(self, opts, args):
-+ """Main method for the buildinfo tool."""
-+ self.output_directory = '.'
-+ (own_opts, args) = getopt.getopt(args, 'o:', ('help',))
-+ for (key, val) in own_opts:
-+ if key == '-o':
-+ self.output_directory = val
-+ elif key == '--help':
-+ self.ShowUsage()
-+ sys.exit(0)
-+ if len(args) > 0:
-+ print('This tool takes exactly one argument: the output directory via -o')
-+ return 2
-+ self.SetOptions(opts)
-+
-+ res_tree = grd_reader.Parse(opts.input, debug=opts.extra_verbose)
-+
-+ langs = {}
-+ for output in res_tree.GetOutputFiles():
-+ if output.attrs['lang']:
-+ langs[output.attrs['lang']] = os.path.dirname(output.GetFilename())
-+
-+ for lang, dirname in langs.items():
-+ old_output_language = res_tree.output_language
-+ res_tree.SetOutputLanguage(lang)
-+ for node in res_tree.ActiveDescendants():
-+ with node:
-+ if (isinstance(node, structure.StructureNode) and
-+ node.HasFileForLanguage()):
-+ path = node.FileForLanguage(lang, dirname, create_file=False,
-+ return_if_not_generated=False)
-+ if path:
-+ path = os.path.join(self.output_directory, path)
-+ path = os.path.normpath(path)
-+ print('%s|%s' % ('rc_all', path))
-+ res_tree.SetOutputLanguage(old_output_language)
-+
-+ for output in res_tree.GetOutputFiles():
-+ path = os.path.join(self.output_directory, output.GetFilename())
-+ path = os.path.normpath(path)
-+ print('%s|%s' % (output.GetType(), path))
-+
-+ for infile in res_tree.GetInputFiles():
-+ print('input|%s' % os.path.normpath(infile))
-diff --git a/tools/grit/grit/tool/buildinfo_unittest.py b/tools/grit/grit/tool/buildinfo_unittest.py
-new file mode 100644
-index 0000000000..24e9ddf8d8
---- /dev/null
-+++ b/tools/grit/grit/tool/buildinfo_unittest.py
-@@ -0,0 +1,90 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+"""Unit tests for the 'grit buildinfo' tool.
-+"""
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+import unittest
-+
-+# This is needed to find some of the imports below.
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+from six import StringIO
-+
-+# pylint: disable-msg=C6204
-+from grit.tool import buildinfo
-+
-+
-+class BuildInfoUnittest(unittest.TestCase):
-+ def setUp(self):
-+ self.old_cwd = os.getcwd()
-+ # Change CWD to make tests work independently of callers CWD.
-+ os.chdir(os.path.dirname(__file__))
-+ os.chdir('..')
-+ self.buf = StringIO()
-+ self.old_stdout = sys.stdout
-+ sys.stdout = self.buf
-+
-+ def tearDown(self):
-+ sys.stdout = self.old_stdout
-+ os.chdir(self.old_cwd)
-+
-+ def testBuildOutput(self):
-+ """Find all of the inputs and outputs for a GRD file."""
-+ info_object = buildinfo.DetermineBuildInfo()
-+
-+ class DummyOpts(object):
-+ def __init__(self):
-+ self.input = '../grit/testdata/buildinfo.grd'
-+ self.print_header = False
-+ self.verbose = False
-+ self.extra_verbose = False
-+ info_object.Run(DummyOpts(), [])
-+ output = self.buf.getvalue().replace('\\', '/')
-+ self.failUnless(output.count(r'rc_all|sv_sidebar_loading.html'))
-+ self.failUnless(output.count(r'rc_header|resource.h'))
-+ self.failUnless(output.count(r'rc_all|en_generated_resources.rc'))
-+ self.failUnless(output.count(r'rc_all|sv_generated_resources.rc'))
-+ self.failUnless(output.count(r'input|../grit/testdata/substitute.xmb'))
-+ self.failUnless(output.count(r'input|../grit/testdata/pr.bmp'))
-+ self.failUnless(output.count(r'input|../grit/testdata/pr2.bmp'))
-+ self.failUnless(
-+ output.count(r'input|../grit/testdata/sidebar_loading.html'))
-+ self.failUnless(output.count(r'input|../grit/testdata/transl.rc'))
-+ self.failUnless(output.count(r'input|../grit/testdata/transl1.rc'))
-+
-+ def testBuildOutputWithDir(self):
-+ """Find all the inputs and outputs for a GRD file with an output dir."""
-+ info_object = buildinfo.DetermineBuildInfo()
-+
-+ class DummyOpts(object):
-+ def __init__(self):
-+ self.input = '../grit/testdata/buildinfo.grd'
-+ self.print_header = False
-+ self.verbose = False
-+ self.extra_verbose = False
-+ info_object.Run(DummyOpts(), ['-o', '../grit/testdata'])
-+ output = self.buf.getvalue().replace('\\', '/')
-+ self.failUnless(
-+ output.count(r'rc_all|../grit/testdata/sv_sidebar_loading.html'))
-+ self.failUnless(output.count(r'rc_header|../grit/testdata/resource.h'))
-+ self.failUnless(
-+ output.count(r'rc_all|../grit/testdata/en_generated_resources.rc'))
-+ self.failUnless(
-+ output.count(r'rc_all|../grit/testdata/sv_generated_resources.rc'))
-+ self.failUnless(output.count(r'input|../grit/testdata/substitute.xmb'))
-+ self.failUnlessEqual(0,
-+ output.count(r'rc_all|../grit/testdata/sv_welcome_toast.html'))
-+ self.failUnless(
-+ output.count(r'rc_all|../grit/testdata/en_welcome_toast.html'))
-+
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/tool/count.py b/tools/grit/grit/tool/count.py
-new file mode 100644
-index 0000000000..ab37f2ddb3
---- /dev/null
-+++ b/tools/grit/grit/tool/count.py
-@@ -0,0 +1,52 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Count number of occurrences of a given message ID.'''
-+
-+from __future__ import print_function
-+
-+import getopt
-+import sys
-+
-+from grit import grd_reader
-+from grit.tool import interface
-+
-+
-+class CountMessage(interface.Tool):
-+ '''Count the number of times a given message ID is used.'''
-+
-+ def __init__(self):
-+ pass
-+
-+ def ShortDescription(self):
-+ return 'Count the number of times a given message ID is used.'
-+
-+ def ParseOptions(self, args):
-+ """Set this objects and return all non-option arguments."""
-+ own_opts, args = getopt.getopt(args, '', ('help',))
-+ for key, val in own_opts:
-+ if key == '--help':
-+ self.ShowUsage()
-+ sys.exit(0)
-+ return args
-+
-+ def Run(self, opts, args):
-+ args = self.ParseOptions(args)
-+ if len(args) != 1:
-+ print('This tool takes a single tool-specific argument, the message '
-+ 'ID to count.')
-+ return 2
-+ self.SetOptions(opts)
-+
-+ id = args[0]
-+ res_tree = grd_reader.Parse(opts.input, debug=opts.extra_verbose)
-+ res_tree.OnlyTheseTranslations([])
-+ res_tree.RunGatherers()
-+
-+ count = 0
-+ for c in res_tree.UberClique().AllCliques():
-+ if c.GetId() == id:
-+ count += 1
-+
-+ print("There are %d occurrences of message %s." % (count, id))
-diff --git a/tools/grit/grit/tool/diff_structures.py b/tools/grit/grit/tool/diff_structures.py
-new file mode 100644
-index 0000000000..d69e009b58
---- /dev/null
-+++ b/tools/grit/grit/tool/diff_structures.py
-@@ -0,0 +1,119 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''The 'grit sdiff' tool.
-+'''
-+
-+from __future__ import print_function
-+
-+import os
-+import getopt
-+import sys
-+import tempfile
-+
-+from grit.node import structure
-+from grit.tool import interface
-+
-+from grit import constants
-+from grit import util
-+
-+# Builds the description for the tool (used as the __doc__
-+# for the DiffStructures class).
-+_class_doc = """\
-+Allows you to view the differences in the structure of two files,
-+disregarding their translateable content. Translateable portions of
-+each file are changed to the string "TTTTTT" before invoking the diff program
-+specified by the P4DIFF environment variable.
-+
-+Usage: grit sdiff [-t TYPE] [-s SECTION] [-e ENCODING] LEFT RIGHT
-+
-+LEFT and RIGHT are the files you want to diff. SECTION is required
-+for structure types like 'dialog' to identify the part of the file to look at.
-+ENCODING indicates the encoding of the left and right files (default 'cp1252').
-+TYPE can be one of the following, defaults to 'tr_html':
-+"""
-+for gatherer in structure._GATHERERS:
-+ _class_doc += " - %s\n" % gatherer
-+
-+
-+class DiffStructures(interface.Tool):
-+ __doc__ = _class_doc
-+
-+ def __init__(self):
-+ self.section = None
-+ self.left_encoding = 'cp1252'
-+ self.right_encoding = 'cp1252'
-+ self.structure_type = 'tr_html'
-+
-+ def ShortDescription(self):
-+ return 'View differences without regard for translateable portions.'
-+
-+ def Run(self, global_opts, args):
-+ (opts, args) = getopt.getopt(args, 's:e:t:',
-+ ('help', 'left_encoding=', 'right_encoding='))
-+ for key, val in opts:
-+ if key == '-s':
-+ self.section = val
-+ elif key == '-e':
-+ self.left_encoding = val
-+ self.right_encoding = val
-+ elif key == '-t':
-+ self.structure_type = val
-+ elif key == '--left_encoding':
-+ self.left_encoding = val
-+ elif key == '--right_encoding':
-+ self.right_encoding == val
-+ elif key == '--help':
-+ self.ShowUsage()
-+ sys.exit(0)
-+
-+ if len(args) != 2:
-+ print("Incorrect usage - 'grit help sdiff' for usage details.")
-+ return 2
-+
-+ if 'P4DIFF' not in os.environ:
-+ print("Environment variable P4DIFF not set; defaulting to 'windiff'.")
-+ diff_program = 'windiff'
-+ else:
-+ diff_program = os.environ['P4DIFF']
-+
-+ left_trans = self.MakeStaticTranslation(args[0], self.left_encoding)
-+ try:
-+ try:
-+ right_trans = self.MakeStaticTranslation(args[1], self.right_encoding)
-+
-+ os.system('%s %s %s' % (diff_program, left_trans, right_trans))
-+ finally:
-+ os.unlink(right_trans)
-+ finally:
-+ os.unlink(left_trans)
-+
-+ def MakeStaticTranslation(self, original_filename, encoding):
-+ """Given the name of the structure type (self.structure_type), the filename
-+ of the file holding the original structure, and optionally the "section" key
-+ identifying the part of the file to look at (self.section), creates a
-+ temporary file holding a "static" translation of the original structure
-+ (i.e. one where all translateable parts have been replaced with "TTTTTT")
-+ and returns the temporary file name. It is the caller's responsibility to
-+ delete the file when finished.
-+
-+ Args:
-+ original_filename: 'c:\\bingo\\bla.rc'
-+
-+ Return:
-+ 'c:\\temp\\werlkjsdf334.tmp'
-+ """
-+ original = structure._GATHERERS[self.structure_type](original_filename,
-+ extkey=self.section,
-+ encoding=encoding)
-+ original.Parse()
-+ translated = original.Translate(constants.CONSTANT_LANGUAGE, False)
-+
-+ fname = tempfile.mktemp()
-+ with util.WrapOutputStream(open(fname, 'wb')) as writer:
-+ writer.write("Original filename: %s\n=============\n\n"
-+ % original_filename)
-+ writer.write(translated) # write in UTF-8
-+
-+ return fname
-diff --git a/tools/grit/grit/tool/diff_structures_unittest.py b/tools/grit/grit/tool/diff_structures_unittest.py
-new file mode 100644
-index 0000000000..a6d7585761
---- /dev/null
-+++ b/tools/grit/grit/tool/diff_structures_unittest.py
-@@ -0,0 +1,46 @@
-+#!/usr/bin/env python
-+# Copyright 2020 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for the 'grit newgrd' tool.'''
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+import unittest
-+
-+from grit.tool import diff_structures
-+
-+
-+class DummyOpts(object):
-+ """Options needed by NewGrd."""
-+
-+
-+class DiffStructuresUnittest(unittest.TestCase):
-+
-+ def testMissingFiles(self):
-+ """Verify failure w/out file inputs."""
-+ tool = diff_structures.DiffStructures()
-+ ret = tool.Run(DummyOpts(), [])
-+ self.assertIsNotNone(ret)
-+ self.assertGreater(ret, 0)
-+
-+ ret = tool.Run(DummyOpts(), ['left'])
-+ self.assertIsNotNone(ret)
-+ self.assertGreater(ret, 0)
-+
-+ def testTooManyArgs(self):
-+ """Verify failure w/too many inputs."""
-+ tool = diff_structures.DiffStructures()
-+ ret = tool.Run(DummyOpts(), ['a', 'b', 'c'])
-+ self.assertIsNotNone(ret)
-+ self.assertGreater(ret, 0)
-+
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/tool/interface.py b/tools/grit/grit/tool/interface.py
-new file mode 100644
-index 0000000000..e923205223
---- /dev/null
-+++ b/tools/grit/grit/tool/interface.py
-@@ -0,0 +1,62 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Base class and interface for tools.
-+'''
-+
-+from __future__ import print_function
-+
-+class Tool(object):
-+ '''Base class for all tools. Tools should use their docstring (i.e. the
-+ class-level docstring) for the help they want to have printed when they
-+ are invoked.'''
-+
-+ #
-+ # Interface (abstract methods)
-+ #
-+
-+ def ShortDescription(self):
-+ '''Returns a short description of the functionality of the tool.'''
-+ raise NotImplementedError()
-+
-+ def Run(self, global_options, my_arguments):
-+ '''Runs the tool.
-+
-+ Args:
-+ global_options: object grit_runner.Options
-+ my_arguments: [arg1 arg2 ...]
-+
-+ Return:
-+ 0 for success, non-0 for error
-+ '''
-+ raise NotImplementedError()
-+
-+ #
-+ # Base class implementation
-+ #
-+
-+ def __init__(self):
-+ self.o = None
-+
-+ def ShowUsage(self):
-+ '''Show usage text for this tool.'''
-+ print(self.__doc__)
-+
-+ def SetOptions(self, opts):
-+ self.o = opts
-+
-+ def Out(self, text):
-+ '''Always writes out 'text'.'''
-+ self.o.output_stream.write(text)
-+
-+ def VerboseOut(self, text):
-+ '''Writes out 'text' if the verbose option is on.'''
-+ if self.o.verbose:
-+ self.o.output_stream.write(text)
-+
-+ def ExtraVerboseOut(self, text):
-+ '''Writes out 'text' if the extra-verbose option is on.
-+ '''
-+ if self.o.extra_verbose:
-+ self.o.output_stream.write(text)
-diff --git a/tools/grit/grit/tool/menu_from_parts.py b/tools/grit/grit/tool/menu_from_parts.py
-new file mode 100644
-index 0000000000..fcec26c5b1
---- /dev/null
-+++ b/tools/grit/grit/tool/menu_from_parts.py
-@@ -0,0 +1,79 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''The 'grit menufromparts' tool.'''
-+
-+from __future__ import print_function
-+
-+import six
-+
-+from grit import grd_reader
-+from grit import util
-+from grit import xtb_reader
-+from grit.tool import interface
-+from grit.tool import transl2tc
-+
-+import grit.extern.tclib
-+
-+
-+class MenuTranslationsFromParts(interface.Tool):
-+ '''One-off tool to generate translated menu messages (where each menu is kept
-+in a single message) based on existing translations of the individual menu
-+items. Was needed when changing menus from being one message per menu item
-+to being one message for the whole menu.'''
-+
-+ def ShortDescription(self):
-+ return ('Create translations of whole menus from existing translations of '
-+ 'menu items.')
-+
-+ def Run(self, globopt, args):
-+ self.SetOptions(globopt)
-+ assert len(args) == 2, "Need exactly two arguments, the XTB file and the output file"
-+
-+ xtb_file = args[0]
-+ output_file = args[1]
-+
-+ grd = grd_reader.Parse(self.o.input, debug=self.o.extra_verbose)
-+ grd.OnlyTheseTranslations([]) # don't load translations
-+ grd.RunGatherers()
-+
-+ xtb = {}
-+ def Callback(msg_id, parts):
-+ msg = []
-+ for part in parts:
-+ if part[0]:
-+ msg = []
-+ break # it had a placeholder so ignore it
-+ else:
-+ msg.append(part[1])
-+ if len(msg):
-+ xtb[msg_id] = ''.join(msg)
-+ with open(xtb_file, 'rb') as f:
-+ xtb_reader.Parse(f, Callback)
-+
-+ translations = [] # list of translations as per transl2tc.WriteTranslations
-+ for node in grd:
-+ if node.name == 'structure' and node.attrs['type'] == 'menu':
-+ assert len(node.GetCliques()) == 1
-+ message = node.GetCliques()[0].GetMessage()
-+ translation = []
-+
-+ contents = message.GetContent()
-+ for part in contents:
-+ if isinstance(part, six.string_types):
-+ id = grit.extern.tclib.GenerateMessageId(part)
-+ if id not in xtb:
-+ print("WARNING didn't find all translations for menu %s" %
-+ (node.attrs['name'],))
-+ translation = []
-+ break
-+ translation.append(xtb[id])
-+ else:
-+ translation.append(part.GetPresentation())
-+
-+ if len(translation):
-+ translations.append([message.GetId(), ''.join(translation)])
-+
-+ with util.WrapOutputStream(open(output_file, 'wb')) as f:
-+ transl2tc.TranslationToTc.WriteTranslations(f, translations)
-diff --git a/tools/grit/grit/tool/newgrd.py b/tools/grit/grit/tool/newgrd.py
-new file mode 100644
-index 0000000000..66a18e9c04
---- /dev/null
-+++ b/tools/grit/grit/tool/newgrd.py
-@@ -0,0 +1,85 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Tool to create a new, empty .grd file with all the basic sections.
-+'''
-+
-+from __future__ import print_function
-+
-+import getopt
-+import sys
-+
-+from grit.tool import interface
-+from grit import constants
-+from grit import util
-+
-+# The contents of the new .grd file
-+_FILE_CONTENTS = '''\
-+<?xml version="1.0" encoding="UTF-8"?>
-+<grit base_dir="." latest_public_release="0" current_release="1"
-+ source_lang_id="en" enc_check="%s">
-+ <outputs>
-+ <!-- TODO add each of your output files. Modify the three below, and add
-+ your own for your various languages. See the user's guide for more
-+ details.
-+ Note that all output references are relative to the output directory
-+ which is specified at build time. -->
-+ <output filename="resource.h" type="rc_header" />
-+ <output filename="en_resource.rc" type="rc_all" />
-+ <output filename="fr_resource.rc" type="rc_all" />
-+ </outputs>
-+ <translations>
-+ <!-- TODO add references to each of the XTB files (from the Translation
-+ Console) that contain translations of messages in your project. Each
-+ takes a form like <file path="english.xtb" />. Remember that all file
-+ references are relative to this .grd file. -->
-+ </translations>
-+ <release seq="1">
-+ <includes>
-+ <!-- TODO add a list of your included resources here, e.g. BMP and GIF
-+ resources. -->
-+ </includes>
-+ <structures>
-+ <!-- TODO add a list of all your structured resources here, e.g. HTML
-+ templates, menus, dialogs etc. Note that for menus, dialogs and version
-+ information resources you reference an .rc file containing them.-->
-+ </structures>
-+ <messages>
-+ <!-- TODO add all of your "string table" messages here. Remember to
-+ change nontranslateable parts of the messages into placeholders (using the
-+ <ph> element). You can also use the 'grit add' tool to help you identify
-+ nontranslateable parts and create placeholders for them. -->
-+ </messages>
-+ </release>
-+</grit>''' % constants.ENCODING_CHECK
-+
-+
-+class NewGrd(interface.Tool):
-+ '''Usage: grit newgrd OUTPUT_FILE
-+
-+Creates a new, empty .grd file OUTPUT_FILE with comments about what to put
-+where in the file.'''
-+
-+ def ShortDescription(self):
-+ return 'Create a new empty .grd file.'
-+
-+ def ParseOptions(self, args):
-+ """Set this objects and return all non-option arguments."""
-+ own_opts, args = getopt.getopt(args, '', ('help',))
-+ for key, val in own_opts:
-+ if key == '--help':
-+ self.ShowUsage()
-+ sys.exit(0)
-+ return args
-+
-+ def Run(self, opts, args):
-+ args = self.ParseOptions(args)
-+ if len(args) != 1:
-+ print('This tool requires exactly one argument, the name of the output '
-+ 'file.')
-+ return 2
-+ filename = args[0]
-+ with util.WrapOutputStream(open(filename, 'wb'), 'utf-8') as out:
-+ out.write(_FILE_CONTENTS)
-+ print("Wrote file %s" % filename)
-diff --git a/tools/grit/grit/tool/newgrd_unittest.py b/tools/grit/grit/tool/newgrd_unittest.py
-new file mode 100644
-index 0000000000..f7c8831df5
---- /dev/null
-+++ b/tools/grit/grit/tool/newgrd_unittest.py
-@@ -0,0 +1,51 @@
-+#!/usr/bin/env python
-+# Copyright 2020 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for the 'grit newgrd' tool.'''
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+import unittest
-+
-+from grit import util
-+from grit.tool import newgrd
-+
-+
-+class DummyOpts(object):
-+ """Options needed by NewGrd."""
-+
-+
-+class NewgrdUnittest(unittest.TestCase):
-+
-+ def testNewFile(self):
-+ """Create a new file."""
-+ tool = newgrd.NewGrd()
-+ with util.TempDir({}) as output_dir:
-+ output_file = os.path.join(output_dir.GetPath(), 'new.grd')
-+ self.assertIsNone(tool.Run(DummyOpts(), [output_file]))
-+ self.assertTrue(os.path.exists(output_file))
-+
-+ def testMissingFile(self):
-+ """Verify failure w/out file output."""
-+ tool = newgrd.NewGrd()
-+ ret = tool.Run(DummyOpts(), [])
-+ self.assertIsNotNone(ret)
-+ self.assertGreater(ret, 0)
-+
-+ def testTooManyArgs(self):
-+ """Verify failure w/too many outputs."""
-+ tool = newgrd.NewGrd()
-+ ret = tool.Run(DummyOpts(), ['a', 'b'])
-+ self.assertIsNotNone(ret)
-+ self.assertGreater(ret, 0)
-+
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/tool/postprocess_interface.py b/tools/grit/grit/tool/postprocess_interface.py
-new file mode 100644
-index 0000000000..4bb8c5871f
---- /dev/null
-+++ b/tools/grit/grit/tool/postprocess_interface.py
-@@ -0,0 +1,29 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+''' Base class for postprocessing of RC files.
-+'''
-+
-+from __future__ import print_function
-+
-+class PostProcessor(object):
-+ ''' Base class for postprocessing of the RC file data before being
-+ output through the RC2GRD tool. You should implement this class if
-+ you want GRIT to do specific things to the RC files after it has
-+ converted the data into GRD format, i.e. change the content of the
-+ RC file, and put it into a P4 changelist, etc.'''
-+
-+
-+ def Process(self, rctext, rcpath, grdnode):
-+ ''' Processes the data in rctext and grdnode.
-+ Args:
-+ rctext: string containing the contents of the RC file being processed.
-+ rcpath: the path used to access the file.
-+ grdtext: the root node of the grd xml data generated by
-+ the rc2grd tool.
-+
-+ Return:
-+ The root node of the processed GRD tree.
-+ '''
-+ raise NotImplementedError()
-diff --git a/tools/grit/grit/tool/postprocess_unittest.py b/tools/grit/grit/tool/postprocess_unittest.py
-new file mode 100644
-index 0000000000..77fe228bbe
---- /dev/null
-+++ b/tools/grit/grit/tool/postprocess_unittest.py
-@@ -0,0 +1,64 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit test that checks postprocessing of files.
-+ Tests postprocessing by having the postprocessor
-+ modify the grd data tree, changing the message name attributes.
-+'''
-+
-+from __future__ import print_function
-+
-+import os
-+import re
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+import unittest
-+
-+import grit.tool.postprocess_interface
-+from grit.tool import rc2grd
-+
-+
-+class PostProcessingUnittest(unittest.TestCase):
-+
-+ def testPostProcessing(self):
-+ rctext = '''STRINGTABLE
-+BEGIN
-+ DUMMY_STRING_1 "String 1"
-+ // Some random description
-+ DUMMY_STRING_2 "This text was added during preprocessing"
-+END
-+ '''
-+ tool = rc2grd.Rc2Grd()
-+ class DummyOpts(object):
-+ verbose = False
-+ extra_verbose = False
-+ tool.o = DummyOpts()
-+ tool.post_process = 'grit.tool.postprocess_unittest.DummyPostProcessor'
-+ result = tool.Process(rctext, '.\resource.rc')
-+
-+ self.failUnless(
-+ result.children[2].children[2].children[0].attrs['name'] == 'SMART_STRING_1')
-+ self.failUnless(
-+ result.children[2].children[2].children[1].attrs['name'] == 'SMART_STRING_2')
-+
-+class DummyPostProcessor(grit.tool.postprocess_interface.PostProcessor):
-+ '''
-+ Post processing replaces all message name attributes containing "DUMMY" to
-+ "SMART".
-+ '''
-+ def Process(self, rctext, rcpath, grdnode):
-+ smarter = re.compile(r'(DUMMY)(.*)')
-+ messages = grdnode.children[2].children[2]
-+ for node in messages.children:
-+ name_attr = node.attrs['name']
-+ m = smarter.search(name_attr)
-+ if m:
-+ node.attrs['name'] = 'SMART' + m.group(2)
-+ return grdnode
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/tool/preprocess_interface.py b/tools/grit/grit/tool/preprocess_interface.py
-new file mode 100644
-index 0000000000..67974e704e
---- /dev/null
-+++ b/tools/grit/grit/tool/preprocess_interface.py
-@@ -0,0 +1,25 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+''' Base class for preprocessing of RC files.
-+'''
-+
-+from __future__ import print_function
-+
-+class PreProcessor(object):
-+ ''' Base class for preprocessing of the RC file data before being
-+ output through the RC2GRD tool. You should implement this class if
-+ you have specific constructs in your RC files that GRIT cannot handle.'''
-+
-+
-+ def Process(self, rctext, rcpath):
-+ ''' Processes the data in rctext.
-+ Args:
-+ rctext: string containing the contents of the RC file being processed
-+ rcpath: the path used to access the file.
-+
-+ Return:
-+ The processed text.
-+ '''
-+ raise NotImplementedError()
-diff --git a/tools/grit/grit/tool/preprocess_unittest.py b/tools/grit/grit/tool/preprocess_unittest.py
-new file mode 100644
-index 0000000000..40b95cd6f8
---- /dev/null
-+++ b/tools/grit/grit/tool/preprocess_unittest.py
-@@ -0,0 +1,50 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit test that checks preprocessing of files.
-+ Tests preprocessing by adding having the preprocessor
-+ provide the actual rctext data.
-+'''
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+import unittest
-+
-+import grit.tool.preprocess_interface
-+from grit.tool import rc2grd
-+
-+
-+class PreProcessingUnittest(unittest.TestCase):
-+
-+ def testPreProcessing(self):
-+ tool = rc2grd.Rc2Grd()
-+ class DummyOpts(object):
-+ verbose = False
-+ extra_verbose = False
-+ tool.o = DummyOpts()
-+ tool.pre_process = 'grit.tool.preprocess_unittest.DummyPreProcessor'
-+ result = tool.Process('', '.\resource.rc')
-+
-+ self.failUnless(
-+ result.children[2].children[2].children[0].attrs['name'] == 'DUMMY_STRING_1')
-+
-+class DummyPreProcessor(grit.tool.preprocess_interface.PreProcessor):
-+ def Process(self, rctext, rcpath):
-+ rctext = '''STRINGTABLE
-+BEGIN
-+ DUMMY_STRING_1 "String 1"
-+ // Some random description
-+ DUMMY_STRING_2 "This text was added during preprocessing"
-+END
-+ '''
-+ return rctext
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/tool/rc2grd.py b/tools/grit/grit/tool/rc2grd.py
-new file mode 100644
-index 0000000000..3195b39000
---- /dev/null
-+++ b/tools/grit/grit/tool/rc2grd.py
-@@ -0,0 +1,418 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''The 'grit rc2grd' tool.'''
-+
-+from __future__ import print_function
-+
-+import os.path
-+import getopt
-+import re
-+import sys
-+
-+import six
-+from six import StringIO
-+
-+import grit.node.empty
-+from grit.node import include
-+from grit.node import structure
-+from grit.node import message
-+
-+from grit.gather import rc
-+from grit.gather import tr_html
-+
-+from grit.tool import interface
-+from grit.tool import postprocess_interface
-+from grit.tool import preprocess_interface
-+
-+from grit import grd_reader
-+from grit import lazy_re
-+from grit import tclib
-+from grit import util
-+
-+
-+# Matches files referenced from an .rc file
-+_FILE_REF = lazy_re.compile(r'''
-+ ^(?P<id>[A-Z_0-9.]+)[ \t]+
-+ (?P<type>[A-Z_0-9]+)[ \t]+
-+ "(?P<file>.*?([^"]|""))"[ \t]*$''', re.VERBOSE | re.MULTILINE)
-+
-+
-+# Matches a dialog section
-+_DIALOG = lazy_re.compile(
-+ r'^(?P<id>[A-Z0-9_]+)\s+DIALOG(EX)?\s.+?^BEGIN\s*$.+?^END\s*$',
-+ re.MULTILINE | re.DOTALL)
-+
-+
-+# Matches a menu section
-+_MENU = lazy_re.compile(r'^(?P<id>[A-Z0-9_]+)\s+MENU.+?^BEGIN\s*$.+?^END\s*$',
-+ re.MULTILINE | re.DOTALL)
-+
-+
-+# Matches a versioninfo section
-+_VERSIONINFO = lazy_re.compile(
-+ r'^(?P<id>[A-Z0-9_]+)\s+VERSIONINFO\s.+?^BEGIN\s*$.+?^END\s*$',
-+ re.MULTILINE | re.DOTALL)
-+
-+
-+# Matches a stringtable
-+_STRING_TABLE = lazy_re.compile(
-+ (r'^STRINGTABLE(\s+(PRELOAD|DISCARDABLE|CHARACTERISTICS.+|LANGUAGE.+|'
-+ r'VERSION.+))*\s*\nBEGIN\s*$(?P<body>.+?)^END\s*$'),
-+ re.MULTILINE | re.DOTALL)
-+
-+
-+# Matches each message inside a stringtable, breaking it up into comments,
-+# the ID of the message, and the (RC-escaped) message text.
-+_MESSAGE = lazy_re.compile(r'''
-+ (?P<comment>(^\s+//.+?)*) # 0 or more lines of comments preceding the message
-+ ^\s*
-+ (?P<id>[A-Za-z0-9_]+) # id
-+ \s+
-+ "(?P<text>.*?([^"]|""))"([^"]|$) # The message itself
-+ ''', re.MULTILINE | re.DOTALL | re.VERBOSE)
-+
-+
-+# Matches each line of comment text in a multi-line comment.
-+_COMMENT_TEXT = lazy_re.compile(r'^\s*//\s*(?P<text>.+?)$', re.MULTILINE)
-+
-+
-+# Matches a string that is empty or all whitespace
-+_WHITESPACE_ONLY = lazy_re.compile(r'\A\s*\Z', re.MULTILINE)
-+
-+
-+# Finds printf and FormatMessage style format specifiers
-+# Uses non-capturing groups except for the outermost group, so the output of
-+# re.split() should include both the normal text and what we intend to
-+# replace with placeholders.
-+# TODO(joi) Check documentation for printf (and Windows variants) and FormatMessage
-+_FORMAT_SPECIFIER = lazy_re.compile(
-+ r'(%[-# +]?(?:[0-9]*|\*)(?:\.(?:[0-9]+|\*))?(?:h|l|L)?' # printf up to last char
-+ r'(?:d|i|o|u|x|X|e|E|f|F|g|G|c|r|s|ls|ws)' # printf last char
-+ r'|\$[1-9][0-9]*)') # FormatMessage
-+
-+
-+class Rc2Grd(interface.Tool):
-+ '''A tool for converting .rc files to .grd files. This tool is only for
-+converting the source (nontranslated) .rc file to a .grd file. For importing
-+existing translations, use the rc2xtb tool.
-+
-+Usage: grit [global options] rc2grd [OPTIONS] RCFILE
-+
-+The tool takes a single argument, which is the path to the .rc file to convert.
-+It outputs a .grd file with the same name in the same directory as the .rc file.
-+The .grd file may have one or more TODO comments for things that have to be
-+cleaned up manually.
-+
-+OPTIONS may be any of the following:
-+
-+ -e ENCODING Specify the ENCODING of the .rc file. Default is 'cp1252'.
-+
-+ -h TYPE Specify the TYPE attribute for HTML structures.
-+ Default is 'tr_html'.
-+
-+ -u ENCODING Specify the ENCODING of HTML files. Default is 'utf-8'.
-+
-+ -n MATCH Specify the regular expression to match in comments that will
-+ indicate that the resource the comment belongs to is not
-+ translateable. Default is 'Not locali(s|z)able'.
-+
-+ -r GRDFILE Specify that GRDFILE should be used as a "role model" for
-+ any placeholders that otherwise would have had TODO names.
-+ This attempts to find an identical message in the GRDFILE
-+ and uses that instead of the automatically placeholderized
-+ message.
-+
-+ --pre CLASS Specify an optional, fully qualified classname, which
-+ has to be a subclass of grit.tool.PreProcessor, to
-+ run on the text of the RC file before conversion occurs.
-+ This can be used to support constructs in the RC files
-+ that GRIT cannot handle on its own.
-+
-+ --post CLASS Specify an optional, fully qualified classname, which
-+ has to be a subclass of grit.tool.PostProcessor, to
-+ run on the text of the converted RC file.
-+ This can be used to alter the content of the RC file
-+ based on the conversion that occured.
-+
-+For menus, dialogs and version info, the .grd file will refer to the original
-+.rc file. Once conversion is complete, you can strip the original .rc file
-+of its string table and all comments as these will be available in the .grd
-+file.
-+
-+Note that this tool WILL NOT obey C preprocessor rules, so even if something
-+is #if 0-ed out it will still be included in the output of this tool
-+Therefore, if your .rc file contains sections like this, you should run the
-+C preprocessor on the .rc file or manually edit it before using this tool.
-+'''
-+
-+ def ShortDescription(self):
-+ return 'A tool for converting .rc source files to .grd files.'
-+
-+ def __init__(self):
-+ self.input_encoding = 'cp1252'
-+ self.html_type = 'tr_html'
-+ self.html_encoding = 'utf-8'
-+ self.not_localizable_re = re.compile('Not locali(s|z)able')
-+ self.role_model = None
-+ self.pre_process = None
-+ self.post_process = None
-+
-+ def ParseOptions(self, args, help_func=None):
-+ '''Given a list of arguments, set this object's options and return
-+ all non-option arguments.
-+ '''
-+ (own_opts, args) = getopt.getopt(args, 'e:h:u:n:r',
-+ ('help', 'pre=', 'post='))
-+ for (key, val) in own_opts:
-+ if key == '-e':
-+ self.input_encoding = val
-+ elif key == '-h':
-+ self.html_type = val
-+ elif key == '-u':
-+ self.html_encoding = val
-+ elif key == '-n':
-+ self.not_localizable_re = re.compile(val)
-+ elif key == '-r':
-+ self.role_model = grd_reader.Parse(val)
-+ elif key == '--pre':
-+ self.pre_process = val
-+ elif key == '--post':
-+ self.post_process = val
-+ elif key == '--help':
-+ if help_func is None:
-+ self.ShowUsage()
-+ else:
-+ help_func()
-+ sys.exit(0)
-+ return args
-+
-+ def Run(self, opts, args):
-+ args = self.ParseOptions(args)
-+ if len(args) != 1:
-+ print('This tool takes a single tool-specific argument, the path to the\n'
-+ '.rc file to process.')
-+ return 2
-+ self.SetOptions(opts)
-+
-+ path = args[0]
-+ out_path = os.path.join(util.dirname(path),
-+ os.path.splitext(os.path.basename(path))[0] + '.grd')
-+
-+ rctext = util.ReadFile(path, self.input_encoding)
-+ grd_text = six.text_type(self.Process(rctext, path))
-+ with util.WrapOutputStream(open(out_path, 'wb'), 'utf-8') as outfile:
-+ outfile.write(grd_text)
-+
-+ print('Wrote output file %s.\nPlease check for TODO items in the file.' %
-+ (out_path,))
-+
-+
-+ def Process(self, rctext, rc_path):
-+ '''Processes 'rctext' and returns a resource tree corresponding to it.
-+
-+ Args:
-+ rctext: complete text of the rc file
-+ rc_path: 'resource\resource.rc'
-+
-+ Return:
-+ grit.node.base.Node subclass
-+ '''
-+
-+ if self.pre_process:
-+ preprocess_class = util.NewClassInstance(self.pre_process,
-+ preprocess_interface.PreProcessor)
-+ if preprocess_class:
-+ rctext = preprocess_class.Process(rctext, rc_path)
-+ else:
-+ self.Out(
-+ 'PreProcessing class could not be found. Skipping preprocessing.\n')
-+
-+ # Start with a basic skeleton for the .grd file
-+ root = grd_reader.Parse(StringIO(
-+ '''<?xml version="1.0" encoding="UTF-8"?>
-+ <grit base_dir="." latest_public_release="0"
-+ current_release="1" source_lang_id="en">
-+ <outputs />
-+ <translations />
-+ <release seq="1">
-+ <includes />
-+ <structures />
-+ <messages />
-+ </release>
-+ </grit>'''), util.dirname(rc_path))
-+ includes = root.children[2].children[0]
-+ structures = root.children[2].children[1]
-+ messages = root.children[2].children[2]
-+ assert (isinstance(includes, grit.node.empty.IncludesNode) and
-+ isinstance(structures, grit.node.empty.StructuresNode) and
-+ isinstance(messages, grit.node.empty.MessagesNode))
-+
-+ self.AddIncludes(rctext, includes)
-+ self.AddStructures(rctext, structures, os.path.basename(rc_path))
-+ self.AddMessages(rctext, messages)
-+
-+ self.VerboseOut('Validating that all IDs are unique...\n')
-+ root.ValidateUniqueIds()
-+ self.ExtraVerboseOut('Done validating that all IDs are unique.\n')
-+
-+ if self.post_process:
-+ postprocess_class = util.NewClassInstance(self.post_process,
-+ postprocess_interface.PostProcessor)
-+ if postprocess_class:
-+ root = postprocess_class.Process(rctext, rc_path, root)
-+ else:
-+ self.Out(
-+ 'PostProcessing class could not be found. Skipping postprocessing.\n')
-+
-+ return root
-+
-+
-+ def IsHtml(self, res_type, fname):
-+ '''Check whether both the type and file extension indicate HTML'''
-+ fext = fname.split('.')[-1].lower()
-+ return res_type == 'HTML' and fext in ('htm', 'html')
-+
-+
-+ def AddIncludes(self, rctext, node):
-+ '''Scans 'rctext' for included resources (e.g. BITMAP, ICON) and
-+ adds each included resource as an <include> child node of 'node'.'''
-+ for m in _FILE_REF.finditer(rctext):
-+ id = m.group('id')
-+ res_type = m.group('type').upper()
-+ fname = rc.Section.UnEscape(m.group('file'))
-+ assert fname.find('\n') == -1
-+ if not self.IsHtml(res_type, fname):
-+ self.VerboseOut('Processing %s with ID %s (filename: %s)\n' %
-+ (res_type, id, fname))
-+ node.AddChild(include.IncludeNode.Construct(node, id, res_type, fname))
-+
-+
-+ def AddStructures(self, rctext, node, rc_filename):
-+ '''Scans 'rctext' for structured resources (e.g. menus, dialogs, version
-+ information resources and HTML templates) and adds each as a <structure>
-+ child of 'node'.'''
-+ # First add HTML includes
-+ for m in _FILE_REF.finditer(rctext):
-+ id = m.group('id')
-+ res_type = m.group('type').upper()
-+ fname = rc.Section.UnEscape(m.group('file'))
-+ if self.IsHtml(type, fname):
-+ node.AddChild(structure.StructureNode.Construct(
-+ node, id, self.html_type, fname, self.html_encoding))
-+
-+ # Then add all RC includes
-+ def AddStructure(res_type, id):
-+ self.VerboseOut('Processing %s with ID %s\n' % (res_type, id))
-+ node.AddChild(structure.StructureNode.Construct(node, id, res_type,
-+ rc_filename,
-+ encoding=self.input_encoding))
-+ for m in _MENU.finditer(rctext):
-+ AddStructure('menu', m.group('id'))
-+ for m in _DIALOG.finditer(rctext):
-+ AddStructure('dialog', m.group('id'))
-+ for m in _VERSIONINFO.finditer(rctext):
-+ AddStructure('version', m.group('id'))
-+
-+
-+ def AddMessages(self, rctext, node):
-+ '''Scans 'rctext' for all messages in string tables, preprocesses them as
-+ much as possible for placeholders (e.g. messages containing $1, $2 or %s, %d
-+ type format specifiers get those specifiers replaced with placeholders, and
-+ HTML-formatted messages get run through the HTML-placeholderizer). Adds
-+ each message as a <message> node child of 'node'.'''
-+ for tm in _STRING_TABLE.finditer(rctext):
-+ table = tm.group('body')
-+ for mm in _MESSAGE.finditer(table):
-+ comment_block = mm.group('comment')
-+ comment_text = []
-+ for cm in _COMMENT_TEXT.finditer(comment_block):
-+ comment_text.append(cm.group('text'))
-+ comment_text = ' '.join(comment_text)
-+
-+ id = mm.group('id')
-+ text = rc.Section.UnEscape(mm.group('text'))
-+
-+ self.VerboseOut('Processing message %s (text: "%s")\n' % (id, text))
-+
-+ msg_obj = self.Placeholderize(text)
-+
-+ # Messages that contain only placeholders do not need translation.
-+ is_translateable = False
-+ for item in msg_obj.GetContent():
-+ if isinstance(item, six.string_types):
-+ if not _WHITESPACE_ONLY.match(item):
-+ is_translateable = True
-+
-+ if self.not_localizable_re.search(comment_text):
-+ is_translateable = False
-+
-+ message_meaning = ''
-+ internal_comment = ''
-+
-+ # If we have a "role model" (existing GRD file) and this node exists
-+ # in the role model, use the description, meaning and translateable
-+ # attributes from the role model.
-+ if self.role_model:
-+ role_node = self.role_model.GetNodeById(id)
-+ if role_node:
-+ is_translateable = role_node.IsTranslateable()
-+ message_meaning = role_node.attrs['meaning']
-+ comment_text = role_node.attrs['desc']
-+ internal_comment = role_node.attrs['internal_comment']
-+
-+ # For nontranslateable messages, we don't want the complexity of
-+ # placeholderizing everything.
-+ if not is_translateable:
-+ msg_obj = tclib.Message(text=text)
-+
-+ msg_node = message.MessageNode.Construct(node, msg_obj, id,
-+ desc=comment_text,
-+ translateable=is_translateable,
-+ meaning=message_meaning)
-+ msg_node.attrs['internal_comment'] = internal_comment
-+
-+ node.AddChild(msg_node)
-+ self.ExtraVerboseOut('Done processing message %s\n' % id)
-+
-+
-+ def Placeholderize(self, text):
-+ '''Creates a tclib.Message object from 'text', attempting to recognize
-+ a few different formats of text that can be automatically placeholderized
-+ (HTML code, printf-style format strings, and FormatMessage-style format
-+ strings).
-+ '''
-+
-+ try:
-+ # First try HTML placeholderizing.
-+ # TODO(joi) Allow use of non-TotalRecall flavors of HTML placeholderizing
-+ msg = tr_html.HtmlToMessage(text, True)
-+ for item in msg.GetContent():
-+ if not isinstance(item, six.string_types):
-+ return msg # Contained at least one placeholder, so we're done
-+
-+ # HTML placeholderization didn't do anything, so try to find printf or
-+ # FormatMessage format specifiers and change them into placeholders.
-+ msg = tclib.Message()
-+ parts = _FORMAT_SPECIFIER.split(text)
-+ todo_counter = 1 # We make placeholder IDs 'TODO_0001' etc.
-+ for part in parts:
-+ if _FORMAT_SPECIFIER.match(part):
-+ msg.AppendPlaceholder(tclib.Placeholder(
-+ 'TODO_%04d' % todo_counter, part, 'TODO'))
-+ todo_counter += 1
-+ elif part != '':
-+ msg.AppendText(part)
-+
-+ if self.role_model and len(parts) > 1: # there are TODO placeholders
-+ role_model_msg = self.role_model.UberClique().BestCliqueByOriginalText(
-+ msg.GetRealContent(), '')
-+ if role_model_msg:
-+ # replace wholesale to get placeholder names and examples
-+ msg = role_model_msg
-+
-+ return msg
-+ except:
-+ print('Exception processing message with text "%s"' % text)
-+ raise
-diff --git a/tools/grit/grit/tool/rc2grd_unittest.py b/tools/grit/grit/tool/rc2grd_unittest.py
-new file mode 100644
-index 0000000000..6d53794c27
---- /dev/null
-+++ b/tools/grit/grit/tool/rc2grd_unittest.py
-@@ -0,0 +1,163 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for grit.tool.rc2grd'''
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+import re
-+import unittest
-+
-+from six import StringIO
-+
-+from grit import grd_reader
-+from grit import util
-+from grit.node import base
-+from grit.tool import rc2grd
-+
-+
-+class Rc2GrdUnittest(unittest.TestCase):
-+ def testPlaceholderize(self):
-+ tool = rc2grd.Rc2Grd()
-+ original = "Hello %s, how are you? I'm $1 years old!"
-+ msg = tool.Placeholderize(original)
-+ self.failUnless(msg.GetPresentableContent() == "Hello TODO_0001, how are you? I'm TODO_0002 years old!")
-+ self.failUnless(msg.GetRealContent() == original)
-+
-+ def testHtmlPlaceholderize(self):
-+ tool = rc2grd.Rc2Grd()
-+ original = "Hello <b>[USERNAME]</b>, how are you? I'm [AGE] years old!"
-+ msg = tool.Placeholderize(original)
-+ self.failUnless(msg.GetPresentableContent() ==
-+ "Hello BEGIN_BOLDX_USERNAME_XEND_BOLD, how are you? I'm X_AGE_X years old!")
-+ self.failUnless(msg.GetRealContent() == original)
-+
-+ def testMenuWithoutWhitespaceRegression(self):
-+ # There was a problem in the original regular expression for parsing out
-+ # menu sections, that would parse the following block of text as a single
-+ # menu instead of two.
-+ two_menus = '''
-+// Hyper context menus
-+IDR_HYPERMENU_FOLDER MENU
-+BEGIN
-+ POPUP "HyperFolder"
-+ BEGIN
-+ MENUITEM "Open Containing Folder", IDM_OPENFOLDER
-+ END
-+END
-+
-+IDR_HYPERMENU_FILE MENU
-+BEGIN
-+ POPUP "HyperFile"
-+ BEGIN
-+ MENUITEM "Open Folder", IDM_OPENFOLDER
-+ END
-+END
-+
-+'''
-+ self.failUnless(len(rc2grd._MENU.findall(two_menus)) == 2)
-+
-+ def testRegressionScriptWithTranslateable(self):
-+ tool = rc2grd.Rc2Grd()
-+
-+ # test rig
-+ class DummyNode(base.Node):
-+ def AddChild(self, item):
-+ self.node = item
-+ verbose = False
-+ extra_verbose = False
-+ tool.not_localizable_re = re.compile('')
-+ tool.o = DummyNode()
-+
-+ rc_text = '''STRINGTABLE\nBEGIN\nID_BINGO "<SPAN id=hp style='BEHAVIOR: url(#default#homepage)'></SPAN><script>if (!hp.isHomePage('[$~HOMEPAGE~$]')) {document.write(""<a href=\\""[$~SETHOMEPAGEURL~$]\\"" >Set As Homepage</a> - "");}</script>"\nEND\n'''
-+ tool.AddMessages(rc_text, tool.o)
-+ self.failUnless(tool.o.node.GetCdata().find('Set As Homepage') != -1)
-+
-+ # TODO(joi) Improve the HTML parser to support translateables inside
-+ # <script> blocks?
-+ self.failUnless(tool.o.node.attrs['translateable'] == 'false')
-+
-+ def testRoleModel(self):
-+ rc_text = ('STRINGTABLE\n'
-+ 'BEGIN\n'
-+ ' // This should not show up\n'
-+ ' IDS_BINGO "Hello %s, how are you?"\n'
-+ ' // The first description\n'
-+ ' IDS_BONGO "Hello %s, my name is %s, and yours?"\n'
-+ ' IDS_PROGRAMS_SHUTDOWN_TEXT "Google Desktop Search needs to close the following programs:\\n\\n$1\\nThe installation will not proceed if you choose to cancel."\n'
-+ 'END\n')
-+ tool = rc2grd.Rc2Grd()
-+ tool.role_model = grd_reader.Parse(StringIO(
-+ '''<?xml version="1.0" encoding="UTF-8"?>
-+ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
-+ <release seq="3">
-+ <messages>
-+ <message name="IDS_BINGO">
-+ Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, how are you?
-+ </message>
-+ <message name="IDS_BONGO" desc="The other description">
-+ Hello <ph name="USERNAME">%s<ex>Jakob</ex></ph>, my name is <ph name="ADMINNAME">%s<ex>Joi</ex></ph>, and yours?
-+ </message>
-+ <message name="IDS_PROGRAMS_SHUTDOWN_TEXT" desc="LIST_OF_PROGRAMS is replaced by a bulleted list of program names.">
-+ Google Desktop Search needs to close the following programs:
-+
-+<ph name="LIST_OF_PROGRAMS">$1<ex>Program 1, Program 2</ex></ph>
-+The installation will not proceed if you choose to cancel.
-+ </message>
-+ </messages>
-+ </release>
-+ </grit>'''), dir='.')
-+
-+ # test rig
-+ class DummyOpts(object):
-+ verbose = False
-+ extra_verbose = False
-+ tool.o = DummyOpts()
-+ result = tool.Process(rc_text, '.\resource.rc')
-+ self.failUnless(
-+ result.children[2].children[2].children[0].attrs['desc'] == '')
-+ self.failUnless(
-+ result.children[2].children[2].children[0].children[0].attrs['name'] == 'USERNAME')
-+ self.failUnless(
-+ result.children[2].children[2].children[1].attrs['desc'] == 'The other description')
-+ self.failUnless(
-+ result.children[2].children[2].children[1].attrs['meaning'] == '')
-+ self.failUnless(
-+ result.children[2].children[2].children[1].children[0].attrs['name'] == 'USERNAME')
-+ self.failUnless(
-+ result.children[2].children[2].children[1].children[1].attrs['name'] == 'ADMINNAME')
-+ self.failUnless(
-+ result.children[2].children[2].children[2].children[0].attrs['name'] == 'LIST_OF_PROGRAMS')
-+
-+ def testRunOutput(self):
-+ """Verify basic correct Run behavior."""
-+ tool = rc2grd.Rc2Grd()
-+ class DummyOpts(object):
-+ verbose = False
-+ extra_verbose = False
-+ with util.TempDir({}) as output_dir:
-+ rcfile = os.path.join(output_dir.GetPath(), 'foo.rc')
-+ open(rcfile, 'w').close()
-+ self.assertIsNone(tool.Run(DummyOpts(), [rcfile]))
-+ self.assertTrue(os.path.exists(os.path.join(output_dir.GetPath(), 'foo.grd')))
-+
-+ def testMissingOutput(self):
-+ """Verify failure with no args."""
-+ tool = rc2grd.Rc2Grd()
-+ class DummyOpts(object):
-+ verbose = False
-+ extra_verbose = False
-+ ret = tool.Run(DummyOpts(), [])
-+ self.assertIsNotNone(ret)
-+ self.assertGreater(ret, 0)
-+
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/tool/resize.py b/tools/grit/grit/tool/resize.py
-new file mode 100644
-index 0000000000..6a897c077e
---- /dev/null
-+++ b/tools/grit/grit/tool/resize.py
-@@ -0,0 +1,295 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''The 'grit resize' tool.
-+'''
-+
-+from __future__ import print_function
-+
-+import getopt
-+import os
-+import sys
-+
-+from grit import grd_reader
-+from grit import pseudo
-+from grit import util
-+from grit.format import rc
-+from grit.format import rc_header
-+from grit.node import include
-+from grit.tool import interface
-+
-+
-+# Template for the .vcproj file, with a couple of [[REPLACEABLE]] parts.
-+PROJECT_TEMPLATE = '''\
-+<?xml version="1.0" encoding="Windows-1252"?>
-+<VisualStudioProject
-+ ProjectType="Visual C++"
-+ Version="7.10"
-+ Name="[[DIALOG_NAME]]"
-+ ProjectGUID="[[PROJECT_GUID]]"
-+ Keyword="Win32Proj">
-+ <Platforms>
-+ <Platform
-+ Name="Win32"/>
-+ </Platforms>
-+ <Configurations>
-+ <Configuration
-+ Name="Debug|Win32"
-+ OutputDirectory="Debug"
-+ IntermediateDirectory="Debug"
-+ ConfigurationType="1"
-+ CharacterSet="2">
-+ </Configuration>
-+ </Configurations>
-+ <References>
-+ </References>
-+ <Files>
-+ <Filter
-+ Name="Resource Files"
-+ Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx"
-+ UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}">
-+ <File
-+ RelativePath=".\\[[DIALOG_NAME]].rc">
-+ </File>
-+ </Filter>
-+ </Files>
-+ <Globals>
-+ </Globals>
-+</VisualStudioProject>'''
-+
-+
-+# Template for the .rc file with a couple of [[REPLACEABLE]] parts.
-+# TODO(joi) Improve this (and the resource.h template) to allow saving and then
-+# reopening of the RC file in Visual Studio. Currently you can only open it
-+# once and change it, then after you close it you won't be able to reopen it.
-+RC_TEMPLATE = '''\
-+// This file is automatically generated by GRIT and intended for editing
-+// the layout of the dialogs contained in it. Do not edit anything but the
-+// dialogs. Any changes made to translateable portions of the dialogs will
-+// be ignored by GRIT.
-+
-+#include "resource.h"
-+#include <winresrc.h>
-+#ifdef IDC_STATIC
-+#undef IDC_STATIC
-+#endif
-+#define IDC_STATIC (-1)
-+
-+LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
-+
-+#pragma code_page([[CODEPAGE_NUM]])
-+
-+[[INCLUDES]]
-+
-+[[DIALOGS]]
-+'''
-+
-+
-+# Template for the resource.h file with a couple of [[REPLACEABLE]] parts.
-+HEADER_TEMPLATE = '''\
-+// This file is automatically generated by GRIT. Do not edit.
-+
-+#pragma once
-+
-+// Edit commands
-+#define ID_EDIT_CLEAR 0xE120
-+#define ID_EDIT_CLEAR_ALL 0xE121
-+#define ID_EDIT_COPY 0xE122
-+#define ID_EDIT_CUT 0xE123
-+#define ID_EDIT_FIND 0xE124
-+#define ID_EDIT_PASTE 0xE125
-+#define ID_EDIT_PASTE_LINK 0xE126
-+#define ID_EDIT_PASTE_SPECIAL 0xE127
-+#define ID_EDIT_REPEAT 0xE128
-+#define ID_EDIT_REPLACE 0xE129
-+#define ID_EDIT_SELECT_ALL 0xE12A
-+#define ID_EDIT_UNDO 0xE12B
-+#define ID_EDIT_REDO 0xE12C
-+
-+
-+[[DEFINES]]
-+'''
-+
-+
-+class ResizeDialog(interface.Tool):
-+ '''Generates an RC file, header and Visual Studio project that you can use
-+with Visual Studio's GUI resource editor to modify the layout of dialogs for
-+the language of your choice. You then use the RC file, after you resize the
-+dialog, for the language or languages of your choice, using the <skeleton> child
-+of the <structure> node for the dialog. The translateable bits of the dialog
-+will be ignored when you use the <skeleton> node (GRIT will instead use the
-+translateable bits from the original dialog) but the layout changes you make
-+will be used. Note that your layout changes must preserve the order of the
-+translateable elements in the RC file.
-+
-+Usage: grit resize [-f BASEFOLDER] [-l LANG] [-e RCENCODING] DIALOGID*
-+
-+Arguments:
-+ DIALOGID The 'name' attribute of a dialog to output for resizing. Zero
-+ or more of these parameters can be used. If none are
-+ specified, all dialogs from the input .grd file are output.
-+
-+Options:
-+
-+ -f BASEFOLDER The project will be created in a subfolder of BASEFOLDER.
-+ The name of the subfolder will be the first DIALOGID you
-+ specify. Defaults to '.'
-+
-+ -l LANG Specifies that the RC file should contain a dialog translated
-+ into the language LANG. The default is a cp1252-representable
-+ pseudotranslation, because Visual Studio's GUI RC editor only
-+ supports single-byte encodings.
-+
-+ -c CODEPAGE Code page number to indicate to the RC compiler the encoding
-+ of the RC file, default is something reasonable for the
-+ language you selected (but this does not work for every single
-+ language). See details on codepages below. NOTE that you do
-+ not need to specify the codepage unless the tool complains
-+ that it's not sure which codepage to use. See the following
-+ page for codepage numbers supported by Windows:
-+ http://www.microsoft.com/globaldev/reference/wincp.mspx
-+
-+ -D NAME[=VAL] Specify a C-preprocessor-like define NAME with optional
-+ value VAL (defaults to 1) which will be used to control
-+ conditional inclusion of resources.
-+
-+
-+IMPORTANT NOTE: For now, the tool outputs a UTF-8 encoded file for any language
-+that can not be represented in cp1252 (i.e. anything other than Western
-+European languages). You will need to open this file in a text editor and
-+save it using the codepage indicated in the #pragma code_page(XXXX) command
-+near the top of the file, before you open it in Visual Studio.
-+
-+'''
-+
-+ # TODO(joi) It would be cool to have this tool note the Perforce revision
-+ # of the original RC file somewhere, such that the <skeleton> node could warn
-+ # if the original RC file gets updated without the skeleton file being updated.
-+
-+ # TODO(joi) Would be cool to have option to add the files to Perforce
-+
-+ def __init__(self):
-+ self.lang = pseudo.PSEUDO_LANG
-+ self.defines = {}
-+ self.base_folder = '.'
-+ self.codepage_number = 1252
-+ self.codepage_number_specified_explicitly = False
-+
-+ def SetLanguage(self, lang):
-+ '''Sets the language code to output things in.
-+ '''
-+ self.lang = lang
-+ if not self.codepage_number_specified_explicitly:
-+ self.codepage_number = util.LanguageToCodepage(lang)
-+
-+ def GetEncoding(self):
-+ if self.codepage_number == 1200:
-+ return 'utf_16'
-+ if self.codepage_number == 65001:
-+ return 'utf_8'
-+ return 'cp%d' % self.codepage_number
-+
-+ def ShortDescription(self):
-+ return 'Generate a file where you can resize a given dialog.'
-+
-+ def Run(self, opts, args):
-+ self.SetOptions(opts)
-+
-+ own_opts, args = getopt.getopt(args, 'l:f:c:D:', ('help',))
-+ for key, val in own_opts:
-+ if key == '-l':
-+ self.SetLanguage(val)
-+ if key == '-f':
-+ self.base_folder = val
-+ if key == '-c':
-+ self.codepage_number = int(val)
-+ self.codepage_number_specified_explicitly = True
-+ if key == '-D':
-+ name, val = util.ParseDefine(val)
-+ self.defines[name] = val
-+ elif key == '--help':
-+ self.ShowUsage()
-+ sys.exit(0)
-+
-+ res_tree = grd_reader.Parse(opts.input, debug=opts.extra_verbose)
-+ res_tree.OnlyTheseTranslations([self.lang])
-+ res_tree.RunGatherers()
-+
-+ # Dialog IDs are either explicitly listed, or we output all dialogs from the
-+ # .grd file
-+ dialog_ids = args
-+ if not len(dialog_ids):
-+ for node in res_tree:
-+ if node.name == 'structure' and node.attrs['type'] == 'dialog':
-+ dialog_ids.append(node.attrs['name'])
-+
-+ self.Process(res_tree, dialog_ids)
-+
-+ def Process(self, grd, dialog_ids):
-+ '''Outputs an RC file and header file for the dialog 'dialog_id' stored in
-+ resource tree 'grd', to self.base_folder, as discussed in this class's
-+ documentation.
-+
-+ Arguments:
-+ grd: grd = grd_reader.Parse(...); grd.RunGatherers()
-+ dialog_ids: ['IDD_MYDIALOG', 'IDD_OTHERDIALOG']
-+ '''
-+ grd.SetOutputLanguage(self.lang)
-+ grd.SetDefines(self.defines)
-+
-+ project_name = dialog_ids[0]
-+
-+ dir_path = os.path.join(self.base_folder, project_name)
-+ if not os.path.isdir(dir_path):
-+ os.mkdir(dir_path)
-+
-+ # If this fails then we're not on Windows (or you don't have the required
-+ # win32all Python libraries installed), so what are you doing mucking
-+ # about with RC files anyway? :)
-+ # pylint: disable=import-error
-+ import pythoncom
-+
-+ # Create the .vcproj file
-+ project_text = PROJECT_TEMPLATE.replace(
-+ '[[PROJECT_GUID]]', str(pythoncom.CreateGuid())
-+ ).replace('[[DIALOG_NAME]]', project_name)
-+ fname = os.path.join(dir_path, '%s.vcproj' % project_name)
-+ self.WriteFile(fname, project_text)
-+ print("Wrote %s" % fname)
-+
-+ # Create the .rc file
-+ # Output all <include> nodes since the dialogs might depend on them (e.g.
-+ # for icons and bitmaps).
-+ include_items = []
-+ for node in grd.ActiveDescendants():
-+ if isinstance(node, include.IncludeNode):
-+ include_items.append(rc.FormatInclude(node, self.lang, '.'))
-+ rc_text = RC_TEMPLATE.replace('[[CODEPAGE_NUM]]',
-+ str(self.codepage_number))
-+ rc_text = rc_text.replace('[[INCLUDES]]', ''.join(include_items))
-+
-+ # Then output the dialogs we have been asked to output.
-+ dialogs = []
-+ for dialog_id in dialog_ids:
-+ node = grd.GetNodeById(dialog_id)
-+ assert node.name == 'structure' and node.attrs['type'] == 'dialog'
-+ # TODO(joi) Add exception handling for better error reporting
-+ dialogs.append(rc.FormatStructure(node, self.lang, '.'))
-+ rc_text = rc_text.replace('[[DIALOGS]]', ''.join(dialogs))
-+
-+ fname = os.path.join(dir_path, '%s.rc' % project_name)
-+ self.WriteFile(fname, rc_text, self.GetEncoding())
-+ print("Wrote %s" % fname)
-+
-+ # Create the resource.h file
-+ header_defines = ''.join(rc_header.FormatDefines(grd))
-+ header_text = HEADER_TEMPLATE.replace('[[DEFINES]]', header_defines)
-+ fname = os.path.join(dir_path, 'resource.h')
-+ self.WriteFile(fname, header_text)
-+ print("Wrote %s" % fname)
-+
-+ def WriteFile(self, filename, contents, encoding='cp1252'):
-+ with open(filename, 'wb') as f:
-+ writer = util.WrapOutputStream(f, encoding)
-+ writer.write(contents)
-diff --git a/tools/grit/grit/tool/test.py b/tools/grit/grit/tool/test.py
-new file mode 100644
-index 0000000000..241a976d74
---- /dev/null
-+++ b/tools/grit/grit/tool/test.py
-@@ -0,0 +1,24 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+from __future__ import print_function
-+
-+from grit.tool import interface
-+
-+class TestTool(interface.Tool):
-+ '''This tool does nothing except print out the global options and
-+tool-specific arguments that it receives. It is intended only for testing,
-+hence the name :)
-+'''
-+
-+ def ShortDescription(self):
-+ return 'A do-nothing tool for testing command-line parsing.'
-+
-+ def Run(self, global_options, my_arguments):
-+ print('NOTE This tool is only for testing the parsing of global options and')
-+ print('tool-specific arguments that it receives. You may have intended to')
-+ print('run "grit unit" which is the unit-test suite for GRIT.')
-+ print('Options: %s' % repr(global_options))
-+ print('Arguments: %s' % repr(my_arguments))
-+ return 0
-diff --git a/tools/grit/grit/tool/transl2tc.py b/tools/grit/grit/tool/transl2tc.py
-new file mode 100644
-index 0000000000..45301bbf58
---- /dev/null
-+++ b/tools/grit/grit/tool/transl2tc.py
-@@ -0,0 +1,251 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''The 'grit transl2tc' tool.
-+'''
-+
-+from __future__ import print_function
-+
-+from grit import grd_reader
-+from grit import util
-+from grit.tool import interface
-+from grit.tool import rc2grd
-+
-+from grit.extern import tclib
-+
-+
-+class TranslationToTc(interface.Tool):
-+ '''A tool for importing existing translations in RC format into the
-+Translation Console.
-+
-+Usage:
-+
-+grit -i GRD transl2tc [-l LIMITS] [RCOPTS] SOURCE_RC TRANSLATED_RC OUT_FILE
-+
-+The tool needs a "source" RC file, i.e. in English, and an RC file that is a
-+translation of precisely the source RC file (not of an older or newer version).
-+
-+The tool also requires you to provide a .grd file (input file) e.g. using the
-+-i global option or the GRIT_INPUT environment variable. The tool uses
-+information from your .grd file to correct placeholder names in the
-+translations and ensure that only translatable items and translations still
-+being used are output.
-+
-+This tool will accept all the same RCOPTS as the 'grit rc2grd' tool. To get
-+a list of these options, run 'grit help rc2grd'.
-+
-+Additionally, you can use the -l option (which must be the first option to the
-+tool) to specify a file containing a list of message IDs to which output should
-+be limited. This is only useful if you are limiting the output to your XMB
-+files using the 'grit xmb' tool's -l option. See 'grit help xmb' for how to
-+generate a file containing a list of the message IDs in an XMB file.
-+
-+The tool will scan through both of the RC files as well as any HTML files they
-+refer to, and match together the source messages and translated messages. It
-+will output a file (OUTPUT_FILE) you can import directly into the TC using the
-+Bulk Translation Upload tool.
-+'''
-+
-+ def ShortDescription(self):
-+ return 'Import existing translations in RC format into the TC'
-+
-+ def Setup(self, globopt, args):
-+ '''Sets the instance up for use.
-+ '''
-+ self.SetOptions(globopt)
-+ self.rc2grd = rc2grd.Rc2Grd()
-+ self.rc2grd.SetOptions(globopt)
-+ self.limits = None
-+ if len(args) and args[0] == '-l':
-+ self.limits = util.ReadFile(args[1], 'utf-8').splitlines()
-+ args = args[2:]
-+ return self.rc2grd.ParseOptions(args, help_func=self.ShowUsage)
-+
-+ def Run(self, globopt, args):
-+ args = self.Setup(globopt, args)
-+
-+ if len(args) != 3:
-+ self.Out('This tool takes exactly three arguments:\n'
-+ ' 1. The path to the original RC file\n'
-+ ' 2. The path to the translated RC file\n'
-+ ' 3. The output file path.\n')
-+ return 2
-+
-+ grd = grd_reader.Parse(self.o.input, debug=self.o.extra_verbose)
-+ grd.RunGatherers()
-+
-+ source_rc = util.ReadFile(args[0], self.rc2grd.input_encoding)
-+ transl_rc = util.ReadFile(args[1], self.rc2grd.input_encoding)
-+ translations = self.ExtractTranslations(grd,
-+ source_rc, args[0],
-+ transl_rc, args[1])
-+
-+ with util.WrapOutputStream(open(args[2], 'wb')) as output_file:
-+ self.WriteTranslations(output_file, translations.items())
-+
-+ self.Out('Wrote output file %s' % args[2])
-+
-+ def ExtractTranslations(self, current_grd, source_rc, source_path,
-+ transl_rc, transl_path):
-+ '''Extracts translations from the translated RC file, matching them with
-+ translations in the source RC file to calculate their ID, and correcting
-+ placeholders, limiting output to translateables, etc. using the supplied
-+ .grd file which is the current .grd file for your project.
-+
-+ If this object's 'limits' attribute is not None but a list, the output of
-+ this function will be further limited to include only messages that have
-+ message IDs in the 'limits' list.
-+
-+ Args:
-+ current_grd: grit.node.base.Node child, that has had RunGatherers() run
-+ on it
-+ source_rc: Complete text of source RC file
-+ source_path: Path to the source RC file
-+ transl_rc: Complete text of translated RC file
-+ transl_path: Path to the translated RC file
-+
-+ Return:
-+ { id1 : text1, '12345678' : 'Hello USERNAME, howzit?' }
-+ '''
-+ source_grd = self.rc2grd.Process(source_rc, source_path)
-+ self.VerboseOut('Read %s into GRIT format, running gatherers.\n' % source_path)
-+ source_grd.SetOutputLanguage(current_grd.output_language)
-+ source_grd.SetDefines(current_grd.defines)
-+ source_grd.RunGatherers(debug=self.o.extra_verbose)
-+ transl_grd = self.rc2grd.Process(transl_rc, transl_path)
-+ transl_grd.SetOutputLanguage(current_grd.output_language)
-+ transl_grd.SetDefines(current_grd.defines)
-+ self.VerboseOut('Read %s into GRIT format, running gatherers.\n' % transl_path)
-+ transl_grd.RunGatherers(debug=self.o.extra_verbose)
-+ self.VerboseOut('Done running gatherers for %s.\n' % transl_path)
-+
-+ # Proceed to create a map from ID to translation, getting the ID from the
-+ # source GRD and the translation from the translated GRD.
-+ id2transl = {}
-+ for source_node in source_grd:
-+ source_cliques = source_node.GetCliques()
-+ if not len(source_cliques):
-+ continue
-+
-+ assert 'name' in source_node.attrs, 'All nodes with cliques should have an ID'
-+ node_id = source_node.attrs['name']
-+ self.ExtraVerboseOut('Processing node %s\n' % node_id)
-+ transl_node = transl_grd.GetNodeById(node_id)
-+
-+ if transl_node:
-+ transl_cliques = transl_node.GetCliques()
-+ if not len(transl_cliques) == len(source_cliques):
-+ self.Out(
-+ 'Warning: Translation for %s has wrong # of cliques, skipping.\n' %
-+ node_id)
-+ continue
-+ else:
-+ self.Out('Warning: No translation for %s, skipping.\n' % node_id)
-+ continue
-+
-+ if source_node.name == 'message':
-+ # Fixup placeholders as well as possible based on information from
-+ # the current .grd file if they are 'TODO_XXXX' placeholders. We need
-+ # to fixup placeholders in the translated message so that it looks right
-+ # and we also need to fixup placeholders in the source message so that
-+ # its calculated ID will match the current message.
-+ current_node = current_grd.GetNodeById(node_id)
-+ if current_node:
-+ assert len(source_cliques) == len(current_node.GetCliques()) == 1
-+
-+ source_msg = source_cliques[0].GetMessage()
-+ current_msg = current_node.GetCliques()[0].GetMessage()
-+
-+ # Only do this for messages whose source version has not changed.
-+ if (source_msg.GetRealContent() != current_msg.GetRealContent()):
-+ self.VerboseOut('Info: Message %s has changed; skipping\n' % node_id)
-+ else:
-+ transl_msg = transl_cliques[0].GetMessage()
-+ transl_content = transl_msg.GetContent()
-+ current_content = current_msg.GetContent()
-+ source_content = source_msg.GetContent()
-+
-+ ok_to_fixup = True
-+ if (len(transl_content) != len(current_content)):
-+ # message structure of translation is different, don't try fixup
-+ ok_to_fixup = False
-+ if ok_to_fixup:
-+ for ix in range(len(transl_content)):
-+ if isinstance(transl_content[ix], tclib.Placeholder):
-+ if not isinstance(current_content[ix], tclib.Placeholder):
-+ ok_to_fixup = False # structure has changed
-+ break
-+ if (transl_content[ix].GetOriginal() !=
-+ current_content[ix].GetOriginal()):
-+ ok_to_fixup = False # placeholders have likely been reordered
-+ break
-+ else: # translated part is not a placeholder but a string
-+ if isinstance(current_content[ix], tclib.Placeholder):
-+ ok_to_fixup = False # placeholders have likely been reordered
-+ break
-+
-+ if not ok_to_fixup:
-+ self.VerboseOut(
-+ 'Info: Structure of message %s has changed; skipping.\n' % node_id)
-+ else:
-+ def Fixup(content, ix):
-+ if (isinstance(content[ix], tclib.Placeholder) and
-+ content[ix].GetPresentation().startswith('TODO_')):
-+ assert isinstance(current_content[ix], tclib.Placeholder)
-+ # Get the placeholder ID and example from the current message
-+ content[ix] = current_content[ix]
-+ for ix in range(len(transl_content)):
-+ Fixup(transl_content, ix)
-+ Fixup(source_content, ix)
-+
-+ # Only put each translation once into the map. Warn if translations
-+ # for the same message are different.
-+ for ix in range(len(transl_cliques)):
-+ source_msg = source_cliques[ix].GetMessage()
-+ source_msg.GenerateId() # needed to refresh ID based on new placeholders
-+ message_id = source_msg.GetId()
-+ translated_content = transl_cliques[ix].GetMessage().GetPresentableContent()
-+
-+ if message_id in id2transl:
-+ existing_translation = id2transl[message_id]
-+ if existing_translation != translated_content:
-+ original_text = source_cliques[ix].GetMessage().GetPresentableContent()
-+ self.Out('Warning: Two different translations for "%s":\n'
-+ ' Translation 1: "%s"\n'
-+ ' Translation 2: "%s"\n' %
-+ (original_text, existing_translation, translated_content))
-+ else:
-+ id2transl[message_id] = translated_content
-+
-+ # Remove translations for messages that do not occur in the current .grd
-+ # or have been marked as not translateable, or do not occur in the 'limits'
-+ # list (if it has been set).
-+ current_message_ids = current_grd.UberClique().AllMessageIds()
-+ for message_id in list(id2transl.keys()):
-+ if (message_id not in current_message_ids or
-+ not current_grd.UberClique().BestClique(message_id).IsTranslateable() or
-+ (self.limits and message_id not in self.limits)):
-+ del id2transl[message_id]
-+
-+ return id2transl
-+
-+ @staticmethod
-+ def WriteTranslations(output_file, translations):
-+ '''Writes the provided list of translations to the provided output file
-+ in the format used by the TC's Bulk Translation Upload tool. The file
-+ must be UTF-8 encoded.
-+
-+ Args:
-+ output_file: util.WrapOutputStream(open('bingo.out', 'wb'))
-+ translations: [ [id1, text1], ['12345678', 'Hello USERNAME, howzit?'] ]
-+
-+ Return:
-+ None
-+ '''
-+ for id, text in translations:
-+ text = text.replace('<', '&lt;').replace('>', '&gt;')
-+ output_file.write(id)
-+ output_file.write(' ')
-+ output_file.write(text)
-+ output_file.write('\n')
-diff --git a/tools/grit/grit/tool/transl2tc_unittest.py b/tools/grit/grit/tool/transl2tc_unittest.py
-new file mode 100644
-index 0000000000..22e937f9f2
---- /dev/null
-+++ b/tools/grit/grit/tool/transl2tc_unittest.py
-@@ -0,0 +1,133 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for the 'grit transl2tc' tool.'''
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+import unittest
-+
-+from six import StringIO
-+
-+from grit.tool import transl2tc
-+from grit import grd_reader
-+from grit import util
-+
-+
-+def MakeOptions():
-+ from grit import grit_runner
-+ return grit_runner.Options()
-+
-+
-+class TranslationToTcUnittest(unittest.TestCase):
-+
-+ def testOutput(self):
-+ buf = StringIO()
-+ tool = transl2tc.TranslationToTc()
-+ translations = [
-+ ['1', 'Hello USERNAME, how are you?'],
-+ ['12', 'Howdie doodie!'],
-+ ['123', 'Hello\n\nthere\n\nhow are you?'],
-+ ['1234', 'Hello is > goodbye but < howdie pardner'],
-+ ]
-+ tool.WriteTranslations(buf, translations)
-+ output = buf.getvalue()
-+ self.failUnless(output.strip() == '''
-+1 Hello USERNAME, how are you?
-+12 Howdie doodie!
-+123 Hello
-+
-+there
-+
-+how are you?
-+1234 Hello is &gt; goodbye but &lt; howdie pardner
-+'''.strip())
-+
-+ def testExtractTranslations(self):
-+ path = util.PathFromRoot('grit/testdata')
-+ current_grd = grd_reader.Parse(StringIO('''<?xml version="1.0" encoding="UTF-8"?>
-+ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
-+ <release seq="3">
-+ <messages>
-+ <message name="IDS_SIMPLE">
-+ One
-+ </message>
-+ <message name="IDS_PLACEHOLDER">
-+ <ph name="NUMBIRDS">%s<ex>3</ex></ph> birds
-+ </message>
-+ <message name="IDS_PLACEHOLDERS">
-+ <ph name="ITEM">%d<ex>1</ex></ph> of <ph name="COUNT">%d<ex>3</ex></ph>
-+ </message>
-+ <message name="IDS_REORDERED_PLACEHOLDERS">
-+ <ph name="ITEM">$1<ex>1</ex></ph> of <ph name="COUNT">$2<ex>3</ex></ph>
-+ </message>
-+ <message name="IDS_CHANGED">
-+ This is the new version
-+ </message>
-+ <message name="IDS_TWIN_1">Hello</message>
-+ <message name="IDS_TWIN_2">Hello</message>
-+ <message name="IDS_NOT_TRANSLATEABLE" translateable="false">:</message>
-+ <message name="IDS_LONGER_TRANSLATED">
-+ Removed document <ph name="FILENAME">$1<ex>c:\temp</ex></ph>
-+ </message>
-+ <message name="IDS_DIFFERENT_TWIN_1">Howdie</message>
-+ <message name="IDS_DIFFERENT_TWIN_2">Howdie</message>
-+ </messages>
-+ <structures>
-+ <structure type="dialog" name="IDD_ABOUTBOX" encoding="utf-16" file="klonk.rc" />
-+ <structure type="menu" name="IDC_KLONKMENU" encoding="utf-16" file="klonk.rc" />
-+ </structures>
-+ </release>
-+ </grit>'''), path)
-+ current_grd.SetOutputLanguage('en')
-+ current_grd.RunGatherers()
-+
-+ source_rc_path = util.PathFromRoot('grit/testdata/source.rc')
-+ source_rc = util.ReadFile(source_rc_path, 'utf-8')
-+ transl_rc_path = util.PathFromRoot('grit/testdata/transl.rc')
-+ transl_rc = util.ReadFile(transl_rc_path, 'utf-8')
-+
-+ tool = transl2tc.TranslationToTc()
-+ output_buf = StringIO()
-+ globopts = MakeOptions()
-+ globopts.verbose = True
-+ globopts.output_stream = output_buf
-+ tool.Setup(globopts, [])
-+ translations = tool.ExtractTranslations(current_grd,
-+ source_rc, source_rc_path,
-+ transl_rc, transl_rc_path)
-+
-+ values = list(translations.values())
-+ output = output_buf.getvalue()
-+
-+ self.failUnless('Ein' in values)
-+ self.failUnless('NUMBIRDS Vogeln' in values)
-+ self.failUnless('ITEM von COUNT' in values)
-+ self.failUnless(values.count('Hallo') == 1)
-+ self.failIf('Dass war die alte Version' in values)
-+ self.failIf(':' in values)
-+ self.failIf('Dokument FILENAME ist entfernt worden' in values)
-+ self.failIf('Nicht verwendet' in values)
-+ self.failUnless(('Howdie' in values or 'Hallo sagt man' in values) and not
-+ ('Howdie' in values and 'Hallo sagt man' in values))
-+
-+ self.failUnless('XX01XX&SkraXX02XX&HaettaXX03XXThetta er "Klonk" sem eg fylaXX04XXgonkurinnXX05XXKlonk && er [good]XX06XX&HjalpXX07XX&Um...XX08XX' in values)
-+
-+ self.failUnless('I lagi' in values)
-+
-+ self.failUnless(output.count('Structure of message IDS_REORDERED_PLACEHOLDERS has changed'))
-+ self.failUnless(output.count('Message IDS_CHANGED has changed'))
-+ self.failUnless(output.count('Structure of message IDS_LONGER_TRANSLATED has changed'))
-+ self.failUnless(output.count('Two different translations for "Howdie"'))
-+ self.failUnless(output.count('IDD_DIFFERENT_LENGTH_IN_TRANSL has wrong # of cliques'))
-+
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/tool/unit.py b/tools/grit/grit/tool/unit.py
-new file mode 100644
-index 0000000000..7e96b699c3
---- /dev/null
-+++ b/tools/grit/grit/tool/unit.py
-@@ -0,0 +1,43 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''GRIT tool that runs the unit test suite for GRIT.'''
-+
-+from __future__ import print_function
-+
-+import getopt
-+import sys
-+import unittest
-+
-+try:
-+ import grit.test_suite_all
-+except ImportError:
-+ pass
-+from grit.tool import interface
-+
-+
-+class UnitTestTool(interface.Tool):
-+ '''By using this tool (e.g. 'grit unit') you run all the unit tests for GRIT.
-+This happens in the environment that is set up by the basic GRIT runner.'''
-+
-+ def ShortDescription(self):
-+ return 'Use this tool to run all the unit tests for GRIT.'
-+
-+ def ParseOptions(self, args):
-+ """Set this objects and return all non-option arguments."""
-+ own_opts, args = getopt.getopt(args, '', ('help',))
-+ for key, val in own_opts:
-+ if key == '--help':
-+ self.ShowUsage()
-+ sys.exit(0)
-+ return args
-+
-+ def Run(self, opts, args):
-+ args = self.ParseOptions(args)
-+ if args:
-+ print('This tool takes no arguments.')
-+ return 2
-+
-+ return unittest.TextTestRunner(verbosity=2).run(
-+ grit.test_suite_all.TestSuiteAll())
-diff --git a/tools/grit/grit/tool/update_resource_ids/__init__.py b/tools/grit/grit/tool/update_resource_ids/__init__.py
-new file mode 100644
-index 0000000000..3006fbffab
---- /dev/null
-+++ b/tools/grit/grit/tool/update_resource_ids/__init__.py
-@@ -0,0 +1,305 @@
-+# Copyright 2019 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+"""Package grit.tool.update_resource_ids
-+
-+Updates GRID resource_ids from linked GRD files, while preserving structure.
-+
-+A resource_ids file is a JSON dict (with Python comments) that maps GRD paths
-+to *items*. Item order is ignored by GRIT, but is important since it establishes
-+a narrative of item dependency (needs non-overlapping IDs) and mutual exclusion
-+(allows ID overlap). Example:
-+
-+{
-+ # The first entry in the file, SRCDIR, is special: It is a relative path from
-+ # this file to the base of your checkout.
-+ "SRCDIR": "../..",
-+
-+ # First GRD file. This entry is an "Item".
-+ "1.grd": {
-+ "messages": [400], # "Tag".
-+ },
-+ # Depends on 1.grd, i.e., 500 >= 400 + (# of IDs used in 1.grd).
-+ "2a.grd": {
-+ "includes": [500], # "Tag".
-+ "structures": [510], # "Tag" (etc.).
-+ },
-+ # Depends on 2a.grd.
-+ "3a.grd": {
-+ "includes": [1000],
-+ },
-+ # Depends on 2a.grd, but overlaps with 3b.grd due to mutually exclusivity.
-+ "3b.grd": {
-+ "includes": [1000],
-+ },
-+ # Depends on {3a.grd, 3b.grd}.
-+ "4.grd": {
-+ "META": {"join": 2}, # Hint for update_resource_ids.
-+ "structures": [1500],
-+ },
-+ # Depends on 1.grd but overlaps with 2a.grd.
-+ "2b.grd": {
-+ "includes": [500],
-+ "structures": [540],
-+ },
-+ # Depends on {4.grd, 2b.grd}.
-+ "5.grd": {
-+ "META": {"join": 2}, # Hint for update_resource_ids.
-+ "includes": [600],
-+ },
-+ # Depends on 5.grd. File is generated, so hint is needed for sizes.
-+ "<(SHARED_INTERMEDIATE_DIR)/6.grd": {
-+ "META": {"sizes": {"includes": [10]}},
-+ "includes": [700],
-+ },
-+}
-+
-+The "structure" within a resouces_ids file are as follows:
-+1. Comments and spacing.
-+2. Item ordering, to establish dependency and grouping.
-+3. Special provision to allow ID overlaps from mutual exclusion.
-+
-+This module parses a resource_ids file, reads ID usages from GRD files it refers
-+to, and generates an updated version of the resource_ids file while preserving
-+structure elements 1-3 stated above.
-+"""
-+
-+from __future__ import print_function
-+
-+import collections
-+import getopt
-+import os
-+import shutil
-+import sys
-+import tempfile
-+
-+from grit.tool import interface
-+from grit.tool.update_resource_ids import assigner, common, parser, reader
-+
-+
-+def _ReadData(input_file):
-+ if input_file == '-':
-+ data = sys.stdin.read()
-+ file_dir = os.getcwd()
-+ else:
-+ with open(input_file, 'rt') as f:
-+ data = f.read()
-+ file_dir = os.path.dirname(input_file)
-+ return data, file_dir
-+
-+
-+def _MultiReplace(data, repl):
-+ """Multi-replacement of text |data| by ranges and replacement text.
-+
-+ Args:
-+ data: Original text.
-+ repl: List of (lo, hi, s) tuples, specifying that |data[lo:hi]| should be
-+ replaced with |s|. The ranges must be inside |data|, and not overlap.
-+ Returns: New text.
-+ """
-+ res = []
-+ prev = 0
-+ for (lo, hi, s) in sorted(repl):
-+ if prev < lo:
-+ res.append(data[prev:lo])
-+ res.append(s)
-+ prev = hi
-+ res.append(data[prev:])
-+ return ''.join(res)
-+
-+
-+def _WriteFileIfChanged(output, new_data):
-+ if not output:
-+ sys.stdout.write(new_data)
-+ return
-+
-+ # Avoid touching outputs if file contents has not changed so that ninja
-+ # does not rebuild dependent when not necessary.
-+ if os.path.exists(output) and _ReadData(output)[0] == new_data:
-+ return
-+
-+ # Write to a temporary file to ensure atomic changes.
-+ with tempfile.NamedTemporaryFile('wt', delete=False) as f:
-+ f.write(new_data)
-+ shutil.move(f.name, output)
-+
-+
-+class _Args:
-+ """Encapsulated arguments for this module."""
-+ def __init__(self):
-+ self.add_header = False
-+ self.analyze_inputs = False
-+ self.count = False
-+ self.depfile = None
-+ self.fake = False
-+ self.input = None
-+ self.naive = False
-+ self.output = None
-+ self.parse = False
-+ self.tokenize = False
-+
-+ @staticmethod
-+ def Parse(raw_args):
-+ own_opts, raw_args = getopt.getopt(raw_args, 'o:cpt', [
-+ 'add-header',
-+ 'analyze-inputs',
-+ 'count',
-+ 'depfile=',
-+ 'fake',
-+ 'naive',
-+ 'parse',
-+ 'tokenize',
-+ ])
-+ args = _Args();
-+ if not len(raw_args) == 1:
-+ print('grit update_resource_ids takes exactly one argument, the path to '
-+ 'the resource ids file.')
-+ return 2
-+ args.input = raw_args[0]
-+ for (key, val) in own_opts:
-+ if key == '-o':
-+ args.output = val
-+ elif key == '--add-header':
-+ args.add_header = True
-+ elif key == '--analyze-inputs':
-+ args.analyze_inputs = True
-+ elif key in ('--count', '-c'):
-+ args.count = True
-+ elif key == '--depfile':
-+ args.depfile = val
-+ elif key == '--fake':
-+ args.fake = True
-+ elif key == '--naive':
-+ args.naive = True
-+ elif key in ('--parse', '-p'):
-+ args.parse = True
-+ elif key in ('--tokenize', '-t'):
-+ args.tokenize = True
-+ return args
-+
-+
-+class UpdateResourceIds(interface.Tool):
-+ """Updates all start IDs in an resource_ids file by reading all GRD files it
-+refers to, estimating the number of required IDs of each type, then rewrites
-+start IDs while preserving structure.
-+
-+Usage: grit update_resource_ids [--parse|-p] [--read-grd|-r] [--tokenize|-t]
-+ [--naive] [--fake] [-o OUTPUT_FILE]
-+ [--analyze-inputs] [--depfile DEPFILE]
-+ [--add-header] RESOURCE_IDS_FILE
-+
-+RESOURCE_IDS_FILE is the path of the input resource_ids file.
-+
-+The -o option redirects output (default stdout) to OUPTUT_FILE, which can also
-+be RESOURCE_IDS_FILE.
-+
-+Other options:
-+
-+ -E NAME=VALUE Sets environment variable NAME to VALUE (within grit).
-+
-+ --count|-c Parses RESOURCE_IDS_FILE, reads the GRD files, and prints
-+ required sizes.
-+
-+ --fake For testing: Skips reading GRD files, and assigns 10 as the
-+ usage of every tag.
-+
-+ --naive Use naive coarse assigner.
-+
-+ --parse|-p Parses RESOURCE_IDS_FILE and dumps its nodes to console.
-+
-+ --tokenize|-t Tokenizes RESOURCE_IDS_FILE and reprints it as syntax-
-+ highlighted output.
-+
-+ --depfile=DEPFILE Write out a depfile for ninja to know about dependencies.
-+ --analyze-inputs Writes dependencies to stdout.
-+ --add-header Adds a "THIS FILE IS GENERATED" header to the output.
-+"""
-+
-+ def __init(self):
-+ super(UpdateResourceIds, self).__init__()
-+
-+ def ShortDescription(self):
-+ return 'Updates a resource_ids file based on usage, preserving structure'
-+
-+ def _DumpTokens(self, data, tok_gen):
-+ # Reprint |data| with syntax highlight.
-+ color_map = {
-+ '#': common.Color.GRAY,
-+ 'S': common.Color.CYAN,
-+ '0': common.Color.RED,
-+ '{': common.Color.YELLOW,
-+ '}': common.Color.YELLOW,
-+ '[': common.Color.GREEN,
-+ ']': common.Color.GREEN,
-+ ':': common.Color.MAGENTA,
-+ ',': common.Color.MAGENTA,
-+ }
-+ for t, lo, hi in tok_gen:
-+ c = color_map.get(t, common.Color.NONE)
-+ sys.stdout.write(c(data[lo:hi]))
-+
-+ def _DumpRootObj(self, root_obj):
-+ print(root_obj)
-+
-+ def _DumpResourceCounts(self, usage_gen):
-+ tot = collections.Counter()
-+ for item, tag_name_to_usage in usage_gen:
-+ c = common.Color.YELLOW if item.grd.startswith('<') else common.Color.CYAN
-+ print('%s: %r' % (c(item.grd), dict(tag_name_to_usage)))
-+ tot += collections.Counter(tag_name_to_usage)
-+ print(common.Color.GRAY('-' * 80))
-+ print('%s: %r' % (common.Color.GREEN('Total'), dict(tot)))
-+ print('%s: %d' % (common.Color.GREEN('Grand Total'), sum(tot.values())))
-+
-+ def Run(self, opts, raw_args):
-+ self.SetOptions(opts)
-+
-+ args = _Args.Parse(raw_args)
-+ data, file_dir = _ReadData(args.input)
-+
-+ tok_gen = parser.Tokenize(data)
-+ if args.tokenize:
-+ return self._DumpTokens(data, tok_gen)
-+
-+ root_obj = parser.ResourceIdParser(data, tok_gen).Parse()
-+ if args.parse:
-+ return self._DumpRootObj(root_obj)
-+ item_list = common.BuildItemList(root_obj)
-+
-+ src_dir = os.path.normpath(os.path.join(file_dir, root_obj['SRCDIR'].val))
-+ seen_files = set()
-+ usage_gen = reader.GenerateResourceUsages(item_list, src_dir, args.fake,
-+ seen_files)
-+ if args.count:
-+ return self._DumpResourceCounts(usage_gen)
-+ for item, tag_name_to_usage in usage_gen:
-+ item.SetUsages(tag_name_to_usage)
-+
-+ if args.analyze_inputs:
-+ print('\n'.join(sorted(seen_files)))
-+ return 0
-+
-+ new_ids_gen = assigner.GenerateNewIds(item_list, args.naive)
-+ # Create replacement specs usable by _MultiReplace().
-+ repl = [(tag.lo, tag.hi, str(new_id)) for tag, new_id in new_ids_gen]
-+ rel_input_dir = args.input
-+ # Update "SRCDIR" entry if output is specified.
-+ if args.output:
-+ new_srcdir = os.path.relpath(src_dir, os.path.dirname(args.output))
-+ repl.append((root_obj['SRCDIR'].lo, root_obj['SRCDIR'].hi,
-+ repr(new_srcdir)))
-+ rel_input_dir = os.path.join('$SRCDIR',
-+ os.path.relpath(rel_input_dir, new_srcdir))
-+
-+ new_data = _MultiReplace(data, repl)
-+ if args.add_header:
-+ header = []
-+ header.append('# GENERATED FILE.')
-+ header.append('# Edit %s instead.' % rel_input_dir)
-+ header.append('#' * 80)
-+ new_data = '\n'.join(header + ['']) + new_data
-+ _WriteFileIfChanged(args.output, new_data)
-+
-+ if args.depfile:
-+ deps_data = '{}: {}'.format(args.output, ' '.join(sorted(seen_files)))
-+ _WriteFileIfChanged(args.depfile, deps_data)
-diff --git a/tools/grit/grit/tool/update_resource_ids/assigner.py b/tools/grit/grit/tool/update_resource_ids/assigner.py
-new file mode 100644
-index 0000000000..6cd46031a6
---- /dev/null
-+++ b/tools/grit/grit/tool/update_resource_ids/assigner.py
-@@ -0,0 +1,286 @@
-+# Copyright 2019 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+"""Assign IDs to resource_ids file based on usage, while preserving structure.
-+
-+resource_ids assignment is divided into two parts:
-+(A) Coarse assignment: Assigns start IDs of items.
-+(B) Quota assignment: Assigns per-tag ID allotments for a given item, allowing
-+ padding for IDs.
-+
-+These parts are interdependent: Start IDs (A) of an item depends on ID
-+allotments (B) of *other* items; and ID allotment (B) of an item depends on its
-+start IDs (A) to compute alignment.
-+
-+(B) hides padding and alignment details of tags so that (A) can be abstracted
-+into the graph construction and traversal problem in DagCoarseIdAssigner.
-+"""
-+
-+import math
-+
-+from grit.tool.update_resource_ids import common
-+
-+
-+class Aligner:
-+ """Helper to allot IDs, given start ID and ID usage.
-+
-+ Args:
-+ expand: Scale factor relative to ID usage. Must be >= 1.0.
-+ slack: Minimum number of reserved ID at end. Must be >= 0.
-+ align: ID alignment of results. Must be >= 1.
-+ """
-+
-+ def __init__(self, expand=1.0, slack=0, align=1):
-+ assert expand >= 1.0 and slack >= 0 and align >= 1
-+ self._expand = expand
-+ self._slack = slack
-+ self._align = align
-+
-+ def Calc(self, cur_id, usage):
-+ quota = max(int(math.ceil(usage * self._expand)), usage + self._slack)
-+ return common.AlignUp(cur_id + quota, self._align)
-+
-+
-+class QuotaAssigner:
-+ """Main class for (B), for ID allotment of tags for an item."""
-+
-+ def __init__(self, aligner):
-+ self._aligner = aligner
-+
-+ def Gen(self, item, start_id):
-+ """Generates per-tag *end* ID in |item|, succeeding |start_id|."""
-+ cur_id = start_id
-+ for tag in item.tags: # Sorted by |tag.lo|.
-+ cur_id = self._aligner.Calc(cur_id, tag.usage)
-+ yield tag, cur_id
-+
-+
-+class BaseCoarseIdAssigner(object):
-+ """Base class for coarse assignment."""
-+
-+ def __init__(self, item_list, align):
-+ self._item_list = item_list
-+ self._align = align
-+
-+ def GenStartIds(self):
-+ """Visits |_item_list| and yields (|item|, new |start_id|).
-+
-+ Visit follows dependency order: If item B succeeds item A, then A is visited
-+ before B. Caller must call FeedWeight() to assign ID allotment.
-+ """
-+ raise NotImplementedError()
-+
-+ def FeedWeight(self, item, weight):
-+ """Callback to assign number of IDs allotted to |item|."""
-+ raise NotImplementedError()
-+
-+
-+class NaiveCoarseIdAssigner(BaseCoarseIdAssigner):
-+ """CoarseIdAssigner that assigns item with non-overlapping start IDs."""
-+
-+ def __init__(self, item_list, align):
-+ super(NaiveCoarseIdAssigner, self).__init__(item_list, align)
-+ first_id = self._item_list[0].tags[0].id
-+ self._cur_id = common.AlignUp(first_id, self._align)
-+
-+ def GenStartIds(self):
-+ """Visits items in array order."""
-+ for item in self._item_list:
-+ yield item, self._cur_id
-+
-+ def FeedWeight(self, item, weight):
-+ self._cur_id = common.AlignUp(self._cur_id + weight, self._align)
-+
-+
-+class DagCoarseIdAssigner(BaseCoarseIdAssigner):
-+ """CoarseIdAssigner that preserves existing structure.
-+
-+Start ID assignment in resource_ids is structured a Series-Parallel Graph, which
-+is a directed, acyclic multi-graph generated by the following:
-+* Start: Single directed edge. S <-- T.
-+* Operation 1: A <-- B becomes A <-- C <-- B.
-+* Operation 2: A <-- B becomes A <== B (add parallel edge).
-+
-+Each vertex (A, B, ...) is a start ID. S = globally minimal ID. T = \infty is an
-+implicit sentinel. Each edge maps to an item, and edge weight is ID allotment.
-+The edge A <-- B means "A's ID assignment needs to be determined before B's"
-+(i.e., "B depends on A"), and requires A < B.
-+
-+resource_ids stores a "flattened" representation of the graph, as a list of
-+items (with meta data). Thus coarse ID assignment consists of the following:
-+(1) Process list of items (with old start ID and meta data) to rebuild graph.
-+(2) Traverse graph in an order that satisfies dependencies.
-+(3) When vertex A has its ID assigned, the weight of each edge "A <--" (i.e., an
-+ item) can have its weight (ID allotment) computed via quota assignment of A.
-+(4) New start IDs satisfy A + w <= B for each edge A <-- B with weight w > 0.
-+
-+The key algorithm challenge is (1). Note that it does not need weight details,
-+so we only assume A < B whenever A <-- B. Now we're faced with 2 subproblems:
-+(1a) How to represent the graph as a list of integers (with meta data)?
-+(1b) Given the list representation, how to recover the graph?
-+
-+For (1a), we start with DFS traversal of the (transposed, i.e., reversed) graph
-+starting from S, and apply the following:
-+* For each edge A <-- B traversed, emit A into sequence,
-+* Traverse a B <-- Y only when all X <-- B have been traversed.
-+
-+The resulting sequence has the length as the number of edges, and has the useful
-+property that a vertex's dependencies always appear before the vertex itself!
-+Note this the sentinel T is omitted.
-+
-+Example 1:
-+ S <-- A <-- B <-- C <-- T => "SABC".
-+
-+Example 2:
-+ S <-- A <-- B <-- C <-- T => "SA|AB|SDEC|SF",
-+ | | | | | or "SF|SA|AB|SDEC",
-+ | + <-- + | | or "SDE|SA|ABC|SF",
-+ | | | or "SF|SDE|SA|ABC".
-+ + <---D <-- E <---+ |
-+ | |
-+ + <-- F <---------------+
-+
-+Here, "X|Y" denotes backtracking between visiting X and visiting Y. This appears
-+if and only if X >= Y, so "|" an optional (but illustrative) character that's
-+not in the actual output. We will use it consistently in comments, and so the
-+absence of "|" denotes the converse. For example, "XY" implies X < Y.
-+
-+In terms of the basic operations:
-+* Start: S <-- T => "S".
-+* Operation 1: "...AB..." => "...ACB..." (or "...A" => "...AB").
-+* Operation 2: "...AB..." => "...A|AB..." (or "...A" => "...A|A").
-+
-+For Example 2, a viable "evolution path" is:
-+"S" => "S|S" => "SC|S" => "S|SC|S" => "SA|SC|S" => "SAB|SC|S" => "SA|AB|SC|S"
-+ => "SA|AB|SDC|S" => "SA|AB|SDEC|S" => "SA|AB|SDEC|SF".
-+(Alternative: "S|S" => "S|SC" => etc.).
-+
-+Note: {A, ...} are *unlabelled* integers, and "spurious equalities" such as
-+A = D or A = F can occur!
-+
-+For (1b), we wish to build the graph from the sequence. This requires (1a) to be
-+injective (up to isomorphism). Unfortunately, this does not always hold.
-+Example:
-+ S <-- A <-- C <-- D <-- T => "SA|SBCD".
-+ | |
-+ + <-- B <---+
-+vs.
-+ S <----- A <----- D <-- T => "SA|SBCD".
-+ | |
-+ + <-- B <-- C <---+
-+
-+To fix this, we prepend a "join" label (*) to each vertex that has multiple
-+dependencies. With this, the example above produce different results:
-+ "SA|SB*CD" != "SA|SBC*D".
-+
-+Unfortunately, this is also inadequate. Example:
-+ S <-------- B <-- T => "S|S|S|S*A*B",
-+ | |
-+ + <---------+
-+ | |
-+ + <-- A <---+
-+ | |
-+ + <---+
-+vs.
-+ S <-------- B <-- T => "S|S|S|S*A*B".
-+ | |
-+ + <---A <---+
-+ | |
-+ + <---+
-+ | |
-+ + <---+
-+
-+To fix this, we also label the number of dependencies. In text representation,
-+we just show multiple (#dependencies - 1) copies of '*'. Now we have:
-+ "S|S|S|S*A**B" != "S|S|S|S**A*B".
-+
-+The "join" label with count adequately addresses the issue (proof omitted). In
-+the resource_ids files, these are stored as the "join" field of an item's meta
-+data.
-+
-+Additional comments for (1b) and other steps are detailed below.
-+"""
-+
-+ class DagNode:
-+ """An element of the implicit graph, corresponding to an item.
-+
-+ This actually represents an edge-vertex pair, corresponding to an item.
-+ A vertex is represented by a collection of DagNode that uses |sib| to link
-+ to a "representative node". The representative node, in turn, holds the list
-+ of all |deps| (dependencies) of the vertex.
-+ """
-+
-+ def __init__(self, item, old_id):
-+ self.item = item
-+ self.old_id = old_id
-+ self.new_id = None
-+ self.weight = None
-+
-+ def __init__(self, item_list, align):
-+ super(DagCoarseIdAssigner, self).__init__(item_list, align)
-+ self._node_dict = {} # Maps from |lo| to item.
-+
-+ def GenStartIds(self):
-+ """Traverses implicit graph and yields new IDs.
-+
-+ Algorithm: Process |old_id| of items sequentially. Successive items A and B
-+ can be "AB" (A < B), "A*...B" (A < B), or "A|B" (A >= B). "AB" and "A*...B"
-+ imply A <-- B, and are accumulated in |trace|. "A|B" are jumps that rewinds
-+ |trace| to the latest B (must exist), and A is pushed into |jumps|. A join
-+ "A*...B" also pops |num_join - 1| items {X_i} in |jump|, and X_i <-- B. In
-+ the end, unprocessed elements in |jumps| all link to sentinel T, and can be
-+ ignored.
-+ """
-+ # DagNode stack of "A" when "AB" is found (increasing |old_id|).
-+ trace = []
-+ # DagNode stack of "A" when "A|B" jumps is found.
-+ jumps = []
-+ for item in self._item_list: # Sorted by |lo|.
-+ meta = item.meta
-+ # |num_join| indicates "*" in "A*...B", and specify B's dependencies: +1
-+ # from A, and +count("*") from |jumps|.
-+ num_join = meta['join'].val if meta and 'join' in meta else None
-+ node = DagCoarseIdAssigner.DagNode(item, item.tags[0].id)
-+ self._node_dict[item.lo] = node
-+ if trace:
-+ if trace[-1].old_id >= node.old_id: # "A|B".
-+ if num_join:
-+ raise ValueError('Cannot join on jump: %d' % node.old_id)
-+ jumps.append(trace[-1]) # Add A to |jumps|, for later join.
-+ while trace and trace[-1].old_id > node.old_id: # Rewind to find B.
-+ trace.pop()
-+ if not trace or trace[-1].old_id != node.old_id: #
-+ raise ValueError('Cannot jump to unvisited: %d' % node.old_id)
-+ node.new_id = trace.pop().new_id # Copy B & remove. Will re-add B.
-+ else: # "AB" or "A*...B".
-+ node.new_id = trace[-1].new_id + trace[-1].weight # A --> B
-+ if num_join: # "A*...B".
-+ for _ in range(1, num_join):
-+ t = jumps.pop()
-+ node.new_id = max(node.new_id, t.new_id + t.weight) # X_i --> B.
-+ else:
-+ node.new_id = node.old_id # Initial S.
-+ trace.append(node) # Add B.
-+ align = meta['align'].val if meta and 'align' in meta else self._align
-+ node.new_id = common.AlignUp(node.new_id, align)
-+ yield node.item, node.new_id
-+ # Expect caller to calling FreedWeight() and update |node.weight|.
-+
-+ def FeedWeight(self, item, weight):
-+ self._node_dict[item.lo].weight = weight
-+
-+
-+def GenerateNewIds(item_list, use_naive):
-+ """Visits all tags in |item_list| and generates new ids.
-+
-+ New ids are generated based on old ids and usages.
-+ """
-+ Assigner = NaiveCoarseIdAssigner if use_naive else DagCoarseIdAssigner
-+ coarse_id_assigner = Assigner(item_list, 10)
-+ quota_assigner = QuotaAssigner(Aligner(expand=1.15, slack=3, align=10))
-+ for item, start_id in coarse_id_assigner.GenStartIds(): # Topo-sorted.
-+ cur_id = start_id
-+ for tag, next_id in quota_assigner.Gen(item, start_id): # Sorted by |lo|.
-+ yield tag, cur_id
-+ cur_id = next_id
-+ coarse_id_assigner.FeedWeight(item, next_id - start_id)
-diff --git a/tools/grit/grit/tool/update_resource_ids/assigner_unittest.py b/tools/grit/grit/tool/update_resource_ids/assigner_unittest.py
-new file mode 100644
-index 0000000000..164d820762
---- /dev/null
-+++ b/tools/grit/grit/tool/update_resource_ids/assigner_unittest.py
-@@ -0,0 +1,154 @@
-+#!/usr/bin/env python
-+# Copyright 2019 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+import traceback
-+import unittest
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../../..'))
-+
-+from grit.tool.update_resource_ids import assigner, common, parser
-+
-+# |spec| format: A comma-separated list of (old) start IDs. Modifiers:
-+# * Prefix with n '*' to assign the item's META "join" field to n + 1.
-+# * Suffix with "+[usage]" to assign |usage| for the item (else default=10)
-+
-+
-+def _RenderTestResourceId(spec):
-+ """Renders barebone resource_ids data based on |spec|."""
-+ data = '{"SRCDIR": ".",'
-+ for i, tok in enumerate(spec.split(',')):
-+ num_star = len(tok) - len(tok.lstrip('*'))
-+ tok = tok[num_star:]
-+ meta = '"META":{"join": %d},' % (num_star + 1) if num_star else ''
-+ start_id = tok.split('+')[0] # Strip '+usage'.
-+ data += '"foo%d.grd": {%s"includes": [%s]},' % (i, meta, start_id)
-+ data += '}'
-+ return data
-+
-+
-+def _CreateTestItemList(spec):
-+ """Creates list of ItemInfo based on |spec|."""
-+ data = _RenderTestResourceId(spec)
-+ item_list = common.BuildItemList(
-+ parser.ResourceIdParser(data, parser.Tokenize(data)).Parse())
-+ # Assign usages from "id+usage", default to 10.
-+ for i, tok in enumerate(spec.split(',')):
-+ item_list[i].tags[0].usage = int((tok.split('+') + ['10'])[1])
-+ return item_list
-+
-+
-+def _RunCoarseIdAssigner(spec):
-+ item_list = _CreateTestItemList(spec)
-+ coarse_id_assigner = assigner.DagCoarseIdAssigner(item_list, 1)
-+ new_id_list = [] # List of new IDs, to check ID assignment.
-+ new_spec_list = [] # List of new tokens to construct new |spec|.
-+ for item, start_id in coarse_id_assigner.GenStartIds(): # Topo-sorted..
-+ new_id_list.append(str(start_id))
-+ meta = item.meta
-+ num_join = meta['join'].val if meta and 'join' in meta else 0
-+ t = '*' * max(0, num_join - 1)
-+ t += str(start_id)
-+ t += '' if item.tags[0].usage == 10 else '+' + str(item.tags[0].usage)
-+ new_spec_list.append((item.lo, t))
-+ coarse_id_assigner.FeedWeight(item, item.tags[0].usage)
-+ new_spec = ','.join(s for _, s in sorted(new_spec_list))
-+ return ','.join(new_id_list), new_spec
-+
-+
-+class AssignerUnittest(unittest.TestCase):
-+
-+ def testDagAssigner(self):
-+ test_cases = [
-+ # Trivial.
-+ ('0', '0'),
-+ ('137', '137'),
-+ ('5,15', '5,6'),
-+ ('11,18', '11+7,12'),
-+ ('5,5', '5,5'),
-+ # Series only.
-+ ('0,10,20,30,40', '0,1,2,3,4'),
-+ ('5,15,25,35,45,55', '5,6,7,8,9,10'),
-+ ('5,15,25,35,45,55', '5,7,100,101,256,1001'),
-+ ('0,10,20,45,85', '0,1,2+25,3+40,4'),
-+ # Branching with and without join.
-+ ('0,0,10,20,20,30,40', '0,0,1,2,2,3,4'),
-+ ('0,0,10,20,20,30,40', '0,0,*1,2,2,*3,4'),
-+ ('0,0,2,12,12,16,26', '0+4,0+2,1,2+8,2+4,3,4'),
-+ ('0,0,4,14,14,22,32', '0+4,0+2,*1,2+8,2+4,*3,4'),
-+ # Wide branching with and without join.
-+ ('0,10,10,10,10,10,10,20,30', '0,1,1,1,1,1,1,2,3'),
-+ ('0,10,10,10,10,10,10,20,30', '0,1,1,1,1,1,1,*****2,3'),
-+ ('0,2,2,2,2,2,2,7,17', '0+2,1+4,1+19,1,1+4,1+2,1+5,2,3'),
-+ ('0,2,2,2,2,2,2,21,31', '0+2,1+4,1+19,1,1+4,1+2,1+5,*****2,3'),
-+ # Expanding different branch, without join.
-+ ('0,10,10,10,60,70,80', '0,1+15,1+15,1+50,2,3,4'),
-+ ('0,10,10,10,25,35,45', '0,1+15,1+50,1+15,2,3,4'),
-+ ('0,10,10,10,25,35,45', '0,1+50,1+15,1+15,2,3,4'),
-+ # ... with join.
-+ ('0,10,10,10,60,70,80', '0,1+15,1+15,1+50,**2,3,4'),
-+ ('0,10,10,10,60,70,80', '0,1+15,1+50,1+15,**2,3,4'),
-+ ('0,10,10,10,60,70,80', '0,1+50,1+15,1+15,**2,3,4'),
-+ # ... with alternative join.
-+ ('0,10,10,10,60,70,80', '0,1+15,1+15,1+50,2,**3,4'),
-+ ('0,10,10,10,25,60,70', '0,1+15,1+50,1+15,2,**3,4'),
-+ ('0,10,10,10,25,60,70', '0,1+50,1+15,1+15,2,**3,4'),
-+ # Examples from assigner.py.
-+ ('0,10,10,20,0,10,20,30,0,10',
-+ '0,1,1,*2,0,4,5,*6,0,7'), # SA|AB|SDEC|SF
-+ ('0,10,0,10,20,30', '0,1,0,2,*3,4'), # SA|SB*CD
-+ ('0,10,0,10,20,30', '0,1,0,2,3,*4'), # SA|SBC*D
-+ ('0,7,0,5,11,21', '0+7,1+4,0+5,2+3,*3,4'), # SA|SB*CD
-+ ('0,7,0,5,8,18', '0+7,1+4,0+5,2+3,3,*4'), # SA|SBC*D
-+ ('0,0,0,0,10,20', '0,0,0,0,*1,**2'), # S|S|S|S*A**B
-+ ('0,0,0,0,10,20', '0,0,0,0,**1,*2'), # S|S|S|S**A*B
-+ ('0,0,0,0,6,16', '0+8,0+7,0+6,0+5,*1,**2'), # S|S|S|S*A**B
-+ ('0,0,0,0,7,17', '0+8,0+7,0+6,0+5,**1,*2'), # S|S|S|S**A*B
-+ # Long branches without join.
-+ ('0,10,0,0,10,20,0,10,20,30', '0,1,0,0,1,2,0,1,2,3'),
-+ ('0,30,0,0,20,30,0,10,13,28', '0+30,1,0+50,0+20,1,2+17,0,1+3,2+15,3'),
-+ # Long branches with join.
-+ ('0,10,0,0,10,20,0,10,20,30', '0,1,0,0,1,2,0,1,2,***3'),
-+ ('0,30,0,0,20,30,0,10,13,50',
-+ '0+30,1,0+50,0+20,1,2+17,0,1+3,2+15,***3'),
-+ # 2-level hierarchy.
-+ ('0,10,10,20,0,10,10,20,30', '0,1,1,*2,0,1,1,*2,*3'),
-+ ('0,2,2,10,0,3,3,6,34', '0+2,1+5,1+8,*2+24,0+3,1+2,1+3,*2+27,*3'),
-+ ('0,2,2,10,0,3,3,6,34', '0+2,1+5,1+8,*2+24,0+3,1+2,1+3,*2+28,*3'),
-+ ('0,2,2,10,0,3,3,6,35', '0+2,1+5,1+8,*2+24,0+3,1+2,1+3,*2+29,*3'),
-+ # Binary hierarchy.
-+ ('0,0,10,0,0,10,20,0,0,10,0,0,10,20,30',
-+ '0,0,*1,0,0,*1,*2,0,0,*1,0,0,*1,*2,*3'),
-+ ('0,0,2,0,0,5,11,0,0,8,0,0,5,14,18',
-+ '0+1,0+2,*1+3,0+4,0+5,*1+6,*2+7,0+8,0+7,*1+6,0+5,0+4,*1+3,*2+2,*3+1'),
-+ # Joining from different heads.
-+ ('0,10,20,30,40,30,20,10,0,50', '0,1,2,3,4,3,2,1,0,****5'),
-+ # Miscellaneous.
-+ ('0,1,0,11', '0+1,1,0,*1'),
-+ ]
-+ for exp, spec in test_cases:
-+ try:
-+ actual, new_spec = _RunCoarseIdAssigner(spec)
-+ self.failUnlessEqual(exp, actual)
-+ # Test that assignment is idempotent.
-+ actual2, new_spec2 = _RunCoarseIdAssigner(new_spec)
-+ self.failUnlessEqual(actual, actual2)
-+ self.failUnlessEqual(new_spec, new_spec2)
-+ except Exception as e:
-+ print(common.Color.RED(traceback.format_exc().rstrip()))
-+ print('Failed spec: %s' % common.Color.CYAN(spec))
-+ print(' Expected: %s' % common.Color.YELLOW(exp))
-+ print(' Actual: %s' % common.Color.YELLOW(actual))
-+ if new_spec != new_spec2:
-+ print('Not idempotent')
-+ if isinstance(e, AssertionError):
-+ raise e
-+
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/tool/update_resource_ids/common.py b/tools/grit/grit/tool/update_resource_ids/common.py
-new file mode 100644
-index 0000000000..004d8aa0e3
---- /dev/null
-+++ b/tools/grit/grit/tool/update_resource_ids/common.py
-@@ -0,0 +1,101 @@
-+# Copyright 2019 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+
-+def AlignUp(v, align):
-+ return (v + align - 1) // align * align
-+
-+
-+def StripPlural(s):
-+ assert s.endswith('s'), 'Expect %s to be plural' % s
-+ return s[:-1]
-+
-+
-+class Color:
-+
-+ def _MakeColor(code):
-+ t = '\033[' + code + 'm%s\033[0m'
-+ return lambda s: t % s
-+
-+ NONE = staticmethod(lambda s: s)
-+ RED = staticmethod(_MakeColor('31'))
-+ GREEN = staticmethod(_MakeColor('32'))
-+ YELLOW = staticmethod(_MakeColor('33'))
-+ BLUE = staticmethod(_MakeColor('34'))
-+ MAGENTA = staticmethod(_MakeColor('35'))
-+ CYAN = staticmethod(_MakeColor('36'))
-+ WHITE = staticmethod(_MakeColor('37'))
-+ GRAY = staticmethod(_MakeColor('30;1'))
-+
-+
-+class TagInfo:
-+ """Stores resource_ids tag entry (e.g., {"includes": 100} pair)."""
-+
-+ def __init__(self, raw_key, raw_value):
-+ """TagInfo Constructor.
-+
-+ Args:
-+ raw_key: parser.AnnotatedValue for the parsed key, e.g., "includes".
-+ raw_value: parser.AnnotatedValue for the parsed value, e.g., 100.
-+ """
-+ # Tag name, e.g., 'include' (no "s" at end).
-+ self.name = StripPlural(raw_key.val)
-+ # |len(raw_value) > 1| is possible, e.g., see grd_reader_unittest.py's
-+ # testAssignFirstIdsMultipleMessages. This feature seems unused though.
-+ # TODO(huangs): Reconcile this (may end up removing multi-value feature).
-+ assert len(raw_value) == 1
-+ # Inclusive start *position* of the tag's start ID in resource_ids.
-+ self.lo = raw_value[0].lo
-+ # Exclusive end *position* of the tag's start ID in resource_ids.
-+ self.hi = raw_value[0].hi
-+ # The tag's start ID. Initially the old value, but may be reassigned to new.
-+ self.id = raw_value[0].val
-+ # The number of IDs the tag uses, to be assigned by ItemInfo.SetUsages().
-+ self.usage = None
-+
-+
-+class ItemInfo:
-+ """resource_ids item, containing multiple TagInfo."""
-+
-+ def __init__(self, lo, grd, raw_item):
-+ # Inclusive start position of the item's key. Serve as unique identifier.
-+ self.lo = lo
-+ # The GRD filename for the item.
-+ self.grd = grd
-+ # Optional META information for the item.
-+ self.meta = None
-+ # List of TagInfo associated witih the item.
-+ self.tags = []
-+ for k, v in raw_item.items():
-+ if k.val == 'META':
-+ assert self.meta is None
-+ self.meta = v # Not flattened.
-+ else:
-+ self.tags.append(TagInfo(k, v))
-+ self.tags.sort(key=lambda tag: tag.lo)
-+
-+ def SetUsages(self, tag_name_to_usage):
-+ for tag in self.tags:
-+ tag.usage = tag_name_to_usage.get(tag.name, 0)
-+
-+
-+def BuildItemList(root_obj):
-+ """Extracts ID assignments and structure from parsed resource_ids.
-+
-+ Returns: A list of ItemInfo, ordered by |lo|.
-+ """
-+ item_list = []
-+ grd_seen = set()
-+ for raw_key, raw_item in root_obj.items(): # Unordered.
-+ grd = raw_key.val
-+ if grd == 'SRCDIR':
-+ continue
-+ if not grd.endswith('.grd'):
-+ raise ValueError('Invalid GRD file: %s' % grd)
-+ if grd in grd_seen:
-+ raise ValueError('Duplicate GRD: %s' % grd)
-+ grd_seen.add(grd)
-+ item_list.append(ItemInfo(raw_key.lo, grd, raw_item))
-+ item_list.sort(key=lambda item: item.lo)
-+ return item_list
-diff --git a/tools/grit/grit/tool/update_resource_ids/parser.py b/tools/grit/grit/tool/update_resource_ids/parser.py
-new file mode 100644
-index 0000000000..da956bbd1c
---- /dev/null
-+++ b/tools/grit/grit/tool/update_resource_ids/parser.py
-@@ -0,0 +1,231 @@
-+# Copyright 2019 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+"""Structure-preserving parser for resource_ids files.
-+
-+Naive usage of eval() destroys resource_ids structure. This module provides a
-+custom parser that annotates source byte ranges of "leaf values" (strings and
-+integers).
-+"""
-+
-+from __future__ import print_function
-+
-+_isWhitespace = lambda ch: ch in ' \t\n'
-+_isNotNewline = lambda ch: ch != '\n'
-+_isDigit = lambda ch: ch.isdigit()
-+
-+
-+def _RenderLineCol(data, pos):
-+ """Renders |pos| within text |data| in as text showing line and column."""
-+ # This is used to pinpoint fatal parse errors, so okay to be inefficient.
-+ new_lines = [i for i in range(pos) if data[i] == '\n']
-+ row = 1 + len(new_lines)
-+ col = (pos - new_lines[-1]) if new_lines else 1 + pos
-+ return 'line %d, column %d' % (row, col)
-+
-+
-+def Tokenize(data):
-+ """Generator to split |data| into tokens.
-+
-+ Each token is specified as |(t, lo, hi)|:
-+ * |t|: Type, with '#' = space / comments, '0' = int, 'S' = string, 'E' = end,
-+ and other characters denoting themselves.
-+ * |lo, hi|: Token's range within |data| (as |data[lo:hi]|).
-+ """
-+
-+ class ctx: # Local context for mutable data shared across inner functions.
-+ pos = 0
-+
-+ def _HasData():
-+ return ctx.pos < len(data)
-+
-+ # Returns True if ended by |not pred()|, or False if ended by EOF.
-+ def _EatWhile(pred):
-+ while _HasData():
-+ if pred(data[ctx.pos]):
-+ ctx.pos += 1
-+ else:
-+ return True
-+ return False
-+
-+ def _NextBlank():
-+ lo = ctx.pos
-+ while True:
-+ if not _EatWhile(_isWhitespace) or data[ctx.pos] != '#':
-+ break
-+ ctx.pos += 1
-+ if not _EatWhile(_isNotNewline):
-+ break
-+ ctx.pos += 1
-+ return None if ctx.pos == lo else (lo, ctx.pos)
-+
-+ def _EatString():
-+ lo = ctx.pos
-+ delim = data[ctx.pos]
-+ is_escaped = False
-+ ctx.pos += 1
-+ while _HasData():
-+ ch = data[ctx.pos]
-+ ctx.pos += 1
-+ if is_escaped:
-+ is_escaped = False
-+ elif ch == '\\':
-+ is_escaped = True
-+ elif ch == delim:
-+ return
-+ raise ValueError('Unterminated string at %s' % _RenderLineCol(data, lo))
-+
-+ while _HasData():
-+ blank = _NextBlank()
-+ if blank is not None:
-+ yield ('#', blank[0], blank[1])
-+ if not _HasData():
-+ break
-+ lo = ctx.pos
-+ ch = data[ctx.pos]
-+ if ch in '{}[],:':
-+ ctx.pos += 1
-+ t = ch
-+ elif ch.isdigit():
-+ _EatWhile(_isDigit)
-+ t = '0'
-+ elif ch in '+-':
-+ ctx.pos += 1
-+ if not _HasData() or not data[ctx.pos].isdigit():
-+ raise ValueError('Invalid int at %s' % _RenderLineCol(data, lo))
-+ _EatWhile(_isDigit)
-+ t = '0'
-+ elif ch in '"\'':
-+ _EatString()
-+ t = 'S'
-+ else:
-+ raise ValueError('Unknown char %s at %s' %
-+ (repr(ch), _RenderLineCol(data, lo)))
-+ yield (t, lo, ctx.pos)
-+ yield ('E', ctx.pos, ctx.pos) # End sentinel.
-+
-+
-+def _SkipBlanks(toks):
-+ """Generator to remove whitespace and comments from Tokenize()."""
-+ for t, lo, hi in toks:
-+ if t != '#':
-+ yield t, lo, hi
-+
-+
-+class AnnotatedValue:
-+ """Container for leaf values (ints or strings) with an annotated range."""
-+
-+ def __init__(self, val, lo, hi):
-+ self.val = val
-+ self.lo = lo
-+ self.hi = hi
-+
-+ def __str__(self):
-+ return '<%s@%d:%d>' % (str(self.val), self.lo, self.hi)
-+
-+ def __repr__(self):
-+ return '<%r@%d:%d>' % (self.val, self.lo, self.hi)
-+
-+ def __hash__(self):
-+ return hash(self.val)
-+
-+ def __eq__(self, other):
-+ return self.val == other
-+
-+
-+class ResourceIdParser:
-+ """resource_ids parser that stores leaf values as AnnotatedValue.
-+
-+ Algorithm: Use Tokenize() to split |data| into tokens and _SkipBlanks() to
-+ ignore comments and spacing, then apply a recursive parsing, using a one-token
-+ look-ahead for decision making.
-+ """
-+
-+ def __init__(self, data, tok_gen):
-+ self.data = data
-+ self.state = []
-+ self.toks = _SkipBlanks(tok_gen)
-+ self.tok_look_ahead = None
-+
-+ def _MakeErr(self, msg, pos):
-+ return ValueError(msg + ' at ' + _RenderLineCol(self.data, pos))
-+
-+ def _PeekTok(self):
-+ if self.tok_look_ahead is None:
-+ self.tok_look_ahead = next(self.toks)
-+ return self.tok_look_ahead
-+
-+ def _NextTok(self):
-+ if self.tok_look_ahead is None:
-+ return next(self.toks)
-+ ret = self.tok_look_ahead
-+ self.tok_look_ahead = None
-+ return ret
-+
-+ def _EatTok(self, exp_t, tok_name=None):
-+ t, lo, _ = self._NextTok()
-+ if t != exp_t:
-+ raise self._MakeErr('Bad token: Expect \'%s\'' % (tok_name or exp_t), lo)
-+
-+ def _NextIntOrString(self):
-+ t, lo, hi = self._NextTok()
-+ if t != '0' and t != 'S':
-+ raise self._MakeErr('Expected number or string', lo)
-+ value = eval(self.data[lo:hi])
-+ return AnnotatedValue(value, lo, hi)
-+
-+ # Consumes separator ',' and returns whether |end_ch| is encountered.
-+ def _EatSep(self, end_ch):
-+ t, lo, _ = self._PeekTok()
-+ if t == ',':
-+ self._EatTok(',')
-+ # Allow trailing ','.
-+ t, _, _ = self._PeekTok()
-+ return t == end_ch
-+ elif t == end_ch:
-+ return True
-+ else:
-+ raise self._MakeErr('Expect \',\' or \'%s\'' % end_ch, lo)
-+
-+ def _NextList(self):
-+ self._EatTok('[')
-+ ret = []
-+ t, _, _ = self._PeekTok()
-+ if t != ']':
-+ while True:
-+ ret.append(self._NextObject())
-+ if self._EatSep(']'):
-+ break
-+ self._EatTok(']')
-+ return ret
-+
-+ def _NextDict(self):
-+ self._EatTok('{')
-+ ret = {}
-+ t, _, _ = self._PeekTok()
-+ if t != '}':
-+ while True:
-+ k = self._NextIntOrString()
-+ self._EatTok(':')
-+ v = self._NextObject()
-+ ret[k] = v
-+ if self._EatSep('}'):
-+ break
-+ self._EatTok('}')
-+ return ret
-+
-+ def _NextObject(self):
-+ t, lo, _ = self._PeekTok()
-+ if t == '[':
-+ return self._NextList()
-+ elif t == '{':
-+ return self._NextDict()
-+ elif t == '0' or t == 'S':
-+ return self._NextIntOrString()
-+ else:
-+ raise self._MakeErr('Bad token: Type = %s' % t, lo)
-+
-+ def Parse(self):
-+ root_obj = self._NextObject()
-+ self._EatTok('E', 'EOF')
-+ return root_obj
-diff --git a/tools/grit/grit/tool/update_resource_ids/reader.py b/tools/grit/grit/tool/update_resource_ids/reader.py
-new file mode 100644
-index 0000000000..0a156d2deb
---- /dev/null
-+++ b/tools/grit/grit/tool/update_resource_ids/reader.py
-@@ -0,0 +1,83 @@
-+# Copyright 2019 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+"""Helpers to read GRD files and estimate resource ID usages.
-+
-+This module uses grit.grd_reader to estimate resource ID usages in GRD
-+(and GRDP) files by counting the occurrences of {include, message, structure}
-+tags. This approach avoids the complexties of conditional inclusions, but
-+produces a conservative estimate of ID usages.
-+"""
-+
-+from __future__ import print_function
-+
-+import collections
-+import os
-+
-+from grit import grd_reader
-+from grit import util
-+from grit.tool.update_resource_ids import common
-+
-+TAGS_OF_INTEREST = set(['include', 'message', 'structure'])
-+
-+def _CountResourceUsage(grd, seen_files):
-+ tag_name_to_count = {tag: set() for tag in TAGS_OF_INTEREST}
-+ # Pass '_chromium', but '_google_chrome' would produce the same result.
-+ root = grd_reader.Parse(grd, defines={'_chromium': True})
-+ seen_files.add(grd)
-+ # Count all descendant tags, regardless of whether they're active.
-+ for node in root.Preorder():
-+ if node.name in TAGS_OF_INTEREST:
-+ tag_name_to_count[node.name].add(node.attrs['name'])
-+ elif node.name == 'part':
-+ part_path = os.path.join(os.path.dirname(grd), node.GetInputPath())
-+ seen_files.add(util.normpath(part_path))
-+ return {k: len(v) for k, v in tag_name_to_count.items() if v}
-+
-+
-+def GenerateResourceUsages(item_list, src_dir, fake, seen_files):
-+ """Visits a list of ItemInfo to generate maps from tag name to usage.
-+
-+ Args:
-+ root_obj: Root dict of a resource_ids file.
-+ src_dir: Absolute directory of Chrome's src/ directory.
-+ fake: For testing: Sets 10 as usages for all tags, to avoid reading GRD.
-+ seen_files: A set to collect paths of files read.
-+ Yields:
-+ Tuple (item, tag_name_to_usage), where |item| is from |item_list| and
-+ |tag_name_to_usage| is a dict() mapping tag name to (int) usage.
-+ """
-+ if fake:
-+ for item in item_list:
-+ tag_name_to_usage = collections.Counter({t.name: 10 for t in item.tags})
-+ yield item, tag_name_to_usage
-+ return
-+ for item in item_list:
-+ supported_tag_names = set(tag.name for tag in item.tags)
-+ if item.meta and 'sizes' in item.meta:
-+ # If META has "sizes" field, use it instead of reading GRD.
-+ tag_name_to_usage = collections.Counter()
-+ for k, vlist in item.meta['sizes'].items():
-+ tag_name_to_usage[common.StripPlural(k.val)] = sum(v.val for v in vlist)
-+ tag_names = set(tag_name_to_usage.keys())
-+ if tag_names != supported_tag_names:
-+ raise ValueError('META "sizes" field have identical fields as actual '
-+ '"sizes" field.')
-+ else:
-+ # Generated GRD start with '<(SHARED_INTERMEDIATE_DIR)'. Just check '<'.
-+ if item.grd.startswith('<'):
-+ raise ValueError('%s: Generated GRD must use META with "sizes" field '
-+ 'to specify size bounds.' % item.grd)
-+ grd_file = os.path.join(src_dir, item.grd)
-+ if not os.path.exists(grd_file):
-+ # Silently skip missing files so that src-internal files do not break
-+ # public checkouts.
-+ yield item, {}
-+ continue
-+ tag_name_to_usage = _CountResourceUsage(grd_file, seen_files)
-+ tag_names = set(tag_name_to_usage.keys())
-+ if not tag_names.issubset(supported_tag_names):
-+ missing = [t + 's' for t in tag_names - supported_tag_names]
-+ raise ValueError(
-+ 'Resource ids for %s needs entry for %s' % (item.grd, missing))
-+ yield item, tag_name_to_usage
-diff --git a/tools/grit/grit/tool/xmb.py b/tools/grit/grit/tool/xmb.py
-new file mode 100644
-index 0000000000..b821308369
---- /dev/null
-+++ b/tools/grit/grit/tool/xmb.py
-@@ -0,0 +1,295 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+"""The 'grit xmb' tool.
-+"""
-+
-+from __future__ import print_function
-+
-+import getopt
-+import os
-+import sys
-+
-+from xml.sax import saxutils
-+
-+import six
-+
-+from grit import grd_reader
-+from grit import lazy_re
-+from grit import tclib
-+from grit import util
-+from grit.tool import interface
-+
-+
-+# Used to collapse presentable content to determine if
-+# xml:space="preserve" is needed.
-+_WHITESPACES_REGEX = lazy_re.compile(r'\s\s*')
-+
-+
-+# See XmlEscape below.
-+_XML_QUOTE_ESCAPES = {
-+ u"'": u'&apos;',
-+ u'"': u'&quot;',
-+}
-+
-+def _XmlEscape(s):
-+ """Returns text escaped for XML in a way compatible with Google's
-+ internal Translation Console tool. May be used for attributes as
-+ well as for contents.
-+ """
-+ return saxutils.escape(six.text_type(s), _XML_QUOTE_ESCAPES).encode('utf-8')
-+
-+
-+def _WriteAttribute(file, name, value):
-+ """Writes an XML attribute to the specified file.
-+
-+ Args:
-+ file: file to write to
-+ name: name of the attribute
-+ value: (unescaped) value of the attribute
-+ """
-+ name = name.encode('utf-8')
-+ if value:
-+ file.write(b' %s="%s"' % (name, _XmlEscape(value)))
-+
-+
-+def _WriteMessage(file, message):
-+ presentable_content = message.GetPresentableContent()
-+ assert (isinstance(presentable_content, six.string_types) or
-+ (len(message.parts) == 1 and
-+ type(message.parts[0] == tclib.Placeholder)))
-+ preserve_space = presentable_content != _WHITESPACES_REGEX.sub(
-+ u' ', presentable_content.strip())
-+
-+ file.write(b'<msg')
-+ _WriteAttribute(file, 'desc', message.GetDescription())
-+ _WriteAttribute(file, 'id', message.GetId())
-+ _WriteAttribute(file, 'meaning', message.GetMeaning())
-+ if preserve_space:
-+ _WriteAttribute(file, 'xml:space', 'preserve')
-+ file.write(b'>')
-+ if not preserve_space:
-+ file.write(b'\n ')
-+
-+ parts = message.GetContent()
-+ for part in parts:
-+ if isinstance(part, tclib.Placeholder):
-+ file.write(b'<ph')
-+ _WriteAttribute(file, 'name', part.GetPresentation())
-+ file.write(b'><ex>')
-+ file.write(_XmlEscape(part.GetExample()))
-+ file.write(b'</ex>')
-+ file.write(_XmlEscape(part.GetOriginal()))
-+ file.write(b'</ph>')
-+ else:
-+ file.write(_XmlEscape(part))
-+ if not preserve_space:
-+ file.write(b'\n')
-+ file.write(b'</msg>\n')
-+
-+
-+def WriteXmbFile(file, messages):
-+ """Writes the given grit.tclib.Message items to the specified open
-+ file-like object in the XMB format.
-+ """
-+ file.write(b"""<?xml version="1.0" encoding="UTF-8"?>
-+<!DOCTYPE messagebundle [
-+<!ELEMENT messagebundle (msg)*>
-+<!ATTLIST messagebundle class CDATA #IMPLIED>
-+
-+<!ELEMENT msg (#PCDATA|ph|source)*>
-+<!ATTLIST msg id CDATA #IMPLIED>
-+<!ATTLIST msg seq CDATA #IMPLIED>
-+<!ATTLIST msg name CDATA #IMPLIED>
-+<!ATTLIST msg desc CDATA #IMPLIED>
-+<!ATTLIST msg meaning CDATA #IMPLIED>
-+<!ATTLIST msg obsolete (obsolete) #IMPLIED>
-+<!ATTLIST msg xml:space (default|preserve) "default">
-+<!ATTLIST msg is_hidden CDATA #IMPLIED>
-+
-+<!ELEMENT source (#PCDATA)>
-+
-+<!ELEMENT ph (#PCDATA|ex)*>
-+<!ATTLIST ph name CDATA #REQUIRED>
-+
-+<!ELEMENT ex (#PCDATA)>
-+]>
-+<messagebundle>
-+""")
-+ for message in messages:
-+ _WriteMessage(file, message)
-+ file.write(b'</messagebundle>')
-+
-+
-+class OutputXmb(interface.Tool):
-+ """Outputs all translateable messages in the .grd input file to an
-+.xmb file, which is the format used to give source messages to
-+Google's internal Translation Console tool. The format could easily
-+be used for other systems.
-+
-+Usage: grit xmb [-i|-h] [-l LIMITFILE] OUTPUTPATH
-+
-+OUTPUTPATH is the path you want to output the .xmb file to.
-+
-+The -l option can be used to output only some of the resources to the .xmb file.
-+LIMITFILE is the path to a file that is used to limit the items output to the
-+xmb file. If the filename extension is .grd, the file must be a .grd file
-+and the tool only output the contents of nodes from the input file that also
-+exist in the limit file (as compared on the 'name' attribute). Otherwise it must
-+contain a list of the IDs that output should be limited to, one ID per line, and
-+the tool will only output nodes with 'name' attributes that match one of the
-+IDs.
-+
-+The -i option causes 'grit xmb' to output an "IDs only" file instead of an XMB
-+file. The "IDs only" file contains the message ID of each message that would
-+normally be output to the XMB file, one message ID per line. It is designed for
-+use with the 'grit transl2tc' tool's -l option.
-+
-+Other options:
-+
-+ -D NAME[=VAL] Specify a C-preprocessor-like define NAME with optional
-+ value VAL (defaults to 1) which will be used to control
-+ conditional inclusion of resources.
-+
-+ -E NAME=VALUE Set environment variable NAME to VALUE (within grit).
-+
-+"""
-+ # The different output formats supported by this tool
-+ FORMAT_XMB = 0
-+ FORMAT_IDS_ONLY = 1
-+
-+ def __init__(self, defines=None):
-+ super(OutputXmb, self).__init__()
-+ self.format = self.FORMAT_XMB
-+ self.defines = defines or {}
-+
-+ def ShortDescription(self):
-+ return 'Exports all translateable messages into an XMB file.'
-+
-+ def Run(self, opts, args):
-+ os.environ['cwd'] = os.getcwd()
-+
-+ self.SetOptions(opts)
-+
-+ limit_file = None
-+ limit_is_grd = False
-+ limit_file_dir = None
-+ own_opts, args = getopt.getopt(args, 'l:D:ih', ('help',))
-+ for key, val in own_opts:
-+ if key == '-l':
-+ limit_file = open(val, 'r')
-+ limit_file_dir = util.dirname(val)
-+ if not len(limit_file_dir):
-+ limit_file_dir = '.'
-+ limit_is_grd = os.path.splitext(val)[1] == '.grd'
-+ elif key == '-i':
-+ self.format = self.FORMAT_IDS_ONLY
-+ elif key == '-D':
-+ name, val = util.ParseDefine(val)
-+ self.defines[name] = val
-+ elif key == '-E':
-+ (env_name, env_value) = val.split('=', 1)
-+ os.environ[env_name] = env_value
-+ elif key == '--help':
-+ self.ShowUsage()
-+ sys.exit(0)
-+ if not len(args) == 1:
-+ print('grit xmb takes exactly one argument, the path to the XMB file '
-+ 'to output.')
-+ return 2
-+
-+ xmb_path = args[0]
-+ res_tree = grd_reader.Parse(opts.input, debug=opts.extra_verbose, defines=self.defines)
-+ res_tree.SetOutputLanguage('en')
-+ res_tree.SetDefines(self.defines)
-+ res_tree.OnlyTheseTranslations([])
-+ res_tree.RunGatherers()
-+
-+ with open(xmb_path, 'wb') as output_file:
-+ self.Process(
-+ res_tree, output_file, limit_file, limit_is_grd, limit_file_dir)
-+ if limit_file:
-+ limit_file.close()
-+ print("Wrote %s" % xmb_path)
-+
-+ def Process(self, res_tree, output_file, limit_file=None, limit_is_grd=False,
-+ dir=None):
-+ """Writes a document with the contents of res_tree into output_file,
-+ limiting output to the IDs specified in limit_file, which is a GRD file if
-+ limit_is_grd is true, otherwise a file with one ID per line.
-+
-+ The format of the output document depends on this object's format attribute.
-+ It can be FORMAT_XMB or FORMAT_IDS_ONLY.
-+
-+ The FORMAT_IDS_ONLY format causes this function to write just a list
-+ of the IDs of all messages that would have been added to the XMB file, one
-+ ID per line.
-+
-+ The FORMAT_XMB format causes this function to output the (default) XMB
-+ format.
-+
-+ Args:
-+ res_tree: base.Node()
-+ output_file: file open for writing
-+ limit_file: None or file open for reading
-+ limit_is_grd: True | False
-+ dir: Directory of the limit file
-+ """
-+ if limit_file:
-+ if limit_is_grd:
-+ limit_list = []
-+ limit_tree = grd_reader.Parse(limit_file,
-+ dir=dir,
-+ debug=self.o.extra_verbose)
-+ for node in limit_tree:
-+ if 'name' in node.attrs:
-+ limit_list.append(node.attrs['name'])
-+ else:
-+ # Not a GRD file, so it's just a file with one ID per line
-+ limit_list = [item.strip() for item in limit_file.read().split('\n')]
-+
-+ ids_already_done = {}
-+ messages = []
-+ for node in res_tree:
-+ if (limit_file and
-+ not ('name' in node.attrs and node.attrs['name'] in limit_list)):
-+ continue
-+ if not node.IsTranslateable():
-+ continue
-+
-+ for clique in node.GetCliques():
-+ if not clique.IsTranslateable():
-+ continue
-+ if not clique.GetMessage().GetRealContent():
-+ continue
-+
-+ # Some explanation is in order here. Note that we can have
-+ # many messages with the same ID.
-+ #
-+ # The way we work around this is to maintain a list of cliques
-+ # per message ID (in the UberClique) and select the "best" one
-+ # (the first one that has a description, or an arbitrary one
-+ # if there is no description) for inclusion in the XMB file.
-+ # The translations are all going to be the same for messages
-+ # with the same ID, although the way we replace placeholders
-+ # might be slightly different.
-+ id = clique.GetMessage().GetId()
-+ if id in ids_already_done:
-+ continue
-+ ids_already_done[id] = 1
-+
-+ message = node.UberClique().BestClique(id).GetMessage()
-+ messages += [message]
-+
-+ # Ensure a stable order of messages, to help regression testing.
-+ messages.sort(key=lambda x:x.GetId())
-+
-+ if self.format == self.FORMAT_IDS_ONLY:
-+ # We just print the list of IDs to the output file.
-+ for msg in messages:
-+ output_file.write(msg.GetId())
-+ output_file.write('\n')
-+ else:
-+ assert self.format == self.FORMAT_XMB
-+ WriteXmbFile(output_file, messages)
-diff --git a/tools/grit/grit/tool/xmb_unittest.py b/tools/grit/grit/tool/xmb_unittest.py
-new file mode 100644
-index 0000000000..3c7e92cee7
---- /dev/null
-+++ b/tools/grit/grit/tool/xmb_unittest.py
-@@ -0,0 +1,132 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for 'grit xmb' tool.'''
-+
-+from __future__ import print_function
-+
-+import io
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-+
-+import unittest
-+import xml.sax
-+
-+from six import StringIO
-+
-+from grit import grd_reader
-+from grit import util
-+from grit.tool import xmb
-+
-+
-+class XmbUnittest(unittest.TestCase):
-+ def setUp(self):
-+ self.res_tree = grd_reader.Parse(
-+ io.BytesIO(u'''<?xml version="1.0" encoding="UTF-8"?>
-+ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
-+ <release seq="3">
-+ <includes>
-+ <include type="gif" name="ID_LOGO" file="images/logo.gif" />
-+ </includes>
-+ <messages>
-+ <message name="GOOD" desc="sub" sub_variable="true">
-+ excellent
-+ </message>
-+ <message name="IDS_GREETING" desc="Printed to greet the currently logged in user">
-+ Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, are you doing [GOOD] today?
-+ </message>
-+ <message name="IDS_BONGOBINGO">
-+ Yibbee
-+ </message>
-+ <message name="IDS_UNICODE">
-+ Ol\xe1, \u4eca\u65e5\u306f! \U0001F60A
-+ </message>
-+ </messages>
-+ <structures>
-+ <structure type="dialog" name="IDD_SPACYBOX" encoding="utf-16" file="grit/testdata/klonk.rc" />
-+ </structures>
-+ </release>
-+ </grit>'''.encode('utf-8')), '.')
-+ self.xmb_file = io.BytesIO()
-+
-+ def testNormalOutput(self):
-+ xmb.OutputXmb().Process(self.res_tree, self.xmb_file)
-+ output = self.xmb_file.getvalue().decode('utf-8')
-+ self.failUnless(output.count('Joi'))
-+ self.failUnless(output.count('Yibbee'))
-+ self.failUnless(output.count(u'Ol\xe1, \u4eca\u65e5\u306f! \U0001F60A'))
-+
-+ def testLimitList(self):
-+ limit_file = StringIO(
-+ 'IDS_BONGOBINGO\nIDS_DOES_NOT_EXIST\nIDS_ALSO_DOES_NOT_EXIST')
-+ xmb.OutputXmb().Process(self.res_tree, self.xmb_file, limit_file, False)
-+ output = self.xmb_file.getvalue().decode('utf-8')
-+ self.failUnless(output.count('Yibbee'))
-+ self.failUnless(not output.count('Joi'))
-+
-+ def testLimitGrd(self):
-+ limit_file = StringIO('''<?xml version="1.0" encoding="UTF-8"?>
-+ <grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
-+ <release seq="3">
-+ <messages>
-+ <message name="IDS_GREETING" desc="Printed to greet the currently logged in user">
-+ Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, how are you doing today?
-+ </message>
-+ </messages>
-+ </release>
-+ </grit>''')
-+ tool = xmb.OutputXmb()
-+ class DummyOpts(object):
-+ extra_verbose = False
-+ tool.o = DummyOpts()
-+ tool.Process(self.res_tree, self.xmb_file, limit_file, True, dir='.')
-+ output = self.xmb_file.getvalue().decode('utf-8')
-+ self.failUnless(output.count('Joi'))
-+ self.failUnless(not output.count('Yibbee'))
-+
-+ def testSubstitution(self):
-+ self.res_tree.SetOutputLanguage('en')
-+ os.chdir(util.PathFromRoot('.')) # so it can find klonk.rc
-+ self.res_tree.RunGatherers()
-+ xmb.OutputXmb().Process(self.res_tree, self.xmb_file)
-+ output = self.xmb_file.getvalue().decode('utf-8')
-+ self.failUnless(output.count(
-+ '<ph name="GOOD_1"><ex>excellent</ex>[GOOD]</ph>'))
-+
-+ def testLeadingTrailingWhitespace(self):
-+ # Regression test for problems outputting messages with leading or
-+ # trailing whitespace (these come in via structures only, as
-+ # message nodes already strip and store whitespace).
-+ self.res_tree.SetOutputLanguage('en')
-+ os.chdir(util.PathFromRoot('.')) # so it can find klonk.rc
-+ self.res_tree.RunGatherers()
-+ xmb.OutputXmb().Process(self.res_tree, self.xmb_file)
-+ output = self.xmb_file.getvalue().decode('utf-8')
-+ self.failUnless(output.count('OK ? </msg>'))
-+
-+ def testDisallowedChars(self):
-+ # Validate that the invalid unicode is not accepted. Since it's not valid,
-+ # we can't specify it in a string literal, so write as a byte sequence.
-+ bad_xml = io.BytesIO()
-+ bad_xml.write(b'''<?xml version="1.0" encoding="UTF-8"?>
-+ <grit latest_public_release="2" source_lang_id="en-US"
-+ current_release="3" base_dir=".">
-+ <release seq="3">
-+ <messages>
-+ <message name="ID_FOO">''')
-+ # UTF-8 corresponding to to \U00110000
-+ # http://apps.timwhitlock.info/unicode/inspect/hex/110000
-+ bad_xml.write(b'\xF4\x90\x80\x80')
-+ bad_xml.write(b'''</message>
-+ </messages>
-+ </release>
-+ </grit>''')
-+ bad_xml.seek(0)
-+ self.assertRaises(xml.sax.SAXParseException, grd_reader.Parse, bad_xml, '.')
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/util.py b/tools/grit/grit/util.py
-new file mode 100644
-index 0000000000..98433d154c
---- /dev/null
-+++ b/tools/grit/grit/util.py
-@@ -0,0 +1,691 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Utilities used by GRIT.
-+'''
-+
-+from __future__ import print_function
-+
-+import codecs
-+import io
-+import os
-+import re
-+import shutil
-+import sys
-+import tempfile
-+from xml.sax import saxutils
-+
-+import six
-+from six import StringIO
-+from six.moves import html_entities as entities
-+
-+from grit import lazy_re
-+
-+_root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
-+
-+
-+# Unique constants for use by ReadFile().
-+BINARY = 0
-+
-+
-+# Unique constants representing data pack encodings.
-+_, UTF8, UTF16 = range(3)
-+
-+
-+def Encode(message, encoding):
-+ '''Returns a byte stream that represents |message| in the given |encoding|.'''
-+ # |message| is a python unicode string, so convert to a byte stream that
-+ # has the correct encoding requested for the datapacks. We skip the first
-+ # 2 bytes of text resources because it is the BOM.
-+ if encoding == UTF8:
-+ return message.encode('utf8')
-+ if encoding == UTF16:
-+ return message.encode('utf16')[2:]
-+ # Default is BINARY
-+ return message
-+
-+
-+# Matches all different types of linebreaks.
-+LINEBREAKS = re.compile('\r\n|\n|\r')
-+
-+def MakeRelativePath(base_path, path_to_make_relative):
-+ """Returns a relative path such from the base_path to
-+ the path_to_make_relative.
-+
-+ In other words, os.join(base_path,
-+ MakeRelativePath(base_path, path_to_make_relative))
-+ is the same location as path_to_make_relative.
-+
-+ Args:
-+ base_path: the root path
-+ path_to_make_relative: an absolute path that is on the same drive
-+ as base_path
-+ """
-+
-+ def _GetPathAfterPrefix(prefix_path, path_with_prefix):
-+ """Gets the subpath within in prefix_path for the path_with_prefix
-+ with no beginning or trailing path separators.
-+
-+ Args:
-+ prefix_path: the base path
-+ path_with_prefix: a path that starts with prefix_path
-+ """
-+ assert path_with_prefix.startswith(prefix_path)
-+ path_without_prefix = path_with_prefix[len(prefix_path):]
-+ normalized_path = os.path.normpath(path_without_prefix.strip(os.path.sep))
-+ if normalized_path == '.':
-+ normalized_path = ''
-+ return normalized_path
-+
-+ def _GetCommonBaseDirectory(*args):
-+ """Returns the common prefix directory for the given paths
-+
-+ Args:
-+ The list of paths (at least one of which should be a directory)
-+ """
-+ prefix = os.path.commonprefix(args)
-+ # prefix is a character-by-character prefix (i.e. it does not end
-+ # on a directory bound, so this code fixes that)
-+
-+ # if the prefix ends with the separator, then it is prefect.
-+ if len(prefix) > 0 and prefix[-1] == os.path.sep:
-+ return prefix
-+
-+ # We need to loop through all paths or else we can get
-+ # tripped up by "c:\a" and "c:\abc". The common prefix
-+ # is "c:\a" which is a directory and looks good with
-+ # respect to the first directory but it is clear that
-+ # isn't a common directory when the second path is
-+ # examined.
-+ for path in args:
-+ assert len(path) >= len(prefix)
-+ # If the prefix the same length as the path,
-+ # then the prefix must be a directory (since one
-+ # of the arguements should be a directory).
-+ if path == prefix:
-+ continue
-+ # if the character after the prefix in the path
-+ # is the separator, then the prefix appears to be a
-+ # valid a directory as well for the given path
-+ if path[len(prefix)] == os.path.sep:
-+ continue
-+ # Otherwise, the prefix is not a directory, so it needs
-+ # to be shortened to be one
-+ index_sep = prefix.rfind(os.path.sep)
-+ # The use "index_sep + 1" because it includes the final sep
-+ # and it handles the case when the index_sep is -1 as well
-+ prefix = prefix[:index_sep + 1]
-+ # At this point we backed up to a directory bound which is
-+ # common to all paths, so we can quit going through all of
-+ # the paths.
-+ break
-+ return prefix
-+
-+ prefix = _GetCommonBaseDirectory(base_path, path_to_make_relative)
-+ # If the paths had no commonality at all, then return the absolute path
-+ # because it is the best that can be done. If the path had to be relative
-+ # then eventually this absolute path will be discovered (when a build breaks)
-+ # and an appropriate fix can be made, but having this allows for the best
-+ # backward compatibility with the absolute path behavior in the past.
-+ if len(prefix) <= 0:
-+ return path_to_make_relative
-+ # Build a path from the base dir to the common prefix
-+ remaining_base_path = _GetPathAfterPrefix(prefix, base_path)
-+
-+ # The follow handles two case: "" and "foo\\bar"
-+ path_pieces = remaining_base_path.split(os.path.sep)
-+ base_depth_from_prefix = len([d for d in path_pieces if len(d)])
-+ base_to_prefix = (".." + os.path.sep) * base_depth_from_prefix
-+
-+ # Put add in the path from the prefix to the path_to_make_relative
-+ remaining_other_path = _GetPathAfterPrefix(prefix, path_to_make_relative)
-+ return base_to_prefix + remaining_other_path
-+
-+
-+KNOWN_SYSTEM_IDENTIFIERS = set()
-+
-+SYSTEM_IDENTIFIERS = None
-+
-+def SetupSystemIdentifiers(ids):
-+ '''Adds ids to a regexp of known system identifiers.
-+
-+ Can be called many times, ids will be accumulated.
-+
-+ Args:
-+ ids: an iterable of strings
-+ '''
-+ KNOWN_SYSTEM_IDENTIFIERS.update(ids)
-+ global SYSTEM_IDENTIFIERS
-+ SYSTEM_IDENTIFIERS = lazy_re.compile(
-+ ' | '.join([r'\b%s\b' % i for i in KNOWN_SYSTEM_IDENTIFIERS]),
-+ re.VERBOSE)
-+
-+
-+# Matches all of the resource IDs predefined by Windows.
-+SetupSystemIdentifiers((
-+ 'IDOK', 'IDCANCEL', 'IDC_STATIC', 'IDYES', 'IDNO',
-+ 'ID_FILE_NEW', 'ID_FILE_OPEN', 'ID_FILE_CLOSE', 'ID_FILE_SAVE',
-+ 'ID_FILE_SAVE_AS', 'ID_FILE_PAGE_SETUP', 'ID_FILE_PRINT_SETUP',
-+ 'ID_FILE_PRINT', 'ID_FILE_PRINT_DIRECT', 'ID_FILE_PRINT_PREVIEW',
-+ 'ID_FILE_UPDATE', 'ID_FILE_SAVE_COPY_AS', 'ID_FILE_SEND_MAIL',
-+ 'ID_FILE_MRU_FIRST', 'ID_FILE_MRU_LAST',
-+ 'ID_EDIT_CLEAR', 'ID_EDIT_CLEAR_ALL', 'ID_EDIT_COPY',
-+ 'ID_EDIT_CUT', 'ID_EDIT_FIND', 'ID_EDIT_PASTE', 'ID_EDIT_PASTE_LINK',
-+ 'ID_EDIT_PASTE_SPECIAL', 'ID_EDIT_REPEAT', 'ID_EDIT_REPLACE',
-+ 'ID_EDIT_SELECT_ALL', 'ID_EDIT_UNDO', 'ID_EDIT_REDO',
-+ 'VS_VERSION_INFO', 'IDRETRY',
-+ 'ID_APP_ABOUT', 'ID_APP_EXIT',
-+ 'ID_NEXT_PANE', 'ID_PREV_PANE',
-+ 'ID_WINDOW_NEW', 'ID_WINDOW_ARRANGE', 'ID_WINDOW_CASCADE',
-+ 'ID_WINDOW_TILE_HORZ', 'ID_WINDOW_TILE_VERT', 'ID_WINDOW_SPLIT',
-+ 'ATL_IDS_SCSIZE', 'ATL_IDS_SCMOVE', 'ATL_IDS_SCMINIMIZE',
-+ 'ATL_IDS_SCMAXIMIZE', 'ATL_IDS_SCNEXTWINDOW', 'ATL_IDS_SCPREVWINDOW',
-+ 'ATL_IDS_SCCLOSE', 'ATL_IDS_SCRESTORE', 'ATL_IDS_SCTASKLIST',
-+ 'ATL_IDS_MDICHILD', 'ATL_IDS_IDLEMESSAGE', 'ATL_IDS_MRU_FILE' ))
-+
-+
-+# Matches character entities, whether specified by name, decimal or hex.
-+_HTML_ENTITY = lazy_re.compile(
-+ '&(#(?P<decimal>[0-9]+)|#x(?P<hex>[a-fA-F0-9]+)|(?P<named>[a-z0-9]+));',
-+ re.IGNORECASE)
-+
-+# Matches characters that should be HTML-escaped. This is <, > and &, but only
-+# if the & is not the start of an HTML character entity.
-+_HTML_CHARS_TO_ESCAPE = lazy_re.compile(
-+ '"|<|>|&(?!#[0-9]+|#x[0-9a-z]+|[a-z]+;)',
-+ re.IGNORECASE | re.MULTILINE)
-+
-+
-+def ReadFile(filename, encoding):
-+ '''Reads and returns the entire contents of the given file.
-+
-+ Args:
-+ filename: The path to the file.
-+ encoding: A Python codec name or the special value: BINARY to read
-+ the file in binary mode.
-+ '''
-+ if encoding == BINARY:
-+ mode = 'rb'
-+ encoding = None
-+ else:
-+ mode = 'rU'
-+
-+ with io.open(filename, mode, encoding=encoding) as f:
-+ return f.read()
-+
-+
-+def WrapOutputStream(stream, encoding = 'utf-8'):
-+ '''Returns a stream that wraps the provided stream, making it write
-+ characters using the specified encoding.'''
-+ return codecs.getwriter(encoding)(stream)
-+
-+
-+def ChangeStdoutEncoding(encoding = 'utf-8'):
-+ '''Changes STDOUT to print characters using the specified encoding.'''
-+ # If we're unittesting, don't reconfigure.
-+ if isinstance(sys.stdout, StringIO):
-+ return
-+
-+ if sys.version_info.major < 3:
-+ # Python 2 has binary streams by default, so reconfigure directly.
-+ sys.stdout = WrapOutputStream(sys.stdout, encoding)
-+ sys.stderr = WrapOutputStream(sys.stderr, encoding)
-+ elif sys.version_info < (3, 7):
-+ # Python 3 has text streams by default, so we have to detach them first.
-+ sys.stdout = WrapOutputStream(sys.stdout.detach(), encoding)
-+ sys.stderr = WrapOutputStream(sys.stderr.detach(), encoding)
-+ else:
-+ # Python 3.7+ provides an API for this specifically.
-+ sys.stdout.reconfigure(encoding=encoding)
-+ sys.stderr.reconfigure(encoding=encoding)
-+
-+
-+def EscapeHtml(text, escape_quotes = False):
-+ '''Returns 'text' with <, > and & (and optionally ") escaped to named HTML
-+ entities. Any existing named entity or HTML entity defined by decimal or
-+ hex code will be left untouched. This is appropriate for escaping text for
-+ inclusion in HTML, but not for XML.
-+ '''
-+ def Replace(match):
-+ if match.group() == '&': return '&amp;'
-+ elif match.group() == '<': return '&lt;'
-+ elif match.group() == '>': return '&gt;'
-+ elif match.group() == '"':
-+ if escape_quotes: return '&quot;'
-+ else: return match.group()
-+ else: assert False
-+ out = _HTML_CHARS_TO_ESCAPE.sub(Replace, text)
-+ return out
-+
-+
-+def UnescapeHtml(text, replace_nbsp=True):
-+ '''Returns 'text' with all HTML character entities (both named character
-+ entities and those specified by decimal or hexadecimal Unicode ordinal)
-+ replaced by their Unicode characters (or latin1 characters if possible).
-+
-+ The only exception is that &nbsp; will not be escaped if 'replace_nbsp' is
-+ False.
-+ '''
-+ def Replace(match):
-+ groups = match.groupdict()
-+ if groups['hex']:
-+ return six.unichr(int(groups['hex'], 16))
-+ elif groups['decimal']:
-+ return six.unichr(int(groups['decimal'], 10))
-+ else:
-+ name = groups['named']
-+ if name == 'nbsp' and not replace_nbsp:
-+ return match.group() # Don't replace &nbsp;
-+ assert name != None
-+ if name in entities.name2codepoint:
-+ return six.unichr(entities.name2codepoint[name])
-+ else:
-+ return match.group() # Unknown HTML character entity - don't replace
-+
-+ out = _HTML_ENTITY.sub(Replace, text)
-+ return out
-+
-+
-+def EncodeCdata(cdata):
-+ '''Returns the provided cdata in either escaped format or <![CDATA[xxx]]>
-+ format, depending on which is more appropriate for easy editing. The data
-+ is escaped for inclusion in an XML element's body.
-+
-+ Args:
-+ cdata: 'If x < y and y < z then x < z'
-+
-+ Return:
-+ '<![CDATA[If x < y and y < z then x < z]]>'
-+ '''
-+ if cdata.count('<') > 1 or cdata.count('>') > 1 and cdata.count(']]>') == 0:
-+ return '<![CDATA[%s]]>' % cdata
-+ else:
-+ return saxutils.escape(cdata)
-+
-+
-+def FixupNamedParam(function, param_name, param_value):
-+ '''Returns a closure that is identical to 'function' but ensures that the
-+ named parameter 'param_name' is always set to 'param_value' unless explicitly
-+ set by the caller.
-+
-+ Args:
-+ function: callable
-+ param_name: 'bingo'
-+ param_value: 'bongo' (any type)
-+
-+ Return:
-+ callable
-+ '''
-+ def FixupClosure(*args, **kw):
-+ if not param_name in kw:
-+ kw[param_name] = param_value
-+ return function(*args, **kw)
-+ return FixupClosure
-+
-+
-+def PathFromRoot(path):
-+ r'''Takes a path relative to the root directory for GRIT (the one that grit.py
-+ resides in) and returns a path that is either absolute or relative to the
-+ current working directory (i.e .a path you can use to open the file).
-+
-+ Args:
-+ path: 'rel_dir\file.ext'
-+
-+ Return:
-+ 'c:\src\tools\rel_dir\file.ext
-+ '''
-+ return os.path.normpath(os.path.join(_root_dir, path))
-+
-+
-+def ParseGrdForUnittest(body, base_dir=None, predetermined_ids_file=None,
-+ run_gatherers=False):
-+ '''Parse a skeleton .grd file and return it, for use in unit tests.
-+
-+ Args:
-+ body: XML that goes inside the <release> element.
-+ base_dir: The base_dir attribute of the <grit> tag.
-+ '''
-+ from grit import grd_reader
-+ if isinstance(body, six.text_type):
-+ body = body.encode('utf-8')
-+ if base_dir is None:
-+ base_dir = PathFromRoot('.')
-+ lines = [b'<?xml version="1.0" encoding="UTF-8"?>']
-+ lines.append(b'<grit latest_public_release="2" current_release="3" '
-+ b'source_lang_id="en" base_dir="%s">' % base_dir.encode('utf-8'))
-+ if b'<outputs>' in body:
-+ lines.append(body)
-+ else:
-+ lines.append(b' <outputs></outputs>')
-+ lines.append(b' <release seq="3">')
-+ lines.append(body)
-+ lines.append(b' </release>')
-+ lines.append(b'</grit>')
-+ ret = grd_reader.Parse(io.BytesIO(b'\n'.join(lines)), dir='.')
-+ ret.SetOutputLanguage('en')
-+ if run_gatherers:
-+ ret.RunGatherers()
-+ ret.SetPredeterminedIdsFile(predetermined_ids_file)
-+ ret.InitializeIds()
-+ return ret
-+
-+
-+def StripBlankLinesAndComments(text):
-+ '''Strips blank lines and comments from C source code, for unit tests.'''
-+ return '\n'.join(line for line in text.splitlines()
-+ if line and not line.startswith('//'))
-+
-+
-+def dirname(filename):
-+ '''Version of os.path.dirname() that never returns empty paths (returns
-+ '.' if the result of os.path.dirname() is empty).
-+ '''
-+ ret = os.path.dirname(filename)
-+ if ret == '':
-+ ret = '.'
-+ return ret
-+
-+
-+def normpath(path):
-+ '''Version of os.path.normpath that also changes backward slashes to
-+ forward slashes when not running on Windows.
-+ '''
-+ # This is safe to always do because the Windows version of os.path.normpath
-+ # will replace forward slashes with backward slashes.
-+ path = path.replace('\\', '/')
-+ return os.path.normpath(path)
-+
-+
-+_LANGUAGE_SPLIT_RE = lazy_re.compile('-|_|/')
-+
-+
-+def CanonicalLanguage(code):
-+ '''Canonicalizes two-part language codes by using a dash and making the
-+ second part upper case. Returns one-part language codes unchanged.
-+
-+ Args:
-+ code: 'zh_cn'
-+
-+ Return:
-+ code: 'zh-CN'
-+ '''
-+ parts = _LANGUAGE_SPLIT_RE.split(code)
-+ code = [ parts[0] ]
-+ for part in parts[1:]:
-+ code.append(part.upper())
-+ return '-'.join(code)
-+
-+
-+_LANG_TO_CODEPAGE = {
-+ 'en' : 1252,
-+ 'fr' : 1252,
-+ 'it' : 1252,
-+ 'de' : 1252,
-+ 'es' : 1252,
-+ 'nl' : 1252,
-+ 'sv' : 1252,
-+ 'no' : 1252,
-+ 'da' : 1252,
-+ 'fi' : 1252,
-+ 'pt-BR' : 1252,
-+ 'ru' : 1251,
-+ 'ja' : 932,
-+ 'zh-TW' : 950,
-+ 'zh-CN' : 936,
-+ 'ko' : 949,
-+}
-+
-+
-+def LanguageToCodepage(lang):
-+ '''Returns the codepage _number_ that can be used to represent 'lang', which
-+ may be either in formats such as 'en', 'pt_br', 'pt-BR', etc.
-+
-+ The codepage returned will be one of the 'cpXXXX' codepage numbers.
-+
-+ Args:
-+ lang: 'de'
-+
-+ Return:
-+ 1252
-+ '''
-+ lang = CanonicalLanguage(lang)
-+ if lang in _LANG_TO_CODEPAGE:
-+ return _LANG_TO_CODEPAGE[lang]
-+ else:
-+ print("Not sure which codepage to use for %s, assuming cp1252" % lang)
-+ return 1252
-+
-+def NewClassInstance(class_name, class_type):
-+ '''Returns an instance of the class specified in classname
-+
-+ Args:
-+ class_name: the fully qualified, dot separated package + classname,
-+ i.e. "my.package.name.MyClass". Short class names are not supported.
-+ class_type: the class or superclass this object must implement
-+
-+ Return:
-+ An instance of the class, or None if none was found
-+ '''
-+ lastdot = class_name.rfind('.')
-+ module_name = ''
-+ if lastdot >= 0:
-+ module_name = class_name[0:lastdot]
-+ if module_name:
-+ class_name = class_name[lastdot+1:]
-+ module = __import__(module_name, globals(), locals(), [''])
-+ if hasattr(module, class_name):
-+ class_ = getattr(module, class_name)
-+ class_instance = class_()
-+ if isinstance(class_instance, class_type):
-+ return class_instance
-+ return None
-+
-+
-+def FixLineEnd(text, line_end):
-+ # First normalize
-+ text = text.replace('\r\n', '\n')
-+ text = text.replace('\r', '\n')
-+ # Then fix
-+ text = text.replace('\n', line_end)
-+ return text
-+
-+
-+def BoolToString(bool):
-+ if bool:
-+ return 'true'
-+ else:
-+ return 'false'
-+
-+
-+verbose = False
-+extra_verbose = False
-+
-+def IsVerbose():
-+ return verbose
-+
-+def IsExtraVerbose():
-+ return extra_verbose
-+
-+def ParseDefine(define):
-+ '''Parses a define argument and returns the name and value.
-+
-+ The format is either "NAME=VAL" or "NAME", using True as the default value.
-+ Values of "1"/"true" and "0"/"false" are transformed to True and False
-+ respectively.
-+
-+ Args:
-+ define: a string of the form "NAME=VAL" or "NAME".
-+
-+ Returns:
-+ A (name, value) pair. name is a string, value a string or boolean.
-+ '''
-+ parts = [part.strip() for part in define.split('=', 1)]
-+ assert len(parts) >= 1
-+ name = parts[0]
-+ val = True
-+ if len(parts) > 1:
-+ val = parts[1]
-+ if val == "1" or val == "true": val = True
-+ elif val == "0" or val == "false": val = False
-+ return (name, val)
-+
-+
-+class Substituter(object):
-+ '''Finds and substitutes variable names in text strings.
-+
-+ Given a dictionary of variable names and values, prepares to
-+ search for patterns of the form [VAR_NAME] in a text.
-+ The value will be substituted back efficiently.
-+ Also applies to tclib.Message objects.
-+ '''
-+
-+ def __init__(self):
-+ '''Create an empty substituter.'''
-+ self.substitutions_ = {}
-+ self.dirty_ = True
-+
-+ def AddSubstitutions(self, subs):
-+ '''Add new values to the substitutor.
-+
-+ Args:
-+ subs: A dictionary of new substitutions.
-+ '''
-+ self.substitutions_.update(subs)
-+ self.dirty_ = True
-+
-+ def AddMessages(self, messages, lang):
-+ '''Adds substitutions extracted from node.Message objects.
-+
-+ Args:
-+ messages: a list of node.Message objects.
-+ lang: The translation language to use in substitutions.
-+ '''
-+ subs = [(str(msg.attrs['name']), msg.Translate(lang)) for msg in messages]
-+ self.AddSubstitutions(dict(subs))
-+ self.dirty_ = True
-+
-+ def GetExp(self):
-+ '''Obtain a regular expression that will find substitution keys in text.
-+
-+ Create and cache if the substituter has been updated. Use the cached value
-+ otherwise. Keys will be enclosed in [square brackets] in text.
-+
-+ Returns:
-+ A regular expression object.
-+ '''
-+ if self.dirty_:
-+ components = [r'\[%s\]' % (k,) for k in self.substitutions_]
-+ self.exp = re.compile(r'(%s)' % ('|'.join(components),))
-+ self.dirty_ = False
-+ return self.exp
-+
-+ def Substitute(self, text):
-+ '''Substitute the variable values in the given text.
-+
-+ Text of the form [message_name] will be replaced by the message's value.
-+
-+ Args:
-+ text: A string of text.
-+
-+ Returns:
-+ A string of text with substitutions done.
-+ '''
-+ return ''.join([self._SubFragment(f) for f in self.GetExp().split(text)])
-+
-+ def _SubFragment(self, fragment):
-+ '''Utility function for Substitute.
-+
-+ Performs a simple substitution if the fragment is exactly of the form
-+ [message_name].
-+
-+ Args:
-+ fragment: A simple string.
-+
-+ Returns:
-+ A string with the substitution done.
-+ '''
-+ if len(fragment) > 2 and fragment[0] == '[' and fragment[-1] == ']':
-+ sub = self.substitutions_.get(fragment[1:-1], None)
-+ if sub is not None:
-+ return sub
-+ return fragment
-+
-+ def SubstituteMessage(self, msg):
-+ '''Apply substitutions to a tclib.Message object.
-+
-+ Text of the form [message_name] will be replaced by a new placeholder,
-+ whose presentation will take the form the message_name_{UsageCount}, and
-+ whose example will be the message's value. Existing placeholders are
-+ not affected.
-+
-+ Args:
-+ msg: A tclib.Message object.
-+
-+ Returns:
-+ A tclib.Message object, with substitutions done.
-+ '''
-+ from grit import tclib # avoid circular import
-+ counts = {}
-+ text = msg.GetPresentableContent()
-+ placeholders = []
-+ newtext = ''
-+ for f in self.GetExp().split(text):
-+ sub = self._SubFragment(f)
-+ if f != sub:
-+ f = str(f)
-+ count = counts.get(f, 0) + 1
-+ counts[f] = count
-+ name = "%s_%d" % (f[1:-1], count)
-+ placeholders.append(tclib.Placeholder(name, f, sub))
-+ newtext += name
-+ else:
-+ newtext += f
-+ if placeholders:
-+ return tclib.Message(newtext, msg.GetPlaceholders() + placeholders,
-+ msg.GetDescription(), msg.GetMeaning())
-+ else:
-+ return msg
-+
-+
-+class TempDir(object):
-+ '''Creates files with the specified contents in a temporary directory,
-+ for unit testing.
-+ '''
-+
-+ def __init__(self, file_data, mode='w'):
-+ self._tmp_dir_name = tempfile.mkdtemp()
-+ assert not os.listdir(self.GetPath())
-+ for name, contents in file_data.items():
-+ file_path = self.GetPath(name)
-+ dir_path = os.path.split(file_path)[0]
-+ if not os.path.exists(dir_path):
-+ os.makedirs(dir_path)
-+ with open(file_path, mode) as f:
-+ f.write(file_data[name])
-+
-+ def __enter__(self):
-+ return self
-+
-+ def __exit__(self, *exc_info):
-+ self.CleanUp()
-+
-+ def CleanUp(self):
-+ shutil.rmtree(self.GetPath())
-+
-+ def GetPath(self, name=''):
-+ name = os.path.join(self._tmp_dir_name, name)
-+ assert name.startswith(self._tmp_dir_name)
-+ return name
-+
-+ def AsCurrentDir(self):
-+ return self._AsCurrentDirClass(self.GetPath())
-+
-+ class _AsCurrentDirClass(object):
-+ def __init__(self, path):
-+ self.path = path
-+ def __enter__(self):
-+ self.oldpath = os.getcwd()
-+ os.chdir(self.path)
-+ def __exit__(self, *exc_info):
-+ os.chdir(self.oldpath)
-diff --git a/tools/grit/grit/util_unittest.py b/tools/grit/grit/util_unittest.py
-new file mode 100644
-index 0000000000..7d6efaf858
---- /dev/null
-+++ b/tools/grit/grit/util_unittest.py
-@@ -0,0 +1,118 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit test that checks some of util functions.
-+'''
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
-+
-+import unittest
-+
-+import six
-+
-+from grit import util
-+
-+
-+class UtilUnittest(unittest.TestCase):
-+ ''' Tests functions from util
-+ '''
-+
-+ def testNewClassInstance(self):
-+ # Test short class name with no fully qualified package name
-+ # Should fail, it is not supported by the function now (as documented)
-+ cls = util.NewClassInstance('grit.util.TestClassToLoad',
-+ TestBaseClassToLoad)
-+ self.failUnless(cls == None)
-+
-+ # Test non existent class name
-+ cls = util.NewClassInstance('grit.util_unittest.NotExistingClass',
-+ TestBaseClassToLoad)
-+ self.failUnless(cls == None)
-+
-+ # Test valid class name and valid base class
-+ cls = util.NewClassInstance('grit.util_unittest.TestClassToLoad',
-+ TestBaseClassToLoad)
-+ self.failUnless(isinstance(cls, TestBaseClassToLoad))
-+
-+ # Test valid class name with wrong hierarchy
-+ cls = util.NewClassInstance('grit.util_unittest.TestClassNoBase',
-+ TestBaseClassToLoad)
-+ self.failUnless(cls == None)
-+
-+ def testCanonicalLanguage(self):
-+ self.failUnless(util.CanonicalLanguage('en') == 'en')
-+ self.failUnless(util.CanonicalLanguage('pt_br') == 'pt-BR')
-+ self.failUnless(util.CanonicalLanguage('pt-br') == 'pt-BR')
-+ self.failUnless(util.CanonicalLanguage('pt-BR') == 'pt-BR')
-+ self.failUnless(util.CanonicalLanguage('pt/br') == 'pt-BR')
-+ self.failUnless(util.CanonicalLanguage('pt/BR') == 'pt-BR')
-+ self.failUnless(util.CanonicalLanguage('no_no_bokmal') == 'no-NO-BOKMAL')
-+
-+ def testUnescapeHtml(self):
-+ self.failUnless(util.UnescapeHtml('&#1010;') == six.unichr(1010))
-+ self.failUnless(util.UnescapeHtml('&#xABcd;') == six.unichr(43981))
-+
-+ def testRelativePath(self):
-+ """ Verify that MakeRelativePath works in some tricky cases."""
-+
-+ def TestRelativePathCombinations(base_path, other_path, expected_result):
-+ """ Verify that the relative path function works for
-+ the given paths regardless of whether or not they end with
-+ a trailing slash."""
-+ for path1 in [base_path, base_path + os.path.sep]:
-+ for path2 in [other_path, other_path + os.path.sep]:
-+ result = util.MakeRelativePath(path1, path2)
-+ self.failUnless(result == expected_result)
-+
-+ # set-up variables
-+ root_dir = 'c:%sa' % os.path.sep
-+ result1 = '..%sabc' % os.path.sep
-+ path1 = root_dir + 'bc'
-+ result2 = 'bc'
-+ path2 = '%s%s%s' % (root_dir, os.path.sep, result2)
-+ # run the tests
-+ TestRelativePathCombinations(root_dir, path1, result1)
-+ TestRelativePathCombinations(root_dir, path2, result2)
-+
-+ def testReadFile(self):
-+ def Test(data, encoding, expected_result):
-+ with open('testfile', 'wb') as f:
-+ f.write(data)
-+ self.assertEqual(util.ReadFile('testfile', encoding), expected_result)
-+
-+ test_std_newline = b'\xEF\xBB\xBFabc\ndef' # EF BB BF is UTF-8 BOM
-+ newlines = [b'\n', b'\r\n', b'\r']
-+
-+ with util.TempDir({}) as tmp_dir:
-+ with tmp_dir.AsCurrentDir():
-+ for newline in newlines:
-+ test = test_std_newline.replace(b'\n', newline)
-+ Test(test, util.BINARY, test)
-+ # utf-8 doesn't strip BOM
-+ Test(test, 'utf-8', test_std_newline.decode('utf-8'))
-+ # utf-8-sig strips BOM
-+ Test(test, 'utf-8-sig', test_std_newline.decode('utf-8')[1:])
-+ # test another encoding
-+ Test(test, 'cp1252', test_std_newline.decode('cp1252'))
-+ self.assertRaises(UnicodeDecodeError, Test, b'\x80', 'utf-8', None)
-+
-+
-+class TestBaseClassToLoad(object):
-+ pass
-+
-+class TestClassToLoad(TestBaseClassToLoad):
-+ pass
-+
-+class TestClassNoBase(object):
-+ pass
-+
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit/xtb_reader.py b/tools/grit/grit/xtb_reader.py
-new file mode 100644
-index 0000000000..e0f842588a
---- /dev/null
-+++ b/tools/grit/grit/xtb_reader.py
-@@ -0,0 +1,140 @@
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Fast and efficient parser for XTB files.
-+'''
-+
-+from __future__ import print_function
-+
-+import sys
-+import xml.sax
-+import xml.sax.handler
-+
-+import grit.node.base
-+
-+
-+class XtbContentHandler(xml.sax.handler.ContentHandler):
-+ '''A content handler that calls a given callback function for each
-+ translation in the XTB file.
-+ '''
-+
-+ def __init__(self, callback, defs=None, debug=False, target_platform=None):
-+ self.callback = callback
-+ self.debug = debug
-+ # 0 if we are not currently parsing a translation, otherwise the message
-+ # ID of that translation.
-+ self.current_id = 0
-+ # Empty if we are not currently parsing a translation, otherwise the
-+ # parts we have for that translation - a list of tuples
-+ # (is_placeholder, text)
-+ self.current_structure = []
-+ # Set to the language ID when we see the <translationbundle> node.
-+ self.language = ''
-+ # Keep track of the if block we're inside. We can't nest ifs.
-+ self.if_expr = None
-+ # Root defines to be used with if expr.
-+ if defs:
-+ self.defines = defs
-+ else:
-+ self.defines = {}
-+ # Target platform for build.
-+ if target_platform:
-+ self.target_platform = target_platform
-+ else:
-+ self.target_platform = sys.platform
-+
-+ def startElement(self, name, attrs):
-+ if name == 'translation':
-+ assert self.current_id == 0 and len(self.current_structure) == 0, (
-+ "Didn't expect a <translation> element here.")
-+ self.current_id = attrs.getValue('id')
-+ elif name == 'ph':
-+ assert self.current_id != 0, "Didn't expect a <ph> element here."
-+ self.current_structure.append((True, attrs.getValue('name')))
-+ elif name == 'translationbundle':
-+ self.language = attrs.getValue('lang')
-+ elif name in ('if', 'then', 'else'):
-+ assert self.if_expr is None, "Can't nest <if> or use <else> in xtb files"
-+ self.if_expr = attrs.getValue('expr')
-+
-+ def endElement(self, name):
-+ if name == 'translation':
-+ assert self.current_id != 0
-+
-+ defs = self.defines
-+ def pp_ifdef(define):
-+ return define in defs
-+ def pp_if(define):
-+ return define in defs and defs[define]
-+
-+ # If we're in an if block, only call the callback (add the translation)
-+ # if the expression is True.
-+ should_run_callback = True
-+ if self.if_expr:
-+ should_run_callback = grit.node.base.Node.EvaluateExpression(
-+ self.if_expr, self.defines, self.target_platform)
-+ if should_run_callback:
-+ self.callback(self.current_id, self.current_structure)
-+
-+ self.current_id = 0
-+ self.current_structure = []
-+ elif name == 'if':
-+ assert self.if_expr is not None
-+ self.if_expr = None
-+
-+ def characters(self, content):
-+ if self.current_id != 0:
-+ # We are inside a <translation> node so just add the characters to our
-+ # structure.
-+ #
-+ # This naive way of handling characters is OK because in the XTB format,
-+ # <ph> nodes are always empty (always <ph name="XXX"/>) and whitespace
-+ # inside the <translation> node should be preserved.
-+ self.current_structure.append((False, content))
-+
-+
-+class XtbErrorHandler(xml.sax.handler.ErrorHandler):
-+ def error(self, exception):
-+ pass
-+
-+ def fatalError(self, exception):
-+ raise exception
-+
-+ def warning(self, exception):
-+ pass
-+
-+
-+def Parse(xtb_file, callback_function, defs=None, debug=False,
-+ target_platform=None):
-+ '''Parse xtb_file, making a call to callback_function for every translation
-+ in the XTB file.
-+
-+ The callback function must have the signature as described below. The 'parts'
-+ parameter is a list of tuples (is_placeholder, text). The 'text' part is
-+ either the raw text (if is_placeholder is False) or the name of the placeholder
-+ (if is_placeholder is True).
-+
-+ Args:
-+ xtb_file: open('fr.xtb', 'rb')
-+ callback_function: def Callback(msg_id, parts): pass
-+ defs: None, or a dictionary of preprocessor definitions.
-+ debug: Default False. Set True for verbose debug output.
-+ target_platform: None, or a sys.platform-like identifier of the build
-+ target platform.
-+
-+ Return:
-+ The language of the XTB, e.g. 'fr'
-+ '''
-+ # Start by advancing the file pointer past the DOCTYPE thing, as the TC
-+ # uses a path to the DTD that only works in Unix.
-+ # TODO(joi) Remove this ugly hack by getting the TC gang to change the
-+ # XTB files somehow?
-+ front_of_file = xtb_file.read(1024)
-+ xtb_file.seek(front_of_file.find(b'<translationbundle'))
-+
-+ handler = XtbContentHandler(callback=callback_function, defs=defs,
-+ debug=debug, target_platform=target_platform)
-+ xml.sax.parse(xtb_file, handler)
-+ assert handler.language != ''
-+ return handler.language
-diff --git a/tools/grit/grit/xtb_reader_unittest.py b/tools/grit/grit/xtb_reader_unittest.py
-new file mode 100644
-index 0000000000..79c0ac9ef1
---- /dev/null
-+++ b/tools/grit/grit/xtb_reader_unittest.py
-@@ -0,0 +1,110 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Unit tests for grit.xtb_reader'''
-+
-+from __future__ import print_function
-+
-+import io
-+import os
-+import sys
-+if __name__ == '__main__':
-+ sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
-+
-+import unittest
-+
-+from grit import util
-+from grit import xtb_reader
-+from grit.node import empty
-+
-+
-+class XtbReaderUnittest(unittest.TestCase):
-+ def testParsing(self):
-+ xtb_file = io.BytesIO(b'''<?xml version="1.0" encoding="UTF-8"?>
-+ <!DOCTYPE translationbundle>
-+ <translationbundle lang="fr">
-+ <translation id="5282608565720904145">Bingo.</translation>
-+ <translation id="2955977306445326147">Bongo longo.</translation>
-+ <translation id="238824332917605038">Hullo</translation>
-+ <translation id="6629135689895381486"><ph name="PROBLEM_REPORT"/> peut <ph name="START_LINK"/>utilisation excessive de majuscules<ph name="END_LINK"/>.</translation>
-+ <translation id="7729135689895381486">Hello
-+this is another line
-+and another
-+
-+and another after a blank line.</translation>
-+ </translationbundle>''')
-+
-+ messages = []
-+ def Callback(id, structure):
-+ messages.append((id, structure))
-+ xtb_reader.Parse(xtb_file, Callback)
-+ self.failUnless(len(messages[0][1]) == 1)
-+ self.failUnless(messages[3][1][0]) # PROBLEM_REPORT placeholder
-+ self.failUnless(messages[4][0] == '7729135689895381486')
-+ self.failUnless(messages[4][1][7][1] == 'and another after a blank line.')
-+
-+ def testParsingIntoMessages(self):
-+ root = util.ParseGrdForUnittest('''
-+ <messages>
-+ <message name="ID_MEGA">Fantastic!</message>
-+ <message name="ID_HELLO_USER">Hello <ph name="USERNAME">%s<ex>Joi</ex></ph></message>
-+ </messages>''')
-+
-+ msgs, = root.GetChildrenOfType(empty.MessagesNode)
-+ clique_mega = msgs.children[0].GetCliques()[0]
-+ msg_mega = clique_mega.GetMessage()
-+ clique_hello_user = msgs.children[1].GetCliques()[0]
-+ msg_hello_user = clique_hello_user.GetMessage()
-+
-+ xtb_file = io.BytesIO(b'''<?xml version="1.0" encoding="UTF-8"?>
-+ <!DOCTYPE translationbundle>
-+ <translationbundle lang="is">
-+ <translation id="%s">Meirihattar!</translation>
-+ <translation id="%s">Saelir <ph name="USERNAME"/></translation>
-+ </translationbundle>''' % (
-+ msg_mega.GetId().encode('utf-8'),
-+ msg_hello_user.GetId().encode('utf-8')))
-+
-+ xtb_reader.Parse(xtb_file,
-+ msgs.UberClique().GenerateXtbParserCallback('is'))
-+ self.assertEqual('Meirihattar!',
-+ clique_mega.MessageForLanguage('is').GetRealContent())
-+ self.failUnless('Saelir %s',
-+ clique_hello_user.MessageForLanguage('is').GetRealContent())
-+
-+ def testIfNodesWithUseNameForId(self):
-+ root = util.ParseGrdForUnittest('''
-+ <messages>
-+ <message name="ID_BINGO" use_name_for_id="true">Bingo!</message>
-+ </messages>''')
-+ msgs, = root.GetChildrenOfType(empty.MessagesNode)
-+ clique = msgs.children[0].GetCliques()[0]
-+ msg = clique.GetMessage()
-+
-+ xtb_file = io.BytesIO(b'''<?xml version="1.0" encoding="UTF-8"?>
-+ <!DOCTYPE translationbundle>
-+ <translationbundle lang="is">
-+ <if expr="is_linux">
-+ <translation id="ID_BINGO">Bongo!</translation>
-+ </if>
-+ <if expr="not is_linux">
-+ <translation id="ID_BINGO">Congo!</translation>
-+ </if>
-+ </translationbundle>''')
-+ xtb_reader.Parse(xtb_file,
-+ msgs.UberClique().GenerateXtbParserCallback('is'),
-+ target_platform='darwin')
-+ self.assertEqual('Congo!', clique.MessageForLanguage('is').GetRealContent())
-+
-+ def testParseLargeFile(self):
-+ def Callback(id, structure):
-+ pass
-+ path = util.PathFromRoot('grit/testdata/generated_resources_fr.xtb')
-+ with open(path, 'rb') as xtb:
-+ xtb_reader.Parse(xtb, Callback)
-+
-+
-+if __name__ == '__main__':
-+ unittest.main()
-diff --git a/tools/grit/grit_info.py b/tools/grit/grit_info.py
-new file mode 100644
-index 0000000000..55738f25f6
---- /dev/null
-+++ b/tools/grit/grit_info.py
-@@ -0,0 +1,173 @@
-+#!/usr/bin/env python
-+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+'''Tool to determine inputs and outputs of a grit file.
-+'''
-+
-+from __future__ import print_function
-+
-+import optparse
-+import os
-+import posixpath
-+import sys
-+
-+from grit import grd_reader
-+from grit import util
-+
-+class WrongNumberOfArguments(Exception):
-+ pass
-+
-+
-+def Outputs(filename, defines, ids_file, target_platform=None):
-+ grd = grd_reader.Parse(
-+ filename, defines=defines, tags_to_ignore=set(['messages']),
-+ first_ids_file=ids_file, target_platform=target_platform)
-+
-+ target = []
-+ lang_folders = {}
-+ # Add all explicitly-specified output files
-+ for output in grd.GetOutputFiles():
-+ path = output.GetFilename()
-+ target.append(path)
-+
-+ if path.endswith('.h'):
-+ path, filename = os.path.split(path)
-+ if output.attrs['lang']:
-+ lang_folders[output.attrs['lang']] = os.path.dirname(path)
-+
-+ return [t.replace('\\', '/') for t in target]
-+
-+
-+def GritSourceFiles():
-+ files = []
-+ grit_root_dir = os.path.relpath(os.path.dirname(__file__), os.getcwd())
-+ for root, dirs, filenames in os.walk(grit_root_dir):
-+ grit_src = [os.path.join(root, f) for f in filenames
-+ if f.endswith('.py') and not f.endswith('_unittest.py')]
-+ files.extend(grit_src)
-+ return sorted(files)
-+
-+
-+def Inputs(filename, defines, ids_file, target_platform=None):
-+ grd = grd_reader.Parse(
-+ filename, debug=False, defines=defines, tags_to_ignore=set(['message']),
-+ first_ids_file=ids_file, target_platform=target_platform)
-+ files = set()
-+ for lang, ctx, fallback in grd.GetConfigurations():
-+ # TODO(tdanderson): Refactor all places which perform the action of setting
-+ # output attributes on the root. See crbug.com/503637.
-+ grd.SetOutputLanguage(lang or grd.GetSourceLanguage())
-+ grd.SetOutputContext(ctx)
-+ grd.SetFallbackToDefaultLayout(fallback)
-+ for node in grd.ActiveDescendants():
-+ with node:
-+ if (node.name == 'structure' or node.name == 'skeleton' or
-+ (node.name == 'file' and node.parent and
-+ node.parent.name == 'translations')):
-+ path = node.GetInputPath()
-+ if path is not None:
-+ files.add(grd.ToRealPath(path))
-+
-+ # If it's a flattened node, grab inlined resources too.
-+ if node.name == 'structure' and node.attrs['flattenhtml'] == 'true':
-+ node.RunPreSubstitutionGatherer()
-+ files.update(node.GetHtmlResourceFilenames())
-+ elif node.name == 'grit':
-+ first_ids_file = node.GetFirstIdsFile()
-+ if first_ids_file:
-+ files.add(first_ids_file)
-+ elif node.name == 'include':
-+ files.add(grd.ToRealPath(node.GetInputPath()))
-+ # If it's a flattened node, grab inlined resources too.
-+ if node.attrs['flattenhtml'] == 'true':
-+ files.update(node.GetHtmlResourceFilenames())
-+ elif node.name == 'part':
-+ files.add(util.normpath(os.path.join(os.path.dirname(filename),
-+ node.GetInputPath())))
-+
-+ cwd = os.getcwd()
-+ return [os.path.relpath(f, cwd) for f in sorted(files)]
-+
-+
-+def PrintUsage():
-+ print('USAGE: ./grit_info.py --inputs [-D foo] [-f resource_ids] <grd-file>')
-+ print(' ./grit_info.py --outputs [-D foo] [-f resource_ids] ' +
-+ '<out-prefix> <grd-file>')
-+
-+
-+def DoMain(argv):
-+ os.environ['cwd'] = os.getcwd()
-+
-+ parser = optparse.OptionParser()
-+ parser.add_option("--inputs", action="store_true", dest="inputs")
-+ parser.add_option("--outputs", action="store_true", dest="outputs")
-+ parser.add_option("-D", action="append", dest="defines", default=[])
-+ # grit build also supports '-E KEY=VALUE', support that to share command
-+ # line flags.
-+ parser.add_option("-E", action="append", dest="build_env", default=[])
-+ parser.add_option("-p", action="store", dest="predetermined_ids_file")
-+ parser.add_option("-w", action="append", dest="whitelist_files", default=[])
-+ parser.add_option("-f", dest="ids_file", default="")
-+ parser.add_option("-t", dest="target_platform", default=None)
-+
-+ options, args = parser.parse_args(argv)
-+
-+ defines = {}
-+ for define in options.defines:
-+ name, val = util.ParseDefine(define)
-+ defines[name] = val
-+
-+ for env_pair in options.build_env:
-+ (env_name, env_value) = env_pair.split('=', 1)
-+ os.environ[env_name] = env_value
-+
-+ if options.inputs:
-+ if len(args) > 1:
-+ raise WrongNumberOfArguments("Expected 0 or 1 arguments for --inputs.")
-+
-+ inputs = []
-+ if len(args) == 1:
-+ filename = args[0]
-+ inputs = Inputs(filename, defines, options.ids_file,
-+ options.target_platform)
-+
-+ # Add in the grit source files. If one of these change, we want to re-run
-+ # grit.
-+ inputs.extend(GritSourceFiles())
-+ inputs = [f.replace('\\', '/') for f in inputs]
-+
-+ if len(args) == 1:
-+ # Include grd file as second input (works around gyp expecting it).
-+ inputs.insert(1, args[0])
-+ if options.whitelist_files:
-+ inputs.extend(options.whitelist_files)
-+ return '\n'.join(inputs)
-+ elif options.outputs:
-+ if len(args) != 2:
-+ raise WrongNumberOfArguments(
-+ "Expected exactly 2 arguments for --outputs.")
-+
-+ prefix, filename = args
-+ outputs = [posixpath.join(prefix, f)
-+ for f in Outputs(filename, defines,
-+ options.ids_file, options.target_platform)]
-+ return '\n'.join(outputs)
-+ else:
-+ raise WrongNumberOfArguments("Expected --inputs or --outputs.")
-+
-+
-+def main(argv):
-+ try:
-+ result = DoMain(argv[1:])
-+ except WrongNumberOfArguments as e:
-+ PrintUsage()
-+ print(e)
-+ return 1
-+ print(result)
-+ return 0
-+
-+
-+if __name__ == '__main__':
-+ sys.exit(main(sys.argv))
-diff --git a/tools/grit/grit_rule.gni b/tools/grit/grit_rule.gni
-new file mode 100644
-index 0000000000..fb107ef1a3
---- /dev/null
-+++ b/tools/grit/grit_rule.gni
-@@ -0,0 +1,485 @@
-+# Copyright 2014 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+# Instantiate grit. This will produce a script target to run grit (named
-+# ${target_name}_grit), and a static library that compiles the .cc files.
-+#
-+# In general, code should depend on the static library. However, if the
-+# generated files are only processed by other actions to generate other
-+# files, it is possible to depend on the script target directly.
-+#
-+# Parameters
-+#
-+# source (required)
-+# Path to .grd file.
-+#
-+# enable_input_discovery_for_gn_analyze (default=true)
-+# Runs grit_info.py via exec_script() when compute_inputs_for_analyze=true
-+# in order to discover all files that affect this target.
-+# Turn this off when the .grd file is generated, or an <include> with
-+# flattenhtml=true points to a generated file.
-+# For "gn analyze" to be correct with this arg disabled, all inputs
-+# must be listed via |inputs|.
-+#
-+# inputs (optional)
-+# List of additional files, required for grit to process source file.
-+#
-+# outputs (required)
-+# List of outputs from grit, relative to the target_gen_dir. Grit will
-+# verify at build time that this list is correct and will fail if there
-+# is a mismatch between the outputs specified by the .grd file and the
-+# outputs list here.
-+#
-+# To get this list, you can look in the .grd file for
-+# <output filename="..." and put those filename here. The base directory
-+# of the list in Grit and the output list specified in the GN grit target
-+# are the same (the target_gen_dir) so you can generally copy the names
-+# exactly.
-+#
-+# To get the list of outputs programatically, run:
-+# python tools/grit/grit_info.py --outputs . path/to/your.grd
-+# And strip the leading "./" from the output files.
-+#
-+# defines (optional)
-+# Extra defines to pass to grit (on top of the global grit_defines list).
-+#
-+# grit_flags (optional)
-+# List of strings containing extra command-line flags to pass to Grit.
-+#
-+# resource_ids (optional)
-+# Path to a grit "firstidsfile". Default is
-+# //tools/gritsettings/resource_ids. Set to "" to use the value specified
-+# in the <grit> nodes of the processed files.
-+#
-+# output_dir (optional)
-+# Directory for generated files. If you specify this, you will often
-+# want to specify output_name if the target name is not particularly
-+# unique, since this can cause files from multiple grit targets to
-+# overwrite each other.
-+#
-+# output_name (optional)
-+# Provide an alternate base name for the generated files, like the .d
-+# files. Normally these are based on the target name and go in the
-+# output_dir, but if multiple targets with the same name end up in
-+# the same output_dir, they can collide.
-+#
-+# configs (optional)
-+# List of additional configs to be applied to the generated target.
-+#
-+# deps (optional)
-+# testonly (optional)
-+# visibility (optional)
-+# Normal meaning.
-+#
-+# Example
-+#
-+# grit("my_resources") {
-+# # Source and outputs are required.
-+# source = "myfile.grd"
-+# outputs = [
-+# "foo_strings.h",
-+# "foo_strings.pak",
-+# ]
-+#
-+# grit_flags = [ "-E", "foo=bar" ] # Optional extra flags.
-+# # You can also put deps here if the grit source depends on generated
-+# # files.
-+# }
-+import("//build/config/chrome_build.gni")
-+import("//build/config/chromeos/ui_mode.gni")
-+import("//build/config/compiler/compiler.gni")
-+import("//build/config/compute_inputs_for_analyze.gni")
-+import("//build/config/crypto.gni")
-+import("//build/config/features.gni")
-+import("//build/config/sanitizers/sanitizers.gni")
-+import("//build/config/ui.gni")
-+import("//build/toolchain/gcc_toolchain.gni")
-+
-+declare_args() {
-+ enable_resource_whitelist_generation = is_android && is_official_build
-+}
-+
-+if (enable_resource_whitelist_generation) {
-+ assert(target_os == "android" || target_os == "win",
-+ "unsupported platform for resource whitelist generation")
-+ assert(
-+ symbol_level > 0 && !strip_debug_info && !is_component_build,
-+ "resource whitelist generation only works on non-component builds with debug info enabled.")
-+}
-+
-+grit_defines = []
-+
-+if (is_mac || is_win || is_linux || is_chromeos || is_ios) {
-+ grit_defines += [
-+ "-D",
-+ "scale_factors=2x",
-+ ]
-+}
-+
-+# Mac and iOS want Title Case strings.
-+use_titlecase_in_grd_files = is_apple
-+if (use_titlecase_in_grd_files) {
-+ grit_defines += [
-+ "-D",
-+ "use_titlecase",
-+ ]
-+}
-+
-+if (is_chrome_branded) {
-+ grit_defines += [
-+ "-D",
-+ "_google_chrome",
-+ "-E",
-+ "CHROMIUM_BUILD=google_chrome",
-+ ]
-+} else {
-+ grit_defines += [
-+ "-D",
-+ "_chromium",
-+ "-E",
-+ "CHROMIUM_BUILD=chromium",
-+ ]
-+}
-+
-+if (is_chromeos) {
-+ grit_defines += [
-+ "-D",
-+ "chromeos",
-+ ]
-+}
-+
-+if (chromeos_is_browser_only) {
-+ grit_defines += [
-+ "-D",
-+ "lacros",
-+ ]
-+}
-+
-+if (is_desktop_linux) {
-+ grit_defines += [
-+ "-D",
-+ "desktop_linux",
-+ ]
-+}
-+
-+if (toolkit_views) {
-+ grit_defines += [
-+ "-D",
-+ "toolkit_views",
-+ ]
-+}
-+
-+if (use_aura) {
-+ grit_defines += [
-+ "-D",
-+ "use_aura",
-+ ]
-+}
-+
-+if (use_nss_certs) {
-+ grit_defines += [
-+ "-D",
-+ "use_nss_certs",
-+ ]
-+}
-+
-+if (use_ozone) {
-+ grit_defines += [
-+ "-D",
-+ "use_ozone",
-+ ]
-+}
-+
-+if (is_android) {
-+ grit_defines += [
-+ "-E",
-+ "ANDROID_JAVA_TAGGED_ONLY=true",
-+ ]
-+}
-+
-+# When cross-compiling, explicitly pass the target system to grit.
-+if (current_toolchain != host_toolchain) {
-+ if (is_android) {
-+ grit_defines += [
-+ "-t",
-+ "android",
-+ ]
-+ }
-+ if (is_ios) {
-+ grit_defines += [
-+ "-t",
-+ "ios",
-+ ]
-+ }
-+ if (is_linux || is_chromeos) {
-+ grit_defines += [
-+ "-t",
-+ "linux2",
-+ ]
-+ }
-+ if (is_mac) {
-+ grit_defines += [
-+ "-t",
-+ "darwin",
-+ ]
-+ }
-+ if (is_win) {
-+ grit_defines += [
-+ "-t",
-+ "win32",
-+ ]
-+ }
-+}
-+
-+_strip_resource_files = is_android && is_official_build
-+_js_minifier = "//tools/grit/minify_with_uglify.py"
-+_css_minifier = "//tools/grit/minimize_css.py"
-+
-+grit_resource_id_target = "//tools/gritsettings:default_resource_ids"
-+grit_resource_id_file =
-+ get_label_info(grit_resource_id_target, "target_gen_dir") +
-+ "/default_resource_ids"
-+grit_info_script = "//tools/grit/grit_info.py"
-+
-+# TODO(asvitkine): Add predetermined ids files for other platforms.
-+grit_predetermined_resource_ids_file = ""
-+if (is_mac) {
-+ grit_predetermined_resource_ids_file =
-+ "//tools/gritsettings/startup_resources_mac.txt"
-+}
-+if (is_win) {
-+ grit_predetermined_resource_ids_file =
-+ "//tools/gritsettings/startup_resources_win.txt"
-+}
-+
-+template("grit") {
-+ if (defined(invoker.output_dir)) {
-+ _output_dir = invoker.output_dir
-+ } else {
-+ _output_dir = target_gen_dir
-+ }
-+
-+ _grit_outputs =
-+ get_path_info(rebase_path(invoker.outputs, ".", _output_dir), "abspath")
-+
-+ # Add .info output for all pak files
-+ _pak_info_outputs = []
-+ foreach(output, _grit_outputs) {
-+ if (get_path_info(output, "extension") == "pak") {
-+ _pak_info_outputs += [ output + ".info" ]
-+ }
-+ }
-+
-+ if (defined(invoker.output_name)) {
-+ _grit_output_name = invoker.output_name
-+ } else {
-+ _grit_output_name = target_name
-+ }
-+
-+ _grit_custom_target = target_name + "_grit"
-+ action(_grit_custom_target) {
-+ testonly = defined(invoker.testonly) && invoker.testonly
-+
-+ script = "//tools/grit/grit.py"
-+ depfile = "$target_gen_dir/$target_name.d"
-+
-+ inputs = [ invoker.source ]
-+ deps = [ "//tools/grit:grit_sources" ]
-+ outputs = [ "${depfile}.stamp" ] + _grit_outputs + _pak_info_outputs
-+
-+ _grit_flags = grit_defines
-+
-+ # Add extra defines with -D flags.
-+ if (defined(invoker.defines)) {
-+ foreach(i, invoker.defines) {
-+ _grit_flags += [
-+ "-D",
-+ i,
-+ ]
-+ }
-+ }
-+
-+ if (defined(invoker.grit_flags)) {
-+ _grit_flags += invoker.grit_flags
-+ }
-+
-+ _rebased_source_path = rebase_path(invoker.source, root_build_dir)
-+ _enable_grit_info =
-+ !defined(invoker.enable_input_discovery_for_gn_analyze) ||
-+ invoker.enable_input_discovery_for_gn_analyze
-+ if (_enable_grit_info && compute_inputs_for_analyze) {
-+ # Only call exec_script when the user has explicitly opted into greater
-+ # precision at the expense of performance.
-+ _rel_inputs = exec_script("//tools/grit/grit_info.py",
-+ [
-+ "--inputs",
-+ _rebased_source_path,
-+ ] + _grit_flags,
-+ "list lines")
-+ inputs += rebase_path(_rel_inputs, ".", root_build_dir)
-+ }
-+
-+ args = [
-+ "-i",
-+ _rebased_source_path,
-+ "build",
-+ "-o",
-+ rebase_path(_output_dir, root_build_dir),
-+ "--depdir",
-+ ".",
-+ "--depfile",
-+ rebase_path(depfile, root_build_dir),
-+ "--write-only-new=1",
-+ "--depend-on-stamp",
-+ ] + _grit_flags
-+
-+ # Add brotli executable if using brotli.
-+ if (defined(invoker.use_brotli) && invoker.use_brotli) {
-+ _brotli_target = "//third_party/brotli:brotli($host_toolchain)"
-+ _brotli_executable = get_label_info(_brotli_target, "root_out_dir") +
-+ "/" + get_label_info(_brotli_target, "name")
-+ if (host_os == "win") {
-+ _brotli_executable += ".exe"
-+ }
-+
-+ inputs += [ _brotli_executable ]
-+ args += [
-+ "--brotli",
-+ rebase_path(_brotli_executable, root_build_dir),
-+ ]
-+ }
-+
-+ _resource_ids = grit_resource_id_file
-+ if (defined(invoker.resource_ids)) {
-+ _resource_ids = invoker.resource_ids
-+ }
-+
-+ if (_resource_ids != "") {
-+ inputs += [ _resource_ids ]
-+ args += [
-+ "-f",
-+ rebase_path(_resource_ids, root_build_dir),
-+ ]
-+ if (_resource_ids == grit_resource_id_file) {
-+ deps += [ grit_resource_id_target ]
-+ }
-+ }
-+ if (grit_predetermined_resource_ids_file != "") {
-+ inputs += [ grit_predetermined_resource_ids_file ]
-+ args += [
-+ "-p",
-+ rebase_path(grit_predetermined_resource_ids_file, root_build_dir),
-+ ]
-+ }
-+
-+ # We want to make sure the declared outputs actually match what Grit is
-+ # writing. We write the list to a file (some of the output lists are long
-+ # enough to not fit on a Windows command line) and ask Grit to verify those
-+ # are the actual outputs at runtime.
-+ _asserted_list_file =
-+ "$target_out_dir/${_grit_output_name}_expected_outputs.txt"
-+ write_file(_asserted_list_file,
-+ rebase_path(invoker.outputs, root_build_dir, _output_dir))
-+ inputs += [ _asserted_list_file ]
-+ args += [
-+ "--assert-file-list",
-+ rebase_path(_asserted_list_file, root_build_dir),
-+ ]
-+
-+ if (enable_resource_whitelist_generation) {
-+ _rc_grit_outputs = []
-+ foreach(output, _grit_outputs) {
-+ if (get_path_info(output, "extension") == "rc") {
-+ _rc_grit_outputs += [ output ]
-+ }
-+ }
-+
-+ if (_rc_grit_outputs != []) {
-+ # Resource whitelisting cannot be used with .rc files.
-+ # Make sure that there aren't any .pak outputs which would require
-+ # whitelist annotations.
-+ assert(_pak_info_outputs == [], "can't combine .pak and .rc outputs")
-+ } else {
-+ args += [ "--whitelist-support" ]
-+ }
-+ }
-+ if (_strip_resource_files) {
-+ _js_minifier_command = rebase_path(_js_minifier, root_build_dir)
-+ _css_minifier_command = rebase_path(_css_minifier, root_build_dir)
-+ args += [
-+ "--js-minifier",
-+ _js_minifier_command,
-+ "--css-minifier",
-+ _css_minifier_command,
-+ ]
-+ inputs += [
-+ _js_minifier,
-+ _css_minifier,
-+ ]
-+ }
-+
-+ if (defined(invoker.visibility)) {
-+ # This needs to include both what the invoker specified (since they
-+ # probably include generated headers from this target), as well as the
-+ # generated source set (since there's no guarantee that the visibility
-+ # specified by the invoker includes our target).
-+ #
-+ # Only define visibility at all if the invoker specified it. Otherwise,
-+ # we want to keep the public "no visibility specified" default.
-+ visibility = [ ":${invoker.target_name}" ] + invoker.visibility
-+ }
-+
-+ if (defined(invoker.use_brotli) && invoker.use_brotli) {
-+ if (is_mac && is_asan) {
-+ deps += [ "//tools/grit:brotli_mac_asan_workaround" ]
-+ } else {
-+ deps += [ "//third_party/brotli:brotli($host_toolchain)" ]
-+ }
-+ }
-+ if (defined(invoker.deps)) {
-+ deps += invoker.deps
-+ }
-+ if (defined(invoker.inputs)) {
-+ inputs += invoker.inputs
-+ }
-+ }
-+
-+ # This is the thing that people actually link with, it must be named the
-+ # same as the argument the template was invoked with.
-+ source_set(target_name) {
-+ testonly = defined(invoker.testonly) && invoker.testonly
-+
-+ # Since we generate a file, we need to be run before the targets that
-+ # depend on us.
-+ sources = []
-+ foreach(_output, _grit_outputs) {
-+ _extension = get_path_info(_output, "extension")
-+ if (_extension != "json" && _extension != "gz" && _extension != "pak" &&
-+ _extension != "xml") {
-+ sources += [ _output ]
-+ }
-+ }
-+
-+ # Deps set on the template invocation will go on the action that runs
-+ # grit above rather than this library. This target needs to depend on the
-+ # action publicly so other scripts can take the outputs from the grit
-+ # script as inputs.
-+ public_deps = [ ":$_grit_custom_target" ]
-+
-+ deps = [ "//base" ]
-+
-+ if (defined(invoker.public_configs)) {
-+ public_configs += invoker.public_configs
-+ }
-+
-+ if (defined(invoker.configs)) {
-+ configs += invoker.configs
-+ }
-+
-+ if (defined(invoker.visibility)) {
-+ visibility = invoker.visibility
-+ }
-+ output_name = _grit_output_name
-+ }
-+}
-diff --git a/tools/grit/minify_with_uglify.py b/tools/grit/minify_with_uglify.py
-new file mode 100644
-index 0000000000..788ffa6a75
---- /dev/null
-+++ b/tools/grit/minify_with_uglify.py
-@@ -0,0 +1,44 @@
-+#!/usr/bin/env python
-+# Copyright 2019 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+import tempfile
-+
-+_HERE_PATH = os.path.dirname(__file__)
-+_SRC_PATH = os.path.normpath(os.path.join(_HERE_PATH, '..', '..'))
-+sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'node'))
-+
-+import node
-+import node_modules
-+
-+def Minify(source):
-+ # Open two temporary files, so that uglify can read the input from one and
-+ # write its output to the other.
-+ with tempfile.NamedTemporaryFile(suffix='.js') as infile, \
-+ tempfile.NamedTemporaryFile(suffix='.js') as outfile:
-+ infile.write(source)
-+ infile.flush();
-+ node.RunNode([
-+ node_modules.PathToUglify(), infile.name, '--output', outfile.name])
-+ result = outfile.read()
-+ return result
-+
-+
-+def main():
-+ orig_stdout = sys.stdout
-+ result = ''
-+ try:
-+ sys.stdout = sys.stderr
-+ result = Minify(sys.stdin.read())
-+ finally:
-+ sys.stdout = orig_stdout
-+ print(result)
-+
-+
-+if __name__ == '__main__':
-+ main()
-diff --git a/tools/grit/minimize_css.py b/tools/grit/minimize_css.py
-new file mode 100644
-index 0000000000..2c3b8aeb1e
---- /dev/null
-+++ b/tools/grit/minimize_css.py
-@@ -0,0 +1,105 @@
-+#!/usr/bin/env python
-+# Copyright 2016 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+import re
-+import sys
-+
-+class CSSMinimizer(object):
-+
-+ INITIAL = 0
-+ MAYBE_COMMENT_START = 1
-+ INSIDE_COMMENT = 2
-+ MAYBE_COMMENT_END = 3
-+ INSIDE_SINGLE_QUOTE = 4
-+ INSIDE_SINGLE_QUOTE_ESCAPE = 5
-+ INSIDE_DOUBLE_QUOTE = 6
-+ INSIDE_DOUBLE_QUOTE_ESCAPE = 7
-+
-+ def __init__(self):
-+ self._output = ''
-+ self._codeblock = ''
-+
-+ def flush_codeblock(self):
-+ stripped = re.sub(r"\s+", ' ', self._codeblock)
-+ stripped = re.sub(r";?\s*(?P<op>[{};])\s*", r'\g<op>', stripped)
-+ self._output += stripped
-+ self._codeblock = ''
-+
-+ def parse(self, content):
-+ state = self.INITIAL
-+ for char in content:
-+ if state == self.INITIAL:
-+ if char == '/':
-+ state = self.MAYBE_COMMENT_START
-+ elif char == "'":
-+ self.flush_codeblock()
-+ self._output += char
-+ state = self.INSIDE_SINGLE_QUOTE
-+ elif char == '"':
-+ self.flush_codeblock()
-+ self._output += char
-+ state = self.INSIDE_DOUBLE_QUOTE
-+ else:
-+ self._codeblock += char
-+ elif state == self.MAYBE_COMMENT_START:
-+ if char == '*':
-+ self.flush_codeblock()
-+ state = self.INSIDE_COMMENT
-+ else:
-+ self._codeblock += '/' + char
-+ state = self.INITIAL
-+ elif state == self.INSIDE_COMMENT:
-+ if char == '*':
-+ state = self.MAYBE_COMMENT_END
-+ else:
-+ pass
-+ elif state == self.MAYBE_COMMENT_END:
-+ if char == '/':
-+ state = self.INITIAL
-+ else:
-+ state = self.INSIDE_COMMENT
-+ elif state == self.INSIDE_SINGLE_QUOTE:
-+ if char == '\\':
-+ self._output += char
-+ state = self.INSIDE_SINGLE_QUOTE_ESCAPE
-+ elif char == "'":
-+ self._output += char
-+ state = self.INITIAL
-+ else:
-+ self._output += char
-+ elif state == self.INSIDE_SINGLE_QUOTE_ESCAPE:
-+ self._output += char
-+ state = self.INSIDE_SINGLE_QUOTE
-+ elif state == self.INSIDE_DOUBLE_QUOTE:
-+ if char == '\\':
-+ self._output += char
-+ state = self.INSIDE_DOUBLE_QUOTE_ESCAPE
-+ elif char == '"':
-+ self._output += char
-+ state = self.INITIAL
-+ else:
-+ self._output += char
-+ elif state == self.INSIDE_DOUBLE_QUOTE_ESCAPE:
-+ self._output += char
-+ state = self.INSIDE_DOUBLE_QUOTE
-+
-+ self.flush_codeblock()
-+ self._output = self._output.strip()
-+ return self._output
-+
-+ @classmethod
-+ def minimize_css(cls, content):
-+ minimizer = CSSMinimizer()
-+ return minimizer.parse(content)
-+
-+def main():
-+ result = ''
-+ try:
-+ result = CSSMinimizer.minimize_css(sys.stdin.read())
-+ finally:
-+ print(result)
-+
-+if __name__ == '__main__':
-+ main()
-diff --git a/tools/grit/minimize_css_unittest.py b/tools/grit/minimize_css_unittest.py
-new file mode 100644
-index 0000000000..cddc313078
---- /dev/null
-+++ b/tools/grit/minimize_css_unittest.py
-@@ -0,0 +1,58 @@
-+#!/usr/bin/env python
-+# Copyright 2016 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+import unittest
-+
-+import minimize_css
-+
-+
-+class CSSMinimizerTest(unittest.TestCase):
-+
-+ def test_simple(self):
-+ source = """
-+ div {
-+ color: blue;
-+ }
-+ """
-+ minimized = minimize_css.CSSMinimizer.minimize_css(source)
-+ self.assertEquals(minimized, "div{color: blue}")
-+
-+ def test_attribute_selectors(self):
-+ source = """
-+ input[type="search" i]::-webkit-textfield-decoration-container {
-+ direction: ltr;
-+ }
-+ """
-+ minimized = minimize_css.CSSMinimizer.minimize_css(source)
-+ self.assertEquals(
-+ minimized,
-+ # pylint: disable=line-too-long
-+ """input[type="search" i]::-webkit-textfield-decoration-container{direction: ltr}""")
-+
-+ def test_strip_comment(self):
-+ source = """
-+ /* header */
-+ html {
-+ /* inside block */
-+ display: block;
-+ }
-+ /* footer */
-+ """
-+ minimized = minimize_css.CSSMinimizer.minimize_css(source)
-+ self.assertEquals(minimized, "html{ display: block}")
-+
-+ def test_no_strip_inside_quotes(self):
-+ source = """div[foo=' bar ']"""
-+ minimized = minimize_css.CSSMinimizer.minimize_css(source)
-+ self.assertEquals(minimized, source)
-+
-+ source = """div[foo=" bar "]"""
-+ minimized = minimize_css.CSSMinimizer.minimize_css(source)
-+ self.assertEquals(minimized, source)
-+
-+ def test_escape_string(self):
-+ source = """content: " <a onclick=\\\"javascript: alert ( 'foobar' ); \\\">";"""
-+ minimized = minimize_css.CSSMinimizer.minimize_css(source)
-+ self.assertEquals(minimized, source)
-diff --git a/tools/grit/pak_util.py b/tools/grit/pak_util.py
-new file mode 100644
-index 0000000000..ede638bbe1
---- /dev/null
-+++ b/tools/grit/pak_util.py
-@@ -0,0 +1,223 @@
-+#!/usr/bin/env python
-+# Copyright 2017 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+"""A tool for interacting with .pak files.
-+
-+For details on the pak file format, see:
-+https://dev.chromium.org/developers/design-documents/linuxresourcesandlocalizedstrings
-+"""
-+
-+from __future__ import print_function
-+
-+import argparse
-+import gzip
-+import hashlib
-+import os
-+import shutil
-+import sys
-+import tempfile
-+
-+# Import grit first to get local third_party modules.
-+import grit # pylint: disable=ungrouped-imports,unused-import
-+
-+import six
-+
-+from grit.format import data_pack
-+
-+
-+def _RepackMain(args):
-+ output_info_filepath = args.output_pak_file + '.info'
-+ if args.compress:
-+ # If the file needs to be compressed, call RePack with a tempfile path,
-+ # then compress the tempfile to args.output_pak_file.
-+ temp_outfile = tempfile.NamedTemporaryFile()
-+ out_path = temp_outfile.name
-+ # Strip any non .pak extension from the .info output file path.
-+ splitext = os.path.splitext(args.output_pak_file)
-+ if splitext[1] != '.pak':
-+ output_info_filepath = splitext[0] + '.info'
-+ else:
-+ out_path = args.output_pak_file
-+ data_pack.RePack(out_path, args.input_pak_files, args.whitelist,
-+ args.suppress_removed_key_output,
-+ output_info_filepath=output_info_filepath)
-+ if args.compress:
-+ with open(args.output_pak_file, 'wb') as out:
-+ with gzip.GzipFile(filename='', mode='wb', fileobj=out, mtime=0) as outgz:
-+ shutil.copyfileobj(temp_outfile, outgz)
-+
-+
-+def _ExtractMain(args):
-+ pak = data_pack.ReadDataPack(args.pak_file)
-+ if args.textual_id:
-+ info_dict = data_pack.ReadGrdInfo(args.pak_file)
-+ for resource_id, payload in pak.resources.items():
-+ filename = (
-+ info_dict[resource_id].textual_id
-+ if args.textual_id else str(resource_id))
-+ path = os.path.join(args.output_dir, filename)
-+ with open(path, 'w') as f:
-+ f.write(payload)
-+
-+
-+def _CreateMain(args):
-+ pak = {}
-+ for name in os.listdir(args.input_dir):
-+ try:
-+ resource_id = int(name)
-+ except:
-+ continue
-+ filename = os.path.join(args.input_dir, name)
-+ if os.path.isfile(filename):
-+ with open(filename, 'rb') as f:
-+ pak[resource_id] = f.read()
-+ data_pack.WriteDataPack(pak, args.output_pak_file, data_pack.UTF8)
-+
-+
-+def _PrintMain(args):
-+ pak = data_pack.ReadDataPack(args.pak_file)
-+ if args.textual_id:
-+ info_dict = data_pack.ReadGrdInfo(args.pak_file)
-+ output = args.output
-+ encoding = 'binary'
-+ if pak.encoding == 1:
-+ encoding = 'utf-8'
-+ elif pak.encoding == 2:
-+ encoding = 'utf-16'
-+ else:
-+ encoding = '?' + str(pak.encoding)
-+
-+ output.write('version: {}\n'.format(pak.version))
-+ output.write('encoding: {}\n'.format(encoding))
-+ output.write('num_resources: {}\n'.format(len(pak.resources)))
-+ output.write('num_aliases: {}\n'.format(len(pak.aliases)))
-+ breakdown = ', '.join('{}: {}'.format(*x) for x in pak.sizes)
-+ output.write('total_size: {} ({})\n'.format(pak.sizes.total, breakdown))
-+
-+ try_decode = args.decode and encoding.startswith('utf')
-+ # Print IDs in ascending order, since that's the order in which they appear in
-+ # the file (order is lost by Python dict).
-+ for resource_id in sorted(pak.resources):
-+ data = pak.resources[resource_id]
-+ canonical_id = pak.aliases.get(resource_id, resource_id)
-+ desc = '<data>'
-+ if try_decode:
-+ try:
-+ desc = six.text_type(data, encoding)
-+ if len(desc) > 60:
-+ desc = desc[:60] + u'...'
-+ desc = desc.replace('\n', '\\n')
-+ except UnicodeDecodeError:
-+ pass
-+ sha1 = hashlib.sha1(data).hexdigest()[:10]
-+ if args.textual_id:
-+ textual_id = info_dict[resource_id].textual_id
-+ canonical_textual_id = info_dict[canonical_id].textual_id
-+ output.write(
-+ u'Entry(id={}, canonical_id={}, size={}, sha1={}): {}\n'.format(
-+ textual_id, canonical_textual_id, len(data), sha1,
-+ desc).encode('utf-8'))
-+ else:
-+ output.write(
-+ u'Entry(id={}, canonical_id={}, size={}, sha1={}): {}\n'.format(
-+ resource_id, canonical_id, len(data), sha1, desc).encode('utf-8'))
-+
-+
-+def _ListMain(args):
-+ pak = data_pack.ReadDataPack(args.pak_file)
-+ if args.textual_id or args.path:
-+ info_dict = data_pack.ReadGrdInfo(args.pak_file)
-+ fmt = ''.join([
-+ '{id}', ' = {textual_id}' if args.textual_id else '',
-+ ' @ {path}' if args.path else '', '\n'
-+ ])
-+ for resource_id in sorted(pak.resources):
-+ item = info_dict[resource_id]
-+ args.output.write(
-+ fmt.format(textual_id=item.textual_id, id=item.id, path=item.path))
-+ else:
-+ for resource_id in sorted(pak.resources):
-+ args.output.write('%d\n' % resource_id)
-+
-+
-+def main():
-+ parser = argparse.ArgumentParser(
-+ description=__doc__, formatter_class=argparse.RawTextHelpFormatter)
-+ # Subparsers are required by default under Python 2. Python 3 changed to
-+ # not required, but didn't include a required option until 3.7. Setting
-+ # the required member works in all versions (and setting dest name).
-+ sub_parsers = parser.add_subparsers(dest='action')
-+ sub_parsers.required = True
-+
-+ sub_parser = sub_parsers.add_parser('repack',
-+ help='Combines several .pak files into one.')
-+ sub_parser.add_argument('output_pak_file', help='File to create.')
-+ sub_parser.add_argument('input_pak_files', nargs='+',
-+ help='Input .pak files.')
-+ sub_parser.add_argument('--whitelist',
-+ help='Path to a whitelist used to filter output pak file resource IDs.')
-+ sub_parser.add_argument('--suppress-removed-key-output', action='store_true',
-+ help='Do not log which keys were removed by the whitelist.')
-+ sub_parser.add_argument('--compress', dest='compress', action='store_true',
-+ default=False, help='Compress output_pak_file using gzip.')
-+ sub_parser.set_defaults(func=_RepackMain)
-+
-+ sub_parser = sub_parsers.add_parser('extract', help='Extracts pak file')
-+ sub_parser.add_argument('pak_file')
-+ sub_parser.add_argument('-o', '--output-dir', default='.',
-+ help='Directory to extract to.')
-+ sub_parser.add_argument(
-+ '-t',
-+ '--textual-id',
-+ action='store_true',
-+ help='Use textual resource ID (name) (from .info file) as filenames.')
-+ sub_parser.set_defaults(func=_ExtractMain)
-+
-+ sub_parser = sub_parsers.add_parser('create',
-+ help='Creates pak file from extracted directory.')
-+ sub_parser.add_argument('output_pak_file', help='File to create.')
-+ sub_parser.add_argument('-i', '--input-dir', default='.',
-+ help='Directory to create from.')
-+ sub_parser.set_defaults(func=_CreateMain)
-+
-+ sub_parser = sub_parsers.add_parser('print',
-+ help='Prints all pak IDs and contents. Useful for diffing.')
-+ sub_parser.add_argument('pak_file')
-+ sub_parser.add_argument('--output', type=argparse.FileType('w'),
-+ default=sys.stdout,
-+ help='The resource list path to write (default stdout)')
-+ sub_parser.add_argument('--no-decode', dest='decode', action='store_false',
-+ default=True, help='Do not print entry data.')
-+ sub_parser.add_argument(
-+ '-t',
-+ '--textual-id',
-+ action='store_true',
-+ help='Print textual ID (name) (from .info file) instead of the ID.')
-+ sub_parser.set_defaults(func=_PrintMain)
-+
-+ sub_parser = sub_parsers.add_parser('list-id',
-+ help='Outputs all resource IDs to a file.')
-+ sub_parser.add_argument('pak_file')
-+ sub_parser.add_argument('--output', type=argparse.FileType('w'),
-+ default=sys.stdout,
-+ help='The resource list path to write (default stdout)')
-+ sub_parser.add_argument(
-+ '-t',
-+ '--textual-id',
-+ action='store_true',
-+ help='Print the textual resource ID (from .info file).')
-+ sub_parser.add_argument(
-+ '-p',
-+ '--path',
-+ action='store_true',
-+ help='Print the resource path (from .info file).')
-+ sub_parser.set_defaults(func=_ListMain)
-+
-+ args = parser.parse_args()
-+ args.func(args)
-+
-+
-+if __name__ == '__main__':
-+ main()
-diff --git a/tools/grit/repack.gni b/tools/grit/repack.gni
-new file mode 100644
-index 0000000000..193f2dc43f
---- /dev/null
-+++ b/tools/grit/repack.gni
-@@ -0,0 +1,189 @@
-+# Copyright 2014 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+import("//tools/grit/grit_rule.gni")
-+
-+# This file defines a template to invoke grit repack in a consistent manner.
-+#
-+# Parameters:
-+# sources [required]
-+# List of pak files that need to be combined.
-+#
-+# output [required]
-+# File name (single string) of the output file.
-+#
-+# copy_data_to_bundle [optional]
-+# Whether to define a bundle_data() for the resulting pak.
-+#
-+# bundle_output [optional]
-+# Path of the file in the application bundle, defaults to
-+# {{bundle_resources_dir}}/{{source_file_part}}.
-+#
-+# compress [optional]
-+# Gzip the resulting bundle (and append .gz to the output name).
-+#
-+# deps [optional]
-+# public_deps [optional]
-+# visibility [optional]
-+# Normal meaning.
-+template("repack") {
-+ _copy_data_to_bundle =
-+ defined(invoker.copy_data_to_bundle) && invoker.copy_data_to_bundle
-+ _repack_target_name = target_name
-+ if (_copy_data_to_bundle) {
-+ _repack_target_name = "${target_name}__repack"
-+ }
-+
-+ _compress = defined(invoker.compress) && invoker.compress
-+
-+ action(_repack_target_name) {
-+ forward_variables_from(invoker,
-+ [
-+ "deps",
-+ "public_deps",
-+ "testonly",
-+ "visibility",
-+ ])
-+ if (defined(visibility) && _copy_data_to_bundle) {
-+ visibility += [ ":${invoker.target_name}" ]
-+ }
-+ assert(defined(invoker.sources), "Need sources for $target_name")
-+ assert(defined(invoker.output), "Need output for $target_name")
-+
-+ script = "//tools/grit/pak_util.py"
-+
-+ inputs = invoker.sources
-+ outputs = [
-+ invoker.output,
-+ "${invoker.output}.info",
-+ ]
-+
-+ args = [ "repack" ]
-+ if (defined(invoker.repack_whitelist)) {
-+ inputs += [ invoker.repack_whitelist ]
-+ _rebased_whitelist = rebase_path(invoker.repack_whitelist)
-+ args += [ "--whitelist=$_rebased_whitelist" ]
-+ args += [ "--suppress-removed-key-output" ]
-+ }
-+ args += [ rebase_path(invoker.output, root_build_dir) ]
-+ args += rebase_path(invoker.sources, root_build_dir)
-+ if (_compress) {
-+ args += [ "--compress" ]
-+ }
-+ }
-+
-+ if (_copy_data_to_bundle) {
-+ bundle_data(target_name) {
-+ forward_variables_from(invoker,
-+ [
-+ "testonly",
-+ "visibility",
-+ ])
-+
-+ public_deps = [ ":$_repack_target_name" ]
-+ sources = [ invoker.output ]
-+ if (defined(invoker.bundle_output)) {
-+ outputs = [ invoker.bundle_output ]
-+ } else {
-+ outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ]
-+ }
-+ }
-+ }
-+}
-+
-+# Repacks a set of .pak files for each locale.
-+#
-+# Parameters:
-+#
-+# input_locales [required]
-+# List of locale names to use as inputs.
-+#
-+# output_locales [required]
-+# A list containing the corresponding output names for each of the
-+# input names. Mac and iOS use different names in some cases.
-+#
-+# source_patterns [required]
-+# The pattern for pak files which need repacked. The filenames always end
-+# with "${locale}.pak".
-+# E.g.:
-+# ${root_gen_dir}/foo_ expands to ${root_gen_dir}/foo_zh-CN.pak
-+# when locale is zh-CN.
-+#
-+# output_dir [optional]
-+# Directory in which to put all pak files.
-+#
-+# deps [optional]
-+# visibility [optional]
-+# testonly [optional]
-+# copy_data_to_bundle [optional]
-+# repack_whitelist [optional]
-+# Normal meaning.
-+template("repack_locales") {
-+ if (defined(invoker.output_dir)) {
-+ _output_dir = invoker.output_dir
-+ } else if (is_ios) {
-+ _output_dir = "$target_gen_dir"
-+ } else {
-+ _output_dir = "$target_gen_dir/$target_name"
-+ }
-+
-+ # GN can't handle invoker.output_locales[foo] (http://crbug.com/614747).
-+ _output_locales = invoker.output_locales
-+
-+ # Collects all targets the loop generates.
-+ _locale_targets = []
-+
-+ # This loop iterates over the input locales and also keeps a counter so it
-+ # can simultaneously iterate over the output locales (using GN's very
-+ # limited looping capabilities).
-+ _current_index = 0
-+ foreach(_input_locale, invoker.input_locales) {
-+ _output_locale = _output_locales[_current_index]
-+
-+ # Compute the name of the target for the current file. Save it for the deps.
-+ _current_name = "${target_name}_${_input_locale}"
-+ _locale_targets += [ ":$_current_name" ]
-+
-+ repack(_current_name) {
-+ forward_variables_from(invoker,
-+ [
-+ "copy_data_to_bundle",
-+ "bundle_output",
-+ "compress",
-+ "deps",
-+ "repack_whitelist",
-+ "testonly",
-+ ])
-+ visibility = [ ":${invoker.target_name}" ]
-+ if (is_ios) {
-+ output = "$_output_dir/${_output_locale}.lproj/locale.pak"
-+ } else {
-+ output = "$_output_dir/${_output_locale}.pak"
-+ }
-+ if (defined(copy_data_to_bundle) && copy_data_to_bundle) {
-+ bundle_output =
-+ "{{bundle_resources_dir}}/${_output_locale}.lproj/locale.pak"
-+ }
-+ sources = []
-+ foreach(_pattern, invoker.source_patterns) {
-+ sources += [ "${_pattern}${_input_locale}.pak" ]
-+ }
-+ }
-+
-+ _current_index = _current_index + 1
-+ }
-+
-+ # The group that external targets depend on which collects all deps.
-+ group(target_name) {
-+ forward_variables_from(invoker,
-+ [
-+ "visibility",
-+ "testonly",
-+ ])
-+ public_deps = _locale_targets
-+ if (!defined(invoker.copy_data_to_bundle) || !invoker.copy_data_to_bundle) {
-+ data_deps = public_deps
-+ }
-+ }
-+}
-diff --git a/tools/grit/setup.py b/tools/grit/setup.py
-new file mode 100644
-index 0000000000..5d86dfc2fc
---- /dev/null
-+++ b/tools/grit/setup.py
-@@ -0,0 +1,46 @@
-+#!/usr/bin/env python3
-+# Copyright 2020 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+"""Install the package!"""
-+
-+from __future__ import absolute_import
-+
-+import setuptools
-+
-+
-+setuptools.setup(
-+ name='grit',
-+ version='0',
-+ entry_points={
-+ 'console_scripts': ['grit = grit.grit_runner:Main'],
-+ },
-+ packages=setuptools.find_packages(),
-+ install_requires=[
-+ 'six >= 1.10',
-+ ],
-+ author='The Chromium Authors',
-+ author_email='chromium-dev@chromium.org',
-+ description='Google Resource and Internationalization Tool for managing '
-+ 'translations & resource files',
-+ license='BSD-3',
-+ url='https://chromium.googlesource.com/chromium/src/tools/grit/',
-+ classifiers=[
-+ 'Development Status :: 6 - Mature',
-+ 'Environment :: Console',
-+ 'Intended Audience :: Developers',
-+ 'License :: OSI Approved :: BSD License',
-+ 'Operating System :: MacOS',
-+ 'Operating System :: Microsoft :: Windows',
-+ 'Operating System :: POSIX :: Linux',
-+ 'Programming Language :: Python',
-+ 'Programming Language :: Python :: 2.7',
-+ 'Programming Language :: Python :: 3',
-+ 'Programming Language :: Python :: 3.6',
-+ 'Programming Language :: Python :: 3.7',
-+ 'Programming Language :: Python :: 3.8',
-+ 'Programming Language :: Python :: 3.9',
-+ 'Topic :: Utilities',
-+ ],
-+)
-diff --git a/tools/grit/stamp_grit_sources.py b/tools/grit/stamp_grit_sources.py
-new file mode 100644
-index 0000000000..bc7265c6cb
---- /dev/null
-+++ b/tools/grit/stamp_grit_sources.py
-@@ -0,0 +1,57 @@
-+# Copyright 2014 The Chromium Authors. All rights reserved.
-+# Use of this source code is governed by a BSD-style license that can be
-+# found in the LICENSE file.
-+
-+# This script enumerates the files in the given directory, writing an empty
-+# stamp file and a .d file listing the inputs required to make the stamp. This
-+# allows us to dynamically depend on the grit sources without enumerating the
-+# grit directory for every invocation of grit (which is what adding the source
-+# files to every .grd file's .d file would entail) or shelling out to grit
-+# synchronously during GN execution to get the list (which would be slow).
-+#
-+# Usage:
-+# stamp_grit_sources.py <directory> <stamp-file> <.d-file>
-+
-+from __future__ import print_function
-+
-+import os
-+import sys
-+
-+def GritSourceFiles(grit_root_dir):
-+ files = []
-+ for root, _, filenames in os.walk(grit_root_dir):
-+ grit_src = [os.path.join(root, f) for f in filenames
-+ if f.endswith('.py') and not f.endswith('_unittest.py')]
-+ files.extend(grit_src)
-+ files = [f.replace('\\', '/') for f in files]
-+ return sorted(files)
-+
-+
-+def WriteDepFile(dep_file, stamp_file, source_files):
-+ with open(dep_file, "w") as f:
-+ f.write(stamp_file)
-+ f.write(": ")
-+ f.write(' '.join(source_files))
-+
-+
-+def WriteStampFile(stamp_file):
-+ with open(stamp_file, "w"):
-+ pass
-+
-+
-+def main(argv):
-+ if len(argv) != 4:
-+ print("Error: expecting 3 args.")
-+ return 1
-+
-+ grit_root_dir = sys.argv[1]
-+ stamp_file = sys.argv[2]
-+ dep_file = sys.argv[3]
-+
-+ WriteStampFile(stamp_file)
-+ WriteDepFile(dep_file, stamp_file, GritSourceFiles(grit_root_dir))
-+ return 0
-+
-+
-+if __name__ == '__main__':
-+ sys.exit(main(sys.argv))
-diff --git a/tools/grit/third_party/six/LICENSE b/tools/grit/third_party/six/LICENSE
-new file mode 100644
-index 0000000000..e558f9d494
---- /dev/null
-+++ b/tools/grit/third_party/six/LICENSE
-@@ -0,0 +1,18 @@
-+Copyright (c) 2010-2015 Benjamin Peterson
-+
-+Permission is hereby granted, free of charge, to any person obtaining a copy of
-+this software and associated documentation files (the "Software"), to deal in
-+the Software without restriction, including without limitation the rights to
-+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
-+the Software, and to permit persons to whom the Software is furnished to do so,
-+subject to the following conditions:
-+
-+The above copyright notice and this permission notice shall be included in all
-+copies or substantial portions of the Software.
-+
-+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
-+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
-+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
-+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-diff --git a/tools/grit/third_party/six/README b/tools/grit/third_party/six/README
-new file mode 100644
-index 0000000000..ee628a9db6
---- /dev/null
-+++ b/tools/grit/third_party/six/README
-@@ -0,0 +1,16 @@
-+Six is a Python 2 and 3 compatibility library. It provides utility functions
-+for smoothing over the differences between the Python versions with the goal of
-+writing Python code that is compatible on both Python versions. See the
-+documentation for more information on what is provided.
-+
-+Six supports every Python version since 2.6. It is contained in only one Python
-+file, so it can be easily copied into your project. (The copyright and license
-+notice must be retained.)
-+
-+Online documentation is at https://pythonhosted.org/six/.
-+
-+Bugs can be reported to https://bitbucket.org/gutworth/six. The code can also
-+be found there.
-+
-+For questions about six or porting in general, email the python-porting mailing
-+list: https://mail.python.org/mailman/listinfo/python-porting
-diff --git a/tools/grit/third_party/six/README.chromium b/tools/grit/third_party/six/README.chromium
-new file mode 100644
-index 0000000000..100b24d046
---- /dev/null
-+++ b/tools/grit/third_party/six/README.chromium
-@@ -0,0 +1,13 @@
-+Name: six
-+Short Name: six
-+URL: https://bitbucket.org/gutworth/six/commits/tag/1.10.0
-+Version: 1.10.0
-+Revision: 403:e5218c3f66a2
-+License: Apache License, Version 2.0
-+
-+Description:
-+Six is a Python 2 and 3 compatibility library.
-+
-+Local Modifications:
-+- Copied six.py as __init__.py.
-+- Kept LICENSE and README.
-diff --git a/tools/grit/third_party/six/__init__.py b/tools/grit/third_party/six/__init__.py
-new file mode 100644
-index 0000000000..56e4272cb3
---- /dev/null
-+++ b/tools/grit/third_party/six/__init__.py
-@@ -0,0 +1,868 @@
-+# Copyright (c) 2010-2015 Benjamin Peterson
-+#
-+# Permission is hereby granted, free of charge, to any person obtaining a copy
-+# of this software and associated documentation files (the "Software"), to deal
-+# in the Software without restriction, including without limitation the rights
-+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-+# copies of the Software, and to permit persons to whom the Software is
-+# furnished to do so, subject to the following conditions:
-+#
-+# The above copyright notice and this permission notice shall be included in all
-+# copies or substantial portions of the Software.
-+#
-+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-+# SOFTWARE.
-+
-+"""Utilities for writing code that runs on Python 2 and 3"""
-+
-+from __future__ import absolute_import
-+
-+import functools
-+import itertools
-+import operator
-+import sys
-+import types
-+
-+__author__ = "Benjamin Peterson <benjamin@python.org>"
-+__version__ = "1.10.0"
-+
-+
-+# Useful for very coarse version differentiation.
-+PY2 = sys.version_info[0] == 2
-+PY3 = sys.version_info[0] == 3
-+PY34 = sys.version_info[0:2] >= (3, 4)
-+
-+if PY3:
-+ string_types = str,
-+ integer_types = int,
-+ class_types = type,
-+ text_type = str
-+ binary_type = bytes
-+
-+ MAXSIZE = sys.maxsize
-+else:
-+ string_types = basestring,
-+ integer_types = (int, long)
-+ class_types = (type, types.ClassType)
-+ text_type = unicode
-+ binary_type = str
-+
-+ if sys.platform.startswith("java"):
-+ # Jython always uses 32 bits.
-+ MAXSIZE = int((1 << 31) - 1)
-+ else:
-+ # It's possible to have sizeof(long) != sizeof(Py_ssize_t).
-+ class X(object):
-+
-+ def __len__(self):
-+ return 1 << 31
-+ try:
-+ len(X())
-+ except OverflowError:
-+ # 32-bit
-+ MAXSIZE = int((1 << 31) - 1)
-+ else:
-+ # 64-bit
-+ MAXSIZE = int((1 << 63) - 1)
-+ del X
-+
-+
-+def _add_doc(func, doc):
-+ """Add documentation to a function."""
-+ func.__doc__ = doc
-+
-+
-+def _import_module(name):
-+ """Import module, returning the module after the last dot."""
-+ __import__(name)
-+ return sys.modules[name]
-+
-+
-+class _LazyDescr(object):
-+
-+ def __init__(self, name):
-+ self.name = name
-+
-+ def __get__(self, obj, tp):
-+ result = self._resolve()
-+ setattr(obj, self.name, result) # Invokes __set__.
-+ try:
-+ # This is a bit ugly, but it avoids running this again by
-+ # removing this descriptor.
-+ delattr(obj.__class__, self.name)
-+ except AttributeError:
-+ pass
-+ return result
-+
-+
-+class MovedModule(_LazyDescr):
-+
-+ def __init__(self, name, old, new=None):
-+ super(MovedModule, self).__init__(name)
-+ if PY3:
-+ if new is None:
-+ new = name
-+ self.mod = new
-+ else:
-+ self.mod = old
-+
-+ def _resolve(self):
-+ return _import_module(self.mod)
-+
-+ def __getattr__(self, attr):
-+ _module = self._resolve()
-+ value = getattr(_module, attr)
-+ setattr(self, attr, value)
-+ return value
-+
-+
-+class _LazyModule(types.ModuleType):
-+
-+ def __init__(self, name):
-+ super(_LazyModule, self).__init__(name)
-+ self.__doc__ = self.__class__.__doc__
-+
-+ def __dir__(self):
-+ attrs = ["__doc__", "__name__"]
-+ attrs += [attr.name for attr in self._moved_attributes]
-+ return attrs
-+
-+ # Subclasses should override this
-+ _moved_attributes = []
-+
-+
-+class MovedAttribute(_LazyDescr):
-+
-+ def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
-+ super(MovedAttribute, self).__init__(name)
-+ if PY3:
-+ if new_mod is None:
-+ new_mod = name
-+ self.mod = new_mod
-+ if new_attr is None:
-+ if old_attr is None:
-+ new_attr = name
-+ else:
-+ new_attr = old_attr
-+ self.attr = new_attr
-+ else:
-+ self.mod = old_mod
-+ if old_attr is None:
-+ old_attr = name
-+ self.attr = old_attr
-+
-+ def _resolve(self):
-+ module = _import_module(self.mod)
-+ return getattr(module, self.attr)
-+
-+
-+class _SixMetaPathImporter(object):
-+
-+ """
-+ A meta path importer to import six.moves and its submodules.
-+
-+ This class implements a PEP302 finder and loader. It should be compatible
-+ with Python 2.5 and all existing versions of Python3
-+ """
-+
-+ def __init__(self, six_module_name):
-+ self.name = six_module_name
-+ self.known_modules = {}
-+
-+ def _add_module(self, mod, *fullnames):
-+ for fullname in fullnames:
-+ self.known_modules[self.name + "." + fullname] = mod
-+
-+ def _get_module(self, fullname):
-+ return self.known_modules[self.name + "." + fullname]
-+
-+ def find_module(self, fullname, path=None):
-+ if fullname in self.known_modules:
-+ return self
-+ return None
-+
-+ def __get_module(self, fullname):
-+ try:
-+ return self.known_modules[fullname]
-+ except KeyError:
-+ raise ImportError("This loader does not know module " + fullname)
-+
-+ def load_module(self, fullname):
-+ try:
-+ # in case of a reload
-+ return sys.modules[fullname]
-+ except KeyError:
-+ pass
-+ mod = self.__get_module(fullname)
-+ if isinstance(mod, MovedModule):
-+ mod = mod._resolve()
-+ else:
-+ mod.__loader__ = self
-+ sys.modules[fullname] = mod
-+ return mod
-+
-+ def is_package(self, fullname):
-+ """
-+ Return true, if the named module is a package.
-+
-+ We need this method to get correct spec objects with
-+ Python 3.4 (see PEP451)
-+ """
-+ return hasattr(self.__get_module(fullname), "__path__")
-+
-+ def get_code(self, fullname):
-+ """Return None
-+
-+ Required, if is_package is implemented"""
-+ self.__get_module(fullname) # eventually raises ImportError
-+ return None
-+ get_source = get_code # same as get_code
-+
-+_importer = _SixMetaPathImporter(__name__)
-+
-+
-+class _MovedItems(_LazyModule):
-+
-+ """Lazy loading of moved objects"""
-+ __path__ = [] # mark as package
-+
-+
-+_moved_attributes = [
-+ MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
-+ MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
-+ MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"),
-+ MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
-+ MovedAttribute("intern", "__builtin__", "sys"),
-+ MovedAttribute("map", "itertools", "builtins", "imap", "map"),
-+ MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"),
-+ MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"),
-+ MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
-+ MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"),
-+ MovedAttribute("reduce", "__builtin__", "functools"),
-+ MovedAttribute("shlex_quote", "pipes", "shlex", "quote"),
-+ MovedAttribute("StringIO", "StringIO", "io"),
-+ MovedAttribute("UserDict", "UserDict", "collections"),
-+ MovedAttribute("UserList", "UserList", "collections"),
-+ MovedAttribute("UserString", "UserString", "collections"),
-+ MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
-+ MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
-+ MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
-+ MovedModule("builtins", "__builtin__"),
-+ MovedModule("configparser", "ConfigParser"),
-+ MovedModule("copyreg", "copy_reg"),
-+ MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
-+ MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"),
-+ MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
-+ MovedModule("http_cookies", "Cookie", "http.cookies"),
-+ MovedModule("html_entities", "htmlentitydefs", "html.entities"),
-+ MovedModule("html_parser", "HTMLParser", "html.parser"),
-+ MovedModule("http_client", "httplib", "http.client"),
-+ MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
-+ MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"),
-+ MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
-+ MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
-+ MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
-+ MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
-+ MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
-+ MovedModule("cPickle", "cPickle", "pickle"),
-+ MovedModule("queue", "Queue"),
-+ MovedModule("reprlib", "repr"),
-+ MovedModule("socketserver", "SocketServer"),
-+ MovedModule("_thread", "thread", "_thread"),
-+ MovedModule("tkinter", "Tkinter"),
-+ MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
-+ MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
-+ MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
-+ MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
-+ MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
-+ MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"),
-+ MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
-+ MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
-+ MovedModule("tkinter_colorchooser", "tkColorChooser",
-+ "tkinter.colorchooser"),
-+ MovedModule("tkinter_commondialog", "tkCommonDialog",
-+ "tkinter.commondialog"),
-+ MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
-+ MovedModule("tkinter_font", "tkFont", "tkinter.font"),
-+ MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
-+ MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
-+ "tkinter.simpledialog"),
-+ MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"),
-+ MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"),
-+ MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"),
-+ MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
-+ MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"),
-+ MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"),
-+]
-+# Add windows specific modules.
-+if sys.platform == "win32":
-+ _moved_attributes += [
-+ MovedModule("winreg", "_winreg"),
-+ ]
-+
-+for attr in _moved_attributes:
-+ setattr(_MovedItems, attr.name, attr)
-+ if isinstance(attr, MovedModule):
-+ _importer._add_module(attr, "moves." + attr.name)
-+del attr
-+
-+_MovedItems._moved_attributes = _moved_attributes
-+
-+moves = _MovedItems(__name__ + ".moves")
-+_importer._add_module(moves, "moves")
-+
-+
-+class Module_six_moves_urllib_parse(_LazyModule):
-+
-+ """Lazy loading of moved objects in six.moves.urllib_parse"""
-+
-+
-+_urllib_parse_moved_attributes = [
-+ MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
-+ MovedAttribute("SplitResult", "urlparse", "urllib.parse"),
-+ MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
-+ MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
-+ MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
-+ MovedAttribute("urljoin", "urlparse", "urllib.parse"),
-+ MovedAttribute("urlparse", "urlparse", "urllib.parse"),
-+ MovedAttribute("urlsplit", "urlparse", "urllib.parse"),
-+ MovedAttribute("urlunparse", "urlparse", "urllib.parse"),
-+ MovedAttribute("urlunsplit", "urlparse", "urllib.parse"),
-+ MovedAttribute("quote", "urllib", "urllib.parse"),
-+ MovedAttribute("quote_plus", "urllib", "urllib.parse"),
-+ MovedAttribute("unquote", "urllib", "urllib.parse"),
-+ MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
-+ MovedAttribute("urlencode", "urllib", "urllib.parse"),
-+ MovedAttribute("splitquery", "urllib", "urllib.parse"),
-+ MovedAttribute("splittag", "urllib", "urllib.parse"),
-+ MovedAttribute("splituser", "urllib", "urllib.parse"),
-+ MovedAttribute("uses_fragment", "urlparse", "urllib.parse"),
-+ MovedAttribute("uses_netloc", "urlparse", "urllib.parse"),
-+ MovedAttribute("uses_params", "urlparse", "urllib.parse"),
-+ MovedAttribute("uses_query", "urlparse", "urllib.parse"),
-+ MovedAttribute("uses_relative", "urlparse", "urllib.parse"),
-+]
-+for attr in _urllib_parse_moved_attributes:
-+ setattr(Module_six_moves_urllib_parse, attr.name, attr)
-+del attr
-+
-+Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes
-+
-+_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"),
-+ "moves.urllib_parse", "moves.urllib.parse")
-+
-+
-+class Module_six_moves_urllib_error(_LazyModule):
-+
-+ """Lazy loading of moved objects in six.moves.urllib_error"""
-+
-+
-+_urllib_error_moved_attributes = [
-+ MovedAttribute("URLError", "urllib2", "urllib.error"),
-+ MovedAttribute("HTTPError", "urllib2", "urllib.error"),
-+ MovedAttribute("ContentTooShortError", "urllib", "urllib.error"),
-+]
-+for attr in _urllib_error_moved_attributes:
-+ setattr(Module_six_moves_urllib_error, attr.name, attr)
-+del attr
-+
-+Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes
-+
-+_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"),
-+ "moves.urllib_error", "moves.urllib.error")
-+
-+
-+class Module_six_moves_urllib_request(_LazyModule):
-+
-+ """Lazy loading of moved objects in six.moves.urllib_request"""
-+
-+
-+_urllib_request_moved_attributes = [
-+ MovedAttribute("urlopen", "urllib2", "urllib.request"),
-+ MovedAttribute("install_opener", "urllib2", "urllib.request"),
-+ MovedAttribute("build_opener", "urllib2", "urllib.request"),
-+ MovedAttribute("pathname2url", "urllib", "urllib.request"),
-+ MovedAttribute("url2pathname", "urllib", "urllib.request"),
-+ MovedAttribute("getproxies", "urllib", "urllib.request"),
-+ MovedAttribute("Request", "urllib2", "urllib.request"),
-+ MovedAttribute("OpenerDirector", "urllib2", "urllib.request"),
-+ MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"),
-+ MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"),
-+ MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"),
-+ MovedAttribute("ProxyHandler", "urllib2", "urllib.request"),
-+ MovedAttribute("BaseHandler", "urllib2", "urllib.request"),
-+ MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"),
-+ MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"),
-+ MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"),
-+ MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"),
-+ MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"),
-+ MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"),
-+ MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"),
-+ MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"),
-+ MovedAttribute("HTTPHandler", "urllib2", "urllib.request"),
-+ MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"),
-+ MovedAttribute("FileHandler", "urllib2", "urllib.request"),
-+ MovedAttribute("FTPHandler", "urllib2", "urllib.request"),
-+ MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"),
-+ MovedAttribute("UnknownHandler", "urllib2", "urllib.request"),
-+ MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
-+ MovedAttribute("urlretrieve", "urllib", "urllib.request"),
-+ MovedAttribute("urlcleanup", "urllib", "urllib.request"),
-+ MovedAttribute("URLopener", "urllib", "urllib.request"),
-+ MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
-+ MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
-+]
-+for attr in _urllib_request_moved_attributes:
-+ setattr(Module_six_moves_urllib_request, attr.name, attr)
-+del attr
-+
-+Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes
-+
-+_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"),
-+ "moves.urllib_request", "moves.urllib.request")
-+
-+
-+class Module_six_moves_urllib_response(_LazyModule):
-+
-+ """Lazy loading of moved objects in six.moves.urllib_response"""
-+
-+
-+_urllib_response_moved_attributes = [
-+ MovedAttribute("addbase", "urllib", "urllib.response"),
-+ MovedAttribute("addclosehook", "urllib", "urllib.response"),
-+ MovedAttribute("addinfo", "urllib", "urllib.response"),
-+ MovedAttribute("addinfourl", "urllib", "urllib.response"),
-+]
-+for attr in _urllib_response_moved_attributes:
-+ setattr(Module_six_moves_urllib_response, attr.name, attr)
-+del attr
-+
-+Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes
-+
-+_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"),
-+ "moves.urllib_response", "moves.urllib.response")
-+
-+
-+class Module_six_moves_urllib_robotparser(_LazyModule):
-+
-+ """Lazy loading of moved objects in six.moves.urllib_robotparser"""
-+
-+
-+_urllib_robotparser_moved_attributes = [
-+ MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"),
-+]
-+for attr in _urllib_robotparser_moved_attributes:
-+ setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
-+del attr
-+
-+Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes
-+
-+_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"),
-+ "moves.urllib_robotparser", "moves.urllib.robotparser")
-+
-+
-+class Module_six_moves_urllib(types.ModuleType):
-+
-+ """Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
-+ __path__ = [] # mark as package
-+ parse = _importer._get_module("moves.urllib_parse")
-+ error = _importer._get_module("moves.urllib_error")
-+ request = _importer._get_module("moves.urllib_request")
-+ response = _importer._get_module("moves.urllib_response")
-+ robotparser = _importer._get_module("moves.urllib_robotparser")
-+
-+ def __dir__(self):
-+ return ['parse', 'error', 'request', 'response', 'robotparser']
-+
-+_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"),
-+ "moves.urllib")
-+
-+
-+def add_move(move):
-+ """Add an item to six.moves."""
-+ setattr(_MovedItems, move.name, move)
-+
-+
-+def remove_move(name):
-+ """Remove item from six.moves."""
-+ try:
-+ delattr(_MovedItems, name)
-+ except AttributeError:
-+ try:
-+ del moves.__dict__[name]
-+ except KeyError:
-+ raise AttributeError("no such move, %r" % (name,))
-+
-+
-+if PY3:
-+ _meth_func = "__func__"
-+ _meth_self = "__self__"
-+
-+ _func_closure = "__closure__"
-+ _func_code = "__code__"
-+ _func_defaults = "__defaults__"
-+ _func_globals = "__globals__"
-+else:
-+ _meth_func = "im_func"
-+ _meth_self = "im_self"
-+
-+ _func_closure = "func_closure"
-+ _func_code = "func_code"
-+ _func_defaults = "func_defaults"
-+ _func_globals = "func_globals"
-+
-+
-+try:
-+ advance_iterator = next
-+except NameError:
-+ def advance_iterator(it):
-+ return it.next()
-+next = advance_iterator
-+
-+
-+try:
-+ callable = callable
-+except NameError:
-+ def callable(obj):
-+ return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
-+
-+
-+if PY3:
-+ def get_unbound_function(unbound):
-+ return unbound
-+
-+ create_bound_method = types.MethodType
-+
-+ def create_unbound_method(func, cls):
-+ return func
-+
-+ Iterator = object
-+else:
-+ def get_unbound_function(unbound):
-+ return unbound.im_func
-+
-+ def create_bound_method(func, obj):
-+ return types.MethodType(func, obj, obj.__class__)
-+
-+ def create_unbound_method(func, cls):
-+ return types.MethodType(func, None, cls)
-+
-+ class Iterator(object):
-+
-+ def next(self):
-+ return type(self).__next__(self)
-+
-+ callable = callable
-+_add_doc(get_unbound_function,
-+ """Get the function out of a possibly unbound function""")
-+
-+
-+get_method_function = operator.attrgetter(_meth_func)
-+get_method_self = operator.attrgetter(_meth_self)
-+get_function_closure = operator.attrgetter(_func_closure)
-+get_function_code = operator.attrgetter(_func_code)
-+get_function_defaults = operator.attrgetter(_func_defaults)
-+get_function_globals = operator.attrgetter(_func_globals)
-+
-+
-+if PY3:
-+ def iterkeys(d, **kw):
-+ return iter(d.keys(**kw))
-+
-+ def itervalues(d, **kw):
-+ return iter(d.values(**kw))
-+
-+ def iteritems(d, **kw):
-+ return iter(d.items(**kw))
-+
-+ def iterlists(d, **kw):
-+ return iter(d.lists(**kw))
-+
-+ viewkeys = operator.methodcaller("keys")
-+
-+ viewvalues = operator.methodcaller("values")
-+
-+ viewitems = operator.methodcaller("items")
-+else:
-+ def iterkeys(d, **kw):
-+ return d.iterkeys(**kw)
-+
-+ def itervalues(d, **kw):
-+ return d.itervalues(**kw)
-+
-+ def iteritems(d, **kw):
-+ return d.iteritems(**kw)
-+
-+ def iterlists(d, **kw):
-+ return d.iterlists(**kw)
-+
-+ viewkeys = operator.methodcaller("viewkeys")
-+
-+ viewvalues = operator.methodcaller("viewvalues")
-+
-+ viewitems = operator.methodcaller("viewitems")
-+
-+_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.")
-+_add_doc(itervalues, "Return an iterator over the values of a dictionary.")
-+_add_doc(iteritems,
-+ "Return an iterator over the (key, value) pairs of a dictionary.")
-+_add_doc(iterlists,
-+ "Return an iterator over the (key, [values]) pairs of a dictionary.")
-+
-+
-+if PY3:
-+ def b(s):
-+ return s.encode("latin-1")
-+
-+ def u(s):
-+ return s
-+ unichr = chr
-+ import struct
-+ int2byte = struct.Struct(">B").pack
-+ del struct
-+ byte2int = operator.itemgetter(0)
-+ indexbytes = operator.getitem
-+ iterbytes = iter
-+ import io
-+ StringIO = io.StringIO
-+ BytesIO = io.BytesIO
-+ _assertCountEqual = "assertCountEqual"
-+ if sys.version_info[1] <= 1:
-+ _assertRaisesRegex = "assertRaisesRegexp"
-+ _assertRegex = "assertRegexpMatches"
-+ else:
-+ _assertRaisesRegex = "assertRaisesRegex"
-+ _assertRegex = "assertRegex"
-+else:
-+ def b(s):
-+ return s
-+ # Workaround for standalone backslash
-+
-+ def u(s):
-+ return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
-+ unichr = unichr
-+ int2byte = chr
-+
-+ def byte2int(bs):
-+ return ord(bs[0])
-+
-+ def indexbytes(buf, i):
-+ return ord(buf[i])
-+ iterbytes = functools.partial(itertools.imap, ord)
-+ import StringIO
-+ StringIO = BytesIO = StringIO.StringIO
-+ _assertCountEqual = "assertItemsEqual"
-+ _assertRaisesRegex = "assertRaisesRegexp"
-+ _assertRegex = "assertRegexpMatches"
-+_add_doc(b, """Byte literal""")
-+_add_doc(u, """Text literal""")
-+
-+
-+def assertCountEqual(self, *args, **kwargs):
-+ return getattr(self, _assertCountEqual)(*args, **kwargs)
-+
-+
-+def assertRaisesRegex(self, *args, **kwargs):
-+ return getattr(self, _assertRaisesRegex)(*args, **kwargs)
-+
-+
-+def assertRegex(self, *args, **kwargs):
-+ return getattr(self, _assertRegex)(*args, **kwargs)
-+
-+
-+if PY3:
-+ exec_ = getattr(moves.builtins, "exec")
-+
-+ def reraise(tp, value, tb=None):
-+ if value is None:
-+ value = tp()
-+ if value.__traceback__ is not tb:
-+ raise value.with_traceback(tb)
-+ raise value
-+
-+else:
-+ def exec_(_code_, _globs_=None, _locs_=None):
-+ """Execute code in a namespace."""
-+ if _globs_ is None:
-+ frame = sys._getframe(1)
-+ _globs_ = frame.f_globals
-+ if _locs_ is None:
-+ _locs_ = frame.f_locals
-+ del frame
-+ elif _locs_ is None:
-+ _locs_ = _globs_
-+ exec("""exec _code_ in _globs_, _locs_""")
-+
-+ exec_("""def reraise(tp, value, tb=None):
-+ raise tp, value, tb
-+""")
-+
-+
-+if sys.version_info[:2] == (3, 2):
-+ exec_("""def raise_from(value, from_value):
-+ if from_value is None:
-+ raise value
-+ raise value from from_value
-+""")
-+elif sys.version_info[:2] > (3, 2):
-+ exec_("""def raise_from(value, from_value):
-+ raise value from from_value
-+""")
-+else:
-+ def raise_from(value, from_value):
-+ raise value
-+
-+
-+print_ = getattr(moves.builtins, "print", None)
-+if print_ is None:
-+ def print_(*args, **kwargs):
-+ """The new-style print function for Python 2.4 and 2.5."""
-+ fp = kwargs.pop("file", sys.stdout)
-+ if fp is None:
-+ return
-+
-+ def write(data):
-+ if not isinstance(data, basestring):
-+ data = str(data)
-+ # If the file has an encoding, encode unicode with it.
-+ if (isinstance(fp, file) and
-+ isinstance(data, unicode) and
-+ fp.encoding is not None):
-+ errors = getattr(fp, "errors", None)
-+ if errors is None:
-+ errors = "strict"
-+ data = data.encode(fp.encoding, errors)
-+ fp.write(data)
-+ want_unicode = False
-+ sep = kwargs.pop("sep", None)
-+ if sep is not None:
-+ if isinstance(sep, unicode):
-+ want_unicode = True
-+ elif not isinstance(sep, str):
-+ raise TypeError("sep must be None or a string")
-+ end = kwargs.pop("end", None)
-+ if end is not None:
-+ if isinstance(end, unicode):
-+ want_unicode = True
-+ elif not isinstance(end, str):
-+ raise TypeError("end must be None or a string")
-+ if kwargs:
-+ raise TypeError("invalid keyword arguments to print()")
-+ if not want_unicode:
-+ for arg in args:
-+ if isinstance(arg, unicode):
-+ want_unicode = True
-+ break
-+ if want_unicode:
-+ newline = unicode("\n")
-+ space = unicode(" ")
-+ else:
-+ newline = "\n"
-+ space = " "
-+ if sep is None:
-+ sep = space
-+ if end is None:
-+ end = newline
-+ for i, arg in enumerate(args):
-+ if i:
-+ write(sep)
-+ write(arg)
-+ write(end)
-+if sys.version_info[:2] < (3, 3):
-+ _print = print_
-+
-+ def print_(*args, **kwargs):
-+ fp = kwargs.get("file", sys.stdout)
-+ flush = kwargs.pop("flush", False)
-+ _print(*args, **kwargs)
-+ if flush and fp is not None:
-+ fp.flush()
-+
-+_add_doc(reraise, """Reraise an exception.""")
-+
-+if sys.version_info[0:2] < (3, 4):
-+ def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
-+ updated=functools.WRAPPER_UPDATES):
-+ def wrapper(f):
-+ f = functools.wraps(wrapped, assigned, updated)(f)
-+ f.__wrapped__ = wrapped
-+ return f
-+ return wrapper
-+else:
-+ wraps = functools.wraps
-+
-+
-+def with_metaclass(meta, *bases):
-+ """Create a base class with a metaclass."""
-+ # This requires a bit of explanation: the basic idea is to make a dummy
-+ # metaclass for one level of class instantiation that replaces itself with
-+ # the actual metaclass.
-+ class metaclass(meta):
-+
-+ def __new__(cls, name, this_bases, d):
-+ return meta(name, bases, d)
-+ return type.__new__(metaclass, 'temporary_class', (), {})
-+
-+
-+def add_metaclass(metaclass):
-+ """Class decorator for creating a class with a metaclass."""
-+ def wrapper(cls):
-+ orig_vars = cls.__dict__.copy()
-+ slots = orig_vars.get('__slots__')
-+ if slots is not None:
-+ if isinstance(slots, str):
-+ slots = [slots]
-+ for slots_var in slots:
-+ orig_vars.pop(slots_var)
-+ orig_vars.pop('__dict__', None)
-+ orig_vars.pop('__weakref__', None)
-+ return metaclass(cls.__name__, cls.__bases__, orig_vars)
-+ return wrapper
-+
-+
-+def python_2_unicode_compatible(klass):
-+ """
-+ A decorator that defines __unicode__ and __str__ methods under Python 2.
-+ Under Python 3 it does nothing.
-+
-+ To support Python 2 and 3 with a single code base, define a __str__ method
-+ returning text and apply this decorator to the class.
-+ """
-+ if PY2:
-+ if '__str__' not in klass.__dict__:
-+ raise ValueError("@python_2_unicode_compatible cannot be applied "
-+ "to %s because it doesn't define __str__()." %
-+ klass.__name__)
-+ klass.__unicode__ = klass.__str__
-+ klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
-+ return klass
-+
-+
-+# Complete the moves implementation.
-+# This code is at the end of this module to speed up module loading.
-+# Turn this module into a package.
-+__path__ = [] # required for PEP 302 and PEP 451
-+__package__ = __name__ # see PEP 366 @ReservedAssignment
-+if globals().get("__spec__") is not None:
-+ __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable
-+# Remove other six meta path importers, since they cause problems. This can
-+# happen if six is removed from sys.modules and then reloaded. (Setuptools does
-+# this for some reason.)
-+if sys.meta_path:
-+ for i, importer in enumerate(sys.meta_path):
-+ # Here's some real nastiness: Another "instance" of the six module might
-+ # be floating around. Therefore, we can't use isinstance() to check for
-+ # the six meta path importer, since the other six instance will have
-+ # inserted an importer with different class.
-+ if (type(importer).__name__ == "_SixMetaPathImporter" and
-+ importer.name == __name__):
-+ del sys.meta_path[i]
-+ break
-+ del i, importer
-+# Finally, add the importer to the meta path import hook.
-+sys.meta_path.append(_importer)
++++ b/tools/generate_stubs/rules.gni
+@@ -0,0 +1,2 @@
++# "empty" file in place of importing new tools/generate_stubs
++# to allow BUILD.gn imports to succeed.
diff --git a/third_party/libwebrtc/moz-patch-stack/0099.patch b/third_party/libwebrtc/moz-patch-stack/0099.patch
index dc3cc7ca1a..6ad5dd698c 100644
--- a/third_party/libwebrtc/moz-patch-stack/0099.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0099.patch
@@ -1,20 +1,64 @@
-From: Michael Froman <mfroman@mozilla.com>
-Date: Wed, 7 Dec 2022 17:09:00 +0000
-Subject: Bug 1744645 - pt1 - add a couple empty gni files to help with
- BUILD.gn corrections. r=ng
+From: Nico Grunbaum <na-g@nostrum.com>
+Date: Wed, 15 Nov 2023 22:33:00 +0000
+Subject: Bug 1863041 - P0 - add device filter control to
+ RTCCameraVideoCapturer;r=pehrsons,webrtc-reviewers
-Differential Revision: https://phabricator.services.mozilla.com/D163991
-Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/81d86382ee468f3b36deed00d0c9d59eb85524be
+I have filed this bug upstream: https://bugs.chromium.org/p/webrtc/issues/detail?id=15639
+
+Differential Revision: https://phabricator.services.mozilla.com/D193172
+Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/91a763d768b74acc9cf4828f91a86df4a7b092ce
---
- tools/generate_stubs/rules.gni | 2 ++
- 1 file changed, 2 insertions(+)
- create mode 100644 tools/generate_stubs/rules.gni
+ .../capturer/RTCCameraVideoCapturer.h | 5 ++++-
+ .../capturer/RTCCameraVideoCapturer.m | 17 +++++++++++++++--
+ 2 files changed, 19 insertions(+), 3 deletions(-)
-diff --git a/tools/generate_stubs/rules.gni b/tools/generate_stubs/rules.gni
-new file mode 100644
-index 0000000000..1d9f36eb72
---- /dev/null
-+++ b/tools/generate_stubs/rules.gni
-@@ -0,0 +1,2 @@
-+# "empty" file in place of importing new tools/generate_stubs
-+# to allow BUILD.gn imports to succeed.
+diff --git a/sdk/objc/components/capturer/RTCCameraVideoCapturer.h b/sdk/objc/components/capturer/RTCCameraVideoCapturer.h
+index 370bfa70f0..b1f3f64f74 100644
+--- a/sdk/objc/components/capturer/RTCCameraVideoCapturer.h
++++ b/sdk/objc/components/capturer/RTCCameraVideoCapturer.h
+@@ -26,7 +26,10 @@ NS_EXTENSION_UNAVAILABLE_IOS("Camera not available in app extensions.")
+ @property(readonly, nonatomic) AVCaptureSession *captureSession;
+
+ // Returns list of available capture devices that support video capture.
+-+ (NSArray<AVCaptureDevice *> *)captureDevices;
+++ (NSArray<AVCaptureDevice *> *)captureDevicesWithDeviceTypes:
++ (NSArray<AVCaptureDeviceType> *)deviceTypes;
++// Returns list of default capture devices types
+++ (NSArray<AVCaptureDeviceType> *)defaultCaptureDeviceTypes;
+ // Returns list of formats that are supported by this class for this device.
+ + (NSArray<AVCaptureDeviceFormat *> *)supportedFormatsForDevice:(AVCaptureDevice *)device;
+
+diff --git a/sdk/objc/components/capturer/RTCCameraVideoCapturer.m b/sdk/objc/components/capturer/RTCCameraVideoCapturer.m
+index e7c47b4e99..1361207faf 100644
+--- a/sdk/objc/components/capturer/RTCCameraVideoCapturer.m
++++ b/sdk/objc/components/capturer/RTCCameraVideoCapturer.m
+@@ -117,14 +117,27 @@ const int64_t kNanosecondsPerSecond = 1000000000;
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ }
+
+-+ (NSArray<AVCaptureDevice *> *)captureDevices {
+++ (NSArray<AVCaptureDevice *> *)captureDevicesWithDeviceTypes:
++ (NSArray<AVCaptureDeviceType> *)deviceTypes {
+ AVCaptureDeviceDiscoverySession *session = [AVCaptureDeviceDiscoverySession
+- discoverySessionWithDeviceTypes:@[ AVCaptureDeviceTypeBuiltInWideAngleCamera ]
++ discoverySessionWithDeviceTypes:deviceTypes
+ mediaType:AVMediaTypeVideo
+ position:AVCaptureDevicePositionUnspecified];
+ return session.devices;
+ }
+
+++ (NSArray<AVCaptureDeviceType> *)defaultCaptureDeviceTypes {
++ NSArray *types = @[ AVCaptureDeviceTypeBuiltInWideAngleCamera ];
++#if !defined(WEBRTC_IOS)
++ if (@available(macOS 14.0, *)) {
++ types = [types arrayByAddingObject:AVCaptureDeviceTypeExternal];
++ } else {
++ types = [types arrayByAddingObject:AVCaptureDeviceTypeExternalUnknown];
++ }
++#endif
++ return types;
++}
++
+ + (NSArray<AVCaptureDeviceFormat *> *)supportedFormatsForDevice:(AVCaptureDevice *)device {
+ // Support opening the device in any format. We make sure it's converted to a format we
+ // can handle, if needed, in the method `-setupVideoDataOutput`.
diff --git a/third_party/libwebrtc/moz-patch-stack/0100.patch b/third_party/libwebrtc/moz-patch-stack/0100.patch
index 6ad5dd698c..7cf1017a12 100644
--- a/third_party/libwebrtc/moz-patch-stack/0100.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0100.patch
@@ -1,64 +1,36 @@
-From: Nico Grunbaum <na-g@nostrum.com>
-Date: Wed, 15 Nov 2023 22:33:00 +0000
-Subject: Bug 1863041 - P0 - add device filter control to
- RTCCameraVideoCapturer;r=pehrsons,webrtc-reviewers
+From: Michael Froman <mfroman@mozilla.com>
+Date: Mon, 4 Dec 2023 12:57:00 -0600
+Subject: Bug 1867099 - (fix-66b7275561) disable wgc capture yellow-line
+ removal. r?ng!
-I have filed this bug upstream: https://bugs.chromium.org/p/webrtc/issues/detail?id=15639
-
-Differential Revision: https://phabricator.services.mozilla.com/D193172
-Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/91a763d768b74acc9cf4828f91a86df4a7b092ce
+This code won't build until we support building with
+Win 10 SDK v10.0.20348.0 or newer.
+Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/0aac94794aad2ddb637f5076bc08706a11866737
---
- .../capturer/RTCCameraVideoCapturer.h | 5 ++++-
- .../capturer/RTCCameraVideoCapturer.m | 17 +++++++++++++++--
- 2 files changed, 19 insertions(+), 3 deletions(-)
+ modules/desktop_capture/win/wgc_capture_session.cc | 6 ++++++
+ 1 file changed, 6 insertions(+)
-diff --git a/sdk/objc/components/capturer/RTCCameraVideoCapturer.h b/sdk/objc/components/capturer/RTCCameraVideoCapturer.h
-index 370bfa70f0..b1f3f64f74 100644
---- a/sdk/objc/components/capturer/RTCCameraVideoCapturer.h
-+++ b/sdk/objc/components/capturer/RTCCameraVideoCapturer.h
-@@ -26,7 +26,10 @@ NS_EXTENSION_UNAVAILABLE_IOS("Camera not available in app extensions.")
- @property(readonly, nonatomic) AVCaptureSession *captureSession;
-
- // Returns list of available capture devices that support video capture.
--+ (NSArray<AVCaptureDevice *> *)captureDevices;
-++ (NSArray<AVCaptureDevice *> *)captureDevicesWithDeviceTypes:
-+ (NSArray<AVCaptureDeviceType> *)deviceTypes;
-+// Returns list of default capture devices types
-++ (NSArray<AVCaptureDeviceType> *)defaultCaptureDeviceTypes;
- // Returns list of formats that are supported by this class for this device.
- + (NSArray<AVCaptureDeviceFormat *> *)supportedFormatsForDevice:(AVCaptureDevice *)device;
+diff --git a/modules/desktop_capture/win/wgc_capture_session.cc b/modules/desktop_capture/win/wgc_capture_session.cc
+index 8c74c2bf24..86afc52411 100644
+--- a/modules/desktop_capture/win/wgc_capture_session.cc
++++ b/modules/desktop_capture/win/wgc_capture_session.cc
+@@ -188,6 +188,11 @@ HRESULT WgcCaptureSession::StartCapture(const DesktopCaptureOptions& options) {
+ }
+ }
-diff --git a/sdk/objc/components/capturer/RTCCameraVideoCapturer.m b/sdk/objc/components/capturer/RTCCameraVideoCapturer.m
-index e7c47b4e99..1361207faf 100644
---- a/sdk/objc/components/capturer/RTCCameraVideoCapturer.m
-+++ b/sdk/objc/components/capturer/RTCCameraVideoCapturer.m
-@@ -117,14 +117,27 @@ const int64_t kNanosecondsPerSecond = 1000000000;
- [[NSNotificationCenter defaultCenter] removeObserver:self];
- }
++// Until Mozilla builds with Win 10 SDK v10.0.20348.0 or newer, this
++// code will not build. Once we support the newer SDK, Bug 1868198
++// exists to decide if we ever want to use this code since it is
++// removing an indicator that capture is happening.
++#if !defined(WEBRTC_MOZILLA_BUILD)
+ // By default, the WGC capture API adds a yellow border around the captured
+ // window or display to indicate that a capture is in progress. The section
+ // below is an attempt to remove this yellow border to make the capture
+@@ -199,6 +204,7 @@ HRESULT WgcCaptureSession::StartCapture(const DesktopCaptureOptions& options) {
+ &session3))) {
+ session3->put_IsBorderRequired(false);
+ }
++#endif
--+ (NSArray<AVCaptureDevice *> *)captureDevices {
-++ (NSArray<AVCaptureDevice *> *)captureDevicesWithDeviceTypes:
-+ (NSArray<AVCaptureDeviceType> *)deviceTypes {
- AVCaptureDeviceDiscoverySession *session = [AVCaptureDeviceDiscoverySession
-- discoverySessionWithDeviceTypes:@[ AVCaptureDeviceTypeBuiltInWideAngleCamera ]
-+ discoverySessionWithDeviceTypes:deviceTypes
- mediaType:AVMediaTypeVideo
- position:AVCaptureDevicePositionUnspecified];
- return session.devices;
- }
+ allow_zero_hertz_ = options.allow_wgc_zero_hertz();
-++ (NSArray<AVCaptureDeviceType> *)defaultCaptureDeviceTypes {
-+ NSArray *types = @[ AVCaptureDeviceTypeBuiltInWideAngleCamera ];
-+#if !defined(WEBRTC_IOS)
-+ if (@available(macOS 14.0, *)) {
-+ types = [types arrayByAddingObject:AVCaptureDeviceTypeExternal];
-+ } else {
-+ types = [types arrayByAddingObject:AVCaptureDeviceTypeExternalUnknown];
-+ }
-+#endif
-+ return types;
-+}
-+
- + (NSArray<AVCaptureDeviceFormat *> *)supportedFormatsForDevice:(AVCaptureDevice *)device {
- // Support opening the device in any format. We make sure it's converted to a format we
- // can handle, if needed, in the method `-setupVideoDataOutput`.
diff --git a/third_party/libwebrtc/moz-patch-stack/0101.patch b/third_party/libwebrtc/moz-patch-stack/0101.patch
index 7cf1017a12..07be6fc934 100644
--- a/third_party/libwebrtc/moz-patch-stack/0101.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0101.patch
@@ -1,36 +1,121 @@
-From: Michael Froman <mfroman@mozilla.com>
-Date: Mon, 4 Dec 2023 12:57:00 -0600
-Subject: Bug 1867099 - (fix-66b7275561) disable wgc capture yellow-line
- removal. r?ng!
+From: Jan Grulich <jgrulich@redhat.com>
+Date: Thu, 30 Nov 2023 11:49:00 +0000
+Subject: Bug 1844020 - Add option to DeviceInfo::GetDeviceName() identifying a
+ placeholder device r=pehrsons,webrtc-reviewers
-This code won't build until we support building with
-Win 10 SDK v10.0.20348.0 or newer.
-Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/0aac94794aad2ddb637f5076bc08706a11866737
+Adds a new parameter "deviceIsPlaceholder" that will be set to true in
+case the returned device is not a real device but a placeholder that is
+just used to inform about camera device existence.
+
+Differential Revision: https://phabricator.services.mozilla.com/D189929
+Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/ed31b2acb5fbca3e2d0691a64bc52e65952070c0
---
- modules/desktop_capture/win/wgc_capture_session.cc | 6 ++++++
- 1 file changed, 6 insertions(+)
+ modules/video_capture/linux/device_info_pipewire.cc | 4 +++-
+ modules/video_capture/linux/device_info_pipewire.h | 3 ++-
+ modules/video_capture/linux/device_info_v4l2.cc | 3 ++-
+ modules/video_capture/linux/device_info_v4l2.h | 3 ++-
+ modules/video_capture/video_capture.h | 3 ++-
+ modules/video_capture/windows/device_info_ds.cc | 3 ++-
+ modules/video_capture/windows/device_info_ds.h | 3 ++-
+ 7 files changed, 15 insertions(+), 7 deletions(-)
-diff --git a/modules/desktop_capture/win/wgc_capture_session.cc b/modules/desktop_capture/win/wgc_capture_session.cc
-index 8c74c2bf24..86afc52411 100644
---- a/modules/desktop_capture/win/wgc_capture_session.cc
-+++ b/modules/desktop_capture/win/wgc_capture_session.cc
-@@ -188,6 +188,11 @@ HRESULT WgcCaptureSession::StartCapture(const DesktopCaptureOptions& options) {
- }
- }
+diff --git a/modules/video_capture/linux/device_info_pipewire.cc b/modules/video_capture/linux/device_info_pipewire.cc
+index fc0554f384..f9f08a9c27 100644
+--- a/modules/video_capture/linux/device_info_pipewire.cc
++++ b/modules/video_capture/linux/device_info_pipewire.cc
+@@ -50,8 +50,10 @@ int32_t DeviceInfoPipeWire::GetDeviceName(uint32_t deviceNumber,
+ uint32_t deviceUniqueIdUTF8Length,
+ char* productUniqueIdUTF8,
+ uint32_t productUniqueIdUTF8Length,
+- pid_t* pid) {
++ pid_t* pid,
++ bool* deviceIsPlaceholder) {
+ RTC_CHECK(pipewire_session_);
++
+ if (deviceNumber >= NumberOfDevices())
+ return -1;
-+// Until Mozilla builds with Win 10 SDK v10.0.20348.0 or newer, this
-+// code will not build. Once we support the newer SDK, Bug 1868198
-+// exists to decide if we ever want to use this code since it is
-+// removing an indicator that capture is happening.
-+#if !defined(WEBRTC_MOZILLA_BUILD)
- // By default, the WGC capture API adds a yellow border around the captured
- // window or display to indicate that a capture is in progress. The section
- // below is an attempt to remove this yellow border to make the capture
-@@ -199,6 +204,7 @@ HRESULT WgcCaptureSession::StartCapture(const DesktopCaptureOptions& options) {
- &session3))) {
- session3->put_IsBorderRequired(false);
- }
-+#endif
+diff --git a/modules/video_capture/linux/device_info_pipewire.h b/modules/video_capture/linux/device_info_pipewire.h
+index 8a33d75892..00715c94bc 100644
+--- a/modules/video_capture/linux/device_info_pipewire.h
++++ b/modules/video_capture/linux/device_info_pipewire.h
+@@ -30,7 +30,8 @@ class DeviceInfoPipeWire : public DeviceInfoImpl {
+ uint32_t deviceUniqueIdUTF8Length,
+ char* productUniqueIdUTF8 = nullptr,
+ uint32_t productUniqueIdUTF8Length = 0,
+- pid_t* pid = 0) override;
++ pid_t* pid = 0,
++ bool* deviceIsPlaceholder = 0) override;
+ /*
+ * Fills the membervariable _captureCapabilities with capabilites for the
+ * given device name.
+diff --git a/modules/video_capture/linux/device_info_v4l2.cc b/modules/video_capture/linux/device_info_v4l2.cc
+index 04caaea592..401c38f9c5 100644
+--- a/modules/video_capture/linux/device_info_v4l2.cc
++++ b/modules/video_capture/linux/device_info_v4l2.cc
+@@ -232,7 +232,8 @@ int32_t DeviceInfoV4l2::GetDeviceName(uint32_t deviceNumber,
+ uint32_t deviceUniqueIdUTF8Length,
+ char* /*productUniqueIdUTF8*/,
+ uint32_t /*productUniqueIdUTF8Length*/,
+- pid_t* /*pid*/) {
++ pid_t* /*pid*/,
++ bool* /*deviceIsPlaceholder*/) {
+ // Travel through /dev/video [0-63]
+ uint32_t count = 0;
+ char device[20];
+diff --git a/modules/video_capture/linux/device_info_v4l2.h b/modules/video_capture/linux/device_info_v4l2.h
+index 0bec3eb765..55415845ad 100644
+--- a/modules/video_capture/linux/device_info_v4l2.h
++++ b/modules/video_capture/linux/device_info_v4l2.h
+@@ -36,7 +36,8 @@ class DeviceInfoV4l2 : public DeviceInfoImpl {
+ uint32_t deviceUniqueIdUTF8Length,
+ char* productUniqueIdUTF8 = 0,
+ uint32_t productUniqueIdUTF8Length = 0,
+- pid_t* pid=0) override;
++ pid_t* pid = 0,
++ bool* deviceIsPlaceholder = 0) override;
+ /*
+ * Fills the membervariable _captureCapabilities with capabilites for the
+ * given device name.
+diff --git a/modules/video_capture/video_capture.h b/modules/video_capture/video_capture.h
+index 43a6a7f832..f59c34f8b2 100644
+--- a/modules/video_capture/video_capture.h
++++ b/modules/video_capture/video_capture.h
+@@ -74,7 +74,8 @@ class VideoCaptureModule : public RefCountInterface {
+ uint32_t deviceUniqueIdUTF8Length,
+ char* productUniqueIdUTF8 = 0,
+ uint32_t productUniqueIdUTF8Length = 0,
+- pid_t* pid = 0) = 0;
++ pid_t* pid = 0,
++ bool* deviceIsPlaceholder = 0) = 0;
- allow_zero_hertz_ = options.allow_wgc_zero_hertz();
+ // Returns the number of capabilities this device.
+ virtual int32_t NumberOfCapabilities(const char* deviceUniqueIdUTF8) = 0;
+diff --git a/modules/video_capture/windows/device_info_ds.cc b/modules/video_capture/windows/device_info_ds.cc
+index f6927281f3..8ca741239c 100644
+--- a/modules/video_capture/windows/device_info_ds.cc
++++ b/modules/video_capture/windows/device_info_ds.cc
+@@ -173,7 +173,8 @@ int32_t DeviceInfoDS::GetDeviceName(uint32_t deviceNumber,
+ uint32_t deviceUniqueIdUTF8Length,
+ char* productUniqueIdUTF8,
+ uint32_t productUniqueIdUTF8Length,
+- pid_t* pid) {
++ pid_t* pid,
++ bool* deviceIsPlaceholder) {
+ MutexLock lock(&_apiLock);
+ const int32_t result = GetDeviceInfo(
+ deviceNumber, deviceNameUTF8, deviceNameLength, deviceUniqueIdUTF8,
+diff --git a/modules/video_capture/windows/device_info_ds.h b/modules/video_capture/windows/device_info_ds.h
+index e6dfaed366..a9a1449b99 100644
+--- a/modules/video_capture/windows/device_info_ds.h
++++ b/modules/video_capture/windows/device_info_ds.h
+@@ -51,7 +51,8 @@ class DeviceInfoDS : public DeviceInfoImpl {
+ uint32_t deviceUniqueIdUTF8Length,
+ char* productUniqueIdUTF8,
+ uint32_t productUniqueIdUTF8Length,
+- pid_t* pid) override;
++ pid_t* pid,
++ bool* deviceIsPlaceholder) override;
+ /*
+ * Display OS /capture device specific settings dialog
diff --git a/third_party/libwebrtc/moz-patch-stack/0102.patch b/third_party/libwebrtc/moz-patch-stack/0102.patch
index 07be6fc934..d232dcb897 100644
--- a/third_party/libwebrtc/moz-patch-stack/0102.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0102.patch
@@ -1,121 +1,107 @@
-From: Jan Grulich <jgrulich@redhat.com>
-Date: Thu, 30 Nov 2023 11:49:00 +0000
-Subject: Bug 1844020 - Add option to DeviceInfo::GetDeviceName() identifying a
- placeholder device r=pehrsons,webrtc-reviewers
+From: Michael Froman <mfroman@mozilla.com>
+Date: Mon, 18 Dec 2023 15:00:00 +0000
+Subject: Bug 1867099 - revert libwebrtc 8602f604e0. r=bwc
-Adds a new parameter "deviceIsPlaceholder" that will be set to true in
-case the returned device is not a real device but a placeholder that is
-just used to inform about camera device existence.
+Upstream 8602f604e0 removed code sending BYEs which breaks some of
+our wpt. They've opened a bug for a real fix here:
+https://bugs.chromium.org/p/webrtc/issues/detail?id=15664
-Differential Revision: https://phabricator.services.mozilla.com/D189929
-Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/ed31b2acb5fbca3e2d0691a64bc52e65952070c0
+I've opened Bug 1870643 to track the real fix and upstream bug.
+
+Differential Revision: https://phabricator.services.mozilla.com/D196729
+Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/d92a578327f524ec3e1c144c82492a4c76b8266f
---
- modules/video_capture/linux/device_info_pipewire.cc | 4 +++-
- modules/video_capture/linux/device_info_pipewire.h | 3 ++-
- modules/video_capture/linux/device_info_v4l2.cc | 3 ++-
- modules/video_capture/linux/device_info_v4l2.h | 3 ++-
- modules/video_capture/video_capture.h | 3 ++-
- modules/video_capture/windows/device_info_ds.cc | 3 ++-
- modules/video_capture/windows/device_info_ds.h | 3 ++-
- 7 files changed, 15 insertions(+), 7 deletions(-)
+ call/rtp_video_sender.cc | 1 +
+ modules/rtp_rtcp/source/rtcp_sender.cc | 19 +++++++++++++++++--
+ .../rtp_rtcp/source/rtcp_sender_unittest.cc | 5 +++--
+ modules/rtp_rtcp/source/rtp_rtcp_impl.cc | 1 +
+ modules/rtp_rtcp/source/rtp_rtcp_interface.h | 2 +-
+ 5 files changed, 23 insertions(+), 5 deletions(-)
-diff --git a/modules/video_capture/linux/device_info_pipewire.cc b/modules/video_capture/linux/device_info_pipewire.cc
-index fc0554f384..f9f08a9c27 100644
---- a/modules/video_capture/linux/device_info_pipewire.cc
-+++ b/modules/video_capture/linux/device_info_pipewire.cc
-@@ -50,8 +50,10 @@ int32_t DeviceInfoPipeWire::GetDeviceName(uint32_t deviceNumber,
- uint32_t deviceUniqueIdUTF8Length,
- char* productUniqueIdUTF8,
- uint32_t productUniqueIdUTF8Length,
-- pid_t* pid) {
-+ pid_t* pid,
-+ bool* deviceIsPlaceholder) {
- RTC_CHECK(pipewire_session_);
+diff --git a/call/rtp_video_sender.cc b/call/rtp_video_sender.cc
+index 1ace08fa32..4d99c61bb4 100644
+--- a/call/rtp_video_sender.cc
++++ b/call/rtp_video_sender.cc
+@@ -510,6 +510,7 @@ void RtpVideoSender::SetActiveModulesLocked(
+ const bool was_active = rtp_module.Sending();
+ const bool should_be_active = active_modules[i];
+
++ // Sends a kRtcpByeCode when going from true to false.
+ rtp_module.SetSendingStatus(active_modules[i]);
+
+ if (was_active && !should_be_active) {
+diff --git a/modules/rtp_rtcp/source/rtcp_sender.cc b/modules/rtp_rtcp/source/rtcp_sender.cc
+index 099b0be1a3..971f49b949 100644
+--- a/modules/rtp_rtcp/source/rtcp_sender.cc
++++ b/modules/rtp_rtcp/source/rtcp_sender.cc
+@@ -212,8 +212,23 @@ bool RTCPSender::Sending() const {
+
+ void RTCPSender::SetSendingStatus(const FeedbackState& feedback_state,
+ bool sending) {
+- MutexLock lock(&mutex_rtcp_sender_);
+- sending_ = sending;
++ bool sendRTCPBye = false;
++ {
++ MutexLock lock(&mutex_rtcp_sender_);
+
- if (deviceNumber >= NumberOfDevices())
- return -1;
++ if (method_ != RtcpMode::kOff) {
++ if (sending == false && sending_ == true) {
++ // Trigger RTCP bye
++ sendRTCPBye = true;
++ }
++ }
++ sending_ = sending;
++ }
++ if (sendRTCPBye) {
++ if (SendRTCP(feedback_state, kRtcpBye) != 0) {
++ RTC_LOG(LS_WARNING) << "Failed to send RTCP BYE";
++ }
++ }
+ }
+
+ void RTCPSender::SetNonSenderRttMeasurement(bool enabled) {
+diff --git a/modules/rtp_rtcp/source/rtcp_sender_unittest.cc b/modules/rtp_rtcp/source/rtcp_sender_unittest.cc
+index 002a5f86f1..1dcb628722 100644
+--- a/modules/rtp_rtcp/source/rtcp_sender_unittest.cc
++++ b/modules/rtp_rtcp/source/rtcp_sender_unittest.cc
+@@ -328,12 +328,13 @@ TEST_F(RtcpSenderTest, SendBye) {
+ EXPECT_EQ(kSenderSsrc, parser()->bye()->sender_ssrc());
+ }
+
+-TEST_F(RtcpSenderTest, StopSendingDoesNotTriggersBye) {
++TEST_F(RtcpSenderTest, StopSendingTriggersBye) {
+ auto rtcp_sender = CreateRtcpSender(GetDefaultConfig());
+ rtcp_sender->SetRTCPStatus(RtcpMode::kReducedSize);
+ rtcp_sender->SetSendingStatus(feedback_state(), true);
+ rtcp_sender->SetSendingStatus(feedback_state(), false);
+- EXPECT_EQ(0, parser()->bye()->num_packets());
++ EXPECT_EQ(1, parser()->bye()->num_packets());
++ EXPECT_EQ(kSenderSsrc, parser()->bye()->sender_ssrc());
+ }
+
+ TEST_F(RtcpSenderTest, SendFir) {
+diff --git a/modules/rtp_rtcp/source/rtp_rtcp_impl.cc b/modules/rtp_rtcp/source/rtp_rtcp_impl.cc
+index cca9a40250..a63067141d 100644
+--- a/modules/rtp_rtcp/source/rtp_rtcp_impl.cc
++++ b/modules/rtp_rtcp/source/rtp_rtcp_impl.cc
+@@ -296,6 +296,7 @@ RTCPSender::FeedbackState ModuleRtpRtcpImpl::GetFeedbackState() {
-diff --git a/modules/video_capture/linux/device_info_pipewire.h b/modules/video_capture/linux/device_info_pipewire.h
-index 8a33d75892..00715c94bc 100644
---- a/modules/video_capture/linux/device_info_pipewire.h
-+++ b/modules/video_capture/linux/device_info_pipewire.h
-@@ -30,7 +30,8 @@ class DeviceInfoPipeWire : public DeviceInfoImpl {
- uint32_t deviceUniqueIdUTF8Length,
- char* productUniqueIdUTF8 = nullptr,
- uint32_t productUniqueIdUTF8Length = 0,
-- pid_t* pid = 0) override;
-+ pid_t* pid = 0,
-+ bool* deviceIsPlaceholder = 0) override;
- /*
- * Fills the membervariable _captureCapabilities with capabilites for the
- * given device name.
-diff --git a/modules/video_capture/linux/device_info_v4l2.cc b/modules/video_capture/linux/device_info_v4l2.cc
-index 04caaea592..401c38f9c5 100644
---- a/modules/video_capture/linux/device_info_v4l2.cc
-+++ b/modules/video_capture/linux/device_info_v4l2.cc
-@@ -232,7 +232,8 @@ int32_t DeviceInfoV4l2::GetDeviceName(uint32_t deviceNumber,
- uint32_t deviceUniqueIdUTF8Length,
- char* /*productUniqueIdUTF8*/,
- uint32_t /*productUniqueIdUTF8Length*/,
-- pid_t* /*pid*/) {
-+ pid_t* /*pid*/,
-+ bool* /*deviceIsPlaceholder*/) {
- // Travel through /dev/video [0-63]
- uint32_t count = 0;
- char device[20];
-diff --git a/modules/video_capture/linux/device_info_v4l2.h b/modules/video_capture/linux/device_info_v4l2.h
-index 0bec3eb765..55415845ad 100644
---- a/modules/video_capture/linux/device_info_v4l2.h
-+++ b/modules/video_capture/linux/device_info_v4l2.h
-@@ -36,7 +36,8 @@ class DeviceInfoV4l2 : public DeviceInfoImpl {
- uint32_t deviceUniqueIdUTF8Length,
- char* productUniqueIdUTF8 = 0,
- uint32_t productUniqueIdUTF8Length = 0,
-- pid_t* pid=0) override;
-+ pid_t* pid = 0,
-+ bool* deviceIsPlaceholder = 0) override;
- /*
- * Fills the membervariable _captureCapabilities with capabilites for the
- * given device name.
-diff --git a/modules/video_capture/video_capture.h b/modules/video_capture/video_capture.h
-index 43a6a7f832..f59c34f8b2 100644
---- a/modules/video_capture/video_capture.h
-+++ b/modules/video_capture/video_capture.h
-@@ -74,7 +74,8 @@ class VideoCaptureModule : public RefCountInterface {
- uint32_t deviceUniqueIdUTF8Length,
- char* productUniqueIdUTF8 = 0,
- uint32_t productUniqueIdUTF8Length = 0,
-- pid_t* pid = 0) = 0;
-+ pid_t* pid = 0,
-+ bool* deviceIsPlaceholder = 0) = 0;
+ int32_t ModuleRtpRtcpImpl::SetSendingStatus(const bool sending) {
+ if (rtcp_sender_.Sending() != sending) {
++ // Sends RTCP BYE when going from true to false
+ rtcp_sender_.SetSendingStatus(GetFeedbackState(), sending);
+ }
+ return 0;
+diff --git a/modules/rtp_rtcp/source/rtp_rtcp_interface.h b/modules/rtp_rtcp/source/rtp_rtcp_interface.h
+index f196d11b58..bc8da63ab6 100644
+--- a/modules/rtp_rtcp/source/rtp_rtcp_interface.h
++++ b/modules/rtp_rtcp/source/rtp_rtcp_interface.h
+@@ -277,7 +277,7 @@ class RtpRtcpInterface : public RtcpFeedbackSenderInterface {
+ // Returns the FlexFEC SSRC, if there is one.
+ virtual absl::optional<uint32_t> FlexfecSsrc() const = 0;
- // Returns the number of capabilities this device.
- virtual int32_t NumberOfCapabilities(const char* deviceUniqueIdUTF8) = 0;
-diff --git a/modules/video_capture/windows/device_info_ds.cc b/modules/video_capture/windows/device_info_ds.cc
-index f6927281f3..8ca741239c 100644
---- a/modules/video_capture/windows/device_info_ds.cc
-+++ b/modules/video_capture/windows/device_info_ds.cc
-@@ -173,7 +173,8 @@ int32_t DeviceInfoDS::GetDeviceName(uint32_t deviceNumber,
- uint32_t deviceUniqueIdUTF8Length,
- char* productUniqueIdUTF8,
- uint32_t productUniqueIdUTF8Length,
-- pid_t* pid) {
-+ pid_t* pid,
-+ bool* deviceIsPlaceholder) {
- MutexLock lock(&_apiLock);
- const int32_t result = GetDeviceInfo(
- deviceNumber, deviceNameUTF8, deviceNameLength, deviceUniqueIdUTF8,
-diff --git a/modules/video_capture/windows/device_info_ds.h b/modules/video_capture/windows/device_info_ds.h
-index e6dfaed366..a9a1449b99 100644
---- a/modules/video_capture/windows/device_info_ds.h
-+++ b/modules/video_capture/windows/device_info_ds.h
-@@ -51,7 +51,8 @@ class DeviceInfoDS : public DeviceInfoImpl {
- uint32_t deviceUniqueIdUTF8Length,
- char* productUniqueIdUTF8,
- uint32_t productUniqueIdUTF8Length,
-- pid_t* pid) override;
-+ pid_t* pid,
-+ bool* deviceIsPlaceholder) override;
+- // Sets sending status.
++ // Sets sending status. Sends kRtcpByeCode when going from true to false.
+ // Returns -1 on failure else 0.
+ virtual int32_t SetSendingStatus(bool sending) = 0;
- /*
- * Display OS /capture device specific settings dialog
diff --git a/third_party/libwebrtc/moz-patch-stack/0103.patch b/third_party/libwebrtc/moz-patch-stack/0103.patch
index d232dcb897..a6da56ee72 100644
--- a/third_party/libwebrtc/moz-patch-stack/0103.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0103.patch
@@ -1,107 +1,33 @@
-From: Michael Froman <mfroman@mozilla.com>
-Date: Mon, 18 Dec 2023 15:00:00 +0000
-Subject: Bug 1867099 - revert libwebrtc 8602f604e0. r=bwc
+From: Andreas Pehrson <apehrson@mozilla.com>
+Date: Fri, 2 Feb 2024 18:43:00 +0000
+Subject: Bug 1878010 - Fix webrtc::VideoCaptureFactory for BSD.
+ r=grulja,gaston,webrtc-reviewers,mjf
-Upstream 8602f604e0 removed code sending BYEs which breaks some of
-our wpt. They've opened a bug for a real fix here:
-https://bugs.chromium.org/p/webrtc/issues/detail?id=15664
-
-I've opened Bug 1870643 to track the real fix and upstream bug.
-
-Differential Revision: https://phabricator.services.mozilla.com/D196729
-Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/d92a578327f524ec3e1c144c82492a4c76b8266f
+Differential Revision: https://phabricator.services.mozilla.com/D200427
+Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/f890637efe5abc0020fab83ff2224313cd0c8460
---
- call/rtp_video_sender.cc | 1 +
- modules/rtp_rtcp/source/rtcp_sender.cc | 19 +++++++++++++++++--
- .../rtp_rtcp/source/rtcp_sender_unittest.cc | 5 +++--
- modules/rtp_rtcp/source/rtp_rtcp_impl.cc | 1 +
- modules/rtp_rtcp/source/rtp_rtcp_interface.h | 2 +-
- 5 files changed, 23 insertions(+), 5 deletions(-)
+ modules/video_capture/video_capture_factory.cc | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
-diff --git a/call/rtp_video_sender.cc b/call/rtp_video_sender.cc
-index 1ace08fa32..4d99c61bb4 100644
---- a/call/rtp_video_sender.cc
-+++ b/call/rtp_video_sender.cc
-@@ -510,6 +510,7 @@ void RtpVideoSender::SetActiveModulesLocked(
- const bool was_active = rtp_module.Sending();
- const bool should_be_active = active_modules[i];
-
-+ // Sends a kRtcpByeCode when going from true to false.
- rtp_module.SetSendingStatus(active_modules[i]);
-
- if (was_active && !should_be_active) {
-diff --git a/modules/rtp_rtcp/source/rtcp_sender.cc b/modules/rtp_rtcp/source/rtcp_sender.cc
-index 099b0be1a3..971f49b949 100644
---- a/modules/rtp_rtcp/source/rtcp_sender.cc
-+++ b/modules/rtp_rtcp/source/rtcp_sender.cc
-@@ -212,8 +212,23 @@ bool RTCPSender::Sending() const {
-
- void RTCPSender::SetSendingStatus(const FeedbackState& feedback_state,
- bool sending) {
-- MutexLock lock(&mutex_rtcp_sender_);
-- sending_ = sending;
-+ bool sendRTCPBye = false;
-+ {
-+ MutexLock lock(&mutex_rtcp_sender_);
-+
-+ if (method_ != RtcpMode::kOff) {
-+ if (sending == false && sending_ == true) {
-+ // Trigger RTCP bye
-+ sendRTCPBye = true;
-+ }
-+ }
-+ sending_ = sending;
-+ }
-+ if (sendRTCPBye) {
-+ if (SendRTCP(feedback_state, kRtcpBye) != 0) {
-+ RTC_LOG(LS_WARNING) << "Failed to send RTCP BYE";
-+ }
-+ }
- }
-
- void RTCPSender::SetNonSenderRttMeasurement(bool enabled) {
-diff --git a/modules/rtp_rtcp/source/rtcp_sender_unittest.cc b/modules/rtp_rtcp/source/rtcp_sender_unittest.cc
-index 002a5f86f1..1dcb628722 100644
---- a/modules/rtp_rtcp/source/rtcp_sender_unittest.cc
-+++ b/modules/rtp_rtcp/source/rtcp_sender_unittest.cc
-@@ -328,12 +328,13 @@ TEST_F(RtcpSenderTest, SendBye) {
- EXPECT_EQ(kSenderSsrc, parser()->bye()->sender_ssrc());
- }
-
--TEST_F(RtcpSenderTest, StopSendingDoesNotTriggersBye) {
-+TEST_F(RtcpSenderTest, StopSendingTriggersBye) {
- auto rtcp_sender = CreateRtcpSender(GetDefaultConfig());
- rtcp_sender->SetRTCPStatus(RtcpMode::kReducedSize);
- rtcp_sender->SetSendingStatus(feedback_state(), true);
- rtcp_sender->SetSendingStatus(feedback_state(), false);
-- EXPECT_EQ(0, parser()->bye()->num_packets());
-+ EXPECT_EQ(1, parser()->bye()->num_packets());
-+ EXPECT_EQ(kSenderSsrc, parser()->bye()->sender_ssrc());
- }
-
- TEST_F(RtcpSenderTest, SendFir) {
-diff --git a/modules/rtp_rtcp/source/rtp_rtcp_impl.cc b/modules/rtp_rtcp/source/rtp_rtcp_impl.cc
-index cca9a40250..a63067141d 100644
---- a/modules/rtp_rtcp/source/rtp_rtcp_impl.cc
-+++ b/modules/rtp_rtcp/source/rtp_rtcp_impl.cc
-@@ -296,6 +296,7 @@ RTCPSender::FeedbackState ModuleRtpRtcpImpl::GetFeedbackState() {
-
- int32_t ModuleRtpRtcpImpl::SetSendingStatus(const bool sending) {
- if (rtcp_sender_.Sending() != sending) {
-+ // Sends RTCP BYE when going from true to false
- rtcp_sender_.SetSendingStatus(GetFeedbackState(), sending);
- }
- return 0;
-diff --git a/modules/rtp_rtcp/source/rtp_rtcp_interface.h b/modules/rtp_rtcp/source/rtp_rtcp_interface.h
-index f196d11b58..bc8da63ab6 100644
---- a/modules/rtp_rtcp/source/rtp_rtcp_interface.h
-+++ b/modules/rtp_rtcp/source/rtp_rtcp_interface.h
-@@ -277,7 +277,7 @@ class RtpRtcpInterface : public RtcpFeedbackSenderInterface {
- // Returns the FlexFEC SSRC, if there is one.
- virtual absl::optional<uint32_t> FlexfecSsrc() const = 0;
-
-- // Sets sending status.
-+ // Sets sending status. Sends kRtcpByeCode when going from true to false.
- // Returns -1 on failure else 0.
- virtual int32_t SetSendingStatus(bool sending) = 0;
-
+diff --git a/modules/video_capture/video_capture_factory.cc b/modules/video_capture/video_capture_factory.cc
+index e085ac2df8..2790fbbe1c 100644
+--- a/modules/video_capture/video_capture_factory.cc
++++ b/modules/video_capture/video_capture_factory.cc
+@@ -24,7 +24,7 @@ rtc::scoped_refptr<VideoCaptureModule> VideoCaptureFactory::Create(
+ const char* deviceUniqueIdUTF8) {
+ // This is only implemented on pure Linux and WEBRTC_LINUX is defined for
+ // Android as well
+-#if !defined(WEBRTC_LINUX) || defined(WEBRTC_ANDROID)
++#if (!defined(WEBRTC_LINUX) && !defined(WEBRTC_BSD)) || defined(WEBRTC_ANDROID)
+ return nullptr;
+ #else
+ return videocapturemodule::VideoCaptureImpl::Create(options,
+@@ -40,7 +40,7 @@ VideoCaptureModule::DeviceInfo* VideoCaptureFactory::CreateDeviceInfo(
+ VideoCaptureOptions* options) {
+ // This is only implemented on pure Linux and WEBRTC_LINUX is defined for
+ // Android as well
+-#if !defined(WEBRTC_LINUX) || defined(WEBRTC_ANDROID)
++#if (!defined(WEBRTC_LINUX) && !defined(WEBRTC_BSD)) || defined(WEBRTC_ANDROID)
+ return nullptr;
+ #else
+ return videocapturemodule::VideoCaptureImpl::CreateDeviceInfo(options);
diff --git a/third_party/libwebrtc/moz-patch-stack/0104.patch b/third_party/libwebrtc/moz-patch-stack/0104.patch
index a6da56ee72..fd05008507 100644
--- a/third_party/libwebrtc/moz-patch-stack/0104.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0104.patch
@@ -1,33 +1,68 @@
-From: Andreas Pehrson <apehrson@mozilla.com>
-Date: Fri, 2 Feb 2024 18:43:00 +0000
-Subject: Bug 1878010 - Fix webrtc::VideoCaptureFactory for BSD.
- r=grulja,gaston,webrtc-reviewers,mjf
+From: Jan Grulich <jgrulich@redhat.com>
+Date: Fri, 2 Feb 2024 11:47:00 +0000
+Subject: Bug 1876896 - WebRTC backport: Allow VideoCaptureModulePipeWire to be
+ shared with more consumers r=pehrsons,webrtc-reviewers
-Differential Revision: https://phabricator.services.mozilla.com/D200427
-Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/f890637efe5abc0020fab83ff2224313cd0c8460
+This is a simple backport of an WebRTC upstream change.
+
+Upstream commit: 958c9ac546f33716d097b5092515dcac705151d3
+
+Differential Revision: https://phabricator.services.mozilla.com/D200142
+Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/2ba2ef65280b2e6f246fed24d6986718981744f5
---
- modules/video_capture/video_capture_factory.cc | 4 ++--
- 1 file changed, 2 insertions(+), 2 deletions(-)
+ .../video_capture/linux/video_capture_pipewire.cc | 15 ++++++++++++++-
+ .../video_capture/linux/video_capture_pipewire.h | 1 +
+ 2 files changed, 15 insertions(+), 1 deletion(-)
-diff --git a/modules/video_capture/video_capture_factory.cc b/modules/video_capture/video_capture_factory.cc
-index e085ac2df8..2790fbbe1c 100644
---- a/modules/video_capture/video_capture_factory.cc
-+++ b/modules/video_capture/video_capture_factory.cc
-@@ -24,7 +24,7 @@ rtc::scoped_refptr<VideoCaptureModule> VideoCaptureFactory::Create(
- const char* deviceUniqueIdUTF8) {
- // This is only implemented on pure Linux and WEBRTC_LINUX is defined for
- // Android as well
--#if !defined(WEBRTC_LINUX) || defined(WEBRTC_ANDROID)
-+#if (!defined(WEBRTC_LINUX) && !defined(WEBRTC_BSD)) || defined(WEBRTC_ANDROID)
- return nullptr;
- #else
- return videocapturemodule::VideoCaptureImpl::Create(options,
-@@ -40,7 +40,7 @@ VideoCaptureModule::DeviceInfo* VideoCaptureFactory::CreateDeviceInfo(
- VideoCaptureOptions* options) {
- // This is only implemented on pure Linux and WEBRTC_LINUX is defined for
- // Android as well
--#if !defined(WEBRTC_LINUX) || defined(WEBRTC_ANDROID)
-+#if (!defined(WEBRTC_LINUX) && !defined(WEBRTC_BSD)) || defined(WEBRTC_ANDROID)
- return nullptr;
- #else
- return videocapturemodule::VideoCaptureImpl::CreateDeviceInfo(options);
+diff --git a/modules/video_capture/linux/video_capture_pipewire.cc b/modules/video_capture/linux/video_capture_pipewire.cc
+index 9d47e3ddbf..fb813e331f 100644
+--- a/modules/video_capture/linux/video_capture_pipewire.cc
++++ b/modules/video_capture/linux/video_capture_pipewire.cc
+@@ -48,7 +48,10 @@ VideoType VideoCaptureModulePipeWire::PipeWireRawFormatToVideoType(
+
+ VideoCaptureModulePipeWire::VideoCaptureModulePipeWire(
+ VideoCaptureOptions* options)
+- : VideoCaptureImpl(), session_(options->pipewire_session()) {}
++ : VideoCaptureImpl(),
++ session_(options->pipewire_session()),
++ initialized_(false),
++ started_(false) {}
+
+ VideoCaptureModulePipeWire::~VideoCaptureModulePipeWire() {
+ RTC_DCHECK_RUN_ON(&api_checker_);
+@@ -121,6 +124,14 @@ int32_t VideoCaptureModulePipeWire::StartCapture(
+ RTC_CHECK_RUNS_SERIALIZED(&capture_checker_);
+ RTC_DCHECK_RUN_ON(&api_checker_);
+
++ if (initialized_) {
++ if (capability == _requestedCapability) {
++ return 0;
++ } else {
++ StopCapture();
++ }
++ }
++
+ uint8_t buffer[1024] = {};
+
+ RTC_LOG(LS_VERBOSE) << "Creating new PipeWire stream for node " << node_id_;
+@@ -171,6 +182,8 @@ int32_t VideoCaptureModulePipeWire::StartCapture(
+ }
+
+ _requestedCapability = capability;
++ initialized_ = true;
++
+ return 0;
+ }
+
+diff --git a/modules/video_capture/linux/video_capture_pipewire.h b/modules/video_capture/linux/video_capture_pipewire.h
+index 620ee520ca..5d6794ed65 100644
+--- a/modules/video_capture/linux/video_capture_pipewire.h
++++ b/modules/video_capture/linux/video_capture_pipewire.h
+@@ -50,6 +50,7 @@ class VideoCaptureModulePipeWire : public VideoCaptureImpl {
+ int node_id_ RTC_GUARDED_BY(capture_checker_);
+ VideoCaptureCapability configured_capability_
+ RTC_GUARDED_BY(pipewire_checker_);
++ bool initialized_ RTC_GUARDED_BY(capture_checker_);
+ bool started_ RTC_GUARDED_BY(api_lock_);
+
+ struct pw_stream* stream_ RTC_GUARDED_BY(pipewire_checker_) = nullptr;
diff --git a/third_party/libwebrtc/moz-patch-stack/0105.patch b/third_party/libwebrtc/moz-patch-stack/0105.patch
index fd05008507..1db996311f 100644
--- a/third_party/libwebrtc/moz-patch-stack/0105.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0105.patch
@@ -1,68 +1,127 @@
From: Jan Grulich <jgrulich@redhat.com>
-Date: Fri, 2 Feb 2024 11:47:00 +0000
-Subject: Bug 1876896 - WebRTC backport: Allow VideoCaptureModulePipeWire to be
- shared with more consumers r=pehrsons,webrtc-reviewers
+Date: Tue, 13 Feb 2024 13:12:00 +0000
+Subject: Bug 1879752 - WebRTC backport: Video capture PipeWire - simplify
+ thread and lock annotations r=pehrsons,webrtc-reviewers
This is a simple backport of an WebRTC upstream change.
-Upstream commit: 958c9ac546f33716d097b5092515dcac705151d3
+Upstream commit: 541f202354e2b4906935c8db6e54386aa8bc8d1f
-Differential Revision: https://phabricator.services.mozilla.com/D200142
-Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/2ba2ef65280b2e6f246fed24d6986718981744f5
+Differential Revision: https://phabricator.services.mozilla.com/D201328
+Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/dc12e6eecaa8dc91ee0a517cfd27570691c441b9
---
- .../video_capture/linux/video_capture_pipewire.cc | 15 ++++++++++++++-
- .../video_capture/linux/video_capture_pipewire.h | 1 +
- 2 files changed, 15 insertions(+), 1 deletion(-)
+ .../linux/video_capture_pipewire.cc | 25 ++++++++++++-------
+ .../linux/video_capture_pipewire.h | 14 +++++------
+ 2 files changed, 22 insertions(+), 17 deletions(-)
diff --git a/modules/video_capture/linux/video_capture_pipewire.cc b/modules/video_capture/linux/video_capture_pipewire.cc
-index 9d47e3ddbf..fb813e331f 100644
+index fb813e331f..8af483636a 100644
--- a/modules/video_capture/linux/video_capture_pipewire.cc
+++ b/modules/video_capture/linux/video_capture_pipewire.cc
-@@ -48,7 +48,10 @@ VideoType VideoCaptureModulePipeWire::PipeWireRawFormatToVideoType(
+@@ -121,7 +121,6 @@ static spa_pod* BuildFormat(spa_pod_builder* builder,
- VideoCaptureModulePipeWire::VideoCaptureModulePipeWire(
- VideoCaptureOptions* options)
-- : VideoCaptureImpl(), session_(options->pipewire_session()) {}
-+ : VideoCaptureImpl(),
-+ session_(options->pipewire_session()),
-+ initialized_(false),
-+ started_(false) {}
-
- VideoCaptureModulePipeWire::~VideoCaptureModulePipeWire() {
- RTC_DCHECK_RUN_ON(&api_checker_);
-@@ -121,6 +124,14 @@ int32_t VideoCaptureModulePipeWire::StartCapture(
- RTC_CHECK_RUNS_SERIALIZED(&capture_checker_);
+ int32_t VideoCaptureModulePipeWire::StartCapture(
+ const VideoCaptureCapability& capability) {
+- RTC_CHECK_RUNS_SERIALIZED(&capture_checker_);
RTC_DCHECK_RUN_ON(&api_checker_);
-+ if (initialized_) {
-+ if (capability == _requestedCapability) {
-+ return 0;
-+ } else {
-+ StopCapture();
-+ }
-+ }
-+
+ if (initialized_) {
+@@ -134,10 +133,17 @@ int32_t VideoCaptureModulePipeWire::StartCapture(
+
uint8_t buffer[1024] = {};
++ // We don't want members above to be guarded by capture_checker_ as
++ // it's meant to be for members that are accessed on the API thread
++ // only when we are not capturing. The code above can be called many
++ // times while sharing instance of VideoCapturePipeWire between
++ // websites and therefore it would not follow the requirements of this
++ // checker.
++ RTC_CHECK_RUNS_SERIALIZED(&capture_checker_);
++ PipeWireThreadLoopLock thread_loop_lock(session_->pw_main_loop_);
++
RTC_LOG(LS_VERBOSE) << "Creating new PipeWire stream for node " << node_id_;
-@@ -171,6 +182,8 @@ int32_t VideoCaptureModulePipeWire::StartCapture(
- }
- _requestedCapability = capability;
-+ initialized_ = true;
-+
- return 0;
+- PipeWireThreadLoopLock thread_loop_lock(session_->pw_main_loop_);
+- RTC_CHECK_RUNS_SERIALIZED(&pipewire_checker_);
+ pw_properties* reuse_props =
+ pw_properties_new_string("pipewire.client.reuse=1");
+ stream_ = pw_stream_new(session_->pw_core_, "camera-stream", reuse_props);
+@@ -188,11 +194,13 @@ int32_t VideoCaptureModulePipeWire::StartCapture(
+ }
+
+ int32_t VideoCaptureModulePipeWire::StopCapture() {
+- RTC_CHECK_RUNS_SERIALIZED(&capture_checker_);
+ RTC_DCHECK_RUN_ON(&api_checker_);
+
+ PipeWireThreadLoopLock thread_loop_lock(session_->pw_main_loop_);
+- RTC_CHECK_RUNS_SERIALIZED(&pipewire_checker_);
++ // PipeWireSession is guarded by API checker so just make sure we do
++ // race detection when the PipeWire loop is locked/stopped to not run
++ // any callback at this point.
++ RTC_CHECK_RUNS_SERIALIZED(&capture_checker_);
+ if (stream_) {
+ pw_stream_destroy(stream_);
+ stream_ = nullptr;
+@@ -225,14 +233,14 @@ void VideoCaptureModulePipeWire::OnStreamParamChanged(
+ VideoCaptureModulePipeWire* that =
+ static_cast<VideoCaptureModulePipeWire*>(data);
+ RTC_DCHECK(that);
+- RTC_CHECK_RUNS_SERIALIZED(&that->pipewire_checker_);
++ RTC_CHECK_RUNS_SERIALIZED(&that->capture_checker_);
+
+ if (format && id == SPA_PARAM_Format)
+ that->OnFormatChanged(format);
}
+ void VideoCaptureModulePipeWire::OnFormatChanged(const struct spa_pod* format) {
+- RTC_CHECK_RUNS_SERIALIZED(&pipewire_checker_);
++ RTC_CHECK_RUNS_SERIALIZED(&capture_checker_);
+
+ uint32_t media_type, media_subtype;
+
+@@ -331,7 +339,6 @@ void VideoCaptureModulePipeWire::OnStreamStateChanged(
+ VideoCaptureModulePipeWire* that =
+ static_cast<VideoCaptureModulePipeWire*>(data);
+ RTC_DCHECK(that);
+- RTC_CHECK_RUNS_SERIALIZED(&that->capture_checker_);
+
+ MutexLock lock(&that->api_lock_);
+ switch (state) {
+@@ -374,7 +381,7 @@ static VideoRotation VideorotationFromPipeWireTransform(uint32_t transform) {
+ }
+
+ void VideoCaptureModulePipeWire::ProcessBuffers() {
+- RTC_CHECK_RUNS_SERIALIZED(&pipewire_checker_);
++ RTC_CHECK_RUNS_SERIALIZED(&capture_checker_);
+
+ while (pw_buffer* buffer = pw_stream_dequeue_buffer(stream_)) {
+ struct spa_meta_header* h;
diff --git a/modules/video_capture/linux/video_capture_pipewire.h b/modules/video_capture/linux/video_capture_pipewire.h
-index 620ee520ca..5d6794ed65 100644
+index 5d6794ed65..eeb3b9497c 100644
--- a/modules/video_capture/linux/video_capture_pipewire.h
+++ b/modules/video_capture/linux/video_capture_pipewire.h
-@@ -50,6 +50,7 @@ class VideoCaptureModulePipeWire : public VideoCaptureImpl {
+@@ -43,18 +43,16 @@ class VideoCaptureModulePipeWire : public VideoCaptureImpl {
+ void OnFormatChanged(const struct spa_pod* format);
+ void ProcessBuffers();
+
+- rtc::RaceChecker pipewire_checker_;
+-
+ const rtc::scoped_refptr<PipeWireSession> session_
+- RTC_GUARDED_BY(capture_checker_);
++ RTC_GUARDED_BY(api_checker_);
++ bool initialized_ RTC_GUARDED_BY(api_checker_);
++ bool started_ RTC_GUARDED_BY(api_lock_);
int node_id_ RTC_GUARDED_BY(capture_checker_);
VideoCaptureCapability configured_capability_
- RTC_GUARDED_BY(pipewire_checker_);
-+ bool initialized_ RTC_GUARDED_BY(capture_checker_);
- bool started_ RTC_GUARDED_BY(api_lock_);
+- RTC_GUARDED_BY(pipewire_checker_);
+- bool initialized_ RTC_GUARDED_BY(capture_checker_);
+- bool started_ RTC_GUARDED_BY(api_lock_);
++ RTC_GUARDED_BY(capture_checker_);
- struct pw_stream* stream_ RTC_GUARDED_BY(pipewire_checker_) = nullptr;
+- struct pw_stream* stream_ RTC_GUARDED_BY(pipewire_checker_) = nullptr;
+- struct spa_hook stream_listener_ RTC_GUARDED_BY(pipewire_checker_);
++ struct pw_stream* stream_ RTC_GUARDED_BY(capture_checker_) = nullptr;
++ struct spa_hook stream_listener_ RTC_GUARDED_BY(capture_checker_);
+ };
+ } // namespace videocapturemodule
+ } // namespace webrtc
diff --git a/third_party/libwebrtc/moz-patch-stack/0106.patch b/third_party/libwebrtc/moz-patch-stack/0106.patch
index 1db996311f..9f518a2ac0 100644
--- a/third_party/libwebrtc/moz-patch-stack/0106.patch
+++ b/third_party/libwebrtc/moz-patch-stack/0106.patch
@@ -1,127 +1,74 @@
-From: Jan Grulich <jgrulich@redhat.com>
-Date: Tue, 13 Feb 2024 13:12:00 +0000
-Subject: Bug 1879752 - WebRTC backport: Video capture PipeWire - simplify
- thread and lock annotations r=pehrsons,webrtc-reviewers
+From: Dan Baker <dbaker@mozilla.com>
+Date: Thu, 14 Mar 2024 10:51:00 -0600
+Subject: Bug 1883116 - (fix-e79e722834) add enviroment_factory to libwebrtc
+ build and enforce providing clock and task_queue when creating Environment
-This is a simple backport of an WebRTC upstream change.
-
-Upstream commit: 541f202354e2b4906935c8db6e54386aa8bc8d1f
-
-Differential Revision: https://phabricator.services.mozilla.com/D201328
-Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/dc12e6eecaa8dc91ee0a517cfd27570691c441b9
+Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/2185cab977988fd4ab03b38dc67f9b06162444da
---
- .../linux/video_capture_pipewire.cc | 25 ++++++++++++-------
- .../linux/video_capture_pipewire.h | 14 +++++------
- 2 files changed, 22 insertions(+), 17 deletions(-)
+ BUILD.gn | 1 +
+ api/environment/environment_factory.cc | 10 ++++++++++
+ api/task_queue/BUILD.gn | 5 +++++
+ 3 files changed, 16 insertions(+)
-diff --git a/modules/video_capture/linux/video_capture_pipewire.cc b/modules/video_capture/linux/video_capture_pipewire.cc
-index fb813e331f..8af483636a 100644
---- a/modules/video_capture/linux/video_capture_pipewire.cc
-+++ b/modules/video_capture/linux/video_capture_pipewire.cc
-@@ -121,7 +121,6 @@ static spa_pod* BuildFormat(spa_pod_builder* builder,
-
- int32_t VideoCaptureModulePipeWire::StartCapture(
- const VideoCaptureCapability& capability) {
-- RTC_CHECK_RUNS_SERIALIZED(&capture_checker_);
- RTC_DCHECK_RUN_ON(&api_checker_);
-
- if (initialized_) {
-@@ -134,10 +133,17 @@ int32_t VideoCaptureModulePipeWire::StartCapture(
-
- uint8_t buffer[1024] = {};
-
-+ // We don't want members above to be guarded by capture_checker_ as
-+ // it's meant to be for members that are accessed on the API thread
-+ // only when we are not capturing. The code above can be called many
-+ // times while sharing instance of VideoCapturePipeWire between
-+ // websites and therefore it would not follow the requirements of this
-+ // checker.
-+ RTC_CHECK_RUNS_SERIALIZED(&capture_checker_);
-+ PipeWireThreadLoopLock thread_loop_lock(session_->pw_main_loop_);
-+
- RTC_LOG(LS_VERBOSE) << "Creating new PipeWire stream for node " << node_id_;
-
-- PipeWireThreadLoopLock thread_loop_lock(session_->pw_main_loop_);
-- RTC_CHECK_RUNS_SERIALIZED(&pipewire_checker_);
- pw_properties* reuse_props =
- pw_properties_new_string("pipewire.client.reuse=1");
- stream_ = pw_stream_new(session_->pw_core_, "camera-stream", reuse_props);
-@@ -188,11 +194,13 @@ int32_t VideoCaptureModulePipeWire::StartCapture(
+diff --git a/BUILD.gn b/BUILD.gn
+index 7feca08e60..85ead4162f 100644
+--- a/BUILD.gn
++++ b/BUILD.gn
+@@ -580,6 +580,7 @@ if (!build_with_chromium) {
+
+ if (build_with_mozilla) {
+ deps += [
++ "api/environment:environment_factory",
+ "api/video:video_frame",
+ "api/video:video_rtp_headers",
+ "test:rtp_test_utils",
+diff --git a/api/environment/environment_factory.cc b/api/environment/environment_factory.cc
+index c0b681aa08..6f0ec40dbe 100644
+--- a/api/environment/environment_factory.cc
++++ b/api/environment/environment_factory.cc
+@@ -97,12 +97,22 @@ Environment EnvironmentFactory::CreateWithDefaults() && {
+ if (field_trials_ == nullptr) {
+ Set(std::make_unique<FieldTrialBasedConfig>());
+ }
++#if defined(WEBRTC_MOZILLA_BUILD)
++ // We want to use our clock, not GetRealTimeClockRaw, and we avoid
++ // building the code under third_party/libwebrtc/task_queue. To
++ // ensure we're setting up things correctly, namely providing an
++ // Environment object with a preset task_queue_factory and clock,
++ // we'll do a release assert here.
++ RTC_CHECK(clock_);
++ RTC_CHECK(task_queue_factory_);
++#else
+ if (clock_ == nullptr) {
+ Set(Clock::GetRealTimeClock());
+ }
+ if (task_queue_factory_ == nullptr) {
+ Set(CreateDefaultTaskQueueFactory(field_trials_));
+ }
++#endif
+ if (event_log_ == nullptr) {
+ Set(std::make_unique<RtcEventLogNull>());
+ }
+diff --git a/api/task_queue/BUILD.gn b/api/task_queue/BUILD.gn
+index b9bc81171f..c24c22a1f6 100644
+--- a/api/task_queue/BUILD.gn
++++ b/api/task_queue/BUILD.gn
+@@ -88,6 +88,10 @@ rtc_library("task_queue_test") {
}
- int32_t VideoCaptureModulePipeWire::StopCapture() {
-- RTC_CHECK_RUNS_SERIALIZED(&capture_checker_);
- RTC_DCHECK_RUN_ON(&api_checker_);
-
- PipeWireThreadLoopLock thread_loop_lock(session_->pw_main_loop_);
-- RTC_CHECK_RUNS_SERIALIZED(&pipewire_checker_);
-+ // PipeWireSession is guarded by API checker so just make sure we do
-+ // race detection when the PipeWire loop is locked/stopped to not run
-+ // any callback at this point.
-+ RTC_CHECK_RUNS_SERIALIZED(&capture_checker_);
- if (stream_) {
- pw_stream_destroy(stream_);
- stream_ = nullptr;
-@@ -225,14 +233,14 @@ void VideoCaptureModulePipeWire::OnStreamParamChanged(
- VideoCaptureModulePipeWire* that =
- static_cast<VideoCaptureModulePipeWire*>(data);
- RTC_DCHECK(that);
-- RTC_CHECK_RUNS_SERIALIZED(&that->pipewire_checker_);
-+ RTC_CHECK_RUNS_SERIALIZED(&that->capture_checker_);
-
- if (format && id == SPA_PARAM_Format)
- that->OnFormatChanged(format);
+ rtc_library("default_task_queue_factory") {
++# Mozilla - disable this entire target to avoid inclusion of code we want
++# to avoid. Better here than trying to wack-a-mole for places that list
++# it as a dependency.
++if (!build_with_mozilla) {
+ visibility = [ "*" ]
+ if (!is_ios && !is_android) {
+ # Internally webrtc shouldn't rely on any specific TaskQueue implementation
+@@ -126,6 +130,7 @@ rtc_library("default_task_queue_factory") {
+ sources += [ "default_task_queue_factory_stdlib.cc" ]
+ deps += [ "../../rtc_base:rtc_task_queue_stdlib" ]
+ }
++} # of if (!build_with_mozilla) {
}
- void VideoCaptureModulePipeWire::OnFormatChanged(const struct spa_pod* format) {
-- RTC_CHECK_RUNS_SERIALIZED(&pipewire_checker_);
-+ RTC_CHECK_RUNS_SERIALIZED(&capture_checker_);
-
- uint32_t media_type, media_subtype;
-
-@@ -331,7 +339,6 @@ void VideoCaptureModulePipeWire::OnStreamStateChanged(
- VideoCaptureModulePipeWire* that =
- static_cast<VideoCaptureModulePipeWire*>(data);
- RTC_DCHECK(that);
-- RTC_CHECK_RUNS_SERIALIZED(&that->capture_checker_);
-
- MutexLock lock(&that->api_lock_);
- switch (state) {
-@@ -374,7 +381,7 @@ static VideoRotation VideorotationFromPipeWireTransform(uint32_t transform) {
- }
-
- void VideoCaptureModulePipeWire::ProcessBuffers() {
-- RTC_CHECK_RUNS_SERIALIZED(&pipewire_checker_);
-+ RTC_CHECK_RUNS_SERIALIZED(&capture_checker_);
-
- while (pw_buffer* buffer = pw_stream_dequeue_buffer(stream_)) {
- struct spa_meta_header* h;
-diff --git a/modules/video_capture/linux/video_capture_pipewire.h b/modules/video_capture/linux/video_capture_pipewire.h
-index 5d6794ed65..eeb3b9497c 100644
---- a/modules/video_capture/linux/video_capture_pipewire.h
-+++ b/modules/video_capture/linux/video_capture_pipewire.h
-@@ -43,18 +43,16 @@ class VideoCaptureModulePipeWire : public VideoCaptureImpl {
- void OnFormatChanged(const struct spa_pod* format);
- void ProcessBuffers();
-
-- rtc::RaceChecker pipewire_checker_;
--
- const rtc::scoped_refptr<PipeWireSession> session_
-- RTC_GUARDED_BY(capture_checker_);
-+ RTC_GUARDED_BY(api_checker_);
-+ bool initialized_ RTC_GUARDED_BY(api_checker_);
-+ bool started_ RTC_GUARDED_BY(api_lock_);
- int node_id_ RTC_GUARDED_BY(capture_checker_);
- VideoCaptureCapability configured_capability_
-- RTC_GUARDED_BY(pipewire_checker_);
-- bool initialized_ RTC_GUARDED_BY(capture_checker_);
-- bool started_ RTC_GUARDED_BY(api_lock_);
-+ RTC_GUARDED_BY(capture_checker_);
-
-- struct pw_stream* stream_ RTC_GUARDED_BY(pipewire_checker_) = nullptr;
-- struct spa_hook stream_listener_ RTC_GUARDED_BY(pipewire_checker_);
-+ struct pw_stream* stream_ RTC_GUARDED_BY(capture_checker_) = nullptr;
-+ struct spa_hook stream_listener_ RTC_GUARDED_BY(capture_checker_);
- };
- } // namespace videocapturemodule
- } // namespace webrtc
+ rtc_library("pending_task_safety_flag") {
diff --git a/third_party/libwebrtc/moz-patch-stack/0107.patch b/third_party/libwebrtc/moz-patch-stack/0107.patch
new file mode 100644
index 0000000000..1fa5c7fc7a
--- /dev/null
+++ b/third_party/libwebrtc/moz-patch-stack/0107.patch
@@ -0,0 +1,28 @@
+From: Jan Grulich <jgrulich@redhat.com>
+Date: Wed, 6 Mar 2024 10:19:00 +0000
+Subject: Bug 1882438 - WebRTC backport: PipeWire camera - use length of device
+ id instead display name r=pehrsons,webrtc-reviewers
+
+This is a simple backport of an WebRTC upstream change.
+
+Upstream commit: 16ac10d9f75cde959f00df062f544c49941882da
+
+Differential Revision: https://phabricator.services.mozilla.com/D203099
+Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/b572eb8b39b03f714234afff4bd80b4612439521
+---
+ modules/video_capture/linux/device_info_pipewire.cc | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/modules/video_capture/linux/device_info_pipewire.cc b/modules/video_capture/linux/device_info_pipewire.cc
+index f9f08a9c27..31d922035b 100644
+--- a/modules/video_capture/linux/device_info_pipewire.cc
++++ b/modules/video_capture/linux/device_info_pipewire.cc
+@@ -96,7 +96,7 @@ int32_t DeviceInfoPipeWire::CreateCapabilityMap(
+ continue;
+
+ _captureCapabilities = node.capabilities();
+- _lastUsedDeviceNameLength = node.display_name().length();
++ _lastUsedDeviceNameLength = node.unique_id().length();
+ _lastUsedDeviceName = static_cast<char*>(
+ realloc(_lastUsedDeviceName, _lastUsedDeviceNameLength + 1));
+ memcpy(_lastUsedDeviceName, deviceUniqueIdUTF8,
diff --git a/third_party/libwebrtc/moz-patch-stack/0108.patch b/third_party/libwebrtc/moz-patch-stack/0108.patch
new file mode 100644
index 0000000000..d3ccb0bbd2
--- /dev/null
+++ b/third_party/libwebrtc/moz-patch-stack/0108.patch
@@ -0,0 +1,27 @@
+From: Jan Grulich <jgrulich@redhat.com>
+Date: Tue, 5 Mar 2024 08:38:00 +0000
+Subject: Bug 1615282 - WebRTC backport: PipeWire capturer - set capturer as
+ failed when session is closed r=pehrsons,webrtc-reviewers
+
+This is a simple backport of an WebRTC upstream change.
+
+Upstream commit: 058bfe3ae37a7a245f9c8c6c03f4f7ac48fe179d
+
+Differential Revision: https://phabricator.services.mozilla.com/D202788
+Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/633149c5da9337f67b6659e5d5bead2233027460
+---
+ modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc b/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc
+index 40764de7ae..81caa9bd2d 100644
+--- a/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc
++++ b/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc
+@@ -112,6 +112,7 @@ void BaseCapturerPipeWire::OnScreenCastSessionClosed() {
+ if (!capturer_failed_) {
+ options_.screencast_stream()->StopScreenCastStream();
+ }
++ capturer_failed_ = true;
+ }
+
+ void BaseCapturerPipeWire::UpdateResolution(uint32_t width, uint32_t height) {
diff --git a/third_party/libwebrtc/moz-patch-stack/0109.patch b/third_party/libwebrtc/moz-patch-stack/0109.patch
new file mode 100644
index 0000000000..7fcb865db5
--- /dev/null
+++ b/third_party/libwebrtc/moz-patch-stack/0109.patch
@@ -0,0 +1,243 @@
+From: Jan Grulich <jgrulich@redhat.com>
+Date: Tue, 5 Mar 2024 08:38:00 +0000
+Subject: Bug 1876895 - WebRTC backport: Video capture PipeWire: add support
+ for DMABuf buffer type r=pehrsons,webrtc-reviewers
+
+This is a simple backport of an WebRTC upstream change.
+
+Upstream commit: 334e9133dcdecb5d00d991332e05c7b80ae26578
+
+Differential Revision: https://phabricator.services.mozilla.com/D202929
+Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/e915ae33e90a5e176f9196a67a201b86e22e498b
+---
+ .../linux/wayland/shared_screencast_stream.cc | 28 -------
+ modules/portal/pipewire_utils.h | 75 +++++++++++++++++++
+ .../linux/video_capture_pipewire.cc | 46 +++++++++---
+ 3 files changed, 110 insertions(+), 39 deletions(-)
+
+diff --git a/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc b/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc
+index 61c6957d27..473f913466 100644
+--- a/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc
++++ b/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc
+@@ -14,7 +14,6 @@
+ #include <libdrm/drm_fourcc.h>
+ #include <pipewire/pipewire.h>
+ #include <spa/param/video/format-utils.h>
+-#include <sys/mman.h>
+
+ #include <vector>
+
+@@ -49,33 +48,6 @@ constexpr int CursorMetaSize(int w, int h) {
+ constexpr PipeWireVersion kDmaBufModifierMinVersion = {0, 3, 33};
+ constexpr PipeWireVersion kDropSingleModifierMinVersion = {0, 3, 40};
+
+-class ScopedBuf {
+- public:
+- ScopedBuf() {}
+- ScopedBuf(uint8_t* map, int map_size, int fd)
+- : map_(map), map_size_(map_size), fd_(fd) {}
+- ~ScopedBuf() {
+- if (map_ != MAP_FAILED) {
+- munmap(map_, map_size_);
+- }
+- }
+-
+- explicit operator bool() { return map_ != MAP_FAILED; }
+-
+- void initialize(uint8_t* map, int map_size, int fd) {
+- map_ = map;
+- map_size_ = map_size;
+- fd_ = fd;
+- }
+-
+- uint8_t* get() { return map_; }
+-
+- protected:
+- uint8_t* map_ = static_cast<uint8_t*>(MAP_FAILED);
+- int map_size_;
+- int fd_;
+-};
+-
+ class SharedScreenCastStreamPrivate {
+ public:
+ SharedScreenCastStreamPrivate();
+diff --git a/modules/portal/pipewire_utils.h b/modules/portal/pipewire_utils.h
+index 8344a8cefb..c1327b85c9 100644
+--- a/modules/portal/pipewire_utils.h
++++ b/modules/portal/pipewire_utils.h
+@@ -11,6 +11,21 @@
+ #ifndef MODULES_PORTAL_PIPEWIRE_UTILS_H_
+ #define MODULES_PORTAL_PIPEWIRE_UTILS_H_
+
++#include <errno.h>
++#include <stdint.h>
++#include <sys/ioctl.h>
++#include <sys/mman.h>
++
++// static
++struct dma_buf_sync {
++ uint64_t flags;
++};
++#define DMA_BUF_SYNC_READ (1 << 0)
++#define DMA_BUF_SYNC_START (0 << 2)
++#define DMA_BUF_SYNC_END (1 << 2)
++#define DMA_BUF_BASE 'b'
++#define DMA_BUF_IOCTL_SYNC _IOW(DMA_BUF_BASE, 0, struct dma_buf_sync)
++
+ struct pw_thread_loop;
+
+ namespace webrtc {
+@@ -32,6 +47,66 @@ class PipeWireThreadLoopLock {
+ pw_thread_loop* const loop_;
+ };
+
++// We should synchronize DMA Buffer object access from CPU to avoid potential
++// cache incoherency and data loss.
++// See
++// https://01.org/linuxgraphics/gfx-docs/drm/driver-api/dma-buf.html#cpu-access-to-dma-buffer-objects
++static bool SyncDmaBuf(int fd, uint64_t start_or_end) {
++ struct dma_buf_sync sync = {0};
++
++ sync.flags = start_or_end | DMA_BUF_SYNC_READ;
++
++ while (true) {
++ int ret;
++ ret = ioctl(fd, DMA_BUF_IOCTL_SYNC, &sync);
++ if (ret == -1 && errno == EINTR) {
++ continue;
++ } else if (ret == -1) {
++ return false;
++ } else {
++ break;
++ }
++ }
++
++ return true;
++}
++
++class ScopedBuf {
++ public:
++ ScopedBuf() {}
++ ScopedBuf(uint8_t* map, int map_size, int fd, bool is_dma_buf = false)
++ : map_(map), map_size_(map_size), fd_(fd), is_dma_buf_(is_dma_buf) {}
++ ~ScopedBuf() {
++ if (map_ != MAP_FAILED) {
++ if (is_dma_buf_) {
++ SyncDmaBuf(fd_, DMA_BUF_SYNC_END);
++ }
++ munmap(map_, map_size_);
++ }
++ }
++
++ explicit operator bool() { return map_ != MAP_FAILED; }
++
++ void initialize(uint8_t* map, int map_size, int fd, bool is_dma_buf = false) {
++ map_ = map;
++ map_size_ = map_size;
++ is_dma_buf_ = is_dma_buf;
++ fd_ = fd;
++
++ if (is_dma_buf_) {
++ SyncDmaBuf(fd_, DMA_BUF_SYNC_START);
++ }
++ }
++
++ uint8_t* get() { return map_; }
++
++ protected:
++ uint8_t* map_ = static_cast<uint8_t*>(MAP_FAILED);
++ int map_size_;
++ int fd_;
++ bool is_dma_buf_;
++};
++
+ } // namespace webrtc
+
+ #endif // MODULES_PORTAL_PIPEWIRE_UTILS_H_
+diff --git a/modules/video_capture/linux/video_capture_pipewire.cc b/modules/video_capture/linux/video_capture_pipewire.cc
+index 8af483636a..319824d3c5 100644
+--- a/modules/video_capture/linux/video_capture_pipewire.cc
++++ b/modules/video_capture/linux/video_capture_pipewire.cc
+@@ -178,8 +178,7 @@ int32_t VideoCaptureModulePipeWire::StartCapture(
+ int res = pw_stream_connect(
+ stream_, PW_DIRECTION_INPUT, node_id_,
+ static_cast<enum pw_stream_flags>(PW_STREAM_FLAG_AUTOCONNECT |
+- PW_STREAM_FLAG_DONT_RECONNECT |
+- PW_STREAM_FLAG_MAP_BUFFERS),
++ PW_STREAM_FLAG_DONT_RECONNECT),
+ params.data(), params.size());
+ if (res != 0) {
+ RTC_LOG(LS_ERROR) << "Could not connect to camera stream: "
+@@ -312,11 +311,11 @@ void VideoCaptureModulePipeWire::OnFormatChanged(const struct spa_pod* format) {
+ 0);
+ }
+
++ const int buffer_types =
++ (1 << SPA_DATA_DmaBuf) | (1 << SPA_DATA_MemFd) | (1 << SPA_DATA_MemPtr);
+ spa_pod_builder_add(
+ &builder, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 1, 32),
+- SPA_PARAM_BUFFERS_dataType,
+- SPA_POD_CHOICE_FLAGS_Int((1 << SPA_DATA_MemFd) | (1 << SPA_DATA_MemPtr)),
+- 0);
++ SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(buffer_types), 0);
+ params.push_back(
+ static_cast<spa_pod*>(spa_pod_builder_pop(&builder, &frame)));
+
+@@ -384,14 +383,15 @@ void VideoCaptureModulePipeWire::ProcessBuffers() {
+ RTC_CHECK_RUNS_SERIALIZED(&capture_checker_);
+
+ while (pw_buffer* buffer = pw_stream_dequeue_buffer(stream_)) {
++ spa_buffer* spaBuffer = buffer->buffer;
+ struct spa_meta_header* h;
+ h = static_cast<struct spa_meta_header*>(
+- spa_buffer_find_meta_data(buffer->buffer, SPA_META_Header, sizeof(*h)));
++ spa_buffer_find_meta_data(spaBuffer, SPA_META_Header, sizeof(*h)));
+
+ struct spa_meta_videotransform* videotransform;
+ videotransform =
+ static_cast<struct spa_meta_videotransform*>(spa_buffer_find_meta_data(
+- buffer->buffer, SPA_META_VideoTransform, sizeof(*videotransform)));
++ spaBuffer, SPA_META_VideoTransform, sizeof(*videotransform)));
+ if (videotransform) {
+ VideoRotation rotation =
+ VideorotationFromPipeWireTransform(videotransform->transform);
+@@ -401,11 +401,35 @@ void VideoCaptureModulePipeWire::ProcessBuffers() {
+
+ if (h->flags & SPA_META_HEADER_FLAG_CORRUPTED) {
+ RTC_LOG(LS_INFO) << "Dropping corruped frame.";
+- } else {
+- IncomingFrame(static_cast<unsigned char*>(buffer->buffer->datas[0].data),
+- buffer->buffer->datas[0].chunk->size,
+- configured_capability_);
++ pw_stream_queue_buffer(stream_, buffer);
++ continue;
++ }
++
++ if (spaBuffer->datas[0].type == SPA_DATA_DmaBuf ||
++ spaBuffer->datas[0].type == SPA_DATA_MemFd) {
++ ScopedBuf frame;
++ frame.initialize(
++ static_cast<uint8_t*>(
++ mmap(nullptr,
++ spaBuffer->datas[0].maxsize + spaBuffer->datas[0].mapoffset,
++ PROT_READ, MAP_PRIVATE, spaBuffer->datas[0].fd, 0)),
++ spaBuffer->datas[0].maxsize + spaBuffer->datas[0].mapoffset,
++ spaBuffer->datas[0].fd, spaBuffer->datas[0].type == SPA_DATA_DmaBuf);
++
++ if (!frame) {
++ RTC_LOG(LS_ERROR) << "Failed to mmap the memory: "
++ << std::strerror(errno);
++ return;
++ }
++
++ IncomingFrame(
++ SPA_MEMBER(frame.get(), spaBuffer->datas[0].mapoffset, uint8_t),
++ spaBuffer->datas[0].chunk->size, configured_capability_);
++ } else { // SPA_DATA_MemPtr
++ IncomingFrame(static_cast<uint8_t*>(spaBuffer->datas[0].data),
++ spaBuffer->datas[0].chunk->size, configured_capability_);
+ }
++
+ pw_stream_queue_buffer(stream_, buffer);
+ }
+ }
diff --git a/third_party/libwebrtc/moz-patch-stack/0110.patch b/third_party/libwebrtc/moz-patch-stack/0110.patch
new file mode 100644
index 0000000000..282205c5e8
--- /dev/null
+++ b/third_party/libwebrtc/moz-patch-stack/0110.patch
@@ -0,0 +1,207 @@
+From: Michael Froman <mjfroman@mac.com>
+Date: Wed, 20 Mar 2024 11:32:05 -0500
+Subject: Bug 1886497 - Cherry-pick upstream libwebrtc commit de3c726121
+ r?dbaker
+
+Upstream commit: https://webrtc.googlesource.com/src/+/de3c726121bd097e79a8f1aa43df48aab7e2237f
+ Update to vpython 3.11 and remove .vpython (v2.x)
+
+ Bug: b/310806212
+ Change-Id: I7fdb12ee4f83410bed9358e7249e4601e773056f
+ Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/335641
+ Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
+ Commit-Queue: Christoffer Dewerin <jansson@google.com>
+ Cr-Commit-Position: refs/heads/main@{#41607}
+---
+ .vpython | 76 -------------------------------------------------------
+ .vpython3 | 38 ++++++++++++++--------------
+ 2 files changed, 19 insertions(+), 95 deletions(-)
+ delete mode 100644 .vpython
+
+diff --git a/.vpython b/.vpython
+deleted file mode 100644
+index d226875f02..0000000000
+--- a/.vpython
++++ /dev/null
+@@ -1,76 +0,0 @@
+-# This is a vpython "spec" file.
+-#
+-# It describes patterns for python wheel dependencies of the python scripts in
+-# the chromium repo, particularly for dependencies that have compiled components
+-# (since pure-python dependencies can be easily vendored into third_party).
+-#
+-# When vpython is invoked, it finds this file and builds a python VirtualEnv,
+-# containing all of the dependencies described in this file, fetching them from
+-# CIPD (the "Chrome Infrastructure Package Deployer" service). Unlike `pip`,
+-# this never requires the end-user machine to have a working python extension
+-# compilation environment. All of these packages are built using:
+-# https://chromium.googlesource.com/infra/infra/+/main/infra/tools/dockerbuild/
+-#
+-# All python scripts in the repo share this same spec, to avoid dependency
+-# fragmentation.
+-#
+-# If you have depot_tools installed in your $PATH, you can invoke python scripts
+-# in this repo by running them as you normally would run them, except
+-# substituting `vpython` instead of `python` on the command line, e.g.:
+-# vpython path/to/script.py some --arguments
+-#
+-# Read more about `vpython` and how to modify this file here:
+-# https://chromium.googlesource.com/infra/infra/+/main/doc/users/vpython.md
+-
+-python_version: "2.7"
+-
+-# Used by:
+-# third_party/catapult
+-wheel: <
+- name: "infra/python/wheels/psutil/${platform}_${py_python}_${py_abi}"
+- version: "version:5.2.2"
+->
+-
+-# Used by tools_webrtc/perf/process_perf_results.py.
+-wheel: <
+- name: "infra/python/wheels/httplib2-py2_py3"
+- version: "version:0.10.3"
+->
+-
+-# Used by:
+-# build/toolchain/win
+-wheel: <
+- name: "infra/python/wheels/pypiwin32/${vpython_platform}"
+- version: "version:219"
+- match_tag: <
+- platform: "win32"
+- >
+- match_tag: <
+- platform: "win_amd64"
+- >
+->
+-
+-wheel: <
+- name: "infra/python/wheels/six-py2_py3"
+- version: "version:1.15.0"
+->
+-wheel: <
+- name: "infra/python/wheels/pbr-py2_py3"
+- version: "version:3.0.0"
+->
+-wheel: <
+- name: "infra/python/wheels/funcsigs-py2_py3"
+- version: "version:1.0.2"
+->
+-wheel: <
+- name: "infra/python/wheels/mock-py2_py3"
+- version: "version:2.0.0"
+->
+-wheel: <
+- name: "infra/python/wheels/protobuf-py2_py3"
+- version: "version:3.13.0"
+->
+-wheel: <
+- name: "infra/python/wheels/requests-py2_py3"
+- version: "version:2.13.0"
+->
+diff --git a/.vpython3 b/.vpython3
+index 3f571df261..2be8efaa0a 100644
+--- a/.vpython3
++++ b/.vpython3
+@@ -22,24 +22,24 @@
+ # Read more about `vpython` and how to modify this file here:
+ # https://chromium.googlesource.com/infra/infra/+/main/doc/users/vpython.md
+
+-python_version: "3.8"
++python_version: "3.11"
+
+ # Used by:
+ # third_party/catapult
+ wheel: <
+ name: "infra/python/wheels/psutil/${vpython_platform}"
+- version: "version:5.8.0.chromium.2"
++ version: "version:5.8.0.chromium.3"
+ >
+
+ # Used by tools_webrtc/perf/process_perf_results.py.
+ wheel: <
+ name: "infra/python/wheels/httplib2-py3"
+- version: "version:0.19.1"
++ version: "version:0.22.0"
+ >
+
+ wheel: <
+- name: "infra/python/wheels/pyparsing-py2_py3"
+- version: "version:2.4.7"
++ name: "infra/python/wheels/pyparsing-py3"
++ version: "version:3.1.1"
+ >
+
+
+@@ -47,7 +47,7 @@ wheel: <
+ # build/toolchain/win
+ wheel: <
+ name: "infra/python/wheels/pywin32/${vpython_platform}"
+- version: "version:300"
++ version: "version:306"
+ match_tag: <
+ platform: "win32"
+ >
+@@ -59,48 +59,48 @@ wheel: <
+ # GRPC used by iOS test.
+ wheel: <
+ name: "infra/python/wheels/grpcio/${vpython_platform}"
+- version: "version:1.44.0"
++ version: "version:1.57.0"
+ >
+
+ wheel: <
+ name: "infra/python/wheels/six-py2_py3"
+- version: "version:1.15.0"
++ version: "version:1.16.0"
+ >
+ wheel: <
+ name: "infra/python/wheels/pbr-py2_py3"
+- version: "version:3.0.0"
++ version: "version:5.9.0"
+ >
+ wheel: <
+ name: "infra/python/wheels/funcsigs-py2_py3"
+ version: "version:1.0.2"
+ >
+ wheel: <
+- name: "infra/python/wheels/mock-py2_py3"
+- version: "version:2.0.0"
++ name: "infra/python/wheels/mock-py3"
++ version: "version:4.0.3"
+ >
+ wheel: <
+ name: "infra/python/wheels/protobuf-py3"
+- version: "version:3.20.0"
++ version: "version:4.25.1"
+ >
+ wheel: <
+ name: "infra/python/wheels/requests-py3"
+ version: "version:2.31.0"
+ >
+ wheel: <
+- name: "infra/python/wheels/idna-py2_py3"
+- version: "version:2.8"
++ name: "infra/python/wheels/idna-py3"
++ version: "version:3.4"
+ >
+ wheel: <
+- name: "infra/python/wheels/urllib3-py2_py3"
+- version: "version:1.26.6"
++ name: "infra/python/wheels/urllib3-py3"
++ version: "version:2.1.0"
+ >
+ wheel: <
+- name: "infra/python/wheels/certifi-py2_py3"
+- version: "version:2020.11.8"
++ name: "infra/python/wheels/certifi-py3"
++ version: "version:2023.11.17"
+ >
+ wheel: <
+ name: "infra/python/wheels/charset_normalizer-py3"
+- version: "version:2.0.4"
++ version: "version:3.3.2"
+ >
+ wheel: <
+ name: "infra/python/wheels/brotli/${vpython_platform}"
diff --git a/third_party/libwebrtc/moz-patch-stack/058bfe3ae3.no-op-cherry-pick-msg b/third_party/libwebrtc/moz-patch-stack/058bfe3ae3.no-op-cherry-pick-msg
deleted file mode 100644
index 40b3e5620d..0000000000
--- a/third_party/libwebrtc/moz-patch-stack/058bfe3ae3.no-op-cherry-pick-msg
+++ /dev/null
@@ -1 +0,0 @@
-We cherry-picked this in bug 1615282.
diff --git a/third_party/libwebrtc/moz-patch-stack/16ac10d9f7.no-op-cherry-pick-msg b/third_party/libwebrtc/moz-patch-stack/16ac10d9f7.no-op-cherry-pick-msg
deleted file mode 100644
index 6e1890cfe7..0000000000
--- a/third_party/libwebrtc/moz-patch-stack/16ac10d9f7.no-op-cherry-pick-msg
+++ /dev/null
@@ -1 +0,0 @@
-We cherry-picked this in bug 1882438.
diff --git a/third_party/libwebrtc/moz-patch-stack/334e9133dc.no-op-cherry-pick-msg b/third_party/libwebrtc/moz-patch-stack/334e9133dc.no-op-cherry-pick-msg
deleted file mode 100644
index 28f8bcdc9d..0000000000
--- a/third_party/libwebrtc/moz-patch-stack/334e9133dc.no-op-cherry-pick-msg
+++ /dev/null
@@ -1 +0,0 @@
-We cherry-picked this in bug 1876895.
diff --git a/third_party/libwebrtc/moz-patch-stack/6a992129fb.no-op-cherry-pick-msg b/third_party/libwebrtc/moz-patch-stack/6a992129fb.no-op-cherry-pick-msg
deleted file mode 100644
index c77102b8ca..0000000000
--- a/third_party/libwebrtc/moz-patch-stack/6a992129fb.no-op-cherry-pick-msg
+++ /dev/null
@@ -1 +0,0 @@
-We cherry-picked this in bug 1871981
diff --git a/third_party/libwebrtc/moz-patch-stack/de3c726121.no-op-cherry-pick-msg b/third_party/libwebrtc/moz-patch-stack/de3c726121.no-op-cherry-pick-msg
new file mode 100644
index 0000000000..f3ae571b0c
--- /dev/null
+++ b/third_party/libwebrtc/moz-patch-stack/de3c726121.no-op-cherry-pick-msg
@@ -0,0 +1 @@
+We cherry-picked this in bug 1886497
diff --git a/third_party/libwebrtc/moz.build b/third_party/libwebrtc/moz.build
index ad1adce757..59472bdc9b 100644
--- a/third_party/libwebrtc/moz.build
+++ b/third_party/libwebrtc/moz.build
@@ -38,10 +38,10 @@ DIRS += [
"/third_party/libwebrtc/api/audio_options_api_gn",
"/third_party/libwebrtc/api/bitrate_allocation_gn",
"/third_party/libwebrtc/api/call_api_gn",
- "/third_party/libwebrtc/api/callfactory_api_gn",
"/third_party/libwebrtc/api/crypto/frame_decryptor_interface_gn",
"/third_party/libwebrtc/api/crypto/frame_encryptor_interface_gn",
"/third_party/libwebrtc/api/crypto/options_gn",
+ "/third_party/libwebrtc/api/environment/environment_factory_gn",
"/third_party/libwebrtc/api/environment/environment_gn",
"/third_party/libwebrtc/api/fec_controller_api_gn",
"/third_party/libwebrtc/api/field_trials_registry_gn",
@@ -73,6 +73,7 @@ DIRS += [
"/third_party/libwebrtc/api/scoped_refptr_gn",
"/third_party/libwebrtc/api/sequence_checker_gn",
"/third_party/libwebrtc/api/simulated_network_api_gn",
+ "/third_party/libwebrtc/api/task_queue/default_task_queue_factory_gn",
"/third_party/libwebrtc/api/task_queue/pending_task_safety_flag_gn",
"/third_party/libwebrtc/api/task_queue/task_queue_gn",
"/third_party/libwebrtc/api/transport/bitrate_settings_gn",
diff --git a/third_party/libwebrtc/net/dcsctp/public/dcsctp_socket.h b/third_party/libwebrtc/net/dcsctp/public/dcsctp_socket.h
index d0a81eaeb2..9989ae8d43 100644
--- a/third_party/libwebrtc/net/dcsctp/public/dcsctp_socket.h
+++ b/third_party/libwebrtc/net/dcsctp/public/dcsctp_socket.h
@@ -13,6 +13,7 @@
#include <cstdint>
#include <memory>
#include <utility>
+#include <vector>
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
@@ -577,6 +578,16 @@ class DcSctpSocketInterface {
virtual SendStatus Send(DcSctpMessage message,
const SendOptions& send_options) = 0;
+ // Sends the messages `messages` using the provided send options.
+ // Sending a message is an asynchronous operation, and the `OnError` callback
+ // may be invoked to indicate any errors in sending the message.
+ //
+ // This has identical semantics to Send, except that it may coalesce many
+ // messages into a single SCTP packet if they would fit.
+ virtual std::vector<SendStatus> SendMany(
+ rtc::ArrayView<DcSctpMessage> messages,
+ const SendOptions& send_options) = 0;
+
// Resetting streams is an asynchronous operation and the results will
// be notified using `DcSctpSocketCallbacks::OnStreamsResetDone()` on success
// and `DcSctpSocketCallbacks::OnStreamsResetFailed()` on failure. Note that
diff --git a/third_party/libwebrtc/net/dcsctp/public/mock_dcsctp_socket.h b/third_party/libwebrtc/net/dcsctp/public/mock_dcsctp_socket.h
index 0fd572bd94..c71c3ae16f 100644
--- a/third_party/libwebrtc/net/dcsctp/public/mock_dcsctp_socket.h
+++ b/third_party/libwebrtc/net/dcsctp/public/mock_dcsctp_socket.h
@@ -10,6 +10,8 @@
#ifndef NET_DCSCTP_PUBLIC_MOCK_DCSCTP_SOCKET_H_
#define NET_DCSCTP_PUBLIC_MOCK_DCSCTP_SOCKET_H_
+#include <vector>
+
#include "net/dcsctp/public/dcsctp_socket.h"
#include "test/gmock.h"
@@ -56,6 +58,12 @@ class MockDcSctpSocket : public DcSctpSocketInterface {
(DcSctpMessage message, const SendOptions& send_options),
(override));
+ MOCK_METHOD(std::vector<SendStatus>,
+ SendMany,
+ (rtc::ArrayView<DcSctpMessage> messages,
+ const SendOptions& send_options),
+ (override));
+
MOCK_METHOD(ResetStreamsStatus,
ResetStreams,
(rtc::ArrayView<const StreamID> outgoing_streams),
diff --git a/third_party/libwebrtc/net/dcsctp/rx/traditional_reassembly_streams.cc b/third_party/libwebrtc/net/dcsctp/rx/traditional_reassembly_streams.cc
index dce6c90131..c94691f0db 100644
--- a/third_party/libwebrtc/net/dcsctp/rx/traditional_reassembly_streams.cc
+++ b/third_party/libwebrtc/net/dcsctp/rx/traditional_reassembly_streams.cc
@@ -86,6 +86,11 @@ TraditionalReassemblyStreams::TraditionalReassemblyStreams(
int TraditionalReassemblyStreams::UnorderedStream::Add(UnwrappedTSN tsn,
Data data) {
+ if (data.is_beginning && data.is_end) {
+ // Fastpath for already assembled chunks.
+ AssembleMessage(tsn, std::move(data));
+ return 0;
+ }
int queued_bytes = data.size();
auto [it, inserted] = chunks_.emplace(tsn, std::move(data));
if (!inserted) {
@@ -124,12 +129,7 @@ size_t TraditionalReassemblyStreams::StreamBase::AssembleMessage(
if (count == 1) {
// Fast path - zero-copy
- const Data& data = start->second;
- size_t payload_size = start->second.size();
- UnwrappedTSN tsns[1] = {start->first};
- DcSctpMessage message(data.stream_id, data.ppid, std::move(data.payload));
- parent_.on_assembled_message_(tsns, std::move(message));
- return payload_size;
+ return AssembleMessage(start->first, std::move(start->second));
}
// Slow path - will need to concatenate the payload.
@@ -155,6 +155,17 @@ size_t TraditionalReassemblyStreams::StreamBase::AssembleMessage(
return payload_size;
}
+size_t TraditionalReassemblyStreams::StreamBase::AssembleMessage(
+ UnwrappedTSN tsn,
+ Data data) {
+ // Fast path - zero-copy
+ size_t payload_size = data.size();
+ UnwrappedTSN tsns[1] = {tsn};
+ DcSctpMessage message(data.stream_id, data.ppid, std::move(data.payload));
+ parent_.on_assembled_message_(tsns, std::move(message));
+ return payload_size;
+}
+
size_t TraditionalReassemblyStreams::UnorderedStream::EraseTo(
UnwrappedTSN tsn) {
auto end_iter = chunks_.upper_bound(tsn);
@@ -202,20 +213,40 @@ size_t TraditionalReassemblyStreams::OrderedStream::TryToAssembleMessages() {
return assembled_bytes;
}
+size_t
+TraditionalReassemblyStreams::OrderedStream::TryToAssembleMessagesFastpath(
+ UnwrappedSSN ssn,
+ UnwrappedTSN tsn,
+ Data data) {
+ RTC_DCHECK(ssn == next_ssn_);
+ size_t assembled_bytes = 0;
+ if (data.is_beginning && data.is_end) {
+ assembled_bytes += AssembleMessage(tsn, std::move(data));
+ next_ssn_.Increment();
+ } else {
+ size_t queued_bytes = data.size();
+ auto [iter, inserted] = chunks_by_ssn_[ssn].emplace(tsn, std::move(data));
+ if (!inserted) {
+ // Not actually assembled, but deduplicated meaning queued size doesn't
+ // include this message.
+ return queued_bytes;
+ }
+ }
+ return assembled_bytes + TryToAssembleMessages();
+}
+
int TraditionalReassemblyStreams::OrderedStream::Add(UnwrappedTSN tsn,
Data data) {
int queued_bytes = data.size();
-
UnwrappedSSN ssn = ssn_unwrapper_.Unwrap(data.ssn);
- auto [unused, inserted] = chunks_by_ssn_[ssn].emplace(tsn, std::move(data));
+ if (ssn == next_ssn_) {
+ return queued_bytes -
+ TryToAssembleMessagesFastpath(ssn, tsn, std::move(data));
+ }
+ auto [iter, inserted] = chunks_by_ssn_[ssn].emplace(tsn, std::move(data));
if (!inserted) {
return 0;
}
-
- if (ssn == next_ssn_) {
- queued_bytes -= TryToAssembleMessages();
- }
-
return queued_bytes;
}
diff --git a/third_party/libwebrtc/net/dcsctp/rx/traditional_reassembly_streams.h b/third_party/libwebrtc/net/dcsctp/rx/traditional_reassembly_streams.h
index d355c599ae..9214a9bc9a 100644
--- a/third_party/libwebrtc/net/dcsctp/rx/traditional_reassembly_streams.h
+++ b/third_party/libwebrtc/net/dcsctp/rx/traditional_reassembly_streams.h
@@ -55,6 +55,7 @@ class TraditionalReassemblyStreams : public ReassemblyStreams {
: parent_(*parent) {}
size_t AssembleMessage(ChunkMap::iterator start, ChunkMap::iterator end);
+ size_t AssembleMessage(UnwrappedTSN tsn, Data data);
TraditionalReassemblyStreams& parent_;
};
@@ -101,6 +102,11 @@ class TraditionalReassemblyStreams : public ReassemblyStreams {
// Returns the number of bytes assembled if a message was assembled.
size_t TryToAssembleMessage();
size_t TryToAssembleMessages();
+ // Same as above but when inserting the first complete message avoid
+ // insertion into the map.
+ size_t TryToAssembleMessagesFastpath(UnwrappedSSN ssn,
+ UnwrappedTSN tsn,
+ Data data);
// This must be an ordered container to be able to iterate in SSN order.
std::map<UnwrappedSSN, ChunkMap> chunks_by_ssn_;
UnwrappedSSN::Unwrapper ssn_unwrapper_;
diff --git a/third_party/libwebrtc/net/dcsctp/socket/BUILD.gn b/third_party/libwebrtc/net/dcsctp/socket/BUILD.gn
index 04f61e5b72..406593e23b 100644
--- a/third_party/libwebrtc/net/dcsctp/socket/BUILD.gn
+++ b/third_party/libwebrtc/net/dcsctp/socket/BUILD.gn
@@ -140,7 +140,6 @@ rtc_library("dcsctp_socket") {
"../../../api:make_ref_counted",
"../../../api:refcountedbase",
"../../../api:scoped_refptr",
- "../../../api:sequence_checker",
"../../../api/task_queue:task_queue",
"../../../rtc_base:checks",
"../../../rtc_base:logging",
@@ -178,6 +177,7 @@ rtc_library("dcsctp_socket") {
"//third_party/abseil-cpp/absl/memory",
"//third_party/abseil-cpp/absl/strings",
"//third_party/abseil-cpp/absl/types:optional",
+ "//third_party/abseil-cpp/absl/types:variant",
]
}
diff --git a/third_party/libwebrtc/net/dcsctp/socket/callback_deferrer.cc b/third_party/libwebrtc/net/dcsctp/socket/callback_deferrer.cc
index 0a24020167..549a592b8d 100644
--- a/third_party/libwebrtc/net/dcsctp/socket/callback_deferrer.cc
+++ b/third_party/libwebrtc/net/dcsctp/socket/callback_deferrer.cc
@@ -12,31 +12,6 @@
#include "api/make_ref_counted.h"
namespace dcsctp {
-namespace {
-// A wrapper around the move-only DcSctpMessage, to let it be captured in a
-// lambda.
-class MessageDeliverer {
- public:
- explicit MessageDeliverer(DcSctpMessage&& message)
- : state_(rtc::make_ref_counted<State>(std::move(message))) {}
-
- void Deliver(DcSctpSocketCallbacks& c) {
- // Really ensure that it's only called once.
- RTC_DCHECK(!state_->has_delivered);
- state_->has_delivered = true;
- c.OnMessageReceived(std::move(state_->message));
- }
-
- private:
- struct State : public webrtc::RefCountInterface {
- explicit State(DcSctpMessage&& m)
- : has_delivered(false), message(std::move(m)) {}
- bool has_delivered;
- DcSctpMessage message;
- };
- rtc::scoped_refptr<State> state_;
-};
-} // namespace
void CallbackDeferrer::Prepare() {
RTC_DCHECK(!prepared_);
@@ -48,12 +23,16 @@ void CallbackDeferrer::TriggerDeferred() {
// callback, and that might result in adding new callbacks to this instance,
// and the vector can't be modified while iterated on.
RTC_DCHECK(prepared_);
- std::vector<std::function<void(DcSctpSocketCallbacks & cb)>> deferred;
- deferred.swap(deferred_);
prepared_ = false;
-
- for (auto& cb : deferred) {
- cb(underlying_);
+ if (deferred_.empty()) {
+ return;
+ }
+ std::vector<std::pair<Callback, CallbackData>> deferred;
+ // Reserve a small buffer to prevent too much reallocation on growth.
+ deferred.reserve(8);
+ deferred.swap(deferred_);
+ for (auto& [cb, data] : deferred) {
+ cb(std::move(data), underlying_);
}
}
@@ -84,40 +63,57 @@ uint32_t CallbackDeferrer::GetRandomInt(uint32_t low, uint32_t high) {
void CallbackDeferrer::OnMessageReceived(DcSctpMessage message) {
RTC_DCHECK(prepared_);
deferred_.emplace_back(
- [deliverer = MessageDeliverer(std::move(message))](
- DcSctpSocketCallbacks& cb) mutable { deliverer.Deliver(cb); });
+ +[](CallbackData data, DcSctpSocketCallbacks& cb) {
+ return cb.OnMessageReceived(absl::get<DcSctpMessage>(std::move(data)));
+ },
+ std::move(message));
}
void CallbackDeferrer::OnError(ErrorKind error, absl::string_view message) {
RTC_DCHECK(prepared_);
deferred_.emplace_back(
- [error, message = std::string(message)](DcSctpSocketCallbacks& cb) {
- cb.OnError(error, message);
- });
+ +[](CallbackData data, DcSctpSocketCallbacks& cb) {
+ Error error = absl::get<Error>(std::move(data));
+ return cb.OnError(error.error, error.message);
+ },
+ Error{error, std::string(message)});
}
void CallbackDeferrer::OnAborted(ErrorKind error, absl::string_view message) {
RTC_DCHECK(prepared_);
deferred_.emplace_back(
- [error, message = std::string(message)](DcSctpSocketCallbacks& cb) {
- cb.OnAborted(error, message);
- });
+ +[](CallbackData data, DcSctpSocketCallbacks& cb) {
+ Error error = absl::get<Error>(std::move(data));
+ return cb.OnAborted(error.error, error.message);
+ },
+ Error{error, std::string(message)});
}
void CallbackDeferrer::OnConnected() {
RTC_DCHECK(prepared_);
- deferred_.emplace_back([](DcSctpSocketCallbacks& cb) { cb.OnConnected(); });
+ deferred_.emplace_back(
+ +[](CallbackData data, DcSctpSocketCallbacks& cb) {
+ return cb.OnConnected();
+ },
+ absl::monostate{});
}
void CallbackDeferrer::OnClosed() {
RTC_DCHECK(prepared_);
- deferred_.emplace_back([](DcSctpSocketCallbacks& cb) { cb.OnClosed(); });
+ deferred_.emplace_back(
+ +[](CallbackData data, DcSctpSocketCallbacks& cb) {
+ return cb.OnClosed();
+ },
+ absl::monostate{});
}
void CallbackDeferrer::OnConnectionRestarted() {
RTC_DCHECK(prepared_);
deferred_.emplace_back(
- [](DcSctpSocketCallbacks& cb) { cb.OnConnectionRestarted(); });
+ +[](CallbackData data, DcSctpSocketCallbacks& cb) {
+ return cb.OnConnectionRestarted();
+ },
+ absl::monostate{});
}
void CallbackDeferrer::OnStreamsResetFailed(
@@ -125,42 +121,53 @@ void CallbackDeferrer::OnStreamsResetFailed(
absl::string_view reason) {
RTC_DCHECK(prepared_);
deferred_.emplace_back(
- [streams = std::vector<StreamID>(outgoing_streams.begin(),
- outgoing_streams.end()),
- reason = std::string(reason)](DcSctpSocketCallbacks& cb) {
- cb.OnStreamsResetFailed(streams, reason);
- });
+ +[](CallbackData data, DcSctpSocketCallbacks& cb) {
+ StreamReset stream_reset = absl::get<StreamReset>(std::move(data));
+ return cb.OnStreamsResetFailed(stream_reset.streams,
+ stream_reset.message);
+ },
+ StreamReset{{outgoing_streams.begin(), outgoing_streams.end()},
+ std::string(reason)});
}
void CallbackDeferrer::OnStreamsResetPerformed(
rtc::ArrayView<const StreamID> outgoing_streams) {
RTC_DCHECK(prepared_);
deferred_.emplace_back(
- [streams = std::vector<StreamID>(outgoing_streams.begin(),
- outgoing_streams.end())](
- DcSctpSocketCallbacks& cb) { cb.OnStreamsResetPerformed(streams); });
+ +[](CallbackData data, DcSctpSocketCallbacks& cb) {
+ StreamReset stream_reset = absl::get<StreamReset>(std::move(data));
+ return cb.OnStreamsResetPerformed(stream_reset.streams);
+ },
+ StreamReset{{outgoing_streams.begin(), outgoing_streams.end()}});
}
void CallbackDeferrer::OnIncomingStreamsReset(
rtc::ArrayView<const StreamID> incoming_streams) {
RTC_DCHECK(prepared_);
deferred_.emplace_back(
- [streams = std::vector<StreamID>(incoming_streams.begin(),
- incoming_streams.end())](
- DcSctpSocketCallbacks& cb) { cb.OnIncomingStreamsReset(streams); });
+ +[](CallbackData data, DcSctpSocketCallbacks& cb) {
+ StreamReset stream_reset = absl::get<StreamReset>(std::move(data));
+ return cb.OnIncomingStreamsReset(stream_reset.streams);
+ },
+ StreamReset{{incoming_streams.begin(), incoming_streams.end()}});
}
void CallbackDeferrer::OnBufferedAmountLow(StreamID stream_id) {
RTC_DCHECK(prepared_);
- deferred_.emplace_back([stream_id](DcSctpSocketCallbacks& cb) {
- cb.OnBufferedAmountLow(stream_id);
- });
+ deferred_.emplace_back(
+ +[](CallbackData data, DcSctpSocketCallbacks& cb) {
+ return cb.OnBufferedAmountLow(absl::get<StreamID>(std::move(data)));
+ },
+ stream_id);
}
void CallbackDeferrer::OnTotalBufferedAmountLow() {
RTC_DCHECK(prepared_);
deferred_.emplace_back(
- [](DcSctpSocketCallbacks& cb) { cb.OnTotalBufferedAmountLow(); });
+ +[](CallbackData data, DcSctpSocketCallbacks& cb) {
+ return cb.OnTotalBufferedAmountLow();
+ },
+ absl::monostate{});
}
void CallbackDeferrer::OnLifecycleMessageExpired(LifecycleId lifecycle_id,
diff --git a/third_party/libwebrtc/net/dcsctp/socket/callback_deferrer.h b/third_party/libwebrtc/net/dcsctp/socket/callback_deferrer.h
index 6659e87155..9d9fbcef06 100644
--- a/third_party/libwebrtc/net/dcsctp/socket/callback_deferrer.h
+++ b/third_party/libwebrtc/net/dcsctp/socket/callback_deferrer.h
@@ -18,6 +18,7 @@
#include <vector>
#include "absl/strings/string_view.h"
+#include "absl/types/variant.h"
#include "api/array_view.h"
#include "api/ref_counted_base.h"
#include "api/scoped_refptr.h"
@@ -89,12 +90,26 @@ class CallbackDeferrer : public DcSctpSocketCallbacks {
void OnLifecycleEnd(LifecycleId lifecycle_id) override;
private:
+ struct Error {
+ ErrorKind error;
+ std::string message;
+ };
+ struct StreamReset {
+ std::vector<StreamID> streams;
+ std::string message;
+ };
+ // Use a pre-sized variant for storage to avoid double heap allocation. This
+ // variant can hold all cases of stored data.
+ using CallbackData = absl::
+ variant<absl::monostate, DcSctpMessage, Error, StreamReset, StreamID>;
+ using Callback = void (*)(CallbackData, DcSctpSocketCallbacks&);
+
void Prepare();
void TriggerDeferred();
DcSctpSocketCallbacks& underlying_;
bool prepared_ = false;
- std::vector<std::function<void(DcSctpSocketCallbacks& cb)>> deferred_;
+ std::vector<std::pair<Callback, CallbackData>> deferred_;
};
} // namespace dcsctp
diff --git a/third_party/libwebrtc/net/dcsctp/socket/dcsctp_socket.cc b/third_party/libwebrtc/net/dcsctp/socket/dcsctp_socket.cc
index f0f9590943..98cd34a111 100644
--- a/third_party/libwebrtc/net/dcsctp/socket/dcsctp_socket.cc
+++ b/third_party/libwebrtc/net/dcsctp/socket/dcsctp_socket.cc
@@ -296,20 +296,14 @@ void DcSctpSocket::SendInit() {
packet_sender_.Send(b, /*write_checksum=*/true);
}
-void DcSctpSocket::MakeConnectionParameters() {
- VerificationTag new_verification_tag(
- callbacks_.GetRandomInt(kMinVerificationTag, kMaxVerificationTag));
- TSN initial_tsn(callbacks_.GetRandomInt(kMinInitialTsn, kMaxInitialTsn));
- connect_params_.initial_tsn = initial_tsn;
- connect_params_.verification_tag = new_verification_tag;
-}
-
void DcSctpSocket::Connect() {
- RTC_DCHECK_RUN_ON(&thread_checker_);
CallbackDeferrer::ScopedDeferrer deferrer(callbacks_);
if (state_ == State::kClosed) {
- MakeConnectionParameters();
+ connect_params_.initial_tsn =
+ TSN(callbacks_.GetRandomInt(kMinInitialTsn, kMaxInitialTsn));
+ connect_params_.verification_tag = VerificationTag(
+ callbacks_.GetRandomInt(kMinVerificationTag, kMaxVerificationTag));
RTC_DLOG(LS_INFO)
<< log_prefix()
<< rtc::StringFormat(
@@ -348,7 +342,6 @@ void DcSctpSocket::CreateTransmissionControlBlock(
}
void DcSctpSocket::RestoreFromState(const DcSctpSocketHandoverState& state) {
- RTC_DCHECK_RUN_ON(&thread_checker_);
CallbackDeferrer::ScopedDeferrer deferrer(callbacks_);
if (state_ != State::kClosed) {
@@ -391,7 +384,6 @@ void DcSctpSocket::RestoreFromState(const DcSctpSocketHandoverState& state) {
}
void DcSctpSocket::Shutdown() {
- RTC_DCHECK_RUN_ON(&thread_checker_);
CallbackDeferrer::ScopedDeferrer deferrer(callbacks_);
if (tcb_ != nullptr) {
@@ -420,7 +412,6 @@ void DcSctpSocket::Shutdown() {
}
void DcSctpSocket::Close() {
- RTC_DCHECK_RUN_ON(&thread_checker_);
CallbackDeferrer::ScopedDeferrer deferrer(callbacks_);
if (state_ != State::kClosed) {
@@ -468,20 +459,51 @@ void DcSctpSocket::InternalClose(ErrorKind error, absl::string_view message) {
void DcSctpSocket::SetStreamPriority(StreamID stream_id,
StreamPriority priority) {
- RTC_DCHECK_RUN_ON(&thread_checker_);
send_queue_.SetStreamPriority(stream_id, priority);
}
StreamPriority DcSctpSocket::GetStreamPriority(StreamID stream_id) const {
- RTC_DCHECK_RUN_ON(&thread_checker_);
return send_queue_.GetStreamPriority(stream_id);
}
SendStatus DcSctpSocket::Send(DcSctpMessage message,
const SendOptions& send_options) {
- RTC_DCHECK_RUN_ON(&thread_checker_);
CallbackDeferrer::ScopedDeferrer deferrer(callbacks_);
- LifecycleId lifecycle_id = send_options.lifecycle_id;
+ SendStatus send_status = InternalSend(message, send_options);
+ if (send_status != SendStatus::kSuccess)
+ return send_status;
+ Timestamp now = callbacks_.Now();
+ ++metrics_.tx_messages_count;
+ send_queue_.Add(now, std::move(message), send_options);
+ if (tcb_ != nullptr)
+ tcb_->SendBufferedPackets(now);
+ RTC_DCHECK(IsConsistent());
+ return SendStatus::kSuccess;
+}
+std::vector<SendStatus> DcSctpSocket::SendMany(
+ rtc::ArrayView<DcSctpMessage> messages,
+ const SendOptions& send_options) {
+ CallbackDeferrer::ScopedDeferrer deferrer(callbacks_);
+ Timestamp now = callbacks_.Now();
+ std::vector<SendStatus> send_statuses;
+ send_statuses.reserve(messages.size());
+ for (DcSctpMessage& message : messages) {
+ SendStatus send_status = InternalSend(message, send_options);
+ send_statuses.push_back(send_status);
+ if (send_status != SendStatus::kSuccess)
+ continue;
+ ++metrics_.tx_messages_count;
+ send_queue_.Add(now, std::move(message), send_options);
+ }
+ if (tcb_ != nullptr)
+ tcb_->SendBufferedPackets(now);
+ RTC_DCHECK(IsConsistent());
+ return send_statuses;
+}
+
+SendStatus DcSctpSocket::InternalSend(const DcSctpMessage& message,
+ const SendOptions& send_options) {
+ LifecycleId lifecycle_id = send_options.lifecycle_id;
if (message.payload().empty()) {
if (lifecycle_id.IsSet()) {
callbacks_.OnLifecycleEnd(lifecycle_id);
@@ -519,21 +541,11 @@ SendStatus DcSctpSocket::Send(DcSctpMessage message,
"Unable to send message as the send queue is full");
return SendStatus::kErrorResourceExhaustion;
}
-
- Timestamp now = callbacks_.Now();
- ++metrics_.tx_messages_count;
- send_queue_.Add(now, std::move(message), send_options);
- if (tcb_ != nullptr) {
- tcb_->SendBufferedPackets(now);
- }
-
- RTC_DCHECK(IsConsistent());
return SendStatus::kSuccess;
}
ResetStreamsStatus DcSctpSocket::ResetStreams(
rtc::ArrayView<const StreamID> outgoing_streams) {
- RTC_DCHECK_RUN_ON(&thread_checker_);
CallbackDeferrer::ScopedDeferrer deferrer(callbacks_);
if (tcb_ == nullptr) {
@@ -555,7 +567,6 @@ ResetStreamsStatus DcSctpSocket::ResetStreams(
}
SocketState DcSctpSocket::state() const {
- RTC_DCHECK_RUN_ON(&thread_checker_);
switch (state_) {
case State::kClosed:
return SocketState::kClosed;
@@ -573,29 +584,23 @@ SocketState DcSctpSocket::state() const {
}
void DcSctpSocket::SetMaxMessageSize(size_t max_message_size) {
- RTC_DCHECK_RUN_ON(&thread_checker_);
options_.max_message_size = max_message_size;
}
size_t DcSctpSocket::buffered_amount(StreamID stream_id) const {
- RTC_DCHECK_RUN_ON(&thread_checker_);
return send_queue_.buffered_amount(stream_id);
}
size_t DcSctpSocket::buffered_amount_low_threshold(StreamID stream_id) const {
- RTC_DCHECK_RUN_ON(&thread_checker_);
return send_queue_.buffered_amount_low_threshold(stream_id);
}
void DcSctpSocket::SetBufferedAmountLowThreshold(StreamID stream_id,
size_t bytes) {
- RTC_DCHECK_RUN_ON(&thread_checker_);
send_queue_.SetBufferedAmountLowThreshold(stream_id, bytes);
}
absl::optional<Metrics> DcSctpSocket::GetMetrics() const {
- RTC_DCHECK_RUN_ON(&thread_checker_);
-
if (tcb_ == nullptr) {
return absl::nullopt;
}
@@ -750,7 +755,6 @@ bool DcSctpSocket::ValidatePacket(const SctpPacket& packet) {
}
void DcSctpSocket::HandleTimeout(TimeoutID timeout_id) {
- RTC_DCHECK_RUN_ON(&thread_checker_);
CallbackDeferrer::ScopedDeferrer deferrer(callbacks_);
timer_manager_.HandleTimeout(timeout_id);
@@ -764,7 +768,6 @@ void DcSctpSocket::HandleTimeout(TimeoutID timeout_id) {
}
void DcSctpSocket::ReceivePacket(rtc::ArrayView<const uint8_t> data) {
- RTC_DCHECK_RUN_ON(&thread_checker_);
CallbackDeferrer::ScopedDeferrer deferrer(callbacks_);
++metrics_.rx_packets_count;
@@ -1153,11 +1156,16 @@ void DcSctpSocket::HandleInit(const CommonHeader& header,
}
TieTag tie_tag(0);
+ VerificationTag my_verification_tag;
+ TSN my_initial_tsn;
if (state_ == State::kClosed) {
RTC_DLOG(LS_VERBOSE) << log_prefix()
<< "Received Init in closed state (normal)";
- MakeConnectionParameters();
+ my_verification_tag = VerificationTag(
+ callbacks_.GetRandomInt(kMinVerificationTag, kMaxVerificationTag));
+ my_initial_tsn =
+ TSN(callbacks_.GetRandomInt(kMinInitialTsn, kMaxInitialTsn));
} else if (state_ == State::kCookieWait || state_ == State::kCookieEchoed) {
// https://tools.ietf.org/html/rfc4960#section-5.2.1
// "This usually indicates an initialization collision, i.e., each
@@ -1170,6 +1178,8 @@ void DcSctpSocket::HandleInit(const CommonHeader& header,
// endpoint) was sent."
RTC_DLOG(LS_VERBOSE) << log_prefix()
<< "Received Init indicating simultaneous connections";
+ my_verification_tag = connect_params_.verification_tag;
+ my_initial_tsn = connect_params_.initial_tsn;
} else {
RTC_DCHECK(tcb_ != nullptr);
// https://tools.ietf.org/html/rfc4960#section-5.2.2
@@ -1184,17 +1194,16 @@ void DcSctpSocket::HandleInit(const CommonHeader& header,
<< "Received Init indicating restarted connection";
// Create a new verification tag - different from the previous one.
for (int tries = 0; tries < 10; ++tries) {
- connect_params_.verification_tag = VerificationTag(
+ my_verification_tag = VerificationTag(
callbacks_.GetRandomInt(kMinVerificationTag, kMaxVerificationTag));
- if (connect_params_.verification_tag != tcb_->my_verification_tag()) {
+ if (my_verification_tag != tcb_->my_verification_tag()) {
break;
}
}
// Make the initial TSN make a large jump, so that there is no overlap
// with the old and new association.
- connect_params_.initial_tsn =
- TSN(*tcb_->retransmission_queue().next_tsn() + 1000000);
+ my_initial_tsn = TSN(*tcb_->retransmission_queue().next_tsn() + 1000000);
tie_tag = tcb_->tie_tag();
}
@@ -1204,8 +1213,8 @@ void DcSctpSocket::HandleInit(const CommonHeader& header,
"Proceeding with connection. my_verification_tag=%08x, "
"my_initial_tsn=%u, peer_verification_tag=%08x, "
"peer_initial_tsn=%u",
- *connect_params_.verification_tag, *connect_params_.initial_tsn,
- *chunk->initiate_tag(), *chunk->initial_tsn());
+ *my_verification_tag, *my_initial_tsn, *chunk->initiate_tag(),
+ *chunk->initial_tsn());
Capabilities capabilities =
ComputeCapabilities(options_, chunk->nbr_outbound_streams(),
@@ -1214,16 +1223,17 @@ void DcSctpSocket::HandleInit(const CommonHeader& header,
SctpPacket::Builder b(chunk->initiate_tag(), options_);
Parameters::Builder params_builder =
Parameters::Builder().Add(StateCookieParameter(
- StateCookie(chunk->initiate_tag(), chunk->initial_tsn(),
- chunk->a_rwnd(), tie_tag, capabilities)
+ StateCookie(chunk->initiate_tag(), my_verification_tag,
+ chunk->initial_tsn(), my_initial_tsn, chunk->a_rwnd(),
+ tie_tag, capabilities)
.Serialize()));
AddCapabilityParameters(options_, params_builder);
- InitAckChunk init_ack(/*initiate_tag=*/connect_params_.verification_tag,
+ InitAckChunk init_ack(/*initiate_tag=*/my_verification_tag,
options_.max_receiver_window_buffer_size,
options_.announced_maximum_outgoing_streams,
options_.announced_maximum_incoming_streams,
- connect_params_.initial_tsn, params_builder.Build());
+ my_initial_tsn, params_builder.Build());
b.Add(init_ack);
// If the peer has signaled that it supports zero checksum, INIT-ACK can then
// have its checksum as zero.
@@ -1309,13 +1319,13 @@ void DcSctpSocket::HandleCookieEcho(
return;
}
} else {
- if (header.verification_tag != connect_params_.verification_tag) {
+ if (header.verification_tag != cookie->my_tag()) {
callbacks_.OnError(
ErrorKind::kParseFailed,
rtc::StringFormat(
"Received CookieEcho with invalid verification tag: %08x, "
"expected %08x",
- *header.verification_tag, *connect_params_.verification_tag));
+ *header.verification_tag, *cookie->my_tag()));
return;
}
}
@@ -1340,10 +1350,10 @@ void DcSctpSocket::HandleCookieEcho(
// send queue is already re-configured, and shouldn't be reset.
send_queue_.Reset();
- CreateTransmissionControlBlock(
- cookie->capabilities(), connect_params_.verification_tag,
- connect_params_.initial_tsn, cookie->initiate_tag(),
- cookie->initial_tsn(), cookie->a_rwnd(), MakeTieTag(callbacks_));
+ CreateTransmissionControlBlock(cookie->capabilities(), cookie->my_tag(),
+ cookie->my_initial_tsn(), cookie->peer_tag(),
+ cookie->peer_initial_tsn(), cookie->a_rwnd(),
+ MakeTieTag(callbacks_));
}
SctpPacket::Builder b = tcb_->PacketBuilder();
@@ -1363,13 +1373,13 @@ bool DcSctpSocket::HandleCookieEchoWithTCB(const CommonHeader& header,
<< *tcb_->my_verification_tag()
<< ", peer_tag=" << *header.verification_tag
<< ", tcb_tag=" << *tcb_->peer_verification_tag()
- << ", cookie_tag=" << *cookie.initiate_tag()
+ << ", peer_tag=" << *cookie.peer_tag()
<< ", local_tie_tag=" << *tcb_->tie_tag()
<< ", peer_tie_tag=" << *cookie.tie_tag();
// https://tools.ietf.org/html/rfc4960#section-5.2.4
// "Handle a COOKIE ECHO when a TCB Exists"
if (header.verification_tag != tcb_->my_verification_tag() &&
- tcb_->peer_verification_tag() != cookie.initiate_tag() &&
+ tcb_->peer_verification_tag() != cookie.peer_tag() &&
cookie.tie_tag() == tcb_->tie_tag()) {
// "A) In this case, the peer may have restarted."
if (state_ == State::kShutdownAckSent) {
@@ -1377,7 +1387,7 @@ bool DcSctpSocket::HandleCookieEchoWithTCB(const CommonHeader& header,
// that the peer has restarted ... it MUST NOT set up a new association
// but instead resend the SHUTDOWN ACK and send an ERROR chunk with a
// "Cookie Received While Shutting Down" error cause to its peer."
- SctpPacket::Builder b(cookie.initiate_tag(), options_);
+ SctpPacket::Builder b(cookie.peer_tag(), options_);
b.Add(ShutdownAckChunk());
b.Add(ErrorChunk(Parameters::Builder()
.Add(CookieReceivedWhileShuttingDownCause())
@@ -1394,7 +1404,7 @@ bool DcSctpSocket::HandleCookieEchoWithTCB(const CommonHeader& header,
tcb_ = nullptr;
callbacks_.OnConnectionRestarted();
} else if (header.verification_tag == tcb_->my_verification_tag() &&
- tcb_->peer_verification_tag() != cookie.initiate_tag()) {
+ tcb_->peer_verification_tag() != cookie.peer_tag()) {
// TODO(boivie): Handle the peer_tag == 0?
// "B) In this case, both sides may be attempting to start an
// association at about the same time, but the peer endpoint started its
@@ -1404,7 +1414,7 @@ bool DcSctpSocket::HandleCookieEchoWithTCB(const CommonHeader& header,
<< "Received COOKIE-ECHO indicating simultaneous connections";
tcb_ = nullptr;
} else if (header.verification_tag != tcb_->my_verification_tag() &&
- tcb_->peer_verification_tag() == cookie.initiate_tag() &&
+ tcb_->peer_verification_tag() == cookie.peer_tag() &&
cookie.tie_tag() == TieTag(0)) {
// "C) In this case, the local endpoint's cookie has arrived late.
// Before it arrived, the local endpoint sent an INIT and received an
@@ -1417,7 +1427,7 @@ bool DcSctpSocket::HandleCookieEchoWithTCB(const CommonHeader& header,
<< "Received COOKIE-ECHO indicating a late COOKIE-ECHO. Discarding";
return false;
} else if (header.verification_tag == tcb_->my_verification_tag() &&
- tcb_->peer_verification_tag() == cookie.initiate_tag()) {
+ tcb_->peer_verification_tag() == cookie.peer_tag()) {
// "D) When both local and remote tags match, the endpoint should enter
// the ESTABLISHED state, if it is in the COOKIE-ECHOED state. It
// should stop any cookie timer that may be running and send a COOKIE
@@ -1761,7 +1771,6 @@ void DcSctpSocket::SendShutdownAck() {
}
HandoverReadinessStatus DcSctpSocket::GetHandoverReadiness() const {
- RTC_DCHECK_RUN_ON(&thread_checker_);
HandoverReadinessStatus status;
if (state_ != State::kClosed && state_ != State::kEstablished) {
status.Add(HandoverUnreadinessReason::kWrongConnectionState);
@@ -1775,7 +1784,6 @@ HandoverReadinessStatus DcSctpSocket::GetHandoverReadiness() const {
absl::optional<DcSctpSocketHandoverState>
DcSctpSocket::GetHandoverStateAndClose() {
- RTC_DCHECK_RUN_ON(&thread_checker_);
CallbackDeferrer::ScopedDeferrer deferrer(callbacks_);
if (!GetHandoverReadiness().IsReady()) {
diff --git a/third_party/libwebrtc/net/dcsctp/socket/dcsctp_socket.h b/third_party/libwebrtc/net/dcsctp/socket/dcsctp_socket.h
index deb6ee23e7..c65571a923 100644
--- a/third_party/libwebrtc/net/dcsctp/socket/dcsctp_socket.h
+++ b/third_party/libwebrtc/net/dcsctp/socket/dcsctp_socket.h
@@ -14,10 +14,10 @@
#include <memory>
#include <string>
#include <utility>
+#include <vector>
#include "absl/strings/string_view.h"
#include "api/array_view.h"
-#include "api/sequence_checker.h"
#include "net/dcsctp/packet/chunk/abort_chunk.h"
#include "net/dcsctp/packet/chunk/chunk.h"
#include "net/dcsctp/packet/chunk/cookie_ack_chunk.h"
@@ -91,6 +91,8 @@ class DcSctpSocket : public DcSctpSocketInterface {
void Close() override;
SendStatus Send(DcSctpMessage message,
const SendOptions& send_options) override;
+ std::vector<SendStatus> SendMany(rtc::ArrayView<DcSctpMessage> messages,
+ const SendOptions& send_options) override;
ResetStreamsStatus ResetStreams(
rtc::ArrayView<const StreamID> outgoing_streams) override;
SocketState state() const override;
@@ -148,8 +150,6 @@ class DcSctpSocket : public DcSctpSocketInterface {
// Changes the socket state, given a `reason` (for debugging/logging).
void SetState(State state, absl::string_view reason);
- // Fills in `connect_params` with random verification tag and initial TSN.
- void MakeConnectionParameters();
// Closes the association. Note that the TCB will not be valid past this call.
void InternalClose(ErrorKind error, absl::string_view message);
// Closes the association, because of too many retransmission errors.
@@ -167,6 +167,9 @@ class DcSctpSocket : public DcSctpSocketInterface {
void MaybeSendShutdownOnPacketReceived(const SctpPacket& packet);
// If there are streams pending to be reset, send a request to reset them.
void MaybeSendResetStreamsRequest();
+ // Performs internal processing shared between Send and SendMany.
+ SendStatus InternalSend(const DcSctpMessage& message,
+ const SendOptions& send_options);
// Sends a INIT chunk.
void SendInit();
// Sends a SHUTDOWN chunk.
@@ -267,7 +270,6 @@ class DcSctpSocket : public DcSctpSocketInterface {
const std::string log_prefix_;
const std::unique_ptr<PacketObserver> packet_observer_;
- RTC_NO_UNIQUE_ADDRESS webrtc::SequenceChecker thread_checker_;
Metrics metrics_;
DcSctpOptions options_;
diff --git a/third_party/libwebrtc/net/dcsctp/socket/dcsctp_socket_test.cc b/third_party/libwebrtc/net/dcsctp/socket/dcsctp_socket_test.cc
index dc76b80a37..413516bae0 100644
--- a/third_party/libwebrtc/net/dcsctp/socket/dcsctp_socket_test.cc
+++ b/third_party/libwebrtc/net/dcsctp/socket/dcsctp_socket_test.cc
@@ -66,6 +66,7 @@ namespace {
using ::testing::_;
using ::testing::AllOf;
using ::testing::ElementsAre;
+using ::testing::ElementsAreArray;
using ::testing::Eq;
using ::testing::HasSubstr;
using ::testing::IsEmpty;
@@ -1561,6 +1562,33 @@ TEST(DcSctpSocketTest, SetMaxMessageSize) {
EXPECT_EQ(a.socket.options().max_message_size, 42u);
}
+TEST_P(DcSctpSocketParametrizedTest, SendManyMessages) {
+ SocketUnderTest a("A");
+ auto z = std::make_unique<SocketUnderTest>("Z");
+
+ ConnectSockets(a, *z);
+ z = MaybeHandoverSocket(std::move(z));
+
+ static constexpr int kIterations = 100;
+ std::vector<DcSctpMessage> messages;
+ std::vector<SendStatus> statuses;
+ for (int i = 0; i < kIterations; ++i) {
+ messages.push_back(DcSctpMessage(StreamID(1), PPID(53), {1, 2}));
+ statuses.push_back(SendStatus::kSuccess);
+ }
+ EXPECT_THAT(a.socket.SendMany(messages, {}), ElementsAreArray(statuses));
+
+ ExchangeMessages(a, *z);
+
+ for (int i = 0; i < kIterations; ++i) {
+ EXPECT_TRUE(z->cb.ConsumeReceivedMessage().has_value());
+ }
+
+ EXPECT_FALSE(z->cb.ConsumeReceivedMessage().has_value());
+
+ MaybeHandoverSocketAndSendMessage(a, std::move(z));
+}
+
TEST_P(DcSctpSocketParametrizedTest, SendsMessagesWithLowLifetime) {
SocketUnderTest a("A");
auto z = std::make_unique<SocketUnderTest>("Z");
@@ -3061,5 +3089,149 @@ TEST(DcSctpSocketTest, HandlesForwardTsnOutOfOrderWithStreamResetting) {
testing::Optional(Property(&DcSctpMessage::ppid, PPID(53))));
}
+TEST(DcSctpSocketTest, ResentInitHasSameParameters) {
+ // If an INIT chunk has to be resent (due to INIT_ACK not received in time),
+ // the resent INIT must have the same properties as the original one.
+ SocketUnderTest a("A");
+ SocketUnderTest z("Z");
+
+ a.socket.Connect();
+ auto packet_1 = a.cb.ConsumeSentPacket();
+
+ // Times out, INIT is re-sent.
+ AdvanceTime(a, z, a.options.t1_init_timeout.ToTimeDelta());
+ auto packet_2 = a.cb.ConsumeSentPacket();
+
+ ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket init_packet_1,
+ SctpPacket::Parse(packet_1, z.options));
+ ASSERT_HAS_VALUE_AND_ASSIGN(
+ InitChunk init_chunk_1,
+ InitChunk::Parse(init_packet_1.descriptors()[0].data));
+
+ ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket init_packet_2,
+ SctpPacket::Parse(packet_2, z.options));
+ ASSERT_HAS_VALUE_AND_ASSIGN(
+ InitChunk init_chunk_2,
+ InitChunk::Parse(init_packet_2.descriptors()[0].data));
+
+ EXPECT_EQ(init_chunk_1.initial_tsn(), init_chunk_2.initial_tsn());
+ EXPECT_EQ(init_chunk_1.initiate_tag(), init_chunk_2.initiate_tag());
+}
+
+TEST(DcSctpSocketTest, ResentInitAckHasDifferentParameters) {
+ // For every INIT, an INIT_ACK is produced. Verify that the socket doesn't
+ // maintain any state by ensuring that two created INIT_ACKs for the same
+ // received INIT are different.
+ SocketUnderTest a("A");
+ SocketUnderTest z("Z");
+
+ a.socket.Connect();
+ auto packet_1 = a.cb.ConsumeSentPacket();
+ EXPECT_THAT(packet_1, HasChunks(ElementsAre(IsChunkType(InitChunk::kType))));
+
+ z.socket.ReceivePacket(packet_1);
+ auto packet_2 = z.cb.ConsumeSentPacket();
+ z.socket.ReceivePacket(packet_1);
+ auto packet_3 = z.cb.ConsumeSentPacket();
+
+ EXPECT_THAT(packet_2,
+ HasChunks(ElementsAre(IsChunkType(InitAckChunk::kType))));
+ EXPECT_THAT(packet_3,
+ HasChunks(ElementsAre(IsChunkType(InitAckChunk::kType))));
+
+ ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket init_ack_packet_1,
+ SctpPacket::Parse(packet_2, z.options));
+ ASSERT_HAS_VALUE_AND_ASSIGN(
+ InitAckChunk init_ack_chunk_1,
+ InitAckChunk::Parse(init_ack_packet_1.descriptors()[0].data));
+
+ ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket init_ack_packet_2,
+ SctpPacket::Parse(packet_3, z.options));
+ ASSERT_HAS_VALUE_AND_ASSIGN(
+ InitAckChunk init_ack_chunk_2,
+ InitAckChunk::Parse(init_ack_packet_2.descriptors()[0].data));
+
+ EXPECT_NE(init_ack_chunk_1.initiate_tag(), init_ack_chunk_2.initiate_tag());
+ EXPECT_NE(init_ack_chunk_1.initial_tsn(), init_ack_chunk_2.initial_tsn());
+}
+
+TEST(DcSctpSocketResendInitTest, ConnectionCanContinueFromFirstInitAck) {
+ // If an INIT chunk has to be resent (due to INIT_ACK not received in time),
+ // another INIT will be sent, and if both INITs were actually received, both
+ // will be responded to by an INIT_ACK. While these two INIT_ACKs may have
+ // different parameters, the connection must be able to finish with the cookie
+ // (as replied to using COOKIE_ECHO) from either INIT_ACK.
+ SocketUnderTest a("A");
+ SocketUnderTest z("Z");
+
+ a.socket.Send(DcSctpMessage(StreamID(1), PPID(53),
+ std::vector<uint8_t>(kLargeMessageSize)),
+ kSendOptions);
+ a.socket.Connect();
+ auto init_1 = a.cb.ConsumeSentPacket();
+
+ // Times out, INIT is re-sent.
+ AdvanceTime(a, z, a.options.t1_init_timeout.ToTimeDelta());
+ auto init_2 = a.cb.ConsumeSentPacket();
+
+ EXPECT_THAT(init_1, HasChunks(ElementsAre(IsChunkType(InitChunk::kType))));
+ EXPECT_THAT(init_2, HasChunks(ElementsAre(IsChunkType(InitChunk::kType))));
+
+ z.socket.ReceivePacket(init_1);
+ z.socket.ReceivePacket(init_2);
+ auto init_ack_1 = z.cb.ConsumeSentPacket();
+ auto init_ack_2 = z.cb.ConsumeSentPacket();
+ EXPECT_THAT(init_ack_1,
+ HasChunks(ElementsAre(IsChunkType(InitAckChunk::kType))));
+ EXPECT_THAT(init_ack_2,
+ HasChunks(ElementsAre(IsChunkType(InitAckChunk::kType))));
+
+ a.socket.ReceivePacket(init_ack_1);
+ // Then let the rest continue.
+ ExchangeMessages(a, z);
+
+ absl::optional<DcSctpMessage> msg = z.cb.ConsumeReceivedMessage();
+ ASSERT_TRUE(msg.has_value());
+ EXPECT_EQ(msg->stream_id(), StreamID(1));
+ EXPECT_THAT(msg->payload(), SizeIs(kLargeMessageSize));
+}
+
+TEST(DcSctpSocketResendInitTest, ConnectionCanContinueFromSecondInitAck) {
+ // Just as above, but discarding the first INIT_ACK.
+ SocketUnderTest a("A");
+ SocketUnderTest z("Z");
+
+ a.socket.Send(DcSctpMessage(StreamID(1), PPID(53),
+ std::vector<uint8_t>(kLargeMessageSize)),
+ kSendOptions);
+ a.socket.Connect();
+ auto init_1 = a.cb.ConsumeSentPacket();
+
+ // Times out, INIT is re-sent.
+ AdvanceTime(a, z, a.options.t1_init_timeout.ToTimeDelta());
+ auto init_2 = a.cb.ConsumeSentPacket();
+
+ EXPECT_THAT(init_1, HasChunks(ElementsAre(IsChunkType(InitChunk::kType))));
+ EXPECT_THAT(init_2, HasChunks(ElementsAre(IsChunkType(InitChunk::kType))));
+
+ z.socket.ReceivePacket(init_1);
+ z.socket.ReceivePacket(init_2);
+ auto init_ack_1 = z.cb.ConsumeSentPacket();
+ auto init_ack_2 = z.cb.ConsumeSentPacket();
+ EXPECT_THAT(init_ack_1,
+ HasChunks(ElementsAre(IsChunkType(InitAckChunk::kType))));
+ EXPECT_THAT(init_ack_2,
+ HasChunks(ElementsAre(IsChunkType(InitAckChunk::kType))));
+
+ a.socket.ReceivePacket(init_ack_2);
+ // Then let the rest continue.
+ ExchangeMessages(a, z);
+
+ absl::optional<DcSctpMessage> msg = z.cb.ConsumeReceivedMessage();
+ ASSERT_TRUE(msg.has_value());
+ EXPECT_EQ(msg->stream_id(), StreamID(1));
+ EXPECT_THAT(msg->payload(), SizeIs(kLargeMessageSize));
+}
+
} // namespace
} // namespace dcsctp
diff --git a/third_party/libwebrtc/net/dcsctp/socket/state_cookie.cc b/third_party/libwebrtc/net/dcsctp/socket/state_cookie.cc
index 624d783a3b..c5ed1d8620 100644
--- a/third_party/libwebrtc/net/dcsctp/socket/state_cookie.cc
+++ b/third_party/libwebrtc/net/dcsctp/socket/state_cookie.cc
@@ -32,17 +32,19 @@ std::vector<uint8_t> StateCookie::Serialize() {
BoundedByteWriter<kCookieSize> buffer(cookie);
buffer.Store32<0>(kMagic1);
buffer.Store32<4>(kMagic2);
- buffer.Store32<8>(*initiate_tag_);
- buffer.Store32<12>(*initial_tsn_);
- buffer.Store32<16>(a_rwnd_);
- buffer.Store32<20>(static_cast<uint32_t>(*tie_tag_ >> 32));
- buffer.Store32<24>(static_cast<uint32_t>(*tie_tag_));
- buffer.Store8<28>(capabilities_.partial_reliability);
- buffer.Store8<29>(capabilities_.message_interleaving);
- buffer.Store8<30>(capabilities_.reconfig);
- buffer.Store16<32>(capabilities_.negotiated_maximum_incoming_streams);
- buffer.Store16<34>(capabilities_.negotiated_maximum_outgoing_streams);
- buffer.Store8<36>(capabilities_.zero_checksum);
+ buffer.Store32<8>(*peer_tag_);
+ buffer.Store32<12>(*my_tag_);
+ buffer.Store32<16>(*peer_initial_tsn_);
+ buffer.Store32<20>(*my_initial_tsn_);
+ buffer.Store32<24>(a_rwnd_);
+ buffer.Store32<28>(static_cast<uint32_t>(*tie_tag_ >> 32));
+ buffer.Store32<32>(static_cast<uint32_t>(*tie_tag_));
+ buffer.Store8<36>(capabilities_.partial_reliability);
+ buffer.Store8<37>(capabilities_.message_interleaving);
+ buffer.Store8<38>(capabilities_.reconfig);
+ buffer.Store16<40>(capabilities_.negotiated_maximum_incoming_streams);
+ buffer.Store16<42>(capabilities_.negotiated_maximum_outgoing_streams);
+ buffer.Store8<44>(capabilities_.zero_checksum);
return cookie;
}
@@ -62,23 +64,25 @@ absl::optional<StateCookie> StateCookie::Deserialize(
return absl::nullopt;
}
- VerificationTag verification_tag(buffer.Load32<8>());
- TSN initial_tsn(buffer.Load32<12>());
- uint32_t a_rwnd = buffer.Load32<16>();
- uint32_t tie_tag_upper = buffer.Load32<20>();
- uint32_t tie_tag_lower = buffer.Load32<24>();
+ VerificationTag peer_tag(buffer.Load32<8>());
+ VerificationTag my_tag(buffer.Load32<12>());
+ TSN peer_initial_tsn(buffer.Load32<16>());
+ TSN my_initial_tsn(buffer.Load32<20>());
+ uint32_t a_rwnd = buffer.Load32<24>();
+ uint32_t tie_tag_upper = buffer.Load32<28>();
+ uint32_t tie_tag_lower = buffer.Load32<32>();
TieTag tie_tag(static_cast<uint64_t>(tie_tag_upper) << 32 |
static_cast<uint64_t>(tie_tag_lower));
Capabilities capabilities;
- capabilities.partial_reliability = buffer.Load8<28>() != 0;
- capabilities.message_interleaving = buffer.Load8<29>() != 0;
- capabilities.reconfig = buffer.Load8<30>() != 0;
- capabilities.negotiated_maximum_incoming_streams = buffer.Load16<32>();
- capabilities.negotiated_maximum_outgoing_streams = buffer.Load16<34>();
- capabilities.zero_checksum = buffer.Load8<36>() != 0;
+ capabilities.partial_reliability = buffer.Load8<36>() != 0;
+ capabilities.message_interleaving = buffer.Load8<37>() != 0;
+ capabilities.reconfig = buffer.Load8<38>() != 0;
+ capabilities.negotiated_maximum_incoming_streams = buffer.Load16<40>();
+ capabilities.negotiated_maximum_outgoing_streams = buffer.Load16<42>();
+ capabilities.zero_checksum = buffer.Load8<44>() != 0;
- return StateCookie(verification_tag, initial_tsn, a_rwnd, tie_tag,
- capabilities);
+ return StateCookie(peer_tag, my_tag, peer_initial_tsn, my_initial_tsn, a_rwnd,
+ tie_tag, capabilities);
}
} // namespace dcsctp
diff --git a/third_party/libwebrtc/net/dcsctp/socket/state_cookie.h b/third_party/libwebrtc/net/dcsctp/socket/state_cookie.h
index 34cd6d3690..b94eedafd4 100644
--- a/third_party/libwebrtc/net/dcsctp/socket/state_cookie.h
+++ b/third_party/libwebrtc/net/dcsctp/socket/state_cookie.h
@@ -27,15 +27,19 @@ namespace dcsctp {
// Do not trust anything in it; no pointers or anything like that.
class StateCookie {
public:
- static constexpr size_t kCookieSize = 37;
+ static constexpr size_t kCookieSize = 45;
- StateCookie(VerificationTag initiate_tag,
- TSN initial_tsn,
+ StateCookie(VerificationTag peer_tag,
+ VerificationTag my_tag,
+ TSN peer_initial_tsn,
+ TSN my_initial_tsn,
uint32_t a_rwnd,
TieTag tie_tag,
Capabilities capabilities)
- : initiate_tag_(initiate_tag),
- initial_tsn_(initial_tsn),
+ : peer_tag_(peer_tag),
+ my_tag_(my_tag),
+ peer_initial_tsn_(peer_initial_tsn),
+ my_initial_tsn_(my_initial_tsn),
a_rwnd_(a_rwnd),
tie_tag_(tie_tag),
capabilities_(capabilities) {}
@@ -47,15 +51,21 @@ class StateCookie {
static absl::optional<StateCookie> Deserialize(
rtc::ArrayView<const uint8_t> cookie);
- VerificationTag initiate_tag() const { return initiate_tag_; }
- TSN initial_tsn() const { return initial_tsn_; }
+ VerificationTag peer_tag() const { return peer_tag_; }
+ VerificationTag my_tag() const { return my_tag_; }
+ TSN peer_initial_tsn() const { return peer_initial_tsn_; }
+ TSN my_initial_tsn() const { return my_initial_tsn_; }
uint32_t a_rwnd() const { return a_rwnd_; }
TieTag tie_tag() const { return tie_tag_; }
const Capabilities& capabilities() const { return capabilities_; }
private:
- const VerificationTag initiate_tag_;
- const TSN initial_tsn_;
+ // Also called "Tag_A" in RFC4960.
+ const VerificationTag peer_tag_;
+ // Also called "Tag_Z" in RFC4960.
+ const VerificationTag my_tag_;
+ const TSN peer_initial_tsn_;
+ const TSN my_initial_tsn_;
const uint32_t a_rwnd_;
const TieTag tie_tag_;
const Capabilities capabilities_;
diff --git a/third_party/libwebrtc/net/dcsctp/socket/state_cookie_test.cc b/third_party/libwebrtc/net/dcsctp/socket/state_cookie_test.cc
index 19be71a1ca..806ea2024b 100644
--- a/third_party/libwebrtc/net/dcsctp/socket/state_cookie_test.cc
+++ b/third_party/libwebrtc/net/dcsctp/socket/state_cookie_test.cc
@@ -24,14 +24,18 @@ TEST(StateCookieTest, SerializeAndDeserialize) {
.zero_checksum = true,
.negotiated_maximum_incoming_streams = 123,
.negotiated_maximum_outgoing_streams = 234};
- StateCookie cookie(VerificationTag(123), TSN(456),
+ StateCookie cookie(/*peer_tag=*/VerificationTag(123),
+ /*my_tag=*/VerificationTag(321),
+ /*peer_initial_tsn=*/TSN(456), /*my_initial_tsn=*/TSN(654),
/*a_rwnd=*/789, TieTag(101112), capabilities);
std::vector<uint8_t> serialized = cookie.Serialize();
EXPECT_THAT(serialized, SizeIs(StateCookie::kCookieSize));
ASSERT_HAS_VALUE_AND_ASSIGN(StateCookie deserialized,
StateCookie::Deserialize(serialized));
- EXPECT_EQ(deserialized.initiate_tag(), VerificationTag(123));
- EXPECT_EQ(deserialized.initial_tsn(), TSN(456));
+ EXPECT_EQ(deserialized.peer_tag(), VerificationTag(123));
+ EXPECT_EQ(deserialized.my_tag(), VerificationTag(321));
+ EXPECT_EQ(deserialized.peer_initial_tsn(), TSN(456));
+ EXPECT_EQ(deserialized.my_initial_tsn(), TSN(654));
EXPECT_EQ(deserialized.a_rwnd(), 789u);
EXPECT_EQ(deserialized.tie_tag(), TieTag(101112));
EXPECT_TRUE(deserialized.capabilities().partial_reliability);
@@ -48,7 +52,9 @@ TEST(StateCookieTest, ValidateMagicValue) {
Capabilities capabilities = {.partial_reliability = true,
.message_interleaving = false,
.reconfig = true};
- StateCookie cookie(VerificationTag(123), TSN(456),
+ StateCookie cookie(/*peer_tag=*/VerificationTag(123),
+ /*my_tag=*/VerificationTag(321),
+ /*peer_initial_tsn=*/TSN(456), /*my_initial_tsn=*/TSN(654),
/*a_rwnd=*/789, TieTag(101112), capabilities);
std::vector<uint8_t> serialized = cookie.Serialize();
ASSERT_THAT(serialized, SizeIs(StateCookie::kCookieSize));
diff --git a/third_party/libwebrtc/net/dcsctp/tx/rr_send_queue.cc b/third_party/libwebrtc/net/dcsctp/tx/rr_send_queue.cc
index 7cbead296c..3e682fdca6 100644
--- a/third_party/libwebrtc/net/dcsctp/tx/rr_send_queue.cc
+++ b/third_party/libwebrtc/net/dcsctp/tx/rr_send_queue.cc
@@ -373,8 +373,9 @@ void RRSendQueue::Add(Timestamp now,
: Timestamp::PlusInfinity(),
.lifecycle_id = send_options.lifecycle_id,
};
- GetOrCreateStreamInfo(message.stream_id())
- .Add(std::move(message), std::move(attributes));
+ StreamID stream_id = message.stream_id();
+ GetOrCreateStreamInfo(stream_id).Add(std::move(message),
+ std::move(attributes));
RTC_DCHECK(IsConsistent());
}
diff --git a/third_party/libwebrtc/p2p/base/basic_ice_controller.cc b/third_party/libwebrtc/p2p/base/basic_ice_controller.cc
index a0ff4cf144..182845cdd7 100644
--- a/third_party/libwebrtc/p2p/base/basic_ice_controller.cc
+++ b/third_party/libwebrtc/p2p/base/basic_ice_controller.cc
@@ -565,9 +565,9 @@ bool BasicIceController::ReadyToSend(const Connection* connection) const {
bool BasicIceController::PresumedWritable(const Connection* conn) const {
return (conn->write_state() == Connection::STATE_WRITE_INIT &&
config_.presume_writable_when_fully_relayed &&
- conn->local_candidate().type() == RELAY_PORT_TYPE &&
- (conn->remote_candidate().type() == RELAY_PORT_TYPE ||
- conn->remote_candidate().type() == PRFLX_PORT_TYPE));
+ conn->local_candidate().is_relay() &&
+ (conn->remote_candidate().is_relay() ||
+ conn->remote_candidate().is_prflx()));
}
// Compare two connections based on their writing, receiving, and connected
diff --git a/third_party/libwebrtc/p2p/base/basic_ice_controller.h b/third_party/libwebrtc/p2p/base/basic_ice_controller.h
index b941a0dd7e..724609d2d7 100644
--- a/third_party/libwebrtc/p2p/base/basic_ice_controller.h
+++ b/third_party/libwebrtc/p2p/base/basic_ice_controller.h
@@ -32,6 +32,9 @@ class BasicIceController : public IceControllerInterface {
void SetSelectedConnection(const Connection* selected_connection) override;
void AddConnection(const Connection* connection) override;
void OnConnectionDestroyed(const Connection* connection) override;
+ rtc::ArrayView<const Connection* const> GetConnections() const override {
+ return connections_;
+ }
rtc::ArrayView<const Connection*> connections() const override {
return rtc::ArrayView<const Connection*>(
const_cast<const Connection**>(connections_.data()),
diff --git a/third_party/libwebrtc/p2p/base/connection.cc b/third_party/libwebrtc/p2p/base/connection.cc
index d0e6f1bff8..bf07dec607 100644
--- a/third_party/libwebrtc/p2p/base/connection.cc
+++ b/third_party/libwebrtc/p2p/base/connection.cc
@@ -590,10 +590,8 @@ void Connection::HandleStunBindingOrGoogPingRequest(IceMessage* msg) {
// This connection should now be receiving.
ReceivedPing(msg->transaction_id());
if (field_trials_->extra_ice_ping && last_ping_response_received_ == 0) {
- if (local_candidate().type() == RELAY_PORT_TYPE ||
- local_candidate().type() == PRFLX_PORT_TYPE ||
- remote_candidate().type() == RELAY_PORT_TYPE ||
- remote_candidate().type() == PRFLX_PORT_TYPE) {
+ if (local_candidate().is_relay() || local_candidate().is_prflx() ||
+ remote_candidate().is_relay() || remote_candidate().is_prflx()) {
const int64_t now = rtc::TimeMillis();
if (last_ping_sent_ + kMinExtraPingDelayMs <= now) {
RTC_LOG(LS_INFO) << ToString()
@@ -1579,8 +1577,7 @@ void Connection::MaybeSetRemoteIceParametersAndGeneration(
void Connection::MaybeUpdatePeerReflexiveCandidate(
const Candidate& new_candidate) {
- if (remote_candidate_.type() == PRFLX_PORT_TYPE &&
- new_candidate.type() != PRFLX_PORT_TYPE &&
+ if (remote_candidate_.is_prflx() && !new_candidate.is_prflx() &&
remote_candidate_.protocol() == new_candidate.protocol() &&
remote_candidate_.address() == new_candidate.address() &&
remote_candidate_.username() == new_candidate.username() &&
diff --git a/third_party/libwebrtc/p2p/base/ice_controller_interface.h b/third_party/libwebrtc/p2p/base/ice_controller_interface.h
index 8b63ed3fc3..fb421cf0f9 100644
--- a/third_party/libwebrtc/p2p/base/ice_controller_interface.h
+++ b/third_party/libwebrtc/p2p/base/ice_controller_interface.h
@@ -19,6 +19,7 @@
#include "p2p/base/connection.h"
#include "p2p/base/ice_switch_reason.h"
#include "p2p/base/ice_transport_internal.h"
+#include "rtc_base/checks.h"
#include "rtc_base/system/rtc_export.h"
namespace cricket {
@@ -53,7 +54,7 @@ struct RTC_EXPORT IceRecheckEvent {
// Connection::ForgetLearnedState - return in SwitchResult
//
// The IceController shall keep track of all connections added
-// (and not destroyed) and give them back using the connections()-function-
+// (and not destroyed) and give them back using the GetConnections() function.
//
// When a Connection gets destroyed
// - signals on Connection::SignalDestroyed
@@ -101,7 +102,17 @@ class IceControllerInterface {
virtual void OnConnectionDestroyed(const Connection* connection) = 0;
// These are all connections that has been added and not destroyed.
- virtual rtc::ArrayView<const Connection*> connections() const = 0;
+ virtual rtc::ArrayView<const Connection* const> GetConnections() const {
+ // Stub implementation to simplify downstream roll.
+ RTC_CHECK_NOTREACHED();
+ return {};
+ }
+ // TODO(bugs.webrtc.org/15702): Remove this after downstream is cleaned up.
+ virtual rtc::ArrayView<const Connection*> connections() const {
+ // Stub implementation to simplify downstream removal.
+ RTC_CHECK_NOTREACHED();
+ return {};
+ }
// Is there a pingable connection ?
// This function is used to boot-strap pinging, after this returns true
diff --git a/third_party/libwebrtc/p2p/base/mock_ice_controller.h b/third_party/libwebrtc/p2p/base/mock_ice_controller.h
index bde9254e7d..f552519be0 100644
--- a/third_party/libwebrtc/p2p/base/mock_ice_controller.h
+++ b/third_party/libwebrtc/p2p/base/mock_ice_controller.h
@@ -35,6 +35,10 @@ class MockIceController : public cricket::IceControllerInterface {
OnConnectionDestroyed,
(const cricket::Connection*),
(override));
+ MOCK_METHOD(rtc::ArrayView<const cricket::Connection* const>,
+ GetConnections,
+ (),
+ (const, override));
MOCK_METHOD(rtc::ArrayView<const cricket::Connection*>,
connections,
(),
diff --git a/third_party/libwebrtc/p2p/base/p2p_transport_channel.cc b/third_party/libwebrtc/p2p/base/p2p_transport_channel.cc
index 0c869ff622..35d7f85d69 100644
--- a/third_party/libwebrtc/p2p/base/p2p_transport_channel.cc
+++ b/third_party/libwebrtc/p2p/base/p2p_transport_channel.cc
@@ -872,20 +872,6 @@ void P2PTransportChannel::MaybeStartGathering() {
SendGatheringStateEvent();
}
- if (!allocator_sessions_.empty()) {
- IceRestartState state;
- if (writable()) {
- state = IceRestartState::CONNECTED;
- } else if (IsGettingPorts()) {
- state = IceRestartState::CONNECTING;
- } else {
- state = IceRestartState::DISCONNECTED;
- }
- RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.IceRestartState",
- static_cast<int>(state),
- static_cast<int>(IceRestartState::MAX_VALUE));
- }
-
for (const auto& session : allocator_sessions_) {
if (session->IsStopped()) {
continue;
@@ -1430,8 +1416,7 @@ bool P2PTransportChannel::CreateConnection(PortInterface* port,
if (ice_field_trials_.skip_relay_to_non_relay_connections) {
if ((port->Type() != remote_candidate.type()) &&
- (port->Type() == RELAY_PORT_TYPE ||
- remote_candidate.type() == RELAY_PORT_TYPE)) {
+ (port->Type() == RELAY_PORT_TYPE || remote_candidate.is_relay())) {
RTC_LOG(LS_INFO) << ToString() << ": skip creating connection "
<< port->Type() << " to " << remote_candidate.type();
return false;
@@ -1722,9 +1707,9 @@ bool P2PTransportChannel::PresumedWritable(const Connection* conn) const {
RTC_DCHECK_RUN_ON(network_thread_);
return (conn->write_state() == Connection::STATE_WRITE_INIT &&
config_.presume_writable_when_fully_relayed &&
- conn->local_candidate().type() == RELAY_PORT_TYPE &&
- (conn->remote_candidate().type() == RELAY_PORT_TYPE ||
- conn->remote_candidate().type() == PRFLX_PORT_TYPE));
+ conn->local_candidate().is_relay() &&
+ (conn->remote_candidate().is_relay() ||
+ conn->remote_candidate().is_prflx()));
}
void P2PTransportChannel::UpdateState() {
@@ -1771,19 +1756,18 @@ bool P2PTransportChannel::PruneConnections(
rtc::NetworkRoute P2PTransportChannel::ConfigureNetworkRoute(
const Connection* conn) {
RTC_DCHECK_RUN_ON(network_thread_);
- return {
- .connected = ReadyToSend(conn),
- .local = CreateRouteEndpointFromCandidate(
- /* local= */ true, conn->local_candidate(),
- /* uses_turn= */
- conn->port()->Type() == RELAY_PORT_TYPE),
- .remote = CreateRouteEndpointFromCandidate(
- /* local= */ false, conn->remote_candidate(),
- /* uses_turn= */ conn->remote_candidate().type() == RELAY_PORT_TYPE),
- .last_sent_packet_id = last_sent_packet_id_,
- .packet_overhead =
- conn->local_candidate().address().ipaddr().overhead() +
- GetProtocolOverhead(conn->local_candidate().protocol())};
+ return {.connected = ReadyToSend(conn),
+ .local = CreateRouteEndpointFromCandidate(
+ /* local= */ true, conn->local_candidate(),
+ /* uses_turn= */
+ conn->port()->Type() == RELAY_PORT_TYPE),
+ .remote = CreateRouteEndpointFromCandidate(
+ /* local= */ false, conn->remote_candidate(),
+ /* uses_turn= */ conn->remote_candidate().is_relay()),
+ .last_sent_packet_id = last_sent_packet_id_,
+ .packet_overhead =
+ conn->local_candidate().address().ipaddr().overhead() +
+ GetProtocolOverhead(conn->local_candidate().protocol())};
}
void P2PTransportChannel::SwitchSelectedConnection(
@@ -2294,7 +2278,7 @@ Candidate P2PTransportChannel::SanitizeRemoteCandidate(
bool use_hostname_address = absl::EndsWith(c.address().hostname(), LOCAL_TLD);
// Remove the address for prflx remote candidates. See
// https://w3c.github.io/webrtc-stats/#dom-rtcicecandidatestats.
- use_hostname_address |= c.type() == PRFLX_PORT_TYPE;
+ use_hostname_address |= c.is_prflx();
return c.ToSanitizedCopy(use_hostname_address,
false /* filter_related_address */);
}
diff --git a/third_party/libwebrtc/p2p/base/p2p_transport_channel.h b/third_party/libwebrtc/p2p/base/p2p_transport_channel.h
index 84325b8bef..47f37c8c67 100644
--- a/third_party/libwebrtc/p2p/base/p2p_transport_channel.h
+++ b/third_party/libwebrtc/p2p/base/p2p_transport_channel.h
@@ -79,10 +79,6 @@ class RtcEventLog;
namespace cricket {
-// Enum for UMA metrics, used to record whether the channel is
-// connected/connecting/disconnected when ICE restart happens.
-enum class IceRestartState { CONNECTING, CONNECTED, DISCONNECTED, MAX_VALUE };
-
static const int MIN_PINGS_AT_WEAK_PING_INTERVAL = 3;
bool IceCredentialsChanged(absl::string_view old_ufrag,
diff --git a/third_party/libwebrtc/p2p/base/p2p_transport_channel_unittest.cc b/third_party/libwebrtc/p2p/base/p2p_transport_channel_unittest.cc
index 44b1bfc5e3..a0446c7965 100644
--- a/third_party/libwebrtc/p2p/base/p2p_transport_channel_unittest.cc
+++ b/third_party/libwebrtc/p2p/base/p2p_transport_channel_unittest.cc
@@ -1478,93 +1478,7 @@ TEST_F(P2PTransportChannelTest, GetStatsSwitchConnection) {
DestroyChannels();
}
-// Tests that UMAs are recorded when ICE restarts while the channel
-// is disconnected.
-TEST_F(P2PTransportChannelTest, TestUMAIceRestartWhileDisconnected) {
- rtc::ScopedFakeClock clock;
- ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts);
-
- CreateChannels();
- EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()),
- kDefaultTimeout, clock);
-
- // Drop all packets so that both channels become not writable.
- fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kPublicAddrs[0]);
- const int kWriteTimeoutDelay = 8000;
- EXPECT_TRUE_SIMULATED_WAIT(!ep1_ch1()->writable() && !ep2_ch1()->writable(),
- kWriteTimeoutDelay, clock);
-
- ep1_ch1()->SetIceParameters(kIceParams[2]);
- ep1_ch1()->SetRemoteIceParameters(kIceParams[3]);
- ep1_ch1()->MaybeStartGathering();
- EXPECT_METRIC_EQ(1, webrtc::metrics::NumEvents(
- "WebRTC.PeerConnection.IceRestartState",
- static_cast<int>(IceRestartState::DISCONNECTED)));
-
- ep2_ch1()->SetIceParameters(kIceParams[3]);
- ep2_ch1()->SetRemoteIceParameters(kIceParams[2]);
- ep2_ch1()->MaybeStartGathering();
- EXPECT_METRIC_EQ(2, webrtc::metrics::NumEvents(
- "WebRTC.PeerConnection.IceRestartState",
- static_cast<int>(IceRestartState::DISCONNECTED)));
-
- DestroyChannels();
-}
-
-// Tests that UMAs are recorded when ICE restarts while the channel
-// is connected.
-TEST_F(P2PTransportChannelTest, TestUMAIceRestartWhileConnected) {
- rtc::ScopedFakeClock clock;
- ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts);
-
- CreateChannels();
- EXPECT_TRUE_SIMULATED_WAIT(CheckConnected(ep1_ch1(), ep2_ch1()),
- kDefaultTimeout, clock);
-
- ep1_ch1()->SetIceParameters(kIceParams[2]);
- ep1_ch1()->SetRemoteIceParameters(kIceParams[3]);
- ep1_ch1()->MaybeStartGathering();
- EXPECT_METRIC_EQ(1, webrtc::metrics::NumEvents(
- "WebRTC.PeerConnection.IceRestartState",
- static_cast<int>(IceRestartState::CONNECTED)));
-
- ep2_ch1()->SetIceParameters(kIceParams[3]);
- ep2_ch1()->SetRemoteIceParameters(kIceParams[2]);
- ep2_ch1()->MaybeStartGathering();
- EXPECT_METRIC_EQ(2, webrtc::metrics::NumEvents(
- "WebRTC.PeerConnection.IceRestartState",
- static_cast<int>(IceRestartState::CONNECTED)));
-
- DestroyChannels();
-}
-
-// Tests that UMAs are recorded when ICE restarts while the channel
-// is connecting.
-TEST_F(P2PTransportChannelTest, TestUMAIceRestartWhileConnecting) {
- rtc::ScopedFakeClock clock;
- ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts);
-
- // Create the channels without waiting for them to become connected.
- CreateChannels();
-
- ep1_ch1()->SetIceParameters(kIceParams[2]);
- ep1_ch1()->SetRemoteIceParameters(kIceParams[3]);
- ep1_ch1()->MaybeStartGathering();
- EXPECT_METRIC_EQ(1, webrtc::metrics::NumEvents(
- "WebRTC.PeerConnection.IceRestartState",
- static_cast<int>(IceRestartState::CONNECTING)));
-
- ep2_ch1()->SetIceParameters(kIceParams[3]);
- ep2_ch1()->SetRemoteIceParameters(kIceParams[2]);
- ep2_ch1()->MaybeStartGathering();
- EXPECT_METRIC_EQ(2, webrtc::metrics::NumEvents(
- "WebRTC.PeerConnection.IceRestartState",
- static_cast<int>(IceRestartState::CONNECTING)));
-
- DestroyChannels();
-}
-
-// Tests that a UMA on ICE regathering is recorded when there is a network
+// Tests that an ICE regathering reason is recorded when there is a network
// change if and only if continual gathering is enabled.
TEST_F(P2PTransportChannelTest,
TestIceRegatheringReasonContinualGatheringByNetworkChange) {
@@ -1600,7 +1514,7 @@ TEST_F(P2PTransportChannelTest,
DestroyChannels();
}
-// Tests that a UMA on ICE regathering is recorded when there is a network
+// Tests that an ICE regathering reason is recorded when there is a network
// failure if and only if continual gathering is enabled.
TEST_F(P2PTransportChannelTest,
TestIceRegatheringReasonContinualGatheringByNetworkFailure) {
@@ -1623,10 +1537,6 @@ TEST_F(P2PTransportChannelTest,
SIMULATED_WAIT(false, kNetworkFailureTimeout, clock);
EXPECT_LE(1, GetEndpoint(0)->GetIceRegatheringCountForReason(
IceRegatheringReason::NETWORK_FAILURE));
- EXPECT_METRIC_LE(
- 1, webrtc::metrics::NumEvents(
- "WebRTC.PeerConnection.IceRegatheringReason",
- static_cast<int>(IceRegatheringReason::NETWORK_FAILURE)));
EXPECT_EQ(0, GetEndpoint(1)->GetIceRegatheringCountForReason(
IceRegatheringReason::NETWORK_FAILURE));
@@ -3601,7 +3511,8 @@ class P2PTransportChannelPingTest : public ::testing::Test,
rtc::ByteBufferWriter buf;
msg.Write(&buf);
conn->OnReadPacket(rtc::ReceivedPacket::CreateFromLegacy(
- buf.Data(), buf.Length(), rtc::TimeMicros()));
+ reinterpret_cast<const char*>(buf.Data()), buf.Length(),
+ rtc::TimeMicros()));
}
void ReceivePingOnConnection(Connection* conn,
@@ -5097,7 +5008,7 @@ class P2PTransportChannelMostLikelyToWorkFirstTest
Connection* conn = FindNextPingableConnectionAndPingIt(channel_.get());
ASSERT_TRUE(conn != nullptr);
EXPECT_EQ(conn->local_candidate().type(), local_candidate_type);
- if (conn->local_candidate().type() == RELAY_PORT_TYPE) {
+ if (conn->local_candidate().is_relay()) {
EXPECT_EQ(conn->local_candidate().relay_protocol(), relay_protocol_type);
}
EXPECT_EQ(conn->remote_candidate().type(), remote_candidate_type);
@@ -5509,7 +5420,7 @@ TEST_F(P2PTransportChannelTest,
for (const auto& candidates_data : GetEndpoint(0)->saved_candidates_) {
const auto& local_candidate_ep1 = candidates_data.candidate;
- if (local_candidate_ep1.type() == LOCAL_PORT_TYPE) {
+ if (local_candidate_ep1.is_local()) {
// This is the underlying private IP address of the same candidate at ep1,
// and let the mock resolver of ep2 receive the correct resolution.
rtc::SocketAddress resolved_address_ep1(local_candidate_ep1.address());
@@ -5538,11 +5449,11 @@ TEST_F(P2PTransportChannelTest,
// Check the stats of ep1 seen by ep1.
for (const auto& connection_info : ice_transport_stats1.connection_infos) {
const auto& local_candidate = connection_info.local_candidate;
- if (local_candidate.type() == LOCAL_PORT_TYPE) {
+ if (local_candidate.is_local()) {
EXPECT_TRUE(local_candidate.address().IsUnresolvedIP());
- } else if (local_candidate.type() == STUN_PORT_TYPE) {
+ } else if (local_candidate.is_stun()) {
EXPECT_TRUE(local_candidate.related_address().IsAnyIP());
- } else if (local_candidate.type() == RELAY_PORT_TYPE) {
+ } else if (local_candidate.is_relay()) {
// The related address of the relay candidate should be equal to the
// srflx address. Note that NAT is not configured, hence the following
// expectation.
@@ -5555,11 +5466,11 @@ TEST_F(P2PTransportChannelTest,
// Check the stats of ep1 seen by ep2.
for (const auto& connection_info : ice_transport_stats2.connection_infos) {
const auto& remote_candidate = connection_info.remote_candidate;
- if (remote_candidate.type() == LOCAL_PORT_TYPE) {
+ if (remote_candidate.is_local()) {
EXPECT_TRUE(remote_candidate.address().IsUnresolvedIP());
- } else if (remote_candidate.type() == STUN_PORT_TYPE) {
+ } else if (remote_candidate.is_stun()) {
EXPECT_TRUE(remote_candidate.related_address().IsAnyIP());
- } else if (remote_candidate.type() == RELAY_PORT_TYPE) {
+ } else if (remote_candidate.is_relay()) {
EXPECT_EQ(kPublicAddrs[0].ipaddr(),
remote_candidate.related_address().ipaddr());
} else {
@@ -5681,7 +5592,7 @@ TEST_F(P2PTransportChannelTest,
ASSERT_EQ_WAIT(1u, GetEndpoint(0)->saved_candidates_.size(), kMediumTimeout);
const auto& candidates_data = GetEndpoint(0)->saved_candidates_[0];
const auto& local_candidate_ep1 = candidates_data.candidate;
- ASSERT_TRUE(local_candidate_ep1.type() == LOCAL_PORT_TYPE);
+ ASSERT_TRUE(local_candidate_ep1.is_local());
// This is the underlying private IP address of the same candidate at ep1,
// and let the mock resolver of ep2 receive the correct resolution.
rtc::SocketAddress resolved_address_ep1(local_candidate_ep1.address());
@@ -5697,12 +5608,12 @@ TEST_F(P2PTransportChannelTest,
const auto pair_ep1 = ep1_ch1()->GetSelectedCandidatePair();
ASSERT_TRUE(pair_ep1.has_value());
- EXPECT_EQ(LOCAL_PORT_TYPE, pair_ep1->local_candidate().type());
+ EXPECT_TRUE(pair_ep1->local_candidate().is_local());
EXPECT_TRUE(pair_ep1->local_candidate().address().IsUnresolvedIP());
const auto pair_ep2 = ep2_ch1()->GetSelectedCandidatePair();
ASSERT_TRUE(pair_ep2.has_value());
- EXPECT_EQ(LOCAL_PORT_TYPE, pair_ep2->remote_candidate().type());
+ EXPECT_TRUE(pair_ep2->remote_candidate().is_local());
EXPECT_TRUE(pair_ep2->remote_candidate().address().IsUnresolvedIP());
DestroyChannels();
diff --git a/third_party/libwebrtc/p2p/base/port.cc b/third_party/libwebrtc/p2p/base/port.cc
index 3069799f7b..a3378fe23a 100644
--- a/third_party/libwebrtc/p2p/base/port.cc
+++ b/third_party/libwebrtc/p2p/base/port.cc
@@ -69,13 +69,6 @@ const int kPortTimeoutDelay = cricket::STUN_TOTAL_TIMEOUT + 5000;
} // namespace
-// TODO(ronghuawu): Use "local", "srflx", "prflx" and "relay". But this requires
-// the signaling part be updated correspondingly as well.
-const char LOCAL_PORT_TYPE[] = "local";
-const char STUN_PORT_TYPE[] = "stun";
-const char PRFLX_PORT_TYPE[] = "prflx";
-const char RELAY_PORT_TYPE[] = "relay";
-
static const char* const PROTO_NAMES[] = {UDP_PROTOCOL_NAME, TCP_PROTOCOL_NAME,
SSLTCP_PROTOCOL_NAME,
TLS_PROTOCOL_NAME};
diff --git a/third_party/libwebrtc/p2p/base/port.h b/third_party/libwebrtc/p2p/base/port.h
index 796e1e1d5b..7b44e534de 100644
--- a/third_party/libwebrtc/p2p/base/port.h
+++ b/third_party/libwebrtc/p2p/base/port.h
@@ -53,11 +53,6 @@
namespace cricket {
-RTC_EXPORT extern const char LOCAL_PORT_TYPE[];
-RTC_EXPORT extern const char STUN_PORT_TYPE[];
-RTC_EXPORT extern const char PRFLX_PORT_TYPE[];
-RTC_EXPORT extern const char RELAY_PORT_TYPE[];
-
// RFC 6544, TCP candidate encoding rules.
extern const int DISCARD_PORT;
extern const char TCPTYPE_ACTIVE_STR[];
diff --git a/third_party/libwebrtc/p2p/base/port_allocator.cc b/third_party/libwebrtc/p2p/base/port_allocator.cc
index 9292319b56..16f5afe36c 100644
--- a/third_party/libwebrtc/p2p/base/port_allocator.cc
+++ b/third_party/libwebrtc/p2p/base/port_allocator.cc
@@ -312,8 +312,7 @@ Candidate PortAllocator::SanitizeCandidate(const Candidate& c) const {
// For a local host candidate, we need to conceal its IP address candidate if
// the mDNS obfuscation is enabled.
bool use_hostname_address =
- (c.type() == LOCAL_PORT_TYPE || c.type() == PRFLX_PORT_TYPE) &&
- MdnsObfuscationEnabled();
+ (c.is_local() || c.is_prflx()) && MdnsObfuscationEnabled();
// If adapter enumeration is disabled or host candidates are disabled,
// clear the raddr of STUN candidates to avoid local address leakage.
bool filter_stun_related_address =
@@ -326,9 +325,9 @@ Candidate PortAllocator::SanitizeCandidate(const Candidate& c) const {
// Sanitize related_address when using MDNS.
bool filter_prflx_related_address = MdnsObfuscationEnabled();
bool filter_related_address =
- ((c.type() == STUN_PORT_TYPE && filter_stun_related_address) ||
- (c.type() == RELAY_PORT_TYPE && filter_turn_related_address) ||
- (c.type() == PRFLX_PORT_TYPE && filter_prflx_related_address));
+ ((c.is_stun() && filter_stun_related_address) ||
+ (c.is_relay() && filter_turn_related_address) ||
+ (c.is_prflx() && filter_prflx_related_address));
return c.ToSanitizedCopy(use_hostname_address, filter_related_address);
}
diff --git a/third_party/libwebrtc/p2p/base/port_unittest.cc b/third_party/libwebrtc/p2p/base/port_unittest.cc
index 96c1bd5ee1..de35d94259 100644
--- a/third_party/libwebrtc/p2p/base/port_unittest.cc
+++ b/third_party/libwebrtc/p2p/base/port_unittest.cc
@@ -257,6 +257,15 @@ class TestPort : public Port {
int type_preference_ = 0;
};
+bool GetStunMessageFromBufferWriter(TestPort* port,
+ ByteBufferWriter* buf,
+ const rtc::SocketAddress& addr,
+ std::unique_ptr<IceMessage>* out_msg,
+ std::string* out_username) {
+ return port->GetStunMessage(reinterpret_cast<const char*>(buf->Data()),
+ buf->Length(), addr, out_msg, out_username);
+}
+
static void SendPingAndReceiveResponse(Connection* lconn,
TestPort* lport,
Connection* rconn,
@@ -620,8 +629,8 @@ class PortTest : public ::testing::Test, public sigslot::has_slots<> {
std::unique_ptr<rtc::NATServer> CreateNatServer(const SocketAddress& addr,
rtc::NATType type) {
- return std::make_unique<rtc::NATServer>(type, ss_.get(), addr, addr,
- ss_.get(), addr);
+ return std::make_unique<rtc::NATServer>(type, main_, ss_.get(), addr, addr,
+ main_, ss_.get(), addr);
}
static const char* StunName(NATType type) {
switch (type) {
@@ -1529,7 +1538,8 @@ TEST_F(PortTest, TestLoopbackCall) {
auto buf = std::make_unique<ByteBufferWriter>();
WriteStunMessage(*modified_req, buf.get());
conn1->OnReadPacket(rtc::ReceivedPacket::CreateFromLegacy(
- buf->Data(), buf->Length(), /*packet_time_us=*/-1));
+ reinterpret_cast<const char*>(buf->Data()), buf->Length(),
+ /*packet_time_us=*/-1));
ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, kDefaultTimeout);
msg = lport->last_stun_msg();
EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE, msg->type());
@@ -2247,8 +2257,8 @@ TEST_F(PortTest, TestHandleStunMessage) {
in_msg->AddMessageIntegrity("rpass");
in_msg->AddFingerprint();
WriteStunMessage(*in_msg, buf.get());
- EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
- &username));
+ EXPECT_TRUE(GetStunMessageFromBufferWriter(port.get(), buf.get(), addr,
+ &out_msg, &username));
EXPECT_TRUE(out_msg.get() != NULL);
EXPECT_EQ("lfrag", username);
@@ -2259,8 +2269,8 @@ TEST_F(PortTest, TestHandleStunMessage) {
in_msg->AddMessageIntegrity("rpass");
in_msg->AddFingerprint();
WriteStunMessage(*in_msg, buf.get());
- EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
- &username));
+ EXPECT_TRUE(GetStunMessageFromBufferWriter(port.get(), buf.get(), addr,
+ &out_msg, &username));
EXPECT_TRUE(out_msg.get() != NULL);
EXPECT_EQ("", username);
@@ -2271,8 +2281,8 @@ TEST_F(PortTest, TestHandleStunMessage) {
STUN_ERROR_REASON_SERVER_ERROR));
in_msg->AddFingerprint();
WriteStunMessage(*in_msg, buf.get());
- EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
- &username));
+ EXPECT_TRUE(GetStunMessageFromBufferWriter(port.get(), buf.get(), addr,
+ &out_msg, &username));
EXPECT_TRUE(out_msg.get() != NULL);
EXPECT_EQ("", username);
ASSERT_TRUE(out_msg->GetErrorCode() != NULL);
@@ -2295,8 +2305,8 @@ TEST_F(PortTest, TestHandleStunMessageBadUsername) {
in_msg->AddMessageIntegrity("rpass");
in_msg->AddFingerprint();
WriteStunMessage(*in_msg, buf.get());
- EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
- &username));
+ EXPECT_TRUE(GetStunMessageFromBufferWriter(port.get(), buf.get(), addr,
+ &out_msg, &username));
EXPECT_TRUE(out_msg.get() == NULL);
EXPECT_EQ("", username);
EXPECT_EQ(STUN_ERROR_BAD_REQUEST, port->last_stun_error_code());
@@ -2306,8 +2316,8 @@ TEST_F(PortTest, TestHandleStunMessageBadUsername) {
in_msg->AddMessageIntegrity("rpass");
in_msg->AddFingerprint();
WriteStunMessage(*in_msg, buf.get());
- EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
- &username));
+ EXPECT_TRUE(GetStunMessageFromBufferWriter(port.get(), buf.get(), addr,
+ &out_msg, &username));
EXPECT_TRUE(out_msg.get() == NULL);
EXPECT_EQ("", username);
EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code());
@@ -2317,8 +2327,8 @@ TEST_F(PortTest, TestHandleStunMessageBadUsername) {
in_msg->AddMessageIntegrity("rpass");
in_msg->AddFingerprint();
WriteStunMessage(*in_msg, buf.get());
- EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
- &username));
+ EXPECT_TRUE(GetStunMessageFromBufferWriter(port.get(), buf.get(), addr,
+ &out_msg, &username));
EXPECT_TRUE(out_msg.get() == NULL);
EXPECT_EQ("", username);
EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code());
@@ -2328,8 +2338,8 @@ TEST_F(PortTest, TestHandleStunMessageBadUsername) {
in_msg->AddMessageIntegrity("rpass");
in_msg->AddFingerprint();
WriteStunMessage(*in_msg, buf.get());
- EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
- &username));
+ EXPECT_TRUE(GetStunMessageFromBufferWriter(port.get(), buf.get(), addr,
+ &out_msg, &username));
EXPECT_TRUE(out_msg.get() == NULL);
EXPECT_EQ("", username);
EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code());
@@ -2339,8 +2349,8 @@ TEST_F(PortTest, TestHandleStunMessageBadUsername) {
in_msg->AddMessageIntegrity("rpass");
in_msg->AddFingerprint();
WriteStunMessage(*in_msg, buf.get());
- EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
- &username));
+ EXPECT_TRUE(GetStunMessageFromBufferWriter(port.get(), buf.get(), addr,
+ &out_msg, &username));
EXPECT_TRUE(out_msg.get() == NULL);
EXPECT_EQ("", username);
EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code());
@@ -2361,8 +2371,8 @@ TEST_F(PortTest, TestHandleStunMessageBadMessageIntegrity) {
in_msg = CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "rfrag:lfrag");
in_msg->AddFingerprint();
WriteStunMessage(*in_msg, buf.get());
- EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
- &username));
+ EXPECT_TRUE(GetStunMessageFromBufferWriter(port.get(), buf.get(), addr,
+ &out_msg, &username));
EXPECT_TRUE(out_msg.get() == NULL);
EXPECT_EQ("", username);
EXPECT_EQ(STUN_ERROR_BAD_REQUEST, port->last_stun_error_code());
@@ -2373,8 +2383,8 @@ TEST_F(PortTest, TestHandleStunMessageBadMessageIntegrity) {
in_msg->AddMessageIntegrity("invalid");
in_msg->AddFingerprint();
WriteStunMessage(*in_msg, buf.get());
- EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
- &username));
+ EXPECT_TRUE(GetStunMessageFromBufferWriter(port.get(), buf.get(), addr,
+ &out_msg, &username));
EXPECT_TRUE(out_msg.get() == NULL);
EXPECT_EQ("", username);
EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code());
@@ -2399,16 +2409,16 @@ TEST_F(PortTest, TestHandleStunMessageBadFingerprint) {
in_msg = CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "rfrag:lfrag");
in_msg->AddMessageIntegrity("rpass");
WriteStunMessage(*in_msg, buf.get());
- EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
- &username));
+ EXPECT_FALSE(GetStunMessageFromBufferWriter(port.get(), buf.get(), addr,
+ &out_msg, &username));
EXPECT_EQ(0, port->last_stun_error_code());
// Now, add a fingerprint, but munge the message so it's not valid.
in_msg->AddFingerprint();
in_msg->SetTransactionIdForTesting("TESTTESTBADD");
WriteStunMessage(*in_msg, buf.get());
- EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
- &username));
+ EXPECT_FALSE(GetStunMessageFromBufferWriter(port.get(), buf.get(), addr,
+ &out_msg, &username));
EXPECT_EQ(0, port->last_stun_error_code());
// Valid BINDING-RESPONSE, except no FINGERPRINT.
@@ -2417,16 +2427,16 @@ TEST_F(PortTest, TestHandleStunMessageBadFingerprint) {
STUN_ATTR_XOR_MAPPED_ADDRESS, kLocalAddr2));
in_msg->AddMessageIntegrity("rpass");
WriteStunMessage(*in_msg, buf.get());
- EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
- &username));
+ EXPECT_FALSE(GetStunMessageFromBufferWriter(port.get(), buf.get(), addr,
+ &out_msg, &username));
EXPECT_EQ(0, port->last_stun_error_code());
// Now, add a fingerprint, but munge the message so it's not valid.
in_msg->AddFingerprint();
in_msg->SetTransactionIdForTesting("TESTTESTBADD");
WriteStunMessage(*in_msg, buf.get());
- EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
- &username));
+ EXPECT_FALSE(GetStunMessageFromBufferWriter(port.get(), buf.get(), addr,
+ &out_msg, &username));
EXPECT_EQ(0, port->last_stun_error_code());
// Valid BINDING-ERROR-RESPONSE, except no FINGERPRINT.
@@ -2436,16 +2446,16 @@ TEST_F(PortTest, TestHandleStunMessageBadFingerprint) {
STUN_ERROR_REASON_SERVER_ERROR));
in_msg->AddMessageIntegrity("rpass");
WriteStunMessage(*in_msg, buf.get());
- EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
- &username));
+ EXPECT_FALSE(GetStunMessageFromBufferWriter(port.get(), buf.get(), addr,
+ &out_msg, &username));
EXPECT_EQ(0, port->last_stun_error_code());
// Now, add a fingerprint, but munge the message so it's not valid.
in_msg->AddFingerprint();
in_msg->SetTransactionIdForTesting("TESTTESTBADD");
WriteStunMessage(*in_msg, buf.get());
- EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
- &username));
+ EXPECT_FALSE(GetStunMessageFromBufferWriter(port.get(), buf.get(), addr,
+ &out_msg, &username));
EXPECT_EQ(0, port->last_stun_error_code());
}
@@ -2472,8 +2482,8 @@ TEST_F(PortTest,
in_msg->AddAttribute(StunAttribute::CreateUInt32(0xdead));
in_msg->AddFingerprint();
WriteStunMessage(*in_msg, buf.get());
- ASSERT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
- &username));
+ ASSERT_TRUE(GetStunMessageFromBufferWriter(port.get(), buf.get(), addr,
+ &out_msg, &username));
IceMessage* error_response = port->last_stun_msg();
ASSERT_NE(nullptr, error_response);
@@ -2522,7 +2532,8 @@ TEST_F(PortTest,
ByteBufferWriter buf;
WriteStunMessage(*modified_response, &buf);
lconn->OnReadPacket(rtc::ReceivedPacket::CreateFromLegacy(
- buf.Data(), buf.Length(), /*packet_time_us=*/-1));
+ reinterpret_cast<const char*>(buf.Data()), buf.Length(),
+ /*packet_time_us=*/-1));
// Response should have been ignored, leaving us unwritable still.
EXPECT_FALSE(lconn->writable());
}
@@ -2570,8 +2581,8 @@ TEST_F(PortTest, TestHandleStunBindingIndication) {
in_msg = CreateStunMessage(STUN_BINDING_INDICATION);
in_msg->AddFingerprint();
WriteStunMessage(*in_msg, buf.get());
- EXPECT_TRUE(lport->GetStunMessage(buf->Data(), buf->Length(), addr, &out_msg,
- &username));
+ EXPECT_TRUE(GetStunMessageFromBufferWriter(lport.get(), buf.get(), addr,
+ &out_msg, &username));
EXPECT_TRUE(out_msg.get() != NULL);
EXPECT_EQ(out_msg->type(), STUN_BINDING_INDICATION);
EXPECT_EQ("", username);
diff --git a/third_party/libwebrtc/p2p/base/pseudo_tcp.cc b/third_party/libwebrtc/p2p/base/pseudo_tcp.cc
index 5a5ce0392b..3aaa2ad065 100644
--- a/third_party/libwebrtc/p2p/base/pseudo_tcp.cc
+++ b/third_party/libwebrtc/p2p/base/pseudo_tcp.cc
@@ -1183,7 +1183,8 @@ void PseudoTcp::queueConnectMessage() {
buf.WriteUInt8(m_rwnd_scale);
}
m_snd_wnd = static_cast<uint32_t>(buf.Length());
- queue(buf.Data(), static_cast<uint32_t>(buf.Length()), true);
+ queue(reinterpret_cast<const char*>(buf.Data()),
+ static_cast<uint32_t>(buf.Length()), true);
}
void PseudoTcp::parseOptions(const char* data, uint32_t len) {
@@ -1212,7 +1213,7 @@ void PseudoTcp::parseOptions(const char* data, uint32_t len) {
// Content of this option.
if (opt_len <= buf.Length()) {
- applyOption(kind, buf.Data(), opt_len);
+ applyOption(kind, reinterpret_cast<const char*>(buf.Data()), opt_len);
buf.Consume(opt_len);
} else {
RTC_LOG(LS_ERROR) << "Invalid option length received.";
diff --git a/third_party/libwebrtc/p2p/base/stun_dictionary.cc b/third_party/libwebrtc/p2p/base/stun_dictionary.cc
index 318bed0ad1..aabb8c394f 100644
--- a/third_party/libwebrtc/p2p/base/stun_dictionary.cc
+++ b/third_party/libwebrtc/p2p/base/stun_dictionary.cc
@@ -214,7 +214,8 @@ StunDictionaryView::ApplyDelta(const StunByteStringAttribute& delta) {
if (attr->value_type() == STUN_VALUE_BYTE_STRING && attr->length() == 0) {
attrs_.erase(attr->type());
} else {
- attrs_[attr->type()] = std::move(attr);
+ int attribute_type = attr->type();
+ attrs_[attribute_type] = std::move(attr);
}
}
}
diff --git a/third_party/libwebrtc/p2p/base/stun_port.cc b/third_party/libwebrtc/p2p/base/stun_port.cc
index 7acaac6dbe..648933fd9e 100644
--- a/third_party/libwebrtc/p2p/base/stun_port.cc
+++ b/third_party/libwebrtc/p2p/base/stun_port.cc
@@ -285,7 +285,7 @@ Connection* UDPPort::CreateConnection(const Candidate& address,
//
// See also the definition of MdnsNameRegistrationStatus::kNotStarted in
// port.h.
- RTC_DCHECK(!SharedSocket() || Candidates()[0].type() == LOCAL_PORT_TYPE ||
+ RTC_DCHECK(!SharedSocket() || Candidates()[0].is_local() ||
mdns_name_registration_status() !=
MdnsNameRegistrationStatus::kNotStarted);
@@ -616,7 +616,7 @@ bool UDPPort::HasStunCandidateWithAddress(
const std::vector<Candidate>& existing_candidates = Candidates();
std::vector<Candidate>::const_iterator it = existing_candidates.begin();
for (; it != existing_candidates.end(); ++it) {
- if (it->type() == STUN_PORT_TYPE && it->address() == addr)
+ if (it->is_stun() && it->address() == addr)
return true;
}
return false;
diff --git a/third_party/libwebrtc/p2p/base/stun_port_unittest.cc b/third_party/libwebrtc/p2p/base/stun_port_unittest.cc
index 04505d26ff..9167081afb 100644
--- a/third_party/libwebrtc/p2p/base/stun_port_unittest.cc
+++ b/third_party/libwebrtc/p2p/base/stun_port_unittest.cc
@@ -445,11 +445,11 @@ TEST_F(StunPortTest, TestStunCandidateGeneratedWithMdnsObfuscationEnabled) {
// One of the generated candidates is a local candidate and the other is a
// stun candidate.
EXPECT_NE(port()->Candidates()[0].type(), port()->Candidates()[1].type());
- if (port()->Candidates()[0].type() == cricket::LOCAL_PORT_TYPE) {
- EXPECT_EQ(port()->Candidates()[1].type(), cricket::STUN_PORT_TYPE);
+ if (port()->Candidates()[0].is_local()) {
+ EXPECT_TRUE(port()->Candidates()[1].is_stun());
} else {
- EXPECT_EQ(port()->Candidates()[0].type(), cricket::STUN_PORT_TYPE);
- EXPECT_EQ(port()->Candidates()[1].type(), cricket::LOCAL_PORT_TYPE);
+ EXPECT_TRUE(port()->Candidates()[0].is_stun());
+ EXPECT_TRUE(port()->Candidates()[1].is_local());
}
}
diff --git a/third_party/libwebrtc/p2p/base/stun_server_unittest.cc b/third_party/libwebrtc/p2p/base/stun_server_unittest.cc
index e4ea30cba4..a2ac300b80 100644
--- a/third_party/libwebrtc/p2p/base/stun_server_unittest.cc
+++ b/third_party/libwebrtc/p2p/base/stun_server_unittest.cc
@@ -44,7 +44,8 @@ class StunServerTest : public ::testing::Test {
void Send(const StunMessage& msg) {
rtc::ByteBufferWriter buf;
msg.Write(&buf);
- Send(buf.Data(), static_cast<int>(buf.Length()));
+ Send(reinterpret_cast<const char*>(buf.Data()),
+ static_cast<int>(buf.Length()));
}
void Send(const char* buf, int len) {
client_->SendTo(buf, len, server_addr);
diff --git a/third_party/libwebrtc/p2p/base/turn_port.cc b/third_party/libwebrtc/p2p/base/turn_port.cc
index e6f5e77114..1fb3b38bfd 100644
--- a/third_party/libwebrtc/p2p/base/turn_port.cc
+++ b/third_party/libwebrtc/p2p/base/turn_port.cc
@@ -228,6 +228,7 @@ TurnPort::TurnPort(TaskQueueBase* thread,
password,
field_trials),
server_address_(server_address),
+ server_url_(ReconstructServerUrl()),
tls_alpn_protocols_(tls_alpn_protocols),
tls_elliptic_curves_(tls_elliptic_curves),
tls_cert_verifier_(tls_cert_verifier),
@@ -271,6 +272,7 @@ TurnPort::TurnPort(TaskQueueBase* thread,
password,
field_trials),
server_address_(server_address),
+ server_url_(ReconstructServerUrl()),
tls_alpn_protocols_(tls_alpn_protocols),
tls_elliptic_curves_(tls_elliptic_curves),
tls_cert_verifier_(tls_cert_verifier),
@@ -583,9 +585,8 @@ Connection* TurnPort::CreateConnection(const Candidate& remote_candidate,
// and TURN candidate later.
for (size_t index = 0; index < Candidates().size(); ++index) {
const Candidate& local_candidate = Candidates()[index];
- if (local_candidate.type() == RELAY_PORT_TYPE &&
- local_candidate.address().family() ==
- remote_candidate.address().family()) {
+ if (local_candidate.is_relay() && local_candidate.address().family() ==
+ remote_candidate.address().family()) {
ProxyConnection* conn =
new ProxyConnection(NewWeakPtr(), index, remote_candidate);
// Create an entry, if needed, so we can get our permissions set up
@@ -886,7 +887,7 @@ void TurnPort::OnAllocateSuccess(const rtc::SocketAddress& address,
ProtoToString(server_address_.proto), // The first hop protocol.
"", // TCP candidate type, empty for turn candidates.
RELAY_PORT_TYPE, GetRelayPreference(server_address_.proto),
- server_priority_, ReconstructedServerUrl(), true);
+ server_priority_, server_url_, true);
}
void TurnPort::OnAllocateError(int error_code, absl::string_view reason) {
@@ -902,9 +903,8 @@ void TurnPort::OnAllocateError(int error_code, absl::string_view reason) {
address.clear();
port = 0;
}
- SignalCandidateError(
- this, IceCandidateErrorEvent(address, port, ReconstructedServerUrl(),
- error_code, reason));
+ SignalCandidateError(this, IceCandidateErrorEvent(address, port, server_url_,
+ error_code, reason));
}
void TurnPort::OnRefreshError() {
@@ -1255,15 +1255,13 @@ bool TurnPort::SetEntryChannelId(const rtc::SocketAddress& address,
return true;
}
-std::string TurnPort::ReconstructedServerUrl() {
- // draft-petithuguenin-behave-turn-uris-01
- // turnURI = scheme ":" turn-host [ ":" turn-port ]
+std::string TurnPort::ReconstructServerUrl() {
+ // https://www.rfc-editor.org/rfc/rfc7065#section-3.1
+ // turnURI = scheme ":" host [ ":" port ]
// [ "?transport=" transport ]
// scheme = "turn" / "turns"
// transport = "udp" / "tcp" / transport-ext
// transport-ext = 1*unreserved
- // turn-host = IP-literal / IPv4address / reg-name
- // turn-port = *DIGIT
std::string scheme = "turn";
std::string transport = "tcp";
switch (server_address_.proto) {
@@ -1278,7 +1276,7 @@ std::string TurnPort::ReconstructedServerUrl() {
break;
}
rtc::StringBuilder url;
- url << scheme << ":" << server_address_.address.hostname() << ":"
+ url << scheme << ":" << server_address_.address.HostAsURIString() << ":"
<< server_address_.address.port() << "?transport=" << transport;
return url.Release();
}
@@ -1802,7 +1800,7 @@ int TurnEntry::Send(const void* data,
// If the channel is bound, we can send the data as a Channel Message.
buf.WriteUInt16(channel_id_);
buf.WriteUInt16(static_cast<uint16_t>(size));
- buf.WriteBytes(reinterpret_cast<const char*>(data), size);
+ buf.WriteBytes(reinterpret_cast<const uint8_t*>(data), size);
}
rtc::PacketOptions modified_options(options);
modified_options.info_signaled_after_sent.turn_overhead_bytes =
diff --git a/third_party/libwebrtc/p2p/base/turn_port.h b/third_party/libwebrtc/p2p/base/turn_port.h
index 686edaf595..b56f862e61 100644
--- a/third_party/libwebrtc/p2p/base/turn_port.h
+++ b/third_party/libwebrtc/p2p/base/turn_port.h
@@ -302,9 +302,6 @@ class TurnPort : public Port {
// pruned (a.k.a. write-timed-out). Returns true if a connection is found.
bool FailAndPruneConnection(const rtc::SocketAddress& address);
- // Reconstruct the URL of the server which the candidate is gathered from.
- std::string ReconstructedServerUrl();
-
void MaybeAddTurnLoggingId(StunMessage* message);
void TurnCustomizerMaybeModifyOutgoingStunMessage(StunMessage* message);
@@ -313,6 +310,12 @@ class TurnPort : public Port {
bool payload);
ProtocolAddress server_address_;
+ // Reconstruct the URL of the server which the candidate is gathered from.
+ // A copy needs to be stored as server_address_ will resolve and clear its
+ // hostname field.
+ std::string ReconstructServerUrl();
+ std::string server_url_;
+
TlsCertPolicy tls_cert_policy_ = TlsCertPolicy::TLS_CERT_POLICY_SECURE;
std::vector<std::string> tls_alpn_protocols_;
std::vector<std::string> tls_elliptic_curves_;
diff --git a/third_party/libwebrtc/p2p/base/turn_port_unittest.cc b/third_party/libwebrtc/p2p/base/turn_port_unittest.cc
index e7efb5e594..169467d76c 100644
--- a/third_party/libwebrtc/p2p/base/turn_port_unittest.cc
+++ b/third_party/libwebrtc/p2p/base/turn_port_unittest.cc
@@ -878,9 +878,10 @@ TEST_F(TurnPortTest, TestReconstructedServerUrlForUdpIPv6) {
turn_server_.AddInternalSocket(kTurnUdpIPv6IntAddr, PROTO_UDP);
CreateTurnPort(kLocalIPv6Addr, kTurnUsername, kTurnPassword,
kTurnUdpIPv6ProtoAddr);
+ // Should add [] around the IPv6.
TestReconstructedServerUrl(
PROTO_UDP,
- "turn:2400:4030:1:2c00:be30:abcd:efab:cdef:3478?transport=udp");
+ "turn:[2400:4030:1:2c00:be30:abcd:efab:cdef]:3478?transport=udp");
}
TEST_F(TurnPortTest, TestReconstructedServerUrlForTcp) {
diff --git a/third_party/libwebrtc/p2p/base/turn_server.cc b/third_party/libwebrtc/p2p/base/turn_server.cc
index 3d633110a7..4e039dec24 100644
--- a/third_party/libwebrtc/p2p/base/turn_server.cc
+++ b/third_party/libwebrtc/p2p/base/turn_server.cc
@@ -784,8 +784,7 @@ void TurnServerAllocation::OnExternalPacket(rtc::AsyncPacketSocket* socket,
rtc::ByteBufferWriter buf;
buf.WriteUInt16(channel->id);
buf.WriteUInt16(static_cast<uint16_t>(packet.payload().size()));
- buf.WriteBytes(reinterpret_cast<const char*>(packet.payload().data()),
- packet.payload().size());
+ buf.WriteBytes(packet.payload().data(), packet.payload().size());
server_->Send(&conn_, buf);
} else if (!server_->enable_permission_checks_ ||
HasPermission(packet.source_address().ipaddr())) {
diff --git a/third_party/libwebrtc/p2p/client/basic_port_allocator.cc b/third_party/libwebrtc/p2p/client/basic_port_allocator.cc
index e8255f1fd5..e95033efeb 100644
--- a/third_party/libwebrtc/p2p/client/basic_port_allocator.cc
+++ b/third_party/libwebrtc/p2p/client/basic_port_allocator.cc
@@ -126,11 +126,15 @@ bool IsAllowedByCandidateFilter(const Candidate& c, uint32_t filter) {
return false;
}
- if (c.type() == RELAY_PORT_TYPE) {
+ if (c.is_relay()) {
return ((filter & CF_RELAY) != 0);
- } else if (c.type() == STUN_PORT_TYPE) {
+ }
+
+ if (c.is_stun()) {
return ((filter & CF_REFLEXIVE) != 0);
- } else if (c.type() == LOCAL_PORT_TYPE) {
+ }
+
+ if (c.is_local()) {
if ((filter & CF_REFLEXIVE) && !c.address().IsPrivateIP()) {
// We allow host candidates if the filter allows server-reflexive
// candidates and the candidate is a public IP. Because we don't generate
@@ -143,6 +147,7 @@ bool IsAllowedByCandidateFilter(const Candidate& c, uint32_t filter) {
return ((filter & CF_HOST) != 0);
}
+
return false;
}
@@ -199,21 +204,6 @@ BasicPortAllocator::BasicPortAllocator(
webrtc::NO_PRUNE, nullptr);
}
-void BasicPortAllocator::OnIceRegathering(PortAllocatorSession* session,
- IceRegatheringReason reason) {
- // If the session has not been taken by an active channel, do not report the
- // metric.
- for (auto& allocator_session : pooled_sessions()) {
- if (allocator_session.get() == session) {
- return;
- }
- }
-
- RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.IceRegatheringReason",
- static_cast<int>(reason),
- static_cast<int>(IceRegatheringReason::MAX_VALUE));
-}
-
BasicPortAllocator::~BasicPortAllocator() {
CheckRunOnValidThreadIfInitialized();
// Our created port allocator sessions depend on us, so destroy our remaining
@@ -251,12 +241,9 @@ PortAllocatorSession* BasicPortAllocator::CreateSessionInternal(
absl::string_view ice_ufrag,
absl::string_view ice_pwd) {
CheckRunOnValidThreadAndInitialized();
- PortAllocatorSession* session = new BasicPortAllocatorSession(
- this, std::string(content_name), component, std::string(ice_ufrag),
- std::string(ice_pwd));
- session->SignalIceRegathering.connect(this,
- &BasicPortAllocator::OnIceRegathering);
- return session;
+ return new BasicPortAllocatorSession(this, std::string(content_name),
+ component, std::string(ice_ufrag),
+ std::string(ice_pwd));
}
void BasicPortAllocator::AddTurnServerForTesting(
diff --git a/third_party/libwebrtc/p2p/client/basic_port_allocator.h b/third_party/libwebrtc/p2p/client/basic_port_allocator.h
index 643904ab27..25201fd016 100644
--- a/third_party/libwebrtc/p2p/client/basic_port_allocator.h
+++ b/third_party/libwebrtc/p2p/client/basic_port_allocator.h
@@ -85,9 +85,6 @@ class RTC_EXPORT BasicPortAllocator : public PortAllocator {
}
private:
- void OnIceRegathering(PortAllocatorSession* session,
- IceRegatheringReason reason);
-
bool MdnsObfuscationEnabled() const override;
webrtc::AlwaysValidPointer<const webrtc::FieldTrialsView,
diff --git a/third_party/libwebrtc/p2p/client/basic_port_allocator_unittest.cc b/third_party/libwebrtc/p2p/client/basic_port_allocator_unittest.cc
index defcab01c9..0c3bf6bc23 100644
--- a/third_party/libwebrtc/p2p/client/basic_port_allocator_unittest.cc
+++ b/third_party/libwebrtc/p2p/client/basic_port_allocator_unittest.cc
@@ -496,8 +496,8 @@ class BasicPortAllocatorTestBase : public ::testing::Test,
bool with_nat) {
if (with_nat) {
nat_server_.reset(new rtc::NATServer(
- rtc::NAT_OPEN_CONE, vss_.get(), kNatUdpAddr, kNatTcpAddr, vss_.get(),
- rtc::SocketAddress(kNatUdpAddr.ipaddr(), 0)));
+ rtc::NAT_OPEN_CONE, thread_, vss_.get(), kNatUdpAddr, kNatTcpAddr,
+ thread_, vss_.get(), rtc::SocketAddress(kNatUdpAddr.ipaddr(), 0)));
} else {
nat_socket_factory_ =
std::make_unique<rtc::BasicPacketSocketFactory>(fss_.get());
@@ -2390,24 +2390,6 @@ TEST_F(BasicPortAllocatorTest,
expected_stun_keepalive_interval);
}
-TEST_F(BasicPortAllocatorTest, IceRegatheringMetricsLoggedWhenNetworkChanges) {
- // Only test local ports to simplify test.
- ResetWithNoServersOrNat();
- AddInterface(kClientAddr, "test_net0");
- ASSERT_TRUE(CreateSession(ICE_CANDIDATE_COMPONENT_RTP));
- session_->StartGettingPorts();
- EXPECT_TRUE_SIMULATED_WAIT(candidate_allocation_done_,
- kDefaultAllocationTimeout, fake_clock);
- candidate_allocation_done_ = false;
- AddInterface(kClientAddr2, "test_net1");
- EXPECT_TRUE_SIMULATED_WAIT(candidate_allocation_done_,
- kDefaultAllocationTimeout, fake_clock);
- EXPECT_METRIC_EQ(1,
- webrtc::metrics::NumEvents(
- "WebRTC.PeerConnection.IceRegatheringReason",
- static_cast<int>(IceRegatheringReason::NETWORK_CHANGE)));
-}
-
// Test that when an mDNS responder is present, the local address of a host
// candidate is concealed by an mDNS hostname and the related address of a srflx
// candidate is set to 0.0.0.0 or ::0.
@@ -2435,7 +2417,7 @@ TEST_F(BasicPortAllocatorTest, HostCandidateAddressIsReplacedByHostname) {
for (const auto& candidate : candidates_) {
const auto& raddr = candidate.related_address();
- if (candidate.type() == LOCAL_PORT_TYPE) {
+ if (candidate.is_local()) {
EXPECT_FALSE(candidate.address().hostname().empty());
EXPECT_TRUE(raddr.IsNil());
if (candidate.protocol() == UDP_PROTOCOL_NAME) {
@@ -2443,13 +2425,13 @@ TEST_F(BasicPortAllocatorTest, HostCandidateAddressIsReplacedByHostname) {
} else {
++num_host_tcp_candidates;
}
- } else if (candidate.type() == STUN_PORT_TYPE) {
+ } else if (candidate.is_stun()) {
// For a srflx candidate, the related address should be set to 0.0.0.0 or
// ::0
EXPECT_TRUE(IPIsAny(raddr.ipaddr()));
EXPECT_EQ(raddr.port(), 0);
++num_srflx_candidates;
- } else if (candidate.type() == RELAY_PORT_TYPE) {
+ } else if (candidate.is_relay()) {
EXPECT_EQ(kNatUdpAddr.ipaddr(), raddr.ipaddr());
EXPECT_EQ(kNatUdpAddr.family(), raddr.family());
++num_relay_candidates;
diff --git a/third_party/libwebrtc/p2p/stunprober/stun_prober.cc b/third_party/libwebrtc/p2p/stunprober/stun_prober.cc
index c60e7ede89..d130d780dc 100644
--- a/third_party/libwebrtc/p2p/stunprober/stun_prober.cc
+++ b/third_party/libwebrtc/p2p/stunprober/stun_prober.cc
@@ -156,8 +156,8 @@ void StunProber::Requester::SendStunRequest() {
// request timing could become too complicated. Callback is ignored by passing
// empty AsyncCallback.
rtc::PacketOptions options;
- int rv = socket_->SendTo(const_cast<char*>(request_packet->Data()),
- request_packet->Length(), addr, options);
+ int rv = socket_->SendTo(request_packet->Data(), request_packet->Length(),
+ addr, options);
if (rv < 0) {
prober_->ReportOnFinished(WRITE_FAILED);
return;
diff --git a/third_party/libwebrtc/pc/BUILD.gn b/third_party/libwebrtc/pc/BUILD.gn
index e9549cdfd8..e351748485 100644
--- a/third_party/libwebrtc/pc/BUILD.gn
+++ b/third_party/libwebrtc/pc/BUILD.gn
@@ -16,7 +16,6 @@
# - rtc_pc
# - session_description
# - simulcast_description
-# - peerconnection
# - sdp_utils
# - media_stream_observer
# - video_track_source
@@ -736,143 +735,6 @@ rtc_library("media_protocol_names") {
absl_deps = [ "//third_party/abseil-cpp/absl/strings" ]
}
-rtc_source_set("peerconnection") {
- # TODO(bugs.webrtc.org/13661): Reduce visibility if possible
- visibility = [ "*" ] # Used by Chromium and others
- allow_poison = [ "environment_construction" ]
- cflags = []
- sources = []
-
- deps = [
- ":audio_rtp_receiver",
- ":audio_track",
- ":connection_context",
- ":data_channel_controller",
- ":data_channel_utils",
- ":dtmf_sender",
- ":ice_server_parsing",
- ":jitter_buffer_delay",
- ":jsep_ice_candidate",
- ":jsep_session_description",
- ":legacy_stats_collector",
- ":legacy_stats_collector_interface",
- ":local_audio_source",
- ":media_protocol_names",
- ":media_stream",
- ":media_stream_observer",
- ":peer_connection",
- ":peer_connection_factory",
- ":peer_connection_internal",
- ":peer_connection_message_handler",
- ":proxy",
- ":remote_audio_source",
- ":rtc_stats_collector",
- ":rtc_stats_traversal",
- ":rtp_parameters_conversion",
- ":rtp_receiver",
- ":rtp_sender",
- ":rtp_transceiver",
- ":rtp_transmission_manager",
- ":sctp_data_channel",
- ":sdp_offer_answer",
- ":sdp_state_provider",
- ":sdp_utils",
- ":session_description",
- ":simulcast_description",
- ":simulcast_sdp_serializer",
- ":stream_collection",
- ":track_media_info_map",
- ":transceiver_list",
- ":usage_pattern",
- ":video_rtp_receiver",
- ":video_track",
- ":video_track_source",
- ":webrtc_sdp",
- ":webrtc_session_description_factory",
- "../api:array_view",
- "../api:async_dns_resolver",
- "../api:audio_options_api",
- "../api:call_api",
- "../api:callfactory_api",
- "../api:fec_controller_api",
- "../api:field_trials_view",
- "../api:frame_transformer_interface",
- "../api:ice_transport_factory",
- "../api:libjingle_logging_api",
- "../api:libjingle_peerconnection_api",
- "../api:media_stream_interface",
- "../api:network_state_predictor_api",
- "../api:packet_socket_factory",
- "../api:priority",
- "../api:rtc_error",
- "../api:rtc_event_log_output_file",
- "../api:rtc_stats_api",
- "../api:rtp_parameters",
- "../api:rtp_transceiver_direction",
- "../api:scoped_refptr",
- "../api:sequence_checker",
- "../api/adaptation:resource_adaptation_api",
- "../api/audio_codecs:audio_codecs_api",
- "../api/crypto:frame_decryptor_interface",
- "../api/crypto:options",
- "../api/neteq:neteq_api",
- "../api/rtc_event_log",
- "../api/task_queue",
- "../api/task_queue:pending_task_safety_flag",
- "../api/transport:bitrate_settings",
- "../api/transport:datagram_transport_interface",
- "../api/transport:enums",
- "../api/transport:field_trial_based_config",
- "../api/transport:network_control",
- "../api/transport:sctp_transport_factory_interface",
- "../api/units:data_rate",
- "../api/video:builtin_video_bitrate_allocator_factory",
- "../api/video:video_bitrate_allocator_factory",
- "../api/video:video_codec_constants",
- "../api/video:video_frame",
- "../api/video:video_rtp_headers",
- "../api/video_codecs:video_codecs_api",
- "../call:call_interfaces",
- "../call:rtp_interfaces",
- "../call:rtp_sender",
- "../common_video",
- "../logging:ice_log",
- "../media:rtc_data_sctp_transport_internal",
- "../media:rtc_media_base",
- "../media:rtc_media_config",
- "../modules/audio_processing:audio_processing_statistics",
- "../modules/rtp_rtcp:rtp_rtcp_format",
- "../p2p:rtc_p2p",
- "../rtc_base:callback_list",
- "../rtc_base:checks",
- "../rtc_base:ip_address",
- "../rtc_base:network_constants",
- "../rtc_base:rtc_operations_chain",
- "../rtc_base:safe_minmax",
- "../rtc_base:socket_address",
- "../rtc_base:threading",
- "../rtc_base:weak_ptr",
- "../rtc_base/experiments:field_trial_parser",
- "../rtc_base/network:sent_packet",
- "../rtc_base/synchronization:mutex",
- "../rtc_base/system:file_wrapper",
- "../rtc_base/system:no_unique_address",
- "../rtc_base/system:rtc_export",
- "../rtc_base/system:unused",
- "../rtc_base/third_party/base64",
- "../rtc_base/third_party/sigslot",
- "../stats",
- "../system_wrappers",
- "../system_wrappers:field_trial",
- "../system_wrappers:metrics",
- ]
- absl_deps = [
- "//third_party/abseil-cpp/absl/algorithm:container",
- "//third_party/abseil-cpp/absl/strings",
- "//third_party/abseil-cpp/absl/types:optional",
- ]
-}
-
rtc_library("sctp_data_channel") {
visibility = [ ":*" ]
sources = [
@@ -930,8 +792,6 @@ rtc_library("connection_context") {
]
deps = [
":media_factory",
- "../api:callfactory_api",
- "../api:field_trials_view",
"../api:libjingle_peerconnection_api",
"../api:media_stream_interface",
"../api:refcountedbase",
@@ -939,7 +799,6 @@ rtc_library("connection_context") {
"../api:sequence_checker",
"../api/environment",
"../api/neteq:neteq_api",
- "../api/transport:field_trial_based_config",
"../api/transport:sctp_transport_factory_interface",
"../media:rtc_data_sctp_transport_factory",
"../media:rtc_media_base",
@@ -1512,7 +1371,6 @@ rtc_source_set("peer_connection_factory") {
":peer_connection_factory_proxy",
":peer_connection_proxy",
"../api:audio_options_api",
- "../api:callfactory_api",
"../api:fec_controller_api",
"../api:field_trials_view",
"../api:ice_transport_interface",
@@ -2064,14 +1922,19 @@ rtc_source_set("legacy_stats_collector_interface") {
]
}
+# This target contains the libraries that are required in order to get an
+# usable peerconnection-using binary.
rtc_source_set("libjingle_peerconnection") {
# TODO(bugs.webrtc.org/13661): Reduce visibility if possible
visibility = [ "*" ] # Used by Chrome and others
allow_poison = [ "environment_construction" ]
deps = [
- ":peerconnection",
+ ":jsep_session_description",
+ ":peer_connection_factory",
+ ":rtc_stats_collector",
"../api:libjingle_peerconnection_api",
+ "../stats",
]
}
@@ -2119,7 +1982,6 @@ if (rtc_include_tests && !build_with_chromium) {
":media_protocol_names",
":media_session",
":pc_test_utils",
- ":peerconnection",
":rtc_pc",
":rtcp_mux_filter",
":rtp_media_utils",
@@ -2220,7 +2082,6 @@ if (rtc_include_tests && !build_with_chromium) {
deps = [
":pc_test_utils",
":peer_connection",
- ":peerconnection",
":peerconnection_wrapper",
"../api:audio_options_api",
"../api:create_peerconnection_factory",
@@ -2273,7 +2134,6 @@ if (rtc_include_tests && !build_with_chromium) {
]
deps = [
":pc_test_utils",
- ":peerconnection",
":sdp_utils",
"../api:function_view",
"../api:libjingle_peerconnection_api",
@@ -2419,7 +2279,6 @@ if (rtc_include_tests && !build_with_chromium) {
":webrtc_sdp",
"../api:array_view",
"../api:audio_options_api",
- "../api:callfactory_api",
"../api:candidate",
"../api:create_peerconnection_factory",
"../api:dtls_transport_interface",
@@ -2632,7 +2491,6 @@ if (rtc_include_tests && !build_with_chromium) {
":peer_connection",
":peer_connection_factory",
":peer_connection_proxy",
- ":peerconnection",
":remote_audio_source",
":rtp_media_utils",
":rtp_parameters_conversion",
@@ -2791,7 +2649,6 @@ if (rtc_include_tests && !build_with_chromium) {
":jitter_buffer_delay",
":libjingle_peerconnection",
":peer_connection_internal",
- ":peerconnection",
":rtp_receiver",
":rtp_sender",
":sctp_data_channel",
diff --git a/third_party/libwebrtc/pc/connection_context.cc b/third_party/libwebrtc/pc/connection_context.cc
index df4522bf13..56a6e91869 100644
--- a/third_party/libwebrtc/pc/connection_context.cc
+++ b/third_party/libwebrtc/pc/connection_context.cc
@@ -15,7 +15,6 @@
#include <vector>
#include "api/environment/environment.h"
-#include "api/transport/field_trial_based_config.h"
#include "media/base/media_engine.h"
#include "media/sctp/sctp_transport_factory.h"
#include "pc/media_factory.h"
@@ -63,8 +62,7 @@ rtc::Thread* MaybeWrapThread(rtc::Thread* signaling_thread,
std::unique_ptr<SctpTransportFactoryInterface> MaybeCreateSctpFactory(
std::unique_ptr<SctpTransportFactoryInterface> factory,
- rtc::Thread* network_thread,
- const FieldTrialsView& field_trials) {
+ rtc::Thread* network_thread) {
if (factory) {
return factory;
}
@@ -113,8 +111,7 @@ ConnectionContext::ConnectionContext(
default_socket_factory_(std::move(dependencies->packet_socket_factory)),
sctp_factory_(
MaybeCreateSctpFactory(std::move(dependencies->sctp_factory),
- network_thread(),
- env_.field_trials())),
+ network_thread())),
use_rtx_(true) {
RTC_DCHECK_RUN_ON(signaling_thread_);
RTC_DCHECK(!(default_network_manager_ && network_monitor_factory_))
diff --git a/third_party/libwebrtc/pc/connection_context.h b/third_party/libwebrtc/pc/connection_context.h
index 893a3b0e52..28b2d1cdd5 100644
--- a/third_party/libwebrtc/pc/connection_context.h
+++ b/third_party/libwebrtc/pc/connection_context.h
@@ -14,9 +14,7 @@
#include <memory>
#include <string>
-#include "api/call/call_factory_interface.h"
#include "api/environment/environment.h"
-#include "api/field_trials_view.h"
#include "api/media_stream_interface.h"
#include "api/peer_connection_interface.h"
#include "api/ref_counted_base.h"
@@ -81,12 +79,6 @@ class ConnectionContext final
// but they are not supposed to change after creating the PeerConnection.
const Environment& env() const { return env_; }
- // Field trials associated with the PeerConnectionFactory.
- // Note: that there can be different field trials for different
- // PeerConnections (but they are not supposed change after creating the
- // PeerConnection).
- const FieldTrialsView& field_trials() const { return env_.field_trials(); }
-
// Accessors only used from the PeerConnectionFactory class
rtc::NetworkManager* default_network_manager() {
RTC_DCHECK_RUN_ON(signaling_thread_);
diff --git a/third_party/libwebrtc/pc/dtls_transport.cc b/third_party/libwebrtc/pc/dtls_transport.cc
index 15eed9e47b..4888d9f9e7 100644
--- a/third_party/libwebrtc/pc/dtls_transport.cc
+++ b/third_party/libwebrtc/pc/dtls_transport.cc
@@ -41,8 +41,18 @@ DtlsTransport::DtlsTransport(
}
DtlsTransport::~DtlsTransport() {
+ // TODO(tommi): Due to a reference being held by the RtpSenderBase
+ // implementation, the last reference to the `DtlsTransport` instance can
+ // be released on the signaling thread.
+ // RTC_DCHECK_RUN_ON(owner_thread_);
+
// We depend on the signaling thread to call Clear() before dropping
// its last reference to this object.
+
+ // If there are non `owner_thread_` references outstanding, and those
+ // references are the last ones released, we depend on Clear() having been
+ // called from the owner_thread before the last reference is deleted.
+ // `Clear()` is currently called from `JsepTransport::~JsepTransport`.
RTC_DCHECK(owner_thread_->IsCurrent() || !internal_dtls_transport_);
}
@@ -72,14 +82,8 @@ void DtlsTransport::Clear() {
RTC_DCHECK(internal());
bool must_send_event =
(internal()->dtls_state() != DtlsTransportState::kClosed);
- // The destructor of cricket::DtlsTransportInternal calls back
- // into DtlsTransport, so we can't hold the lock while releasing.
- std::unique_ptr<cricket::DtlsTransportInternal> transport_to_release;
- {
- MutexLock lock(&lock_);
- transport_to_release = std::move(internal_dtls_transport_);
- ice_transport_->Clear();
- }
+ internal_dtls_transport_.reset();
+ ice_transport_->Clear();
UpdateInformation();
if (observer_ && must_send_event) {
observer_->OnStateChange(Information());
@@ -100,7 +104,6 @@ void DtlsTransport::OnInternalDtlsState(
void DtlsTransport::UpdateInformation() {
RTC_DCHECK_RUN_ON(owner_thread_);
- MutexLock lock(&lock_);
if (internal_dtls_transport_) {
if (internal_dtls_transport_->dtls_state() ==
DtlsTransportState::kConnected) {
@@ -125,23 +128,24 @@ void DtlsTransport::UpdateInformation() {
success &= internal_dtls_transport_->GetSslCipherSuite(&ssl_cipher_suite);
success &= internal_dtls_transport_->GetSrtpCryptoSuite(&srtp_cipher);
if (success) {
- info_ = DtlsTransportInformation(
+ set_info(DtlsTransportInformation(
internal_dtls_transport_->dtls_state(), role, tls_version,
ssl_cipher_suite, srtp_cipher,
- internal_dtls_transport_->GetRemoteSSLCertChain());
+ internal_dtls_transport_->GetRemoteSSLCertChain()));
} else {
RTC_LOG(LS_ERROR) << "DtlsTransport in connected state has incomplete "
"TLS information";
- info_ = DtlsTransportInformation(
+ set_info(DtlsTransportInformation(
internal_dtls_transport_->dtls_state(), role, absl::nullopt,
absl::nullopt, absl::nullopt,
- internal_dtls_transport_->GetRemoteSSLCertChain());
+ internal_dtls_transport_->GetRemoteSSLCertChain()));
}
} else {
- info_ = DtlsTransportInformation(internal_dtls_transport_->dtls_state());
+ set_info(
+ DtlsTransportInformation(internal_dtls_transport_->dtls_state()));
}
} else {
- info_ = DtlsTransportInformation(DtlsTransportState::kClosed);
+ set_info(DtlsTransportInformation(DtlsTransportState::kClosed));
}
}
diff --git a/third_party/libwebrtc/pc/dtls_transport.h b/third_party/libwebrtc/pc/dtls_transport.h
index cca4cc980a..a1893297e6 100644
--- a/third_party/libwebrtc/pc/dtls_transport.h
+++ b/third_party/libwebrtc/pc/dtls_transport.h
@@ -12,6 +12,7 @@
#define PC_DTLS_TRANSPORT_H_
#include <memory>
+#include <utility>
#include "api/dtls_transport_interface.h"
#include "api/ice_transport_interface.h"
@@ -40,18 +41,22 @@ class DtlsTransport : public DtlsTransportInterface {
std::unique_ptr<cricket::DtlsTransportInternal> internal);
rtc::scoped_refptr<IceTransportInterface> ice_transport() override;
+
+ // Currently called from the signaling thread and potentially Chromium's
+ // JS thread.
DtlsTransportInformation Information() override;
+
void RegisterObserver(DtlsTransportObserverInterface* observer) override;
void UnregisterObserver() override;
void Clear();
cricket::DtlsTransportInternal* internal() {
- MutexLock lock(&lock_);
+ RTC_DCHECK_RUN_ON(owner_thread_);
return internal_dtls_transport_.get();
}
const cricket::DtlsTransportInternal* internal() const {
- MutexLock lock(&lock_);
+ RTC_DCHECK_RUN_ON(owner_thread_);
return internal_dtls_transport_.get();
}
@@ -63,12 +68,19 @@ class DtlsTransport : public DtlsTransportInterface {
DtlsTransportState state);
void UpdateInformation();
+ // Called when changing `info_`. We only change the values from the
+ // `owner_thread_` (a.k.a. the network thread).
+ void set_info(DtlsTransportInformation&& info) RTC_RUN_ON(owner_thread_) {
+ MutexLock lock(&lock_);
+ info_ = std::move(info);
+ }
+
DtlsTransportObserverInterface* observer_ = nullptr;
rtc::Thread* owner_thread_;
mutable Mutex lock_;
DtlsTransportInformation info_ RTC_GUARDED_BY(lock_);
std::unique_ptr<cricket::DtlsTransportInternal> internal_dtls_transport_
- RTC_GUARDED_BY(lock_);
+ RTC_GUARDED_BY(owner_thread_);
const rtc::scoped_refptr<IceTransportWithPointer> ice_transport_;
};
diff --git a/third_party/libwebrtc/pc/jsep_session_description.cc b/third_party/libwebrtc/pc/jsep_session_description.cc
index 885c1eb310..7fae4459ec 100644
--- a/third_party/libwebrtc/pc/jsep_session_description.cc
+++ b/third_party/libwebrtc/pc/jsep_session_description.cc
@@ -26,37 +26,15 @@
#include "rtc_base/net_helper.h"
#include "rtc_base/socket_address.h"
+using cricket::Candidate;
using cricket::SessionDescription;
namespace webrtc {
namespace {
-// RFC 5245
-// It is RECOMMENDED that default candidates be chosen based on the
-// likelihood of those candidates to work with the peer that is being
-// contacted. It is RECOMMENDED that relayed > reflexive > host.
-constexpr int kPreferenceUnknown = 0;
-constexpr int kPreferenceHost = 1;
-constexpr int kPreferenceReflexive = 2;
-constexpr int kPreferenceRelayed = 3;
-
constexpr char kDummyAddress[] = "0.0.0.0";
constexpr int kDummyPort = 9;
-int GetCandidatePreferenceFromType(const std::string& type) {
- int preference = kPreferenceUnknown;
- if (type == cricket::LOCAL_PORT_TYPE) {
- preference = kPreferenceHost;
- } else if (type == cricket::STUN_PORT_TYPE) {
- preference = kPreferenceReflexive;
- } else if (type == cricket::RELAY_PORT_TYPE) {
- preference = kPreferenceRelayed;
- } else {
- preference = kPreferenceUnknown;
- }
- return preference;
-}
-
// Update the connection address for the MediaContentDescription based on the
// candidates.
void UpdateConnectionAddress(
@@ -65,7 +43,7 @@ void UpdateConnectionAddress(
int port = kDummyPort;
std::string ip = kDummyAddress;
std::string hostname;
- int current_preference = kPreferenceUnknown;
+ int current_preference = 0; // Start with lowest preference.
int current_family = AF_UNSPEC;
for (size_t i = 0; i < candidate_collection.count(); ++i) {
const IceCandidateInterface* jsep_candidate = candidate_collection.at(i);
@@ -77,8 +55,7 @@ void UpdateConnectionAddress(
if (jsep_candidate->candidate().protocol() != cricket::UDP_PROTOCOL_NAME) {
continue;
}
- const int preference =
- GetCandidatePreferenceFromType(jsep_candidate->candidate().type());
+ const int preference = jsep_candidate->candidate().type_preference();
const int family = jsep_candidate->candidate().address().ipaddr().family();
// See if this candidate is more preferable then the current one if it's the
// same family. Or if the current family is IPv4 already so we could safely
@@ -253,7 +230,7 @@ bool JsepSessionDescription::AddCandidate(
return false;
}
- cricket::Candidate updated_candidate = candidate->candidate();
+ Candidate updated_candidate = candidate->candidate();
if (updated_candidate.username().empty()) {
updated_candidate.set_username(transport_info->description.ice_ufrag);
}
@@ -278,7 +255,7 @@ bool JsepSessionDescription::AddCandidate(
}
size_t JsepSessionDescription::RemoveCandidates(
- const std::vector<cricket::Candidate>& candidates) {
+ const std::vector<Candidate>& candidates) {
size_t num_removed = 0;
for (auto& candidate : candidates) {
int mediasection_index = GetMediasectionIndex(candidate);
@@ -352,8 +329,7 @@ bool JsepSessionDescription::GetMediasectionIndex(
return true;
}
-int JsepSessionDescription::GetMediasectionIndex(
- const cricket::Candidate& candidate) {
+int JsepSessionDescription::GetMediasectionIndex(const Candidate& candidate) {
// Find the description with a matching transport name of the candidate.
const std::string& transport_name = candidate.transport_name();
for (size_t i = 0; i < description_->contents().size(); ++i) {
diff --git a/third_party/libwebrtc/pc/legacy_stats_collector.cc b/third_party/libwebrtc/pc/legacy_stats_collector.cc
index 98b7cb9677..135829abc9 100644
--- a/third_party/libwebrtc/pc/legacy_stats_collector.cc
+++ b/third_party/libwebrtc/pc/legacy_stats_collector.cc
@@ -188,9 +188,10 @@ void ExtractStats(const cricket::VoiceReceiverInfo& info,
{StatsReport::kStatsValueNameAccelerateRate, info.accelerate_rate},
{StatsReport::kStatsValueNamePreemptiveExpandRate,
info.preemptive_expand_rate},
- {StatsReport::kStatsValueNameTotalAudioEnergy, info.total_output_energy},
+ {StatsReport::kStatsValueNameTotalAudioEnergy,
+ static_cast<float>(info.total_output_energy)},
{StatsReport::kStatsValueNameTotalSamplesDuration,
- info.total_output_duration}};
+ static_cast<float>(info.total_output_duration)}};
const IntForAdd ints[] = {
{StatsReport::kStatsValueNameCurrentDelayMs, info.delay_estimate_ms},
@@ -244,9 +245,10 @@ void ExtractStats(const cricket::VoiceSenderInfo& info,
SetAudioProcessingStats(report, info.apm_statistics);
const FloatForAdd floats[] = {
- {StatsReport::kStatsValueNameTotalAudioEnergy, info.total_input_energy},
+ {StatsReport::kStatsValueNameTotalAudioEnergy,
+ static_cast<float>(info.total_input_energy)},
{StatsReport::kStatsValueNameTotalSamplesDuration,
- info.total_input_duration}};
+ static_cast<float>(info.total_input_duration)}};
RTC_DCHECK_GE(info.audio_level, 0);
const IntForAdd ints[] = {
@@ -340,7 +342,8 @@ void ExtractStats(const cricket::VideoReceiverInfo& info,
{StatsReport::kStatsValueNamePlisSent, info.plis_sent},
{StatsReport::kStatsValueNameRenderDelayMs, info.render_delay_ms},
{StatsReport::kStatsValueNameTargetDelayMs, info.target_delay_ms},
- {StatsReport::kStatsValueNameFramesDecoded, info.frames_decoded},
+ {StatsReport::kStatsValueNameFramesDecoded,
+ static_cast<int>(info.frames_decoded)},
};
for (const auto& i : ints)
@@ -383,15 +386,19 @@ void ExtractStats(const cricket::VideoSenderInfo& info,
info.encode_usage_percent},
{StatsReport::kStatsValueNameFirsReceived, info.firs_received},
{StatsReport::kStatsValueNameFrameHeightSent, info.send_frame_height},
- {StatsReport::kStatsValueNameFrameRateInput, round(info.framerate_input)},
+ {StatsReport::kStatsValueNameFrameRateInput,
+ static_cast<int>(round(info.framerate_input))},
{StatsReport::kStatsValueNameFrameRateSent, info.framerate_sent},
{StatsReport::kStatsValueNameFrameWidthSent, info.send_frame_width},
- {StatsReport::kStatsValueNameNacksReceived, info.nacks_received},
+ {StatsReport::kStatsValueNameNacksReceived,
+ static_cast<int>(info.nacks_received)},
{StatsReport::kStatsValueNamePacketsLost, info.packets_lost},
{StatsReport::kStatsValueNamePacketsSent, info.packets_sent},
{StatsReport::kStatsValueNamePlisReceived, info.plis_received},
- {StatsReport::kStatsValueNameFramesEncoded, info.frames_encoded},
- {StatsReport::kStatsValueNameHugeFramesSent, info.huge_frames_sent},
+ {StatsReport::kStatsValueNameFramesEncoded,
+ static_cast<int>(info.frames_encoded)},
+ {StatsReport::kStatsValueNameHugeFramesSent,
+ static_cast<int>(info.huge_frames_sent)},
};
for (const auto& i : ints)
@@ -489,17 +496,17 @@ void ExtractStatsFromList(
} // namespace
-const char* IceCandidateTypeToStatsType(const std::string& candidate_type) {
- if (candidate_type == cricket::LOCAL_PORT_TYPE) {
+const char* IceCandidateTypeToStatsType(const cricket::Candidate& candidate) {
+ if (candidate.is_local()) {
return STATSREPORT_LOCAL_PORT_TYPE;
}
- if (candidate_type == cricket::STUN_PORT_TYPE) {
+ if (candidate.is_stun()) {
return STATSREPORT_STUN_PORT_TYPE;
}
- if (candidate_type == cricket::PRFLX_PORT_TYPE) {
+ if (candidate.is_prflx()) {
return STATSREPORT_PRFLX_PORT_TYPE;
}
- if (candidate_type == cricket::RELAY_PORT_TYPE) {
+ if (candidate.is_relay()) {
return STATSREPORT_RELAY_PORT_TYPE;
}
RTC_DCHECK_NOTREACHED();
@@ -778,19 +785,25 @@ StatsReport* LegacyStatsCollector::AddConnectionInfoReport(
AddCandidateReport(remote_candidate_stats, false)->id());
const Int64ForAdd int64s[] = {
- {StatsReport::kStatsValueNameBytesReceived, info.recv_total_bytes},
- {StatsReport::kStatsValueNameBytesSent, info.sent_total_bytes},
- {StatsReport::kStatsValueNamePacketsSent, info.sent_total_packets},
- {StatsReport::kStatsValueNameRtt, info.rtt},
+ {StatsReport::kStatsValueNameBytesReceived,
+ static_cast<int64_t>(info.recv_total_bytes)},
+ {StatsReport::kStatsValueNameBytesSent,
+ static_cast<int64_t>(info.sent_total_bytes)},
+ {StatsReport::kStatsValueNamePacketsSent,
+ static_cast<int64_t>(info.sent_total_packets)},
+ {StatsReport::kStatsValueNameRtt, static_cast<int64_t>(info.rtt)},
{StatsReport::kStatsValueNameSendPacketsDiscarded,
- info.sent_discarded_packets},
+ static_cast<int64_t>(info.sent_discarded_packets)},
{StatsReport::kStatsValueNameSentPingRequestsTotal,
- info.sent_ping_requests_total},
+ static_cast<int64_t>(info.sent_ping_requests_total)},
{StatsReport::kStatsValueNameSentPingRequestsBeforeFirstResponse,
- info.sent_ping_requests_before_first_response},
- {StatsReport::kStatsValueNameSentPingResponses, info.sent_ping_responses},
- {StatsReport::kStatsValueNameRecvPingRequests, info.recv_ping_requests},
- {StatsReport::kStatsValueNameRecvPingResponses, info.recv_ping_responses},
+ static_cast<int64_t>(info.sent_ping_requests_before_first_response)},
+ {StatsReport::kStatsValueNameSentPingResponses,
+ static_cast<int64_t>(info.sent_ping_responses)},
+ {StatsReport::kStatsValueNameRecvPingRequests,
+ static_cast<int64_t>(info.recv_ping_requests)},
+ {StatsReport::kStatsValueNameRecvPingResponses,
+ static_cast<int64_t>(info.recv_ping_responses)},
};
for (const auto& i : int64s)
report->AddInt64(i.name, i.value);
@@ -831,7 +844,7 @@ StatsReport* LegacyStatsCollector::AddCandidateReport(
report->AddInt(StatsReport::kStatsValueNameCandidatePriority,
candidate.priority());
report->AddString(StatsReport::kStatsValueNameCandidateType,
- IceCandidateTypeToStatsType(candidate.type()));
+ IceCandidateTypeToStatsType(candidate));
report->AddString(StatsReport::kStatsValueNameCandidateTransportType,
candidate.protocol());
}
diff --git a/third_party/libwebrtc/pc/legacy_stats_collector.h b/third_party/libwebrtc/pc/legacy_stats_collector.h
index 1c7aad0636..e0371638ee 100644
--- a/third_party/libwebrtc/pc/legacy_stats_collector.h
+++ b/third_party/libwebrtc/pc/legacy_stats_collector.h
@@ -26,6 +26,7 @@
#include <vector>
#include "absl/types/optional.h"
+#include "api/candidate.h"
#include "api/field_trials_view.h"
#include "api/legacy_stats_types.h"
#include "api/media_stream_interface.h"
@@ -45,7 +46,7 @@ namespace webrtc {
// Conversion function to convert candidate type string to the corresponding one
// from enum RTCStatsIceCandidateType.
-const char* IceCandidateTypeToStatsType(const std::string& candidate_type);
+const char* IceCandidateTypeToStatsType(const cricket::Candidate& candidate);
// Conversion function to convert adapter type to report string which are more
// fitting to the general style of http://w3c.github.io/webrtc-stats. This is
diff --git a/third_party/libwebrtc/pc/legacy_stats_collector_unittest.cc b/third_party/libwebrtc/pc/legacy_stats_collector_unittest.cc
index 3099d1188a..5f6140da54 100644
--- a/third_party/libwebrtc/pc/legacy_stats_collector_unittest.cc
+++ b/third_party/libwebrtc/pc/legacy_stats_collector_unittest.cc
@@ -1391,7 +1391,7 @@ TEST_F(LegacyStatsCollectorTest, IceCandidateReport) {
ExtractStatsValue(StatsReport::kStatsReportTypeIceLocalCandidate, reports,
StatsReport::kStatsValueNameCandidatePriority));
EXPECT_EQ(
- IceCandidateTypeToStatsType(cricket::LOCAL_PORT_TYPE),
+ IceCandidateTypeToStatsType(local),
ExtractStatsValue(StatsReport::kStatsReportTypeIceLocalCandidate, reports,
StatsReport::kStatsValueNameCandidateType));
EXPECT_EQ(
@@ -1421,7 +1421,7 @@ TEST_F(LegacyStatsCollectorTest, IceCandidateReport) {
reports,
StatsReport::kStatsValueNameCandidatePriority));
EXPECT_EQ(
- IceCandidateTypeToStatsType(cricket::PRFLX_PORT_TYPE),
+ IceCandidateTypeToStatsType(remote),
ExtractStatsValue(StatsReport::kStatsReportTypeIceRemoteCandidate,
reports, StatsReport::kStatsValueNameCandidateType));
EXPECT_EQ(kNotFound,
diff --git a/third_party/libwebrtc/pc/media_session.cc b/third_party/libwebrtc/pc/media_session.cc
index 573e35225e..a118beebb0 100644
--- a/third_party/libwebrtc/pc/media_session.cc
+++ b/third_party/libwebrtc/pc/media_session.cc
@@ -728,6 +728,16 @@ void NegotiatePacketization(const Codec& local_codec,
: absl::nullopt;
}
+#ifdef RTC_ENABLE_H265
+void NegotiateTxMode(const Codec& local_codec,
+ const Codec& remote_codec,
+ Codec* negotiated_codec) {
+ negotiated_codec->tx_mode = (local_codec.tx_mode == remote_codec.tx_mode)
+ ? local_codec.tx_mode
+ : absl::nullopt;
+}
+#endif
+
// Finds a codec in `codecs2` that matches `codec_to_match`, which is
// a member of `codecs1`. If `codec_to_match` is an RED or RTX codec, both
// the codecs themselves and their associated codecs must match.
@@ -849,6 +859,13 @@ void NegotiateCodecs(const std::vector<Codec>& local_codecs,
webrtc::H264GenerateProfileLevelIdForAnswer(ours.params, theirs->params,
&negotiated.params);
}
+#ifdef RTC_ENABLE_H265
+ if (absl::EqualsIgnoreCase(ours.name, kH265CodecName)) {
+ webrtc::H265GenerateProfileTierLevelForAnswer(
+ ours.params, theirs->params, &negotiated.params);
+ NegotiateTxMode(ours, *theirs, &negotiated);
+ }
+#endif
negotiated.id = theirs->id;
negotiated.name = theirs->name;
negotiated_codecs->push_back(std::move(negotiated));
@@ -1864,11 +1881,13 @@ MediaSessionDescriptionFactory::CreateOfferOrError(
// Be conservative and signal using both a=msid and a=ssrc lines. Unified
// Plan answerers will look at a=msid and Plan B answerers will look at the
// a=ssrc MSID line.
- offer->set_msid_signaling(cricket::kMsidSignalingMediaSection |
+ offer->set_msid_signaling(cricket::kMsidSignalingSemantic |
+ cricket::kMsidSignalingMediaSection |
cricket::kMsidSignalingSsrcAttribute);
} else {
// Plan B always signals MSID using a=ssrc lines.
- offer->set_msid_signaling(cricket::kMsidSignalingSsrcAttribute);
+ offer->set_msid_signaling(cricket::kMsidSignalingSemantic |
+ cricket::kMsidSignalingSsrcAttribute);
}
offer->set_extmap_allow_mixed(session_options.offer_extmap_allow_mixed);
@@ -2041,7 +2060,16 @@ MediaSessionDescriptionFactory::CreateAnswerOrError(
if (is_unified_plan_) {
// Unified Plan needs to look at what the offer included to find the most
// compatible answer.
- if (offer->msid_signaling() == 0) {
+ int msid_signaling = offer->msid_signaling();
+ if (msid_signaling ==
+ (cricket::kMsidSignalingSemantic | cricket::kMsidSignalingMediaSection |
+ cricket::kMsidSignalingSsrcAttribute)) {
+ // If both a=msid and a=ssrc MSID signaling methods were used, we're
+ // probably talking to a Unified Plan endpoint so respond with just
+ // a=msid.
+ answer->set_msid_signaling(cricket::kMsidSignalingSemantic |
+ cricket::kMsidSignalingMediaSection);
+ } else if (msid_signaling == cricket::kMsidSignalingSemantic) {
// We end up here in one of three cases:
// 1. An empty offer. We'll reply with an empty answer so it doesn't
// matter what we pick here.
@@ -2050,23 +2078,19 @@ MediaSessionDescriptionFactory::CreateAnswerOrError(
// 3. Media that's either sendonly or inactive from the remote endpoint.
// We don't have any information to say whether the endpoint is Plan B
// or Unified Plan, so be conservative and send both.
- answer->set_msid_signaling(cricket::kMsidSignalingMediaSection |
+ answer->set_msid_signaling(cricket::kMsidSignalingSemantic |
+ cricket::kMsidSignalingMediaSection |
cricket::kMsidSignalingSsrcAttribute);
- } else if (offer->msid_signaling() ==
- (cricket::kMsidSignalingMediaSection |
- cricket::kMsidSignalingSsrcAttribute)) {
- // If both a=msid and a=ssrc MSID signaling methods were used, we're
- // probably talking to a Unified Plan endpoint so respond with just
- // a=msid.
- answer->set_msid_signaling(cricket::kMsidSignalingMediaSection);
} else {
// Otherwise, it's clear which method the offerer is using so repeat that
- // back to them.
- answer->set_msid_signaling(offer->msid_signaling());
+ // back to them. This includes the case where the msid-semantic line is
+ // not included.
+ answer->set_msid_signaling(msid_signaling);
}
} else {
// Plan B always signals MSID using a=ssrc lines.
- answer->set_msid_signaling(cricket::kMsidSignalingSsrcAttribute);
+ answer->set_msid_signaling(cricket::kMsidSignalingSemantic |
+ cricket::kMsidSignalingSsrcAttribute);
}
return answer;
diff --git a/third_party/libwebrtc/pc/media_session_unittest.cc b/third_party/libwebrtc/pc/media_session_unittest.cc
index 641f638e72..f4fd09cba0 100644
--- a/third_party/libwebrtc/pc/media_session_unittest.cc
+++ b/third_party/libwebrtc/pc/media_session_unittest.cc
@@ -4323,6 +4323,80 @@ TEST_F(MediaSessionDescriptionFactoryTest,
EXPECT_EQ(vcd1->codecs()[0].id, vcd2->codecs()[0].id);
}
+#ifdef RTC_ENABLE_H265
+// Test verifying that negotiating codecs with the same tx-mode retains the
+// tx-mode value.
+TEST_F(MediaSessionDescriptionFactoryTest, H265TxModeIsEqualRetainIt) {
+ std::vector f1_codecs = {CreateVideoCodec(96, "H265")};
+ f1_codecs.back().tx_mode = "mrst";
+ f1_.set_video_codecs(f1_codecs, f1_codecs);
+
+ std::vector f2_codecs = {CreateVideoCodec(96, "H265")};
+ f2_codecs.back().tx_mode = "mrst";
+ f2_.set_video_codecs(f2_codecs, f2_codecs);
+
+ MediaSessionOptions opts;
+ AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video1",
+ RtpTransceiverDirection::kSendRecv, kActive,
+ &opts);
+
+ // Create an offer with two video sections using same codecs.
+ std::unique_ptr<SessionDescription> offer =
+ f1_.CreateOfferOrError(opts, nullptr).MoveValue();
+ ASSERT_TRUE(offer);
+ ASSERT_EQ(1u, offer->contents().size());
+ const MediaContentDescription* vcd1 =
+ offer->contents()[0].media_description();
+ ASSERT_EQ(1u, vcd1->codecs().size());
+ EXPECT_EQ(vcd1->codecs()[0].tx_mode, "mrst");
+
+ // Create answer and negotiate the codecs.
+ std::unique_ptr<SessionDescription> answer =
+ f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
+ ASSERT_TRUE(answer);
+ ASSERT_EQ(1u, answer->contents().size());
+ vcd1 = answer->contents()[0].media_description();
+ ASSERT_EQ(1u, vcd1->codecs().size());
+ EXPECT_EQ(vcd1->codecs()[0].tx_mode, "mrst");
+}
+
+// Test verifying that negotiating codecs with different tx_mode removes
+// the tx_mode value.
+TEST_F(MediaSessionDescriptionFactoryTest, H265TxModeIsDifferentDropCodecs) {
+ std::vector f1_codecs = {CreateVideoCodec(96, "H265")};
+ f1_codecs.back().tx_mode = "mrst";
+ f1_.set_video_codecs(f1_codecs, f1_codecs);
+
+ std::vector f2_codecs = {CreateVideoCodec(96, "H265")};
+ f2_codecs.back().tx_mode = "mrmt";
+ f2_.set_video_codecs(f2_codecs, f2_codecs);
+
+ MediaSessionOptions opts;
+ AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video1",
+ RtpTransceiverDirection::kSendRecv, kActive,
+ &opts);
+
+ // Create an offer with two video sections using same codecs.
+ std::unique_ptr<SessionDescription> offer =
+ f1_.CreateOfferOrError(opts, nullptr).MoveValue();
+ ASSERT_TRUE(offer);
+ ASSERT_EQ(1u, offer->contents().size());
+ const VideoContentDescription* vcd1 =
+ offer->contents()[0].media_description()->as_video();
+ ASSERT_EQ(1u, vcd1->codecs().size());
+ EXPECT_EQ(vcd1->codecs()[0].tx_mode, "mrst");
+
+ // Create answer and negotiate the codecs.
+ std::unique_ptr<SessionDescription> answer =
+ f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
+ ASSERT_TRUE(answer);
+ ASSERT_EQ(1u, answer->contents().size());
+ vcd1 = answer->contents()[0].media_description()->as_video();
+ ASSERT_EQ(1u, vcd1->codecs().size());
+ EXPECT_EQ(vcd1->codecs()[0].tx_mode, absl::nullopt);
+}
+#endif
+
// Test verifying that negotiating codecs with the same packetization retains
// the packetization value.
TEST_F(MediaSessionDescriptionFactoryTest, PacketizationIsEqual) {
diff --git a/third_party/libwebrtc/pc/peer_connection.cc b/third_party/libwebrtc/pc/peer_connection.cc
index 26b70c63db..8c9b0cbab6 100644
--- a/third_party/libwebrtc/pc/peer_connection.cc
+++ b/third_party/libwebrtc/pc/peer_connection.cc
@@ -78,19 +78,10 @@ using cricket::SimulcastLayerList;
using cricket::StreamParams;
using cricket::TransportInfo;
-using cricket::LOCAL_PORT_TYPE;
-using cricket::PRFLX_PORT_TYPE;
-using cricket::RELAY_PORT_TYPE;
-using cricket::STUN_PORT_TYPE;
-
namespace webrtc {
namespace {
-// UMA metric names.
-const char kSimulcastNumberOfEncodings[] =
- "WebRTC.PeerConnection.Simulcast.NumberOfSendEncodings";
-
static const int REPORT_USAGE_PATTERN_DELAY_MS = 60000;
uint32_t ConvertIceTransportTypeToCandidateFilter(
@@ -115,10 +106,10 @@ IceCandidatePairType GetIceCandidatePairCounter(
const cricket::Candidate& remote) {
const auto& l = local.type();
const auto& r = remote.type();
- const auto& host = LOCAL_PORT_TYPE;
- const auto& srflx = STUN_PORT_TYPE;
- const auto& relay = RELAY_PORT_TYPE;
- const auto& prflx = PRFLX_PORT_TYPE;
+ const auto& host = cricket::LOCAL_PORT_TYPE;
+ const auto& srflx = cricket::STUN_PORT_TYPE;
+ const auto& relay = cricket::RELAY_PORT_TYPE;
+ const auto& prflx = cricket::PRFLX_PORT_TYPE;
if (l == host && r == host) {
bool local_hostname =
!local.address().hostname().empty() && local.address().IsUnresolvedIP();
@@ -1031,18 +1022,6 @@ PeerConnection::AddTransceiver(
return AddTransceiver(track, RtpTransceiverInit());
}
-RtpTransportInternal* PeerConnection::GetRtpTransport(const std::string& mid) {
- // TODO(bugs.webrtc.org/9987): Avoid the thread jump.
- // This might be done by caching the value on the signaling thread.
- RTC_DCHECK_RUN_ON(signaling_thread());
- return network_thread()->BlockingCall([this, &mid] {
- RTC_DCHECK_RUN_ON(network_thread());
- auto rtp_transport = transport_controller_->GetRtpTransport(mid);
- RTC_DCHECK(rtp_transport);
- return rtp_transport;
- });
-}
-
RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>>
PeerConnection::AddTransceiver(
rtc::scoped_refptr<MediaStreamTrackInterface> track,
@@ -1112,9 +1091,6 @@ PeerConnection::AddTransceiver(
: cricket::MEDIA_TYPE_VIDEO));
}
- RTC_HISTOGRAM_COUNTS_LINEAR(kSimulcastNumberOfEncodings,
- init.send_encodings.size(), 0, 7, 8);
-
size_t num_rids = absl::c_count_if(init.send_encodings,
[](const RtpEncodingParameters& encoding) {
return !encoding.rid.empty();
@@ -2095,10 +2071,8 @@ void PeerConnection::OnSelectedCandidatePairChanged(
return;
}
- if (event.selected_candidate_pair.local_candidate().type() ==
- LOCAL_PORT_TYPE &&
- event.selected_candidate_pair.remote_candidate().type() ==
- LOCAL_PORT_TYPE) {
+ if (event.selected_candidate_pair.local_candidate().is_local() &&
+ event.selected_candidate_pair.remote_candidate().is_local()) {
NoteUsageEvent(UsageEvent::DIRECT_CONNECTION_SELECTED);
}
@@ -2806,7 +2780,7 @@ void PeerConnection::ReportBestConnectionState(
// Increment the counter for IceCandidatePairType.
if (local.protocol() == cricket::TCP_PROTOCOL_NAME ||
- (local.type() == RELAY_PORT_TYPE &&
+ (local.is_relay() &&
local.relay_protocol() == cricket::TCP_PROTOCOL_NAME)) {
RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.CandidatePairType_TCP",
GetIceCandidatePairCounter(local, remote),
diff --git a/third_party/libwebrtc/pc/peer_connection.h b/third_party/libwebrtc/pc/peer_connection.h
index e6037a2698..406bc1cef2 100644
--- a/third_party/libwebrtc/pc/peer_connection.h
+++ b/third_party/libwebrtc/pc/peer_connection.h
@@ -417,9 +417,6 @@ class PeerConnection : public PeerConnectionInternal,
const RtpTransceiverInit& init,
bool fire_callback = true) override;
- // Returns rtp transport, result can not be nullptr.
- RtpTransportInternal* GetRtpTransport(const std::string& mid);
-
// Returns true if SRTP (either using DTLS-SRTP or SDES) is required by
// this session.
bool SrtpRequired() const override;
diff --git a/third_party/libwebrtc/pc/peer_connection_crypto_unittest.cc b/third_party/libwebrtc/pc/peer_connection_crypto_unittest.cc
index a65988ab05..3b3f502e1f 100644
--- a/third_party/libwebrtc/pc/peer_connection_crypto_unittest.cc
+++ b/third_party/libwebrtc/pc/peer_connection_crypto_unittest.cc
@@ -55,6 +55,7 @@
#include "rtc_base/rtc_certificate_generator.h"
#include "rtc_base/ssl_fingerprint.h"
#include "rtc_base/thread.h"
+#include "test/gmock.h"
#include "test/gtest.h"
#include "test/scoped_key_value_config.h"
#ifdef WEBRTC_ANDROID
@@ -70,6 +71,7 @@ namespace webrtc {
using RTCConfiguration = PeerConnectionInterface::RTCConfiguration;
using RTCOfferAnswerOptions = PeerConnectionInterface::RTCOfferAnswerOptions;
using ::testing::Combine;
+using ::testing::HasSubstr;
using ::testing::Values;
constexpr int kGenerateCertTimeout = 1000;
@@ -789,16 +791,13 @@ TEST_P(PeerConnectionCryptoTest, SessionErrorIfFingerprintInvalid) {
// Set the invalid answer and expect a fingerprint error.
std::string error;
ASSERT_FALSE(callee->SetLocalDescription(std::move(invalid_answer), &error));
- EXPECT_PRED_FORMAT2(AssertStringContains, error,
- "Local fingerprint does not match identity.");
+ EXPECT_THAT(error, HasSubstr("Local fingerprint does not match identity."));
// Make sure that setting a valid remote offer or local answer also fails now.
ASSERT_FALSE(callee->SetRemoteDescription(caller->CreateOffer(), &error));
- EXPECT_PRED_FORMAT2(AssertStringContains, error,
- "Session error code: ERROR_CONTENT.");
+ EXPECT_THAT(error, HasSubstr("Session error code: ERROR_CONTENT."));
ASSERT_FALSE(callee->SetLocalDescription(std::move(valid_answer), &error));
- EXPECT_PRED_FORMAT2(AssertStringContains, error,
- "Session error code: ERROR_CONTENT.");
+ EXPECT_THAT(error, HasSubstr("Session error code: ERROR_CONTENT."));
}
INSTANTIATE_TEST_SUITE_P(PeerConnectionCryptoTest,
diff --git a/third_party/libwebrtc/pc/peer_connection_encodings_integrationtest.cc b/third_party/libwebrtc/pc/peer_connection_encodings_integrationtest.cc
index ae238671c2..4a93e915df 100644
--- a/third_party/libwebrtc/pc/peer_connection_encodings_integrationtest.cc
+++ b/third_party/libwebrtc/pc/peer_connection_encodings_integrationtest.cc
@@ -74,19 +74,10 @@ struct StringParamToString {
}
};
-// RTX, RED and FEC are reliability mechanisms used in combinations with other
-// codecs, but are not themselves a specific codec. Typically you don't want to
-// filter these out of the list of codec preferences.
-bool IsReliabilityMechanism(const RtpCodecCapability& codec) {
- return absl::EqualsIgnoreCase(codec.name, cricket::kRtxCodecName) ||
- absl::EqualsIgnoreCase(codec.name, cricket::kRedCodecName) ||
- absl::EqualsIgnoreCase(codec.name, cricket::kUlpfecCodecName);
-}
-
std::string GetCurrentCodecMimeType(
rtc::scoped_refptr<const RTCStatsReport> report,
const RTCOutboundRtpStreamStats& outbound_rtp) {
- return outbound_rtp.codec_id.is_defined()
+ return outbound_rtp.codec_id.has_value()
? *report->GetAs<RTCCodecStats>(*outbound_rtp.codec_id)->mime_type
: "";
}
@@ -101,7 +92,7 @@ const RTCOutboundRtpStreamStats* FindOutboundRtpByRid(
const std::vector<const RTCOutboundRtpStreamStats*>& outbound_rtps,
const absl::string_view& rid) {
for (const auto* outbound_rtp : outbound_rtps) {
- if (outbound_rtp->rid.is_defined() && *outbound_rtp->rid == rid) {
+ if (outbound_rtp->rid.has_value() && *outbound_rtp->rid == rid) {
return outbound_rtp;
}
}
@@ -163,7 +154,7 @@ class PeerConnectionEncodingsIntegrationTest : public ::testing::Test {
.codecs;
codecs.erase(std::remove_if(codecs.begin(), codecs.end(),
[&codec_name](const RtpCodecCapability& codec) {
- return !IsReliabilityMechanism(codec) &&
+ return !codec.IsResiliencyCodec() &&
!absl::EqualsIgnoreCase(codec.name,
codec_name);
}),
@@ -270,7 +261,7 @@ class PeerConnectionEncodingsIntegrationTest : public ::testing::Test {
}
size_t num_sending_layers = 0;
for (const auto* outbound_rtp : outbound_rtps) {
- if (outbound_rtp->bytes_sent.is_defined() &&
+ if (outbound_rtp->bytes_sent.has_value() &&
*outbound_rtp->bytes_sent > 0u) {
++num_sending_layers;
}
@@ -287,11 +278,11 @@ class PeerConnectionEncodingsIntegrationTest : public ::testing::Test {
std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps =
report->GetStatsOfType<RTCOutboundRtpStreamStats>();
auto* outbound_rtp = FindOutboundRtpByRid(outbound_rtps, rid);
- if (!outbound_rtp || !outbound_rtp->scalability_mode.is_defined() ||
+ if (!outbound_rtp || !outbound_rtp->scalability_mode.has_value() ||
*outbound_rtp->scalability_mode != expected_scalability_mode) {
return false;
}
- if (outbound_rtp->frame_height.is_defined()) {
+ if (outbound_rtp->frame_height.has_value()) {
RTC_LOG(LS_INFO) << "Waiting for target resolution (" << frame_height
<< "p). Currently at " << *outbound_rtp->frame_height
<< "p...";
@@ -299,7 +290,7 @@ class PeerConnectionEncodingsIntegrationTest : public ::testing::Test {
RTC_LOG(LS_INFO)
<< "Waiting for target resolution. No frames encoded yet...";
}
- if (!outbound_rtp->frame_height.is_defined() ||
+ if (!outbound_rtp->frame_height.has_value() ||
*outbound_rtp->frame_height != frame_height) {
// Sleep to avoid log spam when this is used in ASSERT_TRUE_WAIT().
rtc::Thread::Current()->SleepMs(1000);
@@ -321,8 +312,8 @@ class PeerConnectionEncodingsIntegrationTest : public ::testing::Test {
} else if (outbound_rtps.size() == 1u) {
outbound_rtp = outbound_rtps[0];
}
- if (!outbound_rtp || !outbound_rtp->frame_width.is_defined() ||
- !outbound_rtp->frame_height.is_defined()) {
+ if (!outbound_rtp || !outbound_rtp->frame_width.has_value() ||
+ !outbound_rtp->frame_height.has_value()) {
// RTP not found by rid or has not encoded a frame yet.
RTC_LOG(LS_ERROR) << "rid=" << resolution.rid << " does not have "
<< "resolution metrics";
@@ -1200,7 +1191,7 @@ TEST_F(PeerConnectionEncodingsIntegrationTest,
ASSERT_EQ(outbound_rtps.size(), 1u);
std::string codec_name = GetCurrentCodecMimeType(report, *outbound_rtps[0]);
EXPECT_STRCASEEQ(("video/" + vp9->name).c_str(), codec_name.c_str());
- EXPECT_EQ(outbound_rtps[0]->scalability_mode.ValueOrDefault(""), "L3T3");
+ EXPECT_EQ(outbound_rtps[0]->scalability_mode.value_or(""), "L3T3");
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
diff --git a/third_party/libwebrtc/pc/peer_connection_end_to_end_unittest.cc b/third_party/libwebrtc/pc/peer_connection_end_to_end_unittest.cc
index a21d455ec5..5881cf45b5 100644
--- a/third_party/libwebrtc/pc/peer_connection_end_to_end_unittest.cc
+++ b/third_party/libwebrtc/pc/peer_connection_end_to_end_unittest.cc
@@ -319,8 +319,7 @@ struct AudioEncoderUnicornSparklesRainbow {
using Config = webrtc::AudioEncoderL16::Config;
static absl::optional<Config> SdpToConfig(webrtc::SdpAudioFormat format) {
if (absl::EqualsIgnoreCase(format.name, "UnicornSparklesRainbow")) {
- const webrtc::SdpAudioFormat::Parameters expected_params = {
- {"num_horns", "1"}};
+ const webrtc::CodecParameterMap expected_params = {{"num_horns", "1"}};
EXPECT_EQ(expected_params, format.parameters);
format.parameters.clear();
format.name = "L16";
@@ -356,8 +355,7 @@ struct AudioDecoderUnicornSparklesRainbow {
using Config = webrtc::AudioDecoderL16::Config;
static absl::optional<Config> SdpToConfig(webrtc::SdpAudioFormat format) {
if (absl::EqualsIgnoreCase(format.name, "UnicornSparklesRainbow")) {
- const webrtc::SdpAudioFormat::Parameters expected_params = {
- {"num_horns", "1"}};
+ const webrtc::CodecParameterMap expected_params = {{"num_horns", "1"}};
EXPECT_EQ(expected_params, format.parameters);
format.parameters.clear();
format.name = "L16";
diff --git a/third_party/libwebrtc/pc/peer_connection_factory.cc b/third_party/libwebrtc/pc/peer_connection_factory.cc
index 8ce44d374f..6bf0ef944a 100644
--- a/third_party/libwebrtc/pc/peer_connection_factory.cc
+++ b/third_party/libwebrtc/pc/peer_connection_factory.cc
@@ -103,7 +103,8 @@ PeerConnectionFactory::PeerConnectionFactory(
(dependencies->transport_controller_send_factory)
? std::move(dependencies->transport_controller_send_factory)
: std::make_unique<RtpTransportControllerSendFactory>()),
- metronome_(std::move(dependencies->metronome)) {}
+ decode_metronome_(std::move(dependencies->decode_metronome)),
+ encode_metronome_(std::move(dependencies->encode_metronome)) {}
PeerConnectionFactory::PeerConnectionFactory(
PeerConnectionFactoryDependencies dependencies)
@@ -118,7 +119,8 @@ PeerConnectionFactory::~PeerConnectionFactory() {
RTC_DCHECK_RUN_ON(signaling_thread());
worker_thread()->BlockingCall([this] {
RTC_DCHECK_RUN_ON(worker_thread());
- metronome_ = nullptr;
+ decode_metronome_ = nullptr;
+ encode_metronome_ = nullptr;
});
}
@@ -343,7 +345,9 @@ std::unique_ptr<Call> PeerConnectionFactory::CreateCall_w(
call_config.rtp_transport_controller_send_factory =
transport_controller_send_factory_.get();
- call_config.metronome = metronome_.get();
+ call_config.decode_metronome = decode_metronome_.get();
+ call_config.encode_metronome = encode_metronome_.get();
+ call_config.pacer_burst_interval = configuration.pacer_burst_interval;
return context_->call_factory()->CreateCall(call_config);
}
diff --git a/third_party/libwebrtc/pc/peer_connection_factory.h b/third_party/libwebrtc/pc/peer_connection_factory.h
index c3760c02c9..a8af9f5efa 100644
--- a/third_party/libwebrtc/pc/peer_connection_factory.h
+++ b/third_party/libwebrtc/pc/peer_connection_factory.h
@@ -109,7 +109,7 @@ class PeerConnectionFactory : public PeerConnectionFactoryInterface {
}
const FieldTrialsView& field_trials() const {
- return context_->field_trials();
+ return context_->env().field_trials();
}
cricket::MediaEngineInterface* media_engine() const;
@@ -147,7 +147,8 @@ class PeerConnectionFactory : public PeerConnectionFactoryInterface {
std::unique_ptr<NetEqFactory> neteq_factory_;
const std::unique_ptr<RtpTransportControllerSendFactoryInterface>
transport_controller_send_factory_;
- std::unique_ptr<Metronome> metronome_ RTC_GUARDED_BY(worker_thread());
+ std::unique_ptr<Metronome> decode_metronome_ RTC_GUARDED_BY(worker_thread());
+ std::unique_ptr<Metronome> encode_metronome_ RTC_GUARDED_BY(worker_thread());
};
} // namespace webrtc
diff --git a/third_party/libwebrtc/pc/peer_connection_field_trial_tests.cc b/third_party/libwebrtc/pc/peer_connection_field_trial_tests.cc
index 4cbe24986c..ae566359e4 100644
--- a/third_party/libwebrtc/pc/peer_connection_field_trial_tests.cc
+++ b/third_party/libwebrtc/pc/peer_connection_field_trial_tests.cc
@@ -264,7 +264,7 @@ TEST_F(PeerConnectionFieldTrialTest, ApplyFakeNetworkConfig) {
std::vector<const RTCOutboundRtpStreamStats*> outbound_rtp_stats =
caller->GetStats()->GetStatsOfType<RTCOutboundRtpStreamStats>();
ASSERT_GE(outbound_rtp_stats.size(), 1u);
- ASSERT_TRUE(outbound_rtp_stats[0]->target_bitrate.is_defined());
+ ASSERT_TRUE(outbound_rtp_stats[0]->target_bitrate.has_value());
// Link capacity is limited to 500k, so BWE is expected to be close to 500k.
ASSERT_LE(*outbound_rtp_stats[0]->target_bitrate, 500'000 * 1.1);
}
diff --git a/third_party/libwebrtc/pc/peer_connection_header_extension_unittest.cc b/third_party/libwebrtc/pc/peer_connection_header_extension_unittest.cc
index 277979b330..15b1ae6d1c 100644
--- a/third_party/libwebrtc/pc/peer_connection_header_extension_unittest.cc
+++ b/third_party/libwebrtc/pc/peer_connection_header_extension_unittest.cc
@@ -90,8 +90,7 @@ class PeerConnectionHeaderExtensionTest
EnableFakeMedia(factory_dependencies, std::move(media_engine));
factory_dependencies.event_log_factory =
- std::make_unique<RtcEventLogFactory>(
- factory_dependencies.task_queue_factory.get());
+ std::make_unique<RtcEventLogFactory>();
auto pc_factory =
CreateModularPeerConnectionFactory(std::move(factory_dependencies));
diff --git a/third_party/libwebrtc/pc/peer_connection_histogram_unittest.cc b/third_party/libwebrtc/pc/peer_connection_histogram_unittest.cc
index 58bd6ebb48..365f58a806 100644
--- a/third_party/libwebrtc/pc/peer_connection_histogram_unittest.cc
+++ b/third_party/libwebrtc/pc/peer_connection_histogram_unittest.cc
@@ -15,7 +15,6 @@
#include <vector>
#include "absl/types/optional.h"
-#include "api/call/call_factory_interface.h"
#include "api/jsep.h"
#include "api/jsep_session_description.h"
#include "api/peer_connection_interface.h"
diff --git a/third_party/libwebrtc/pc/peer_connection_integrationtest.cc b/third_party/libwebrtc/pc/peer_connection_integrationtest.cc
index bfff86ee93..c960a36b5e 100644
--- a/third_party/libwebrtc/pc/peer_connection_integrationtest.cc
+++ b/third_party/libwebrtc/pc/peer_connection_integrationtest.cc
@@ -1356,15 +1356,15 @@ TEST_P(PeerConnectionIntegrationTest, NewGetStatsManyAudioAndManyVideoStreams) {
ASSERT_EQ(outbound_stream_stats.size(), 4u);
std::vector<std::string> outbound_track_ids;
for (const auto& stat : outbound_stream_stats) {
- ASSERT_TRUE(stat->bytes_sent.is_defined());
+ ASSERT_TRUE(stat->bytes_sent.has_value());
EXPECT_LT(0u, *stat->bytes_sent);
if (*stat->kind == "video") {
- ASSERT_TRUE(stat->key_frames_encoded.is_defined());
+ ASSERT_TRUE(stat->key_frames_encoded.has_value());
EXPECT_GT(*stat->key_frames_encoded, 0u);
- ASSERT_TRUE(stat->frames_encoded.is_defined());
+ ASSERT_TRUE(stat->frames_encoded.has_value());
EXPECT_GE(*stat->frames_encoded, *stat->key_frames_encoded);
}
- ASSERT_TRUE(stat->media_source_id.is_defined());
+ ASSERT_TRUE(stat->media_source_id.has_value());
const RTCMediaSourceStats* media_source =
static_cast<const RTCMediaSourceStats*>(
caller_report->Get(*stat->media_source_id));
@@ -1381,12 +1381,12 @@ TEST_P(PeerConnectionIntegrationTest, NewGetStatsManyAudioAndManyVideoStreams) {
ASSERT_EQ(4u, inbound_stream_stats.size());
std::vector<std::string> inbound_track_ids;
for (const auto& stat : inbound_stream_stats) {
- ASSERT_TRUE(stat->bytes_received.is_defined());
+ ASSERT_TRUE(stat->bytes_received.has_value());
EXPECT_LT(0u, *stat->bytes_received);
if (*stat->kind == "video") {
- ASSERT_TRUE(stat->key_frames_decoded.is_defined());
+ ASSERT_TRUE(stat->key_frames_decoded.has_value());
EXPECT_GT(*stat->key_frames_decoded, 0u);
- ASSERT_TRUE(stat->frames_decoded.is_defined());
+ ASSERT_TRUE(stat->frames_decoded.has_value());
EXPECT_GE(*stat->frames_decoded, *stat->key_frames_decoded);
}
inbound_track_ids.push_back(*stat->track_identifier);
@@ -1417,7 +1417,7 @@ TEST_P(PeerConnectionIntegrationTest,
auto inbound_stream_stats =
report->GetStatsOfType<RTCInboundRtpStreamStats>();
ASSERT_EQ(1U, inbound_stream_stats.size());
- ASSERT_TRUE(inbound_stream_stats[0]->bytes_received.is_defined());
+ ASSERT_TRUE(inbound_stream_stats[0]->bytes_received.has_value());
ASSERT_GT(*inbound_stream_stats[0]->bytes_received, 0U);
}
@@ -1464,7 +1464,7 @@ TEST_P(PeerConnectionIntegrationTest,
auto inbound_rtps = report->GetStatsOfType<RTCInboundRtpStreamStats>();
auto index = FindFirstMediaStatsIndexByKind("audio", inbound_rtps);
ASSERT_GE(index, 0);
- EXPECT_TRUE(inbound_rtps[index]->audio_level.is_defined());
+ EXPECT_TRUE(inbound_rtps[index]->audio_level.has_value());
}
// Test that DTLS 1.0 is used if both sides only support DTLS 1.0.
@@ -2952,7 +2952,7 @@ double GetAudioEnergyStat(PeerConnectionIntegrationWrapper* pc) {
auto inbound_rtps = report->GetStatsOfType<RTCInboundRtpStreamStats>();
RTC_CHECK(!inbound_rtps.empty());
auto* inbound_rtp = inbound_rtps[0];
- if (!inbound_rtp->total_audio_energy.is_defined()) {
+ if (!inbound_rtp->total_audio_energy.has_value()) {
return 0.0;
}
return *inbound_rtp->total_audio_energy;
@@ -3776,7 +3776,7 @@ int NacksReceivedCount(PeerConnectionIntegrationWrapper& pc) {
ADD_FAILURE();
return 0;
}
- if (!sender_stats[0]->nack_count.is_defined()) {
+ if (!sender_stats[0]->nack_count.has_value()) {
return 0;
}
return *sender_stats[0]->nack_count;
@@ -3789,7 +3789,7 @@ int NacksSentCount(PeerConnectionIntegrationWrapper& pc) {
ADD_FAILURE();
return 0;
}
- if (!receiver_stats[0]->nack_count.is_defined()) {
+ if (!receiver_stats[0]->nack_count.has_value()) {
return 0;
}
return *receiver_stats[0]->nack_count;
diff --git a/third_party/libwebrtc/pc/peer_connection_interface_unittest.cc b/third_party/libwebrtc/pc/peer_connection_interface_unittest.cc
index 5ee9881b84..61794bb0f9 100644
--- a/third_party/libwebrtc/pc/peer_connection_interface_unittest.cc
+++ b/third_party/libwebrtc/pc/peer_connection_interface_unittest.cc
@@ -641,8 +641,7 @@ class PeerConnectionFactoryForTest : public PeerConnectionFactory {
// level, and using a real one could make tests flaky when run in parallel.
dependencies.adm = FakeAudioCaptureModule::Create();
EnableMediaWithDefaults(dependencies);
- dependencies.event_log_factory = std::make_unique<RtcEventLogFactory>(
- dependencies.task_queue_factory.get());
+ dependencies.event_log_factory = std::make_unique<RtcEventLogFactory>();
return rtc::make_ref_counted<PeerConnectionFactoryForTest>(
std::move(dependencies));
@@ -2815,7 +2814,7 @@ TEST_F(PeerConnectionInterfaceTestPlanB,
// This tests that a default MediaStream is not created if a remote session
// description is updated to not have any MediaStreams.
// Don't run under Unified Plan since this behavior is Plan B specific.
-TEST_F(PeerConnectionInterfaceTestPlanB, VerifyDefaultStreamIsNotCreated) {
+TEST_F(PeerConnectionInterfaceTestPlanB, VerifyDefaultStreamIsNotRecreated) {
RTCConfiguration config;
CreatePeerConnection(config);
CreateAndSetRemoteOffer(GetSdpStringWithStream1());
@@ -2823,7 +2822,7 @@ TEST_F(PeerConnectionInterfaceTestPlanB, VerifyDefaultStreamIsNotCreated) {
EXPECT_TRUE(
CompareStreamCollections(observer_.remote_streams(), reference.get()));
- CreateAndSetRemoteOffer(kSdpStringWithoutStreams);
+ CreateAndSetRemoteOffer(kSdpStringWithMsidWithoutStreams);
EXPECT_EQ(0u, observer_.remote_streams()->count());
}
diff --git a/third_party/libwebrtc/pc/peer_connection_media_unittest.cc b/third_party/libwebrtc/pc/peer_connection_media_unittest.cc
index 387094cc4f..b892eacb78 100644
--- a/third_party/libwebrtc/pc/peer_connection_media_unittest.cc
+++ b/third_party/libwebrtc/pc/peer_connection_media_unittest.cc
@@ -66,7 +66,6 @@
#ifdef WEBRTC_ANDROID
#include "pc/test/android_test_initializer.h"
#endif
-#include "rtc_base/gunit.h"
#include "rtc_base/virtual_socket_server.h"
#include "test/gmock.h"
@@ -78,6 +77,7 @@ using RTCOfferAnswerOptions = PeerConnectionInterface::RTCOfferAnswerOptions;
using ::testing::Bool;
using ::testing::Combine;
using ::testing::ElementsAre;
+using ::testing::HasSubstr;
using ::testing::NotNull;
using ::testing::Values;
@@ -175,8 +175,7 @@ class PeerConnectionMediaBaseTest : public ::testing::Test {
factory_dependencies.task_queue_factory = CreateDefaultTaskQueueFactory();
EnableFakeMedia(factory_dependencies, std::move(media_engine));
factory_dependencies.event_log_factory =
- std::make_unique<RtcEventLogFactory>(
- factory_dependencies.task_queue_factory.get());
+ std::make_unique<RtcEventLogFactory>();
auto pc_factory =
CreateModularPeerConnectionFactory(std::move(factory_dependencies));
@@ -287,8 +286,8 @@ TEST_P(PeerConnectionMediaTest,
std::string error;
ASSERT_FALSE(callee->SetRemoteDescription(caller->CreateOffer(), &error));
- EXPECT_PRED_FORMAT2(AssertStartsWith, error,
- "Failed to set remote offer sdp: Failed to create");
+ EXPECT_THAT(error,
+ HasSubstr("Failed to set remote offer sdp: Failed to create"));
}
TEST_P(PeerConnectionMediaTest,
@@ -298,8 +297,8 @@ TEST_P(PeerConnectionMediaTest,
std::string error;
ASSERT_FALSE(caller->SetLocalDescription(caller->CreateOffer(), &error));
- EXPECT_PRED_FORMAT2(AssertStartsWith, error,
- "Failed to set local offer sdp: Failed to create");
+ EXPECT_THAT(error,
+ HasSubstr("Failed to set local offer sdp: Failed to create"));
}
std::vector<std::string> GetIds(
diff --git a/third_party/libwebrtc/pc/peer_connection_rampup_tests.cc b/third_party/libwebrtc/pc/peer_connection_rampup_tests.cc
index 0fd3c27f7d..cc645a0ea7 100644
--- a/third_party/libwebrtc/pc/peer_connection_rampup_tests.cc
+++ b/third_party/libwebrtc/pc/peer_connection_rampup_tests.cc
@@ -309,15 +309,17 @@ class PeerConnectionRampUpTest : public ::testing::Test {
auto stats = caller_->GetStats();
auto transport_stats = stats->GetStatsOfType<RTCTransportStats>();
if (transport_stats.size() == 0u ||
- !transport_stats[0]->selected_candidate_pair_id.is_defined()) {
+ !transport_stats[0]->selected_candidate_pair_id.has_value()) {
return 0;
}
std::string selected_ice_id =
- transport_stats[0]->selected_candidate_pair_id.ValueToString();
+ transport_stats[0]
+ ->GetAttribute(transport_stats[0]->selected_candidate_pair_id)
+ .ToString();
// Use the selected ICE candidate pair ID to get the appropriate ICE stats.
const RTCIceCandidatePairStats ice_candidate_pair_stats =
stats->Get(selected_ice_id)->cast_to<const RTCIceCandidatePairStats>();
- if (ice_candidate_pair_stats.available_outgoing_bitrate.is_defined()) {
+ if (ice_candidate_pair_stats.available_outgoing_bitrate.has_value()) {
return *ice_candidate_pair_stats.available_outgoing_bitrate;
}
// We couldn't get the `available_outgoing_bitrate` for the active candidate
diff --git a/third_party/libwebrtc/pc/peer_connection_rtp_unittest.cc b/third_party/libwebrtc/pc/peer_connection_rtp_unittest.cc
index 1a97a4bb44..77c8cecbb2 100644
--- a/third_party/libwebrtc/pc/peer_connection_rtp_unittest.cc
+++ b/third_party/libwebrtc/pc/peer_connection_rtp_unittest.cc
@@ -1836,14 +1836,16 @@ TEST_F(PeerConnectionMsidSignalingTest, UnifiedPlanTalkingToOurself) {
// Offer should have had both a=msid and a=ssrc MSID lines.
auto* offer = callee->pc()->remote_description();
- EXPECT_EQ((cricket::kMsidSignalingMediaSection |
- cricket::kMsidSignalingSsrcAttribute),
- offer->description()->msid_signaling());
+ EXPECT_EQ(
+ (cricket::kMsidSignalingSemantic | cricket::kMsidSignalingMediaSection |
+ cricket::kMsidSignalingSsrcAttribute),
+ offer->description()->msid_signaling());
// Answer should have had only a=msid lines.
auto* answer = caller->pc()->remote_description();
- EXPECT_EQ(cricket::kMsidSignalingMediaSection,
- answer->description()->msid_signaling());
+ EXPECT_EQ(
+ cricket::kMsidSignalingSemantic | cricket::kMsidSignalingMediaSection,
+ answer->description()->msid_signaling());
}
TEST_F(PeerConnectionMsidSignalingTest, PlanBOfferToUnifiedPlanAnswer) {
@@ -1856,13 +1858,15 @@ TEST_F(PeerConnectionMsidSignalingTest, PlanBOfferToUnifiedPlanAnswer) {
// Offer should have only a=ssrc MSID lines.
auto* offer = callee->pc()->remote_description();
- EXPECT_EQ(cricket::kMsidSignalingSsrcAttribute,
- offer->description()->msid_signaling());
+ EXPECT_EQ(
+ cricket::kMsidSignalingSemantic | cricket::kMsidSignalingSsrcAttribute,
+ offer->description()->msid_signaling());
// Answer should have only a=ssrc MSID lines to match the offer.
auto* answer = caller->pc()->remote_description();
- EXPECT_EQ(cricket::kMsidSignalingSsrcAttribute,
- answer->description()->msid_signaling());
+ EXPECT_EQ(
+ cricket::kMsidSignalingSemantic | cricket::kMsidSignalingSsrcAttribute,
+ answer->description()->msid_signaling());
}
// This tests that a Plan B endpoint appropriately sets the remote description
@@ -1884,9 +1888,10 @@ TEST_F(PeerConnectionMsidSignalingTest, UnifiedPlanToPlanBAnswer) {
// Offer should have had both a=msid and a=ssrc MSID lines.
auto* offer = callee->pc()->remote_description();
- EXPECT_EQ((cricket::kMsidSignalingMediaSection |
- cricket::kMsidSignalingSsrcAttribute),
- offer->description()->msid_signaling());
+ EXPECT_EQ(
+ (cricket::kMsidSignalingSemantic | cricket::kMsidSignalingMediaSection |
+ cricket::kMsidSignalingSsrcAttribute),
+ offer->description()->msid_signaling());
// Callee should always have 1 stream for all of it's receivers.
const auto& track_events = callee->observer()->add_track_events_;
@@ -1907,7 +1912,8 @@ TEST_F(PeerConnectionMsidSignalingTest, PureUnifiedPlanToUs) {
auto offer = caller->CreateOffer();
// Simulate a pure Unified Plan offerer by setting the MSID signaling to media
// section only.
- offer->description()->set_msid_signaling(cricket::kMsidSignalingMediaSection);
+ offer->description()->set_msid_signaling(cricket::kMsidSignalingSemantic |
+ cricket::kMsidSignalingMediaSection);
ASSERT_TRUE(
caller->SetLocalDescription(CloneSessionDescription(offer.get())));
@@ -1915,8 +1921,9 @@ TEST_F(PeerConnectionMsidSignalingTest, PureUnifiedPlanToUs) {
// Answer should have only a=msid to match the offer.
auto answer = callee->CreateAnswer();
- EXPECT_EQ(cricket::kMsidSignalingMediaSection,
- answer->description()->msid_signaling());
+ EXPECT_EQ(
+ cricket::kMsidSignalingSemantic | cricket::kMsidSignalingMediaSection,
+ answer->description()->msid_signaling());
}
// Sender setups in a call.
diff --git a/third_party/libwebrtc/pc/peer_connection_signaling_unittest.cc b/third_party/libwebrtc/pc/peer_connection_signaling_unittest.cc
index 190fb38b43..7764be923d 100644
--- a/third_party/libwebrtc/pc/peer_connection_signaling_unittest.cc
+++ b/third_party/libwebrtc/pc/peer_connection_signaling_unittest.cc
@@ -64,6 +64,7 @@
#include "rtc_base/rtc_certificate_generator.h"
#include "rtc_base/string_encode.h"
#include "rtc_base/thread.h"
+#include "test/gmock.h"
#include "test/gtest.h"
#ifdef WEBRTC_ANDROID
#include "pc/test/android_test_initializer.h"
@@ -80,6 +81,7 @@ using RTCConfiguration = PeerConnectionInterface::RTCConfiguration;
using RTCOfferAnswerOptions = PeerConnectionInterface::RTCOfferAnswerOptions;
using ::testing::Bool;
using ::testing::Combine;
+using ::testing::StartsWith;
using ::testing::Values;
namespace {
@@ -343,8 +345,7 @@ TEST_P(PeerConnectionSignalingStateTest, CreateOffer) {
} else {
std::string error;
ASSERT_FALSE(wrapper->CreateOffer(RTCOfferAnswerOptions(), &error));
- EXPECT_PRED_FORMAT2(AssertStartsWith, error,
- "CreateOffer called when PeerConnection is closed.");
+ EXPECT_EQ(error, "CreateOffer called when PeerConnection is closed.");
}
}
@@ -379,9 +380,9 @@ TEST_P(PeerConnectionSignalingStateTest, SetLocalOffer) {
std::string error;
ASSERT_FALSE(wrapper->SetLocalDescription(std::move(offer), &error));
- EXPECT_PRED_FORMAT2(
- AssertStartsWith, error,
- "Failed to set local offer sdp: Called in wrong state:");
+ EXPECT_THAT(
+ error,
+ StartsWith("Failed to set local offer sdp: Called in wrong state:"));
}
}
@@ -398,9 +399,9 @@ TEST_P(PeerConnectionSignalingStateTest, SetLocalPrAnswer) {
} else {
std::string error;
ASSERT_FALSE(wrapper->SetLocalDescription(std::move(pranswer), &error));
- EXPECT_PRED_FORMAT2(
- AssertStartsWith, error,
- "Failed to set local pranswer sdp: Called in wrong state:");
+ EXPECT_THAT(
+ error,
+ StartsWith("Failed to set local pranswer sdp: Called in wrong state:"));
}
}
@@ -416,9 +417,9 @@ TEST_P(PeerConnectionSignalingStateTest, SetLocalAnswer) {
} else {
std::string error;
ASSERT_FALSE(wrapper->SetLocalDescription(std::move(answer), &error));
- EXPECT_PRED_FORMAT2(
- AssertStartsWith, error,
- "Failed to set local answer sdp: Called in wrong state:");
+ EXPECT_THAT(
+ error,
+ StartsWith("Failed to set local answer sdp: Called in wrong state:"));
}
}
@@ -435,9 +436,9 @@ TEST_P(PeerConnectionSignalingStateTest, SetRemoteOffer) {
} else {
std::string error;
ASSERT_FALSE(wrapper->SetRemoteDescription(std::move(offer), &error));
- EXPECT_PRED_FORMAT2(
- AssertStartsWith, error,
- "Failed to set remote offer sdp: Called in wrong state:");
+ EXPECT_THAT(
+ error,
+ StartsWith("Failed to set remote offer sdp: Called in wrong state:"));
}
}
@@ -454,9 +455,10 @@ TEST_P(PeerConnectionSignalingStateTest, SetRemotePrAnswer) {
} else {
std::string error;
ASSERT_FALSE(wrapper->SetRemoteDescription(std::move(pranswer), &error));
- EXPECT_PRED_FORMAT2(
- AssertStartsWith, error,
- "Failed to set remote pranswer sdp: Called in wrong state:");
+ EXPECT_THAT(
+ error,
+ StartsWith(
+ "Failed to set remote pranswer sdp: Called in wrong state:"));
}
}
@@ -472,9 +474,9 @@ TEST_P(PeerConnectionSignalingStateTest, SetRemoteAnswer) {
} else {
std::string error;
ASSERT_FALSE(wrapper->SetRemoteDescription(std::move(answer), &error));
- EXPECT_PRED_FORMAT2(
- AssertStartsWith, error,
- "Failed to set remote answer sdp: Called in wrong state:");
+ EXPECT_THAT(
+ error,
+ StartsWith("Failed to set remote answer sdp: Called in wrong state:"));
}
}
diff --git a/third_party/libwebrtc/pc/peer_connection_simulcast_unittest.cc b/third_party/libwebrtc/pc/peer_connection_simulcast_unittest.cc
index bffb5d9e9f..06f38848e1 100644
--- a/third_party/libwebrtc/pc/peer_connection_simulcast_unittest.cc
+++ b/third_party/libwebrtc/pc/peer_connection_simulcast_unittest.cc
@@ -102,21 +102,6 @@ std::ostream& operator<<( // no-presubmit-check TODO(webrtc:8982)
} // namespace cricket
-namespace {
-
-#if RTC_METRICS_ENABLED
-std::vector<SimulcastLayer> CreateLayers(int num_layers, bool active) {
- rtc::UniqueStringGenerator rid_generator;
- std::vector<std::string> rids;
- for (int i = 0; i < num_layers; ++i) {
- rids.push_back(rid_generator.GenerateString());
- }
- return webrtc::CreateLayers(rids, active);
-}
-#endif
-
-} // namespace
-
namespace webrtc {
class PeerConnectionSimulcastTests : public ::testing::Test {
@@ -214,16 +199,6 @@ class PeerConnectionSimulcastTests : public ::testing::Test {
rtc::scoped_refptr<PeerConnectionFactoryInterface> pc_factory_;
};
-#if RTC_METRICS_ENABLED
-// This class is used to test the metrics emitted for simulcast.
-class PeerConnectionSimulcastMetricsTests
- : public PeerConnectionSimulcastTests,
- public ::testing::WithParamInterface<int> {
- protected:
- PeerConnectionSimulcastMetricsTests() { metrics::Reset(); }
-};
-#endif
-
// Validates that RIDs are supported arguments when adding a transceiver.
TEST_F(PeerConnectionSimulcastTests, CanCreateTransceiverWithRid) {
auto pc = CreatePeerConnectionWrapper();
@@ -603,27 +578,4 @@ TEST_F(PeerConnectionSimulcastTests, SimulcastSldModificationRejected) {
EXPECT_TRUE(modified_offer);
EXPECT_TRUE(local->SetLocalDescription(std::move(modified_offer)));
}
-
-#if RTC_METRICS_ENABLED
-
-const int kMaxLayersInMetricsTest = 8;
-
-// Checks that the number of send encodings is logged in a metric.
-TEST_P(PeerConnectionSimulcastMetricsTests, NumberOfSendEncodingsIsLogged) {
- auto local = CreatePeerConnectionWrapper();
- auto num_layers = GetParam();
- auto layers = ::CreateLayers(num_layers, true);
- AddTransceiver(local.get(), layers);
- EXPECT_EQ(1, metrics::NumSamples(
- "WebRTC.PeerConnection.Simulcast.NumberOfSendEncodings"));
- EXPECT_EQ(1, metrics::NumEvents(
- "WebRTC.PeerConnection.Simulcast.NumberOfSendEncodings",
- num_layers));
-}
-
-INSTANTIATE_TEST_SUITE_P(NumberOfSendEncodings,
- PeerConnectionSimulcastMetricsTests,
- ::testing::Range(0, kMaxLayersInMetricsTest));
-#endif
-
} // namespace webrtc
diff --git a/third_party/libwebrtc/pc/rtc_stats_collector.cc b/third_party/libwebrtc/pc/rtc_stats_collector.cc
index 2bac176aac..a5a3067fa1 100644
--- a/third_party/libwebrtc/pc/rtc_stats_collector.cc
+++ b/third_party/libwebrtc/pc/rtc_stats_collector.cc
@@ -164,14 +164,14 @@ std::string RTCMediaSourceStatsIDFromKindAndAttachment(
return sb.str();
}
-const char* CandidateTypeToRTCIceCandidateType(const std::string& type) {
- if (type == cricket::LOCAL_PORT_TYPE)
+const char* CandidateTypeToRTCIceCandidateType(const cricket::Candidate& c) {
+ if (c.is_local())
return "host";
- if (type == cricket::STUN_PORT_TYPE)
+ if (c.is_stun())
return "srflx";
- if (type == cricket::PRFLX_PORT_TYPE)
+ if (c.is_prflx())
return "prflx";
- if (type == cricket::RELAY_PORT_TYPE)
+ if (c.is_relay())
return "relay";
RTC_DCHECK_NOTREACHED();
return nullptr;
@@ -551,7 +551,7 @@ CreateRemoteOutboundAudioStreamStats(
stats->ssrc = voice_receiver_info.ssrc();
stats->kind = "audio";
stats->transport_id = transport_id;
- if (inbound_audio_stats.codec_id.is_defined()) {
+ if (inbound_audio_stats.codec_id.has_value()) {
stats->codec_id = *inbound_audio_stats.codec_id;
}
// - RTCSentRtpStreamStats.
@@ -890,7 +890,7 @@ ProduceRemoteInboundRtpStreamStatsFromReportBlockData(
// transport paired with the RTP transport, otherwise the same
// transport is used for RTCP and RTP.
remote_inbound->transport_id =
- transport.rtcp_transport_stats_id.is_defined()
+ transport.rtcp_transport_stats_id.has_value()
? *transport.rtcp_transport_stats_id
: *outbound_rtp.transport_id;
}
@@ -898,13 +898,13 @@ ProduceRemoteInboundRtpStreamStatsFromReportBlockData(
// codec is switched out on the fly we may have received a Report Block
// based on the previous codec and there is no way to tell which point in
// time the codec changed for the remote end.
- const auto* codec_from_id = outbound_rtp.codec_id.is_defined()
+ const auto* codec_from_id = outbound_rtp.codec_id.has_value()
? report.Get(*outbound_rtp.codec_id)
: nullptr;
if (codec_from_id) {
remote_inbound->codec_id = *outbound_rtp.codec_id;
const auto& codec = codec_from_id->cast_to<RTCCodecStats>();
- if (codec.clock_rate.is_defined()) {
+ if (codec.clock_rate.has_value()) {
remote_inbound->jitter =
report_block.jitter(*codec.clock_rate).seconds<double>();
}
@@ -1001,7 +1001,7 @@ const std::string& ProduceIceCandidateStats(Timestamp timestamp,
candidate_stats->port = static_cast<int32_t>(candidate.address().port());
candidate_stats->protocol = candidate.protocol();
candidate_stats->candidate_type =
- CandidateTypeToRTCIceCandidateType(candidate.type());
+ CandidateTypeToRTCIceCandidateType(candidate);
candidate_stats->priority = static_cast<int32_t>(candidate.priority());
candidate_stats->foundation = candidate.foundation();
auto related_address = candidate.related_address();
@@ -1051,7 +1051,7 @@ RTCStatsCollector::CreateReportFilteredBySelector(
auto encodings = sender_selector->GetParametersInternal().encodings;
for (const auto* outbound_rtp :
report->GetStatsOfType<RTCOutboundRtpStreamStats>()) {
- RTC_DCHECK(outbound_rtp->ssrc.is_defined());
+ RTC_DCHECK(outbound_rtp->ssrc.has_value());
auto it = std::find_if(encodings.begin(), encodings.end(),
[ssrc = *outbound_rtp->ssrc](
const RtpEncodingParameters& encoding) {
@@ -1071,7 +1071,7 @@ RTCStatsCollector::CreateReportFilteredBySelector(
if (ssrc.has_value()) {
for (const auto* inbound_rtp :
report->GetStatsOfType<RTCInboundRtpStreamStats>()) {
- RTC_DCHECK(inbound_rtp->ssrc.is_defined());
+ RTC_DCHECK(inbound_rtp->ssrc.has_value());
if (*inbound_rtp->ssrc == *ssrc) {
rtpstream_ids.push_back(inbound_rtp->id());
}
@@ -2124,7 +2124,9 @@ void RTCStatsCollector::PrepareTransceiverStatsInfosAndCallStats_s_w_n() {
}
}
- // Create the TrackMediaInfoMap for each transceiver stats object.
+ // Create the TrackMediaInfoMap for each transceiver stats object
+ // and keep track of whether we have at least one audio receiver.
+ bool has_audio_receiver = false;
for (auto& stats : transceiver_stats_infos_) {
auto transceiver = stats.transceiver;
absl::optional<cricket::VoiceMediaInfo> voice_media_info;
@@ -2159,10 +2161,14 @@ void RTCStatsCollector::PrepareTransceiverStatsInfosAndCallStats_s_w_n() {
stats.track_media_info_map.Initialize(std::move(voice_media_info),
std::move(video_media_info),
senders, receivers);
+ if (transceiver->media_type() == cricket::MEDIA_TYPE_AUDIO) {
+ has_audio_receiver |= !receivers.empty();
+ }
}
call_stats_ = pc_->GetCallStats();
- audio_device_stats_ = pc_->GetAudioDeviceStats();
+ audio_device_stats_ =
+ has_audio_receiver ? pc_->GetAudioDeviceStats() : absl::nullopt;
});
for (auto& stats : transceiver_stats_infos_) {
@@ -2188,14 +2194,4 @@ void RTCStatsCollector::OnSctpDataChannelStateChanged(
}
}
-const char* CandidateTypeToRTCIceCandidateTypeForTesting(
- const std::string& type) {
- return CandidateTypeToRTCIceCandidateType(type);
-}
-
-const char* DataStateToRTCDataChannelStateForTesting(
- DataChannelInterface::DataState state) {
- return DataStateToRTCDataChannelState(state);
-}
-
} // namespace webrtc
diff --git a/third_party/libwebrtc/pc/rtc_stats_collector.h b/third_party/libwebrtc/pc/rtc_stats_collector.h
index 4c68e77086..505979c5ea 100644
--- a/third_party/libwebrtc/pc/rtc_stats_collector.h
+++ b/third_party/libwebrtc/pc/rtc_stats_collector.h
@@ -322,11 +322,6 @@ class RTCStatsCollector : public rtc::RefCountInterface {
InternalRecord internal_record_;
};
-const char* CandidateTypeToRTCIceCandidateTypeForTesting(
- const std::string& type);
-const char* DataStateToRTCDataChannelStateForTesting(
- DataChannelInterface::DataState state);
-
} // namespace webrtc
#endif // PC_RTC_STATS_COLLECTOR_H_
diff --git a/third_party/libwebrtc/pc/rtc_stats_collector_unittest.cc b/third_party/libwebrtc/pc/rtc_stats_collector_unittest.cc
index 055be6fe99..61b3bca1db 100644
--- a/third_party/libwebrtc/pc/rtc_stats_collector_unittest.cc
+++ b/third_party/libwebrtc/pc/rtc_stats_collector_unittest.cc
@@ -29,6 +29,7 @@
#include "api/media_stream_track.h"
#include "api/rtp_parameters.h"
#include "api/rtp_transceiver_direction.h"
+#include "api/stats/attribute.h"
#include "api/stats/rtc_stats.h"
#include "api/stats/rtc_stats_report.h"
#include "api/stats/rtcstats_objects.h"
@@ -2303,7 +2304,7 @@ TEST_F(RTCStatsCollectorTest, CollectRTCInboundRtpStreamStats_Audio_PlayoutId) {
ASSERT_TRUE(report->Get("ITTransportName1A1"));
auto stats =
report->Get("ITTransportName1A1")->cast_to<RTCInboundRtpStreamStats>();
- ASSERT_FALSE(stats.playout_id.is_defined());
+ ASSERT_FALSE(stats.playout_id.has_value());
}
{
// We do expect a playout id when receiving.
@@ -2314,7 +2315,7 @@ TEST_F(RTCStatsCollectorTest, CollectRTCInboundRtpStreamStats_Audio_PlayoutId) {
ASSERT_TRUE(report->Get("ITTransportName1A1"));
auto stats =
report->Get("ITTransportName1A1")->cast_to<RTCInboundRtpStreamStats>();
- ASSERT_TRUE(stats.playout_id.is_defined());
+ ASSERT_TRUE(stats.playout_id.has_value());
EXPECT_EQ(*stats.playout_id, "AP");
}
}
@@ -2478,6 +2479,10 @@ TEST_F(RTCStatsCollectorTest, CollectRTCAudioPlayoutStats) {
audio_device_stats.total_playout_delay_s = 5;
pc_->SetAudioDeviceStats(audio_device_stats);
+ pc_->AddVoiceChannel("AudioMid", "TransportName", {});
+ stats_->SetupRemoteTrackAndReceiver(
+ cricket::MEDIA_TYPE_AUDIO, "RemoteAudioTrackID", "RemoteStreamId", 1);
+
rtc::scoped_refptr<const RTCStatsReport> report = stats_->GetStatsReport();
auto stats_of_track_type = report->GetStatsOfType<RTCAudioPlayoutStats>();
ASSERT_EQ(1U, stats_of_track_type.size());
@@ -2526,7 +2531,7 @@ TEST_F(RTCStatsCollectorTest, CollectGoogTimingFrameInfo) {
rtc::scoped_refptr<const RTCStatsReport> report = stats_->GetStatsReport();
auto inbound_rtps = report->GetStatsOfType<RTCInboundRtpStreamStats>();
ASSERT_EQ(inbound_rtps.size(), 1u);
- ASSERT_TRUE(inbound_rtps[0]->goog_timing_frame_info.is_defined());
+ ASSERT_TRUE(inbound_rtps[0]->goog_timing_frame_info.has_value());
EXPECT_EQ(*inbound_rtps[0]->goog_timing_frame_info,
"1,2,3,4,5,6,7,8,9,10,11,12,13,1,0");
}
@@ -3135,8 +3140,8 @@ TEST_F(RTCStatsCollectorTest,
rtc::scoped_refptr<const RTCStatsReport> report = stats_->GetStatsReport();
ASSERT_TRUE(report->Get("SV42"));
auto video_stats = report->Get("SV42")->cast_to<RTCVideoSourceStats>();
- EXPECT_FALSE(video_stats.frames_per_second.is_defined());
- EXPECT_FALSE(video_stats.frames.is_defined());
+ EXPECT_FALSE(video_stats.frames_per_second.has_value());
+ EXPECT_FALSE(video_stats.frames.has_value());
}
// The track not having a source is not expected to be true in practise, but
@@ -3165,8 +3170,8 @@ TEST_F(RTCStatsCollectorTest,
rtc::scoped_refptr<const RTCStatsReport> report = stats_->GetStatsReport();
ASSERT_TRUE(report->Get("SV42"));
auto video_stats = report->Get("SV42")->cast_to<RTCVideoSourceStats>();
- EXPECT_FALSE(video_stats.width.is_defined());
- EXPECT_FALSE(video_stats.height.is_defined());
+ EXPECT_FALSE(video_stats.width.has_value());
+ EXPECT_FALSE(video_stats.height.has_value());
}
TEST_F(RTCStatsCollectorTest,
@@ -3367,9 +3372,9 @@ TEST_P(RTCStatsCollectorTestWithParamKind,
auto& remote_inbound_rtp = report->Get(remote_inbound_rtp_id)
->cast_to<RTCRemoteInboundRtpStreamStats>();
- EXPECT_TRUE(remote_inbound_rtp.round_trip_time_measurements.is_defined());
+ EXPECT_TRUE(remote_inbound_rtp.round_trip_time_measurements.has_value());
EXPECT_EQ(0, *remote_inbound_rtp.round_trip_time_measurements);
- EXPECT_FALSE(remote_inbound_rtp.round_trip_time.is_defined());
+ EXPECT_FALSE(remote_inbound_rtp.round_trip_time.has_value());
}
TEST_P(RTCStatsCollectorTestWithParamKind,
@@ -3431,10 +3436,10 @@ TEST_P(RTCStatsCollectorTestWithParamKind,
auto& remote_inbound_rtp = report->Get(remote_inbound_rtp_id)
->cast_to<RTCRemoteInboundRtpStreamStats>();
- EXPECT_TRUE(remote_inbound_rtp.codec_id.is_defined());
+ EXPECT_TRUE(remote_inbound_rtp.codec_id.has_value());
EXPECT_TRUE(report->Get(*remote_inbound_rtp.codec_id));
- EXPECT_TRUE(remote_inbound_rtp.jitter.is_defined());
+ EXPECT_TRUE(remote_inbound_rtp.jitter.has_value());
// The jitter (in seconds) is the report block's jitter divided by the codec's
// clock rate.
EXPECT_EQ(5.0, *remote_inbound_rtp.jitter);
@@ -3471,7 +3476,7 @@ TEST_P(RTCStatsCollectorTestWithParamKind,
auto& remote_inbound_rtp = report->Get(remote_inbound_rtp_id)
->cast_to<RTCRemoteInboundRtpStreamStats>();
- EXPECT_TRUE(remote_inbound_rtp.transport_id.is_defined());
+ EXPECT_TRUE(remote_inbound_rtp.transport_id.has_value());
EXPECT_EQ("TTransportName2", // 2 for RTCP
*remote_inbound_rtp.transport_id);
EXPECT_TRUE(report->Get(*remote_inbound_rtp.transport_id));
@@ -3716,12 +3721,15 @@ class RTCTestStats : public RTCStats {
WEBRTC_RTCSTATS_DECL();
RTCTestStats(const std::string& id, Timestamp timestamp)
- : RTCStats(id, timestamp), dummy_stat("dummyStat") {}
+ : RTCStats(id, timestamp) {}
RTCStatsMember<int32_t> dummy_stat;
};
-WEBRTC_RTCSTATS_IMPL(RTCTestStats, RTCStats, "test-stats", &dummy_stat)
+WEBRTC_RTCSTATS_IMPL(RTCTestStats,
+ RTCStats,
+ "test-stats",
+ AttributeInit("dummyStat", &dummy_stat))
// Overrides the stats collection to verify thread usage and that the resulting
// partial reports are merged.
diff --git a/third_party/libwebrtc/pc/rtc_stats_integrationtest.cc b/third_party/libwebrtc/pc/rtc_stats_integrationtest.cc
index 648efab69a..002f9d34b5 100644
--- a/third_party/libwebrtc/pc/rtc_stats_integrationtest.cc
+++ b/third_party/libwebrtc/pc/rtc_stats_integrationtest.cc
@@ -206,106 +206,112 @@ class RTCStatsVerifier {
: report_(report), stats_(stats), all_tests_successful_(true) {
RTC_CHECK(report_);
RTC_CHECK(stats_);
- for (const RTCStatsMemberInterface* member : stats_->Members()) {
- untested_members_.insert(member);
+ for (const auto& attribute : stats_->Attributes()) {
+ untested_attribute_names_.insert(attribute.name());
}
}
- void MarkMemberTested(const RTCStatsMemberInterface& member,
- bool test_successful) {
- untested_members_.erase(&member);
+ template <typename T>
+ void MarkAttributeTested(const RTCStatsMember<T>& field,
+ bool test_successful) {
+ untested_attribute_names_.erase(stats_->GetAttribute(field).name());
all_tests_successful_ &= test_successful;
}
- void TestMemberIsDefined(const RTCStatsMemberInterface& member) {
- EXPECT_TRUE(member.is_defined())
- << stats_->type() << "." << member.name() << "[" << stats_->id()
- << "] was undefined.";
- MarkMemberTested(member, member.is_defined());
+ template <typename T>
+ void TestAttributeIsDefined(const RTCStatsMember<T>& field) {
+ EXPECT_TRUE(field.has_value())
+ << stats_->type() << "." << stats_->GetAttribute(field).name() << "["
+ << stats_->id() << "] was undefined.";
+ MarkAttributeTested(field, field.has_value());
}
- void TestMemberIsUndefined(const RTCStatsMemberInterface& member) {
- EXPECT_FALSE(member.is_defined())
- << stats_->type() << "." << member.name() << "[" << stats_->id()
- << "] was defined (" << member.ValueToString() << ").";
- MarkMemberTested(member, !member.is_defined());
+ template <typename T>
+ void TestAttributeIsUndefined(const RTCStatsMember<T>& field) {
+ Attribute attribute = stats_->GetAttribute(field);
+ EXPECT_FALSE(field.has_value())
+ << stats_->type() << "." << attribute.name() << "[" << stats_->id()
+ << "] was defined (" << attribute.ToString() << ").";
+ MarkAttributeTested(field, !field.has_value());
}
template <typename T>
- void TestMemberIsPositive(const RTCStatsMemberInterface& member) {
- EXPECT_TRUE(member.is_defined())
- << stats_->type() << "." << member.name() << "[" << stats_->id()
- << "] was undefined.";
- if (!member.is_defined()) {
- MarkMemberTested(member, false);
+ void TestAttributeIsPositive(const RTCStatsMember<T>& field) {
+ Attribute attribute = stats_->GetAttribute(field);
+ EXPECT_TRUE(field.has_value()) << stats_->type() << "." << attribute.name()
+ << "[" << stats_->id() << "] was undefined.";
+ if (!field.has_value()) {
+ MarkAttributeTested(field, false);
return;
}
- bool is_positive = *member.cast_to<RTCStatsMember<T>>() > T(0);
+ bool is_positive = field.value() > T(0);
EXPECT_TRUE(is_positive)
- << stats_->type() << "." << member.name() << "[" << stats_->id()
- << "] was not positive (" << member.ValueToString() << ").";
- MarkMemberTested(member, is_positive);
+ << stats_->type() << "." << attribute.name() << "[" << stats_->id()
+ << "] was not positive (" << attribute.ToString() << ").";
+ MarkAttributeTested(field, is_positive);
}
template <typename T>
- void TestMemberIsNonNegative(const RTCStatsMemberInterface& member) {
- EXPECT_TRUE(member.is_defined())
- << stats_->type() << "." << member.name() << "[" << stats_->id()
- << "] was undefined.";
- if (!member.is_defined()) {
- MarkMemberTested(member, false);
+ void TestAttributeIsNonNegative(const RTCStatsMember<T>& field) {
+ Attribute attribute = stats_->GetAttribute(field);
+ EXPECT_TRUE(field.has_value()) << stats_->type() << "." << attribute.name()
+ << "[" << stats_->id() << "] was undefined.";
+ if (!field.has_value()) {
+ MarkAttributeTested(field, false);
return;
}
- bool is_non_negative = *member.cast_to<RTCStatsMember<T>>() >= T(0);
+ bool is_non_negative = field.value() >= T(0);
EXPECT_TRUE(is_non_negative)
- << stats_->type() << "." << member.name() << "[" << stats_->id()
- << "] was not non-negative (" << member.ValueToString() << ").";
- MarkMemberTested(member, is_non_negative);
+ << stats_->type() << "." << attribute.name() << "[" << stats_->id()
+ << "] was not non-negative (" << attribute.ToString() << ").";
+ MarkAttributeTested(field, is_non_negative);
}
- void TestMemberIsIDReference(const RTCStatsMemberInterface& member,
- const char* expected_type) {
- TestMemberIsIDReference(member, expected_type, false);
+ template <typename T>
+ void TestAttributeIsIDReference(const RTCStatsMember<T>& field,
+ const char* expected_type) {
+ TestAttributeIsIDReference(field, expected_type, false);
}
- void TestMemberIsOptionalIDReference(const RTCStatsMemberInterface& member,
- const char* expected_type) {
- TestMemberIsIDReference(member, expected_type, true);
+ template <typename T>
+ void TestAttributeIsOptionalIDReference(const RTCStatsMember<T>& field,
+ const char* expected_type) {
+ TestAttributeIsIDReference(field, expected_type, true);
}
- bool ExpectAllMembersSuccessfullyTested() {
- if (untested_members_.empty())
+ bool ExpectAllAttributesSuccessfullyTested() {
+ if (untested_attribute_names_.empty())
return all_tests_successful_;
- for (const RTCStatsMemberInterface* member : untested_members_) {
- EXPECT_TRUE(false) << stats_->type() << "." << member->name() << "["
- << stats_->id() << "] was not tested.";
+ for (const char* name : untested_attribute_names_) {
+ EXPECT_TRUE(false) << stats_->type() << "." << name << "[" << stats_->id()
+ << "] was not tested.";
}
return false;
}
private:
- void TestMemberIsIDReference(const RTCStatsMemberInterface& member,
- const char* expected_type,
- bool optional) {
- if (optional && !member.is_defined()) {
- MarkMemberTested(member, true);
+ template <typename T>
+ void TestAttributeIsIDReference(const RTCStatsMember<T>& field,
+ const char* expected_type,
+ bool optional) {
+ if (optional && !field.has_value()) {
+ MarkAttributeTested(field, true);
return;
}
+ Attribute attribute = stats_->GetAttribute(field);
bool valid_reference = false;
- if (member.is_defined()) {
- if (member.type() == RTCStatsMemberInterface::kString) {
+ if (attribute.has_value()) {
+ if (attribute.holds_alternative<std::string>()) {
// A single ID.
- const RTCStatsMember<std::string>& id =
- member.cast_to<RTCStatsMember<std::string>>();
- const RTCStats* referenced_stats = report_->Get(*id);
+ const RTCStats* referenced_stats =
+ report_->Get(attribute.get<std::string>());
valid_reference =
referenced_stats && referenced_stats->type() == expected_type;
- } else if (member.type() == RTCStatsMemberInterface::kSequenceString) {
+ } else if (attribute.holds_alternative<std::vector<std::string>>()) {
// A vector of IDs.
valid_reference = true;
- const RTCStatsMember<std::vector<std::string>>& ids =
- member.cast_to<RTCStatsMember<std::vector<std::string>>>();
- for (const std::string& id : *ids) {
+ for (const std::string& id :
+ attribute.get<std::vector<std::string>>()) {
const RTCStats* referenced_stats = report_->Get(id);
if (!referenced_stats || referenced_stats->type() != expected_type) {
valid_reference = false;
@@ -315,17 +321,16 @@ class RTCStatsVerifier {
}
}
EXPECT_TRUE(valid_reference)
- << stats_->type() << "." << member.name()
+ << stats_->type() << "." << attribute.name()
<< " is not a reference to an "
"existing dictionary of type "
- << expected_type << " (value: "
- << (member.is_defined() ? member.ValueToString() : "null") << ").";
- MarkMemberTested(member, valid_reference);
+ << expected_type << " (value: " << attribute.ToString() << ").";
+ MarkAttributeTested(field, valid_reference);
}
rtc::scoped_refptr<const RTCStatsReport> report_;
const RTCStats* stats_;
- std::set<const RTCStatsMemberInterface*> untested_members_;
+ std::set<const char*> untested_attribute_names_;
bool all_tests_successful_;
};
@@ -429,122 +434,129 @@ class RTCStatsReportVerifier {
bool VerifyRTCCertificateStats(const RTCCertificateStats& certificate) {
RTCStatsVerifier verifier(report_.get(), &certificate);
- verifier.TestMemberIsDefined(certificate.fingerprint);
- verifier.TestMemberIsDefined(certificate.fingerprint_algorithm);
- verifier.TestMemberIsDefined(certificate.base64_certificate);
- verifier.TestMemberIsOptionalIDReference(certificate.issuer_certificate_id,
- RTCCertificateStats::kType);
- return verifier.ExpectAllMembersSuccessfullyTested();
+ verifier.TestAttributeIsDefined(certificate.fingerprint);
+ verifier.TestAttributeIsDefined(certificate.fingerprint_algorithm);
+ verifier.TestAttributeIsDefined(certificate.base64_certificate);
+ verifier.TestAttributeIsOptionalIDReference(
+ certificate.issuer_certificate_id, RTCCertificateStats::kType);
+ return verifier.ExpectAllAttributesSuccessfullyTested();
}
bool VerifyRTCCodecStats(const RTCCodecStats& codec) {
RTCStatsVerifier verifier(report_.get(), &codec);
- verifier.TestMemberIsIDReference(codec.transport_id,
- RTCTransportStats::kType);
- verifier.TestMemberIsDefined(codec.payload_type);
- verifier.TestMemberIsDefined(codec.mime_type);
- verifier.TestMemberIsPositive<uint32_t>(codec.clock_rate);
+ verifier.TestAttributeIsIDReference(codec.transport_id,
+ RTCTransportStats::kType);
+ verifier.TestAttributeIsDefined(codec.payload_type);
+ verifier.TestAttributeIsDefined(codec.mime_type);
+ verifier.TestAttributeIsPositive<uint32_t>(codec.clock_rate);
if (codec.mime_type->rfind("audio", 0) == 0)
- verifier.TestMemberIsPositive<uint32_t>(codec.channels);
+ verifier.TestAttributeIsPositive<uint32_t>(codec.channels);
else
- verifier.TestMemberIsUndefined(codec.channels);
+ verifier.TestAttributeIsUndefined(codec.channels);
// sdp_fmtp_line is an optional field.
- verifier.MarkMemberTested(codec.sdp_fmtp_line, true);
- return verifier.ExpectAllMembersSuccessfullyTested();
+ verifier.MarkAttributeTested(codec.sdp_fmtp_line, true);
+ return verifier.ExpectAllAttributesSuccessfullyTested();
}
bool VerifyRTCDataChannelStats(const RTCDataChannelStats& data_channel) {
RTCStatsVerifier verifier(report_.get(), &data_channel);
- verifier.TestMemberIsDefined(data_channel.label);
- verifier.TestMemberIsDefined(data_channel.protocol);
- verifier.TestMemberIsDefined(data_channel.data_channel_identifier);
- verifier.TestMemberIsDefined(data_channel.state);
- verifier.TestMemberIsNonNegative<uint32_t>(data_channel.messages_sent);
- verifier.TestMemberIsNonNegative<uint64_t>(data_channel.bytes_sent);
- verifier.TestMemberIsNonNegative<uint32_t>(data_channel.messages_received);
- verifier.TestMemberIsNonNegative<uint64_t>(data_channel.bytes_received);
- return verifier.ExpectAllMembersSuccessfullyTested();
+ verifier.TestAttributeIsDefined(data_channel.label);
+ verifier.TestAttributeIsDefined(data_channel.protocol);
+ verifier.TestAttributeIsDefined(data_channel.data_channel_identifier);
+ verifier.TestAttributeIsDefined(data_channel.state);
+ verifier.TestAttributeIsNonNegative<uint32_t>(data_channel.messages_sent);
+ verifier.TestAttributeIsNonNegative<uint64_t>(data_channel.bytes_sent);
+ verifier.TestAttributeIsNonNegative<uint32_t>(
+ data_channel.messages_received);
+ verifier.TestAttributeIsNonNegative<uint64_t>(data_channel.bytes_received);
+ return verifier.ExpectAllAttributesSuccessfullyTested();
}
bool VerifyRTCIceCandidatePairStats(
const RTCIceCandidatePairStats& candidate_pair,
bool is_selected_pair) {
RTCStatsVerifier verifier(report_.get(), &candidate_pair);
- verifier.TestMemberIsIDReference(candidate_pair.transport_id,
- RTCTransportStats::kType);
- verifier.TestMemberIsIDReference(candidate_pair.local_candidate_id,
- RTCLocalIceCandidateStats::kType);
- verifier.TestMemberIsIDReference(candidate_pair.remote_candidate_id,
- RTCRemoteIceCandidateStats::kType);
- verifier.TestMemberIsDefined(candidate_pair.state);
- verifier.TestMemberIsNonNegative<uint64_t>(candidate_pair.priority);
- verifier.TestMemberIsDefined(candidate_pair.nominated);
- verifier.TestMemberIsDefined(candidate_pair.writable);
- verifier.TestMemberIsNonNegative<uint64_t>(candidate_pair.packets_sent);
- verifier.TestMemberIsNonNegative<uint64_t>(
+ verifier.TestAttributeIsIDReference(candidate_pair.transport_id,
+ RTCTransportStats::kType);
+ verifier.TestAttributeIsIDReference(candidate_pair.local_candidate_id,
+ RTCLocalIceCandidateStats::kType);
+ verifier.TestAttributeIsIDReference(candidate_pair.remote_candidate_id,
+ RTCRemoteIceCandidateStats::kType);
+ verifier.TestAttributeIsDefined(candidate_pair.state);
+ verifier.TestAttributeIsNonNegative<uint64_t>(candidate_pair.priority);
+ verifier.TestAttributeIsDefined(candidate_pair.nominated);
+ verifier.TestAttributeIsDefined(candidate_pair.writable);
+ verifier.TestAttributeIsNonNegative<uint64_t>(candidate_pair.packets_sent);
+ verifier.TestAttributeIsNonNegative<uint64_t>(
candidate_pair.packets_discarded_on_send);
- verifier.TestMemberIsNonNegative<uint64_t>(candidate_pair.packets_received);
- verifier.TestMemberIsNonNegative<uint64_t>(candidate_pair.bytes_sent);
- verifier.TestMemberIsNonNegative<uint64_t>(
+ verifier.TestAttributeIsNonNegative<uint64_t>(
+ candidate_pair.packets_received);
+ verifier.TestAttributeIsNonNegative<uint64_t>(candidate_pair.bytes_sent);
+ verifier.TestAttributeIsNonNegative<uint64_t>(
candidate_pair.bytes_discarded_on_send);
- verifier.TestMemberIsNonNegative<uint64_t>(candidate_pair.bytes_received);
- verifier.TestMemberIsNonNegative<double>(
+ verifier.TestAttributeIsNonNegative<uint64_t>(
+ candidate_pair.bytes_received);
+ verifier.TestAttributeIsNonNegative<double>(
candidate_pair.total_round_trip_time);
- verifier.TestMemberIsNonNegative<double>(
+ verifier.TestAttributeIsNonNegative<double>(
candidate_pair.current_round_trip_time);
if (is_selected_pair) {
- verifier.TestMemberIsNonNegative<double>(
+ verifier.TestAttributeIsNonNegative<double>(
candidate_pair.available_outgoing_bitrate);
// A pair should be nominated in order to be selected.
EXPECT_TRUE(*candidate_pair.nominated);
} else {
- verifier.TestMemberIsUndefined(candidate_pair.available_outgoing_bitrate);
+ verifier.TestAttributeIsUndefined(
+ candidate_pair.available_outgoing_bitrate);
}
- verifier.TestMemberIsUndefined(candidate_pair.available_incoming_bitrate);
- verifier.TestMemberIsNonNegative<uint64_t>(
+ verifier.TestAttributeIsUndefined(
+ candidate_pair.available_incoming_bitrate);
+ verifier.TestAttributeIsNonNegative<uint64_t>(
candidate_pair.requests_received);
- verifier.TestMemberIsNonNegative<uint64_t>(candidate_pair.requests_sent);
- verifier.TestMemberIsNonNegative<uint64_t>(
+ verifier.TestAttributeIsNonNegative<uint64_t>(candidate_pair.requests_sent);
+ verifier.TestAttributeIsNonNegative<uint64_t>(
candidate_pair.responses_received);
- verifier.TestMemberIsNonNegative<uint64_t>(candidate_pair.responses_sent);
- verifier.TestMemberIsNonNegative<uint64_t>(
+ verifier.TestAttributeIsNonNegative<uint64_t>(
+ candidate_pair.responses_sent);
+ verifier.TestAttributeIsNonNegative<uint64_t>(
candidate_pair.consent_requests_sent);
- verifier.TestMemberIsDefined(candidate_pair.last_packet_received_timestamp);
- verifier.TestMemberIsDefined(candidate_pair.last_packet_sent_timestamp);
+ verifier.TestAttributeIsDefined(
+ candidate_pair.last_packet_received_timestamp);
+ verifier.TestAttributeIsDefined(candidate_pair.last_packet_sent_timestamp);
- return verifier.ExpectAllMembersSuccessfullyTested();
+ return verifier.ExpectAllAttributesSuccessfullyTested();
}
bool VerifyRTCIceCandidateStats(const RTCIceCandidateStats& candidate) {
RTCStatsVerifier verifier(report_.get(), &candidate);
- verifier.TestMemberIsIDReference(candidate.transport_id,
- RTCTransportStats::kType);
- verifier.TestMemberIsDefined(candidate.is_remote);
+ verifier.TestAttributeIsIDReference(candidate.transport_id,
+ RTCTransportStats::kType);
+ verifier.TestAttributeIsDefined(candidate.is_remote);
if (*candidate.is_remote) {
- verifier.TestMemberIsUndefined(candidate.network_type);
- verifier.TestMemberIsUndefined(candidate.network_adapter_type);
- verifier.TestMemberIsUndefined(candidate.vpn);
+ verifier.TestAttributeIsUndefined(candidate.network_type);
+ verifier.TestAttributeIsUndefined(candidate.network_adapter_type);
+ verifier.TestAttributeIsUndefined(candidate.vpn);
} else {
- verifier.TestMemberIsDefined(candidate.network_type);
- verifier.TestMemberIsDefined(candidate.network_adapter_type);
- verifier.TestMemberIsDefined(candidate.vpn);
+ verifier.TestAttributeIsDefined(candidate.network_type);
+ verifier.TestAttributeIsDefined(candidate.network_adapter_type);
+ verifier.TestAttributeIsDefined(candidate.vpn);
}
- verifier.TestMemberIsDefined(candidate.ip);
- verifier.TestMemberIsDefined(candidate.address);
- verifier.TestMemberIsNonNegative<int32_t>(candidate.port);
- verifier.TestMemberIsDefined(candidate.protocol);
- verifier.TestMemberIsDefined(candidate.candidate_type);
- verifier.TestMemberIsNonNegative<int32_t>(candidate.priority);
- verifier.TestMemberIsUndefined(candidate.url);
- verifier.TestMemberIsUndefined(candidate.relay_protocol);
- verifier.TestMemberIsDefined(candidate.foundation);
- verifier.TestMemberIsUndefined(candidate.related_address);
- verifier.TestMemberIsUndefined(candidate.related_port);
- verifier.TestMemberIsDefined(candidate.username_fragment);
- verifier.TestMemberIsUndefined(candidate.tcp_type);
- return verifier.ExpectAllMembersSuccessfullyTested();
+ verifier.TestAttributeIsDefined(candidate.ip);
+ verifier.TestAttributeIsDefined(candidate.address);
+ verifier.TestAttributeIsNonNegative<int32_t>(candidate.port);
+ verifier.TestAttributeIsDefined(candidate.protocol);
+ verifier.TestAttributeIsDefined(candidate.candidate_type);
+ verifier.TestAttributeIsNonNegative<int32_t>(candidate.priority);
+ verifier.TestAttributeIsUndefined(candidate.url);
+ verifier.TestAttributeIsUndefined(candidate.relay_protocol);
+ verifier.TestAttributeIsDefined(candidate.foundation);
+ verifier.TestAttributeIsUndefined(candidate.related_address);
+ verifier.TestAttributeIsUndefined(candidate.related_port);
+ verifier.TestAttributeIsDefined(candidate.username_fragment);
+ verifier.TestAttributeIsUndefined(candidate.tcp_type);
+ return verifier.ExpectAllAttributesSuccessfullyTested();
}
bool VerifyRTCLocalIceCandidateStats(
@@ -560,226 +572,235 @@ class RTCStatsReportVerifier {
bool VerifyRTCPeerConnectionStats(
const RTCPeerConnectionStats& peer_connection) {
RTCStatsVerifier verifier(report_.get(), &peer_connection);
- verifier.TestMemberIsNonNegative<uint32_t>(
+ verifier.TestAttributeIsNonNegative<uint32_t>(
peer_connection.data_channels_opened);
- verifier.TestMemberIsNonNegative<uint32_t>(
+ verifier.TestAttributeIsNonNegative<uint32_t>(
peer_connection.data_channels_closed);
- return verifier.ExpectAllMembersSuccessfullyTested();
+ return verifier.ExpectAllAttributesSuccessfullyTested();
}
void VerifyRTCRtpStreamStats(const RTCRtpStreamStats& stream,
RTCStatsVerifier& verifier) {
- verifier.TestMemberIsDefined(stream.ssrc);
- verifier.TestMemberIsDefined(stream.kind);
- verifier.TestMemberIsIDReference(stream.transport_id,
- RTCTransportStats::kType);
- verifier.TestMemberIsIDReference(stream.codec_id, RTCCodecStats::kType);
+ verifier.TestAttributeIsDefined(stream.ssrc);
+ verifier.TestAttributeIsDefined(stream.kind);
+ verifier.TestAttributeIsIDReference(stream.transport_id,
+ RTCTransportStats::kType);
+ verifier.TestAttributeIsIDReference(stream.codec_id, RTCCodecStats::kType);
}
void VerifyRTCSentRtpStreamStats(const RTCSentRtpStreamStats& sent_stream,
RTCStatsVerifier& verifier) {
VerifyRTCRtpStreamStats(sent_stream, verifier);
- verifier.TestMemberIsNonNegative<uint64_t>(sent_stream.packets_sent);
- verifier.TestMemberIsNonNegative<uint64_t>(sent_stream.bytes_sent);
+ verifier.TestAttributeIsNonNegative<uint64_t>(sent_stream.packets_sent);
+ verifier.TestAttributeIsNonNegative<uint64_t>(sent_stream.bytes_sent);
}
bool VerifyRTCInboundRtpStreamStats(
const RTCInboundRtpStreamStats& inbound_stream) {
RTCStatsVerifier verifier(report_.get(), &inbound_stream);
VerifyRTCReceivedRtpStreamStats(inbound_stream, verifier);
- verifier.TestMemberIsOptionalIDReference(
+ verifier.TestAttributeIsOptionalIDReference(
inbound_stream.remote_id, RTCRemoteOutboundRtpStreamStats::kType);
- verifier.TestMemberIsDefined(inbound_stream.mid);
- verifier.TestMemberIsDefined(inbound_stream.track_identifier);
- if (inbound_stream.kind.is_defined() && *inbound_stream.kind == "video") {
- verifier.TestMemberIsNonNegative<uint64_t>(inbound_stream.qp_sum);
- verifier.TestMemberIsDefined(inbound_stream.decoder_implementation);
- verifier.TestMemberIsDefined(inbound_stream.power_efficient_decoder);
+ verifier.TestAttributeIsDefined(inbound_stream.mid);
+ verifier.TestAttributeIsDefined(inbound_stream.track_identifier);
+ if (inbound_stream.kind.has_value() && *inbound_stream.kind == "video") {
+ verifier.TestAttributeIsNonNegative<uint64_t>(inbound_stream.qp_sum);
+ verifier.TestAttributeIsDefined(inbound_stream.decoder_implementation);
+ verifier.TestAttributeIsDefined(inbound_stream.power_efficient_decoder);
} else {
- verifier.TestMemberIsUndefined(inbound_stream.qp_sum);
- verifier.TestMemberIsUndefined(inbound_stream.decoder_implementation);
- verifier.TestMemberIsUndefined(inbound_stream.power_efficient_decoder);
+ verifier.TestAttributeIsUndefined(inbound_stream.qp_sum);
+ verifier.TestAttributeIsUndefined(inbound_stream.decoder_implementation);
+ verifier.TestAttributeIsUndefined(inbound_stream.power_efficient_decoder);
}
- verifier.TestMemberIsNonNegative<uint32_t>(inbound_stream.packets_received);
- if (inbound_stream.kind.is_defined() && *inbound_stream.kind == "audio") {
- verifier.TestMemberIsNonNegative<uint64_t>(
+ verifier.TestAttributeIsNonNegative<uint32_t>(
+ inbound_stream.packets_received);
+ if (inbound_stream.kind.has_value() && *inbound_stream.kind == "audio") {
+ verifier.TestAttributeIsNonNegative<uint64_t>(
inbound_stream.packets_discarded);
- verifier.TestMemberIsNonNegative<uint64_t>(
+ verifier.TestAttributeIsNonNegative<uint64_t>(
inbound_stream.fec_packets_received);
- verifier.TestMemberIsNonNegative<uint64_t>(
+ verifier.TestAttributeIsNonNegative<uint64_t>(
inbound_stream.fec_packets_discarded);
- verifier.TestMemberIsUndefined(inbound_stream.fec_bytes_received);
+ verifier.TestAttributeIsUndefined(inbound_stream.fec_bytes_received);
} else {
- verifier.TestMemberIsUndefined(inbound_stream.packets_discarded);
+ verifier.TestAttributeIsUndefined(inbound_stream.packets_discarded);
// FEC stats are only present when FlexFEC was negotiated which is guarded
// by the WebRTC-FlexFEC-03-Advertised/Enabled/ field trial and off by
// default.
- verifier.TestMemberIsUndefined(inbound_stream.fec_bytes_received);
- verifier.TestMemberIsUndefined(inbound_stream.fec_packets_received);
- verifier.TestMemberIsUndefined(inbound_stream.fec_packets_discarded);
- verifier.TestMemberIsUndefined(inbound_stream.fec_ssrc);
+ verifier.TestAttributeIsUndefined(inbound_stream.fec_bytes_received);
+ verifier.TestAttributeIsUndefined(inbound_stream.fec_packets_received);
+ verifier.TestAttributeIsUndefined(inbound_stream.fec_packets_discarded);
+ verifier.TestAttributeIsUndefined(inbound_stream.fec_ssrc);
}
- verifier.TestMemberIsNonNegative<uint64_t>(inbound_stream.bytes_received);
- verifier.TestMemberIsNonNegative<uint64_t>(
+ verifier.TestAttributeIsNonNegative<uint64_t>(
+ inbound_stream.bytes_received);
+ verifier.TestAttributeIsNonNegative<uint64_t>(
inbound_stream.header_bytes_received);
- verifier.TestMemberIsDefined(inbound_stream.last_packet_received_timestamp);
- if (inbound_stream.frames_received.ValueOrDefault(0) > 0) {
- verifier.TestMemberIsNonNegative<uint32_t>(inbound_stream.frame_width);
- verifier.TestMemberIsNonNegative<uint32_t>(inbound_stream.frame_height);
+ verifier.TestAttributeIsDefined(
+ inbound_stream.last_packet_received_timestamp);
+ if (inbound_stream.frames_received.value_or(0) > 0) {
+ verifier.TestAttributeIsNonNegative<uint32_t>(inbound_stream.frame_width);
+ verifier.TestAttributeIsNonNegative<uint32_t>(
+ inbound_stream.frame_height);
} else {
- verifier.TestMemberIsUndefined(inbound_stream.frame_width);
- verifier.TestMemberIsUndefined(inbound_stream.frame_height);
+ verifier.TestAttributeIsUndefined(inbound_stream.frame_width);
+ verifier.TestAttributeIsUndefined(inbound_stream.frame_height);
}
- if (inbound_stream.frames_per_second.is_defined()) {
- verifier.TestMemberIsNonNegative<double>(
+ if (inbound_stream.frames_per_second.has_value()) {
+ verifier.TestAttributeIsNonNegative<double>(
inbound_stream.frames_per_second);
} else {
- verifier.TestMemberIsUndefined(inbound_stream.frames_per_second);
+ verifier.TestAttributeIsUndefined(inbound_stream.frames_per_second);
}
- verifier.TestMemberIsNonNegative<double>(
+ verifier.TestAttributeIsNonNegative<double>(
inbound_stream.jitter_buffer_delay);
- verifier.TestMemberIsNonNegative<uint64_t>(
+ verifier.TestAttributeIsNonNegative<uint64_t>(
inbound_stream.jitter_buffer_emitted_count);
- verifier.TestMemberIsNonNegative<double>(
+ verifier.TestAttributeIsNonNegative<double>(
inbound_stream.jitter_buffer_target_delay);
- verifier.TestMemberIsNonNegative<double>(
+ verifier.TestAttributeIsNonNegative<double>(
inbound_stream.jitter_buffer_minimum_delay);
- if (inbound_stream.kind.is_defined() && *inbound_stream.kind == "video") {
- verifier.TestMemberIsUndefined(inbound_stream.total_samples_received);
- verifier.TestMemberIsUndefined(inbound_stream.concealed_samples);
- verifier.TestMemberIsUndefined(inbound_stream.silent_concealed_samples);
- verifier.TestMemberIsUndefined(inbound_stream.concealment_events);
- verifier.TestMemberIsUndefined(
+ if (inbound_stream.kind.has_value() && *inbound_stream.kind == "video") {
+ verifier.TestAttributeIsUndefined(inbound_stream.total_samples_received);
+ verifier.TestAttributeIsUndefined(inbound_stream.concealed_samples);
+ verifier.TestAttributeIsUndefined(
+ inbound_stream.silent_concealed_samples);
+ verifier.TestAttributeIsUndefined(inbound_stream.concealment_events);
+ verifier.TestAttributeIsUndefined(
inbound_stream.inserted_samples_for_deceleration);
- verifier.TestMemberIsUndefined(
+ verifier.TestAttributeIsUndefined(
inbound_stream.removed_samples_for_acceleration);
- verifier.TestMemberIsUndefined(inbound_stream.audio_level);
- verifier.TestMemberIsUndefined(inbound_stream.total_audio_energy);
- verifier.TestMemberIsUndefined(inbound_stream.total_samples_duration);
- verifier.TestMemberIsNonNegative<uint32_t>(
+ verifier.TestAttributeIsUndefined(inbound_stream.audio_level);
+ verifier.TestAttributeIsUndefined(inbound_stream.total_audio_energy);
+ verifier.TestAttributeIsUndefined(inbound_stream.total_samples_duration);
+ verifier.TestAttributeIsNonNegative<uint32_t>(
inbound_stream.frames_received);
- verifier.TestMemberIsNonNegative<uint32_t>(inbound_stream.fir_count);
- verifier.TestMemberIsNonNegative<uint32_t>(inbound_stream.pli_count);
- verifier.TestMemberIsNonNegative<uint32_t>(inbound_stream.nack_count);
+ verifier.TestAttributeIsNonNegative<uint32_t>(inbound_stream.fir_count);
+ verifier.TestAttributeIsNonNegative<uint32_t>(inbound_stream.pli_count);
+ verifier.TestAttributeIsNonNegative<uint32_t>(inbound_stream.nack_count);
} else {
- verifier.TestMemberIsUndefined(inbound_stream.fir_count);
- verifier.TestMemberIsUndefined(inbound_stream.pli_count);
- verifier.TestMemberIsUndefined(inbound_stream.nack_count);
- verifier.TestMemberIsPositive<uint64_t>(
+ verifier.TestAttributeIsUndefined(inbound_stream.fir_count);
+ verifier.TestAttributeIsUndefined(inbound_stream.pli_count);
+ verifier.TestAttributeIsUndefined(inbound_stream.nack_count);
+ verifier.TestAttributeIsPositive<uint64_t>(
inbound_stream.total_samples_received);
- verifier.TestMemberIsNonNegative<uint64_t>(
+ verifier.TestAttributeIsNonNegative<uint64_t>(
inbound_stream.concealed_samples);
- verifier.TestMemberIsNonNegative<uint64_t>(
+ verifier.TestAttributeIsNonNegative<uint64_t>(
inbound_stream.silent_concealed_samples);
- verifier.TestMemberIsNonNegative<uint64_t>(
+ verifier.TestAttributeIsNonNegative<uint64_t>(
inbound_stream.concealment_events);
- verifier.TestMemberIsNonNegative<uint64_t>(
+ verifier.TestAttributeIsNonNegative<uint64_t>(
inbound_stream.inserted_samples_for_deceleration);
- verifier.TestMemberIsNonNegative<uint64_t>(
+ verifier.TestAttributeIsNonNegative<uint64_t>(
inbound_stream.removed_samples_for_acceleration);
- verifier.TestMemberIsNonNegative<double>(
+ verifier.TestAttributeIsNonNegative<double>(
inbound_stream.jitter_buffer_target_delay);
- verifier.TestMemberIsNonNegative<double>(
+ verifier.TestAttributeIsNonNegative<double>(
inbound_stream.jitter_buffer_minimum_delay);
- verifier.TestMemberIsPositive<double>(inbound_stream.audio_level);
- verifier.TestMemberIsPositive<double>(inbound_stream.total_audio_energy);
- verifier.TestMemberIsPositive<double>(
+ verifier.TestAttributeIsPositive<double>(inbound_stream.audio_level);
+ verifier.TestAttributeIsPositive<double>(
+ inbound_stream.total_audio_energy);
+ verifier.TestAttributeIsPositive<double>(
inbound_stream.total_samples_duration);
- verifier.TestMemberIsUndefined(inbound_stream.frames_received);
+ verifier.TestAttributeIsUndefined(inbound_stream.frames_received);
}
// RTX stats are typically only defined for video where RTX is negotiated.
- if (inbound_stream.kind.is_defined() && *inbound_stream.kind == "video") {
- verifier.TestMemberIsNonNegative<uint64_t>(
+ if (inbound_stream.kind.has_value() && *inbound_stream.kind == "video") {
+ verifier.TestAttributeIsNonNegative<uint64_t>(
inbound_stream.retransmitted_packets_received);
- verifier.TestMemberIsNonNegative<uint64_t>(
+ verifier.TestAttributeIsNonNegative<uint64_t>(
inbound_stream.retransmitted_bytes_received);
- verifier.TestMemberIsNonNegative<uint32_t>(inbound_stream.rtx_ssrc);
+ verifier.TestAttributeIsNonNegative<uint32_t>(inbound_stream.rtx_ssrc);
} else {
- verifier.TestMemberIsUndefined(
+ verifier.TestAttributeIsUndefined(
inbound_stream.retransmitted_packets_received);
- verifier.TestMemberIsUndefined(
+ verifier.TestAttributeIsUndefined(
inbound_stream.retransmitted_bytes_received);
- verifier.TestMemberIsUndefined(inbound_stream.rtx_ssrc);
- verifier.TestMemberIsUndefined(inbound_stream.fec_ssrc);
+ verifier.TestAttributeIsUndefined(inbound_stream.rtx_ssrc);
+ verifier.TestAttributeIsUndefined(inbound_stream.fec_ssrc);
}
// Test runtime too short to get an estimate (at least two RTCP sender
// reports need to be received).
- verifier.MarkMemberTested(inbound_stream.estimated_playout_timestamp, true);
- if (inbound_stream.kind.is_defined() && *inbound_stream.kind == "video") {
- verifier.TestMemberIsDefined(inbound_stream.frames_decoded);
- verifier.TestMemberIsDefined(inbound_stream.key_frames_decoded);
- verifier.TestMemberIsNonNegative<uint32_t>(inbound_stream.frames_dropped);
- verifier.TestMemberIsNonNegative<double>(
+ verifier.MarkAttributeTested(inbound_stream.estimated_playout_timestamp,
+ true);
+ if (inbound_stream.kind.has_value() && *inbound_stream.kind == "video") {
+ verifier.TestAttributeIsDefined(inbound_stream.frames_decoded);
+ verifier.TestAttributeIsDefined(inbound_stream.key_frames_decoded);
+ verifier.TestAttributeIsNonNegative<uint32_t>(
+ inbound_stream.frames_dropped);
+ verifier.TestAttributeIsNonNegative<double>(
inbound_stream.total_decode_time);
- verifier.TestMemberIsNonNegative<double>(
+ verifier.TestAttributeIsNonNegative<double>(
inbound_stream.total_processing_delay);
- verifier.TestMemberIsNonNegative<double>(
+ verifier.TestAttributeIsNonNegative<double>(
inbound_stream.total_assembly_time);
- verifier.TestMemberIsDefined(
+ verifier.TestAttributeIsDefined(
inbound_stream.frames_assembled_from_multiple_packets);
- verifier.TestMemberIsNonNegative<double>(
+ verifier.TestAttributeIsNonNegative<double>(
inbound_stream.total_inter_frame_delay);
- verifier.TestMemberIsNonNegative<double>(
+ verifier.TestAttributeIsNonNegative<double>(
inbound_stream.total_squared_inter_frame_delay);
- verifier.TestMemberIsNonNegative<uint32_t>(inbound_stream.pause_count);
- verifier.TestMemberIsNonNegative<double>(
+ verifier.TestAttributeIsNonNegative<uint32_t>(inbound_stream.pause_count);
+ verifier.TestAttributeIsNonNegative<double>(
inbound_stream.total_pauses_duration);
- verifier.TestMemberIsNonNegative<uint32_t>(inbound_stream.freeze_count);
- verifier.TestMemberIsNonNegative<double>(
+ verifier.TestAttributeIsNonNegative<uint32_t>(
+ inbound_stream.freeze_count);
+ verifier.TestAttributeIsNonNegative<double>(
inbound_stream.total_freezes_duration);
// The integration test is not set up to test screen share; don't require
// this to be present.
- verifier.MarkMemberTested(inbound_stream.content_type, true);
- verifier.TestMemberIsUndefined(inbound_stream.jitter_buffer_flushes);
- verifier.TestMemberIsUndefined(
+ verifier.MarkAttributeTested(inbound_stream.content_type, true);
+ verifier.TestAttributeIsUndefined(inbound_stream.jitter_buffer_flushes);
+ verifier.TestAttributeIsUndefined(
inbound_stream.delayed_packet_outage_samples);
- verifier.TestMemberIsUndefined(
+ verifier.TestAttributeIsUndefined(
inbound_stream.relative_packet_arrival_delay);
- verifier.TestMemberIsUndefined(inbound_stream.interruption_count);
- verifier.TestMemberIsUndefined(
+ verifier.TestAttributeIsUndefined(inbound_stream.interruption_count);
+ verifier.TestAttributeIsUndefined(
inbound_stream.total_interruption_duration);
- verifier.TestMemberIsNonNegative<double>(
+ verifier.TestAttributeIsNonNegative<double>(
inbound_stream.min_playout_delay);
- verifier.TestMemberIsDefined(inbound_stream.goog_timing_frame_info);
+ verifier.TestAttributeIsDefined(inbound_stream.goog_timing_frame_info);
} else {
- verifier.TestMemberIsUndefined(inbound_stream.frames_decoded);
- verifier.TestMemberIsUndefined(inbound_stream.key_frames_decoded);
- verifier.TestMemberIsUndefined(inbound_stream.frames_dropped);
- verifier.TestMemberIsUndefined(inbound_stream.total_decode_time);
- verifier.TestMemberIsUndefined(inbound_stream.total_processing_delay);
- verifier.TestMemberIsUndefined(inbound_stream.total_assembly_time);
- verifier.TestMemberIsUndefined(
+ verifier.TestAttributeIsUndefined(inbound_stream.frames_decoded);
+ verifier.TestAttributeIsUndefined(inbound_stream.key_frames_decoded);
+ verifier.TestAttributeIsUndefined(inbound_stream.frames_dropped);
+ verifier.TestAttributeIsUndefined(inbound_stream.total_decode_time);
+ verifier.TestAttributeIsUndefined(inbound_stream.total_processing_delay);
+ verifier.TestAttributeIsUndefined(inbound_stream.total_assembly_time);
+ verifier.TestAttributeIsUndefined(
inbound_stream.frames_assembled_from_multiple_packets);
- verifier.TestMemberIsUndefined(inbound_stream.total_inter_frame_delay);
- verifier.TestMemberIsUndefined(
+ verifier.TestAttributeIsUndefined(inbound_stream.total_inter_frame_delay);
+ verifier.TestAttributeIsUndefined(
inbound_stream.total_squared_inter_frame_delay);
- verifier.TestMemberIsUndefined(inbound_stream.pause_count);
- verifier.TestMemberIsUndefined(inbound_stream.total_pauses_duration);
- verifier.TestMemberIsUndefined(inbound_stream.freeze_count);
- verifier.TestMemberIsUndefined(inbound_stream.total_freezes_duration);
- verifier.TestMemberIsUndefined(inbound_stream.content_type);
- verifier.TestMemberIsNonNegative<uint64_t>(
+ verifier.TestAttributeIsUndefined(inbound_stream.pause_count);
+ verifier.TestAttributeIsUndefined(inbound_stream.total_pauses_duration);
+ verifier.TestAttributeIsUndefined(inbound_stream.freeze_count);
+ verifier.TestAttributeIsUndefined(inbound_stream.total_freezes_duration);
+ verifier.TestAttributeIsUndefined(inbound_stream.content_type);
+ verifier.TestAttributeIsNonNegative<uint64_t>(
inbound_stream.jitter_buffer_flushes);
- verifier.TestMemberIsNonNegative<uint64_t>(
+ verifier.TestAttributeIsNonNegative<uint64_t>(
inbound_stream.delayed_packet_outage_samples);
- verifier.TestMemberIsNonNegative<double>(
+ verifier.TestAttributeIsNonNegative<double>(
inbound_stream.relative_packet_arrival_delay);
- verifier.TestMemberIsNonNegative<uint32_t>(
+ verifier.TestAttributeIsNonNegative<uint32_t>(
inbound_stream.interruption_count);
- verifier.TestMemberIsNonNegative<double>(
+ verifier.TestAttributeIsNonNegative<double>(
inbound_stream.total_interruption_duration);
- verifier.TestMemberIsUndefined(inbound_stream.min_playout_delay);
- verifier.TestMemberIsUndefined(inbound_stream.goog_timing_frame_info);
+ verifier.TestAttributeIsUndefined(inbound_stream.min_playout_delay);
+ verifier.TestAttributeIsUndefined(inbound_stream.goog_timing_frame_info);
}
- if (inbound_stream.kind.is_defined() && *inbound_stream.kind == "audio") {
- verifier.TestMemberIsDefined(inbound_stream.playout_id);
+ if (inbound_stream.kind.has_value() && *inbound_stream.kind == "audio") {
+ verifier.TestAttributeIsDefined(inbound_stream.playout_id);
} else {
- verifier.TestMemberIsUndefined(inbound_stream.playout_id);
+ verifier.TestAttributeIsUndefined(inbound_stream.playout_id);
}
- return verifier.ExpectAllMembersSuccessfullyTested();
+ return verifier.ExpectAllAttributesSuccessfullyTested();
}
bool VerifyRTCOutboundRtpStreamStats(
@@ -787,122 +808,128 @@ class RTCStatsReportVerifier {
RTCStatsVerifier verifier(report_.get(), &outbound_stream);
VerifyRTCSentRtpStreamStats(outbound_stream, verifier);
- verifier.TestMemberIsDefined(outbound_stream.mid);
- verifier.TestMemberIsDefined(outbound_stream.active);
- if (outbound_stream.kind.is_defined() && *outbound_stream.kind == "video") {
- verifier.TestMemberIsIDReference(outbound_stream.media_source_id,
- RTCVideoSourceStats::kType);
- verifier.TestMemberIsNonNegative<uint32_t>(outbound_stream.fir_count);
- verifier.TestMemberIsNonNegative<uint32_t>(outbound_stream.pli_count);
+ verifier.TestAttributeIsDefined(outbound_stream.mid);
+ verifier.TestAttributeIsDefined(outbound_stream.active);
+ if (outbound_stream.kind.has_value() && *outbound_stream.kind == "video") {
+ verifier.TestAttributeIsIDReference(outbound_stream.media_source_id,
+ RTCVideoSourceStats::kType);
+ verifier.TestAttributeIsNonNegative<uint32_t>(outbound_stream.fir_count);
+ verifier.TestAttributeIsNonNegative<uint32_t>(outbound_stream.pli_count);
if (*outbound_stream.frames_encoded > 0) {
- verifier.TestMemberIsNonNegative<uint64_t>(outbound_stream.qp_sum);
+ verifier.TestAttributeIsNonNegative<uint64_t>(outbound_stream.qp_sum);
} else {
- verifier.TestMemberIsUndefined(outbound_stream.qp_sum);
+ verifier.TestAttributeIsUndefined(outbound_stream.qp_sum);
}
} else {
- verifier.TestMemberIsUndefined(outbound_stream.fir_count);
- verifier.TestMemberIsUndefined(outbound_stream.pli_count);
- verifier.TestMemberIsIDReference(outbound_stream.media_source_id,
- RTCAudioSourceStats::kType);
- verifier.TestMemberIsUndefined(outbound_stream.qp_sum);
+ verifier.TestAttributeIsUndefined(outbound_stream.fir_count);
+ verifier.TestAttributeIsUndefined(outbound_stream.pli_count);
+ verifier.TestAttributeIsIDReference(outbound_stream.media_source_id,
+ RTCAudioSourceStats::kType);
+ verifier.TestAttributeIsUndefined(outbound_stream.qp_sum);
}
- verifier.TestMemberIsNonNegative<uint32_t>(outbound_stream.nack_count);
- verifier.TestMemberIsOptionalIDReference(
+ verifier.TestAttributeIsNonNegative<uint32_t>(outbound_stream.nack_count);
+ verifier.TestAttributeIsOptionalIDReference(
outbound_stream.remote_id, RTCRemoteInboundRtpStreamStats::kType);
- verifier.TestMemberIsNonNegative<double>(
+ verifier.TestAttributeIsNonNegative<double>(
outbound_stream.total_packet_send_delay);
- verifier.TestMemberIsNonNegative<uint64_t>(
+ verifier.TestAttributeIsNonNegative<uint64_t>(
outbound_stream.retransmitted_packets_sent);
- verifier.TestMemberIsNonNegative<uint64_t>(
+ verifier.TestAttributeIsNonNegative<uint64_t>(
outbound_stream.header_bytes_sent);
- verifier.TestMemberIsNonNegative<uint64_t>(
+ verifier.TestAttributeIsNonNegative<uint64_t>(
outbound_stream.retransmitted_bytes_sent);
- verifier.TestMemberIsNonNegative<double>(outbound_stream.target_bitrate);
- if (outbound_stream.kind.is_defined() && *outbound_stream.kind == "video") {
- verifier.TestMemberIsDefined(outbound_stream.frames_encoded);
- verifier.TestMemberIsDefined(outbound_stream.key_frames_encoded);
- verifier.TestMemberIsNonNegative<double>(
+ verifier.TestAttributeIsNonNegative<double>(outbound_stream.target_bitrate);
+ if (outbound_stream.kind.has_value() && *outbound_stream.kind == "video") {
+ verifier.TestAttributeIsDefined(outbound_stream.frames_encoded);
+ verifier.TestAttributeIsDefined(outbound_stream.key_frames_encoded);
+ verifier.TestAttributeIsNonNegative<double>(
outbound_stream.total_encode_time);
- verifier.TestMemberIsNonNegative<uint64_t>(
+ verifier.TestAttributeIsNonNegative<uint64_t>(
outbound_stream.total_encoded_bytes_target);
- verifier.TestMemberIsDefined(outbound_stream.quality_limitation_reason);
- verifier.TestMemberIsDefined(
+ verifier.TestAttributeIsDefined(
+ outbound_stream.quality_limitation_reason);
+ verifier.TestAttributeIsDefined(
outbound_stream.quality_limitation_durations);
- verifier.TestMemberIsNonNegative<uint32_t>(
+ verifier.TestAttributeIsNonNegative<uint32_t>(
outbound_stream.quality_limitation_resolution_changes);
// The integration test is not set up to test screen share; don't require
// this to be present.
- verifier.MarkMemberTested(outbound_stream.content_type, true);
- verifier.TestMemberIsDefined(outbound_stream.encoder_implementation);
- verifier.TestMemberIsDefined(outbound_stream.power_efficient_encoder);
+ verifier.MarkAttributeTested(outbound_stream.content_type, true);
+ verifier.TestAttributeIsDefined(outbound_stream.encoder_implementation);
+ verifier.TestAttributeIsDefined(outbound_stream.power_efficient_encoder);
// Unless an implementation-specific amount of time has passed and at
// least one frame has been encoded, undefined is reported. Because it
// is hard to tell what is the case here, we treat FPS as optional.
// TODO(hbos): Update the tests to run until all implemented metrics
// should be populated.
- if (outbound_stream.frames_per_second.is_defined()) {
- verifier.TestMemberIsNonNegative<double>(
+ if (outbound_stream.frames_per_second.has_value()) {
+ verifier.TestAttributeIsNonNegative<double>(
outbound_stream.frames_per_second);
} else {
- verifier.TestMemberIsUndefined(outbound_stream.frames_per_second);
+ verifier.TestAttributeIsUndefined(outbound_stream.frames_per_second);
}
- verifier.TestMemberIsNonNegative<uint32_t>(outbound_stream.frame_height);
- verifier.TestMemberIsNonNegative<uint32_t>(outbound_stream.frame_width);
- verifier.TestMemberIsNonNegative<uint32_t>(outbound_stream.frames_sent);
- verifier.TestMemberIsNonNegative<uint32_t>(
+ verifier.TestAttributeIsNonNegative<uint32_t>(
+ outbound_stream.frame_height);
+ verifier.TestAttributeIsNonNegative<uint32_t>(
+ outbound_stream.frame_width);
+ verifier.TestAttributeIsNonNegative<uint32_t>(
+ outbound_stream.frames_sent);
+ verifier.TestAttributeIsNonNegative<uint32_t>(
outbound_stream.huge_frames_sent);
- verifier.MarkMemberTested(outbound_stream.rid, true);
- verifier.TestMemberIsDefined(outbound_stream.scalability_mode);
- verifier.TestMemberIsNonNegative<uint32_t>(outbound_stream.rtx_ssrc);
+ verifier.MarkAttributeTested(outbound_stream.rid, true);
+ verifier.TestAttributeIsDefined(outbound_stream.scalability_mode);
+ verifier.TestAttributeIsNonNegative<uint32_t>(outbound_stream.rtx_ssrc);
} else {
- verifier.TestMemberIsUndefined(outbound_stream.frames_encoded);
- verifier.TestMemberIsUndefined(outbound_stream.key_frames_encoded);
- verifier.TestMemberIsUndefined(outbound_stream.total_encode_time);
- verifier.TestMemberIsUndefined(
+ verifier.TestAttributeIsUndefined(outbound_stream.frames_encoded);
+ verifier.TestAttributeIsUndefined(outbound_stream.key_frames_encoded);
+ verifier.TestAttributeIsUndefined(outbound_stream.total_encode_time);
+ verifier.TestAttributeIsUndefined(
outbound_stream.total_encoded_bytes_target);
- verifier.TestMemberIsUndefined(outbound_stream.quality_limitation_reason);
- verifier.TestMemberIsUndefined(
+ verifier.TestAttributeIsUndefined(
+ outbound_stream.quality_limitation_reason);
+ verifier.TestAttributeIsUndefined(
outbound_stream.quality_limitation_durations);
- verifier.TestMemberIsUndefined(
+ verifier.TestAttributeIsUndefined(
outbound_stream.quality_limitation_resolution_changes);
- verifier.TestMemberIsUndefined(outbound_stream.content_type);
+ verifier.TestAttributeIsUndefined(outbound_stream.content_type);
// TODO(hbos): Implement for audio as well.
- verifier.TestMemberIsUndefined(outbound_stream.encoder_implementation);
- verifier.TestMemberIsUndefined(outbound_stream.power_efficient_encoder);
- verifier.TestMemberIsUndefined(outbound_stream.rid);
- verifier.TestMemberIsUndefined(outbound_stream.frames_per_second);
- verifier.TestMemberIsUndefined(outbound_stream.frame_height);
- verifier.TestMemberIsUndefined(outbound_stream.frame_width);
- verifier.TestMemberIsUndefined(outbound_stream.frames_sent);
- verifier.TestMemberIsUndefined(outbound_stream.huge_frames_sent);
- verifier.TestMemberIsUndefined(outbound_stream.scalability_mode);
- verifier.TestMemberIsUndefined(outbound_stream.rtx_ssrc);
+ verifier.TestAttributeIsUndefined(outbound_stream.encoder_implementation);
+ verifier.TestAttributeIsUndefined(
+ outbound_stream.power_efficient_encoder);
+ verifier.TestAttributeIsUndefined(outbound_stream.rid);
+ verifier.TestAttributeIsUndefined(outbound_stream.frames_per_second);
+ verifier.TestAttributeIsUndefined(outbound_stream.frame_height);
+ verifier.TestAttributeIsUndefined(outbound_stream.frame_width);
+ verifier.TestAttributeIsUndefined(outbound_stream.frames_sent);
+ verifier.TestAttributeIsUndefined(outbound_stream.huge_frames_sent);
+ verifier.TestAttributeIsUndefined(outbound_stream.scalability_mode);
+ verifier.TestAttributeIsUndefined(outbound_stream.rtx_ssrc);
}
- return verifier.ExpectAllMembersSuccessfullyTested();
+ return verifier.ExpectAllAttributesSuccessfullyTested();
}
void VerifyRTCReceivedRtpStreamStats(
const RTCReceivedRtpStreamStats& received_rtp,
RTCStatsVerifier& verifier) {
VerifyRTCRtpStreamStats(received_rtp, verifier);
- verifier.TestMemberIsNonNegative<double>(received_rtp.jitter);
- verifier.TestMemberIsDefined(received_rtp.packets_lost);
+ verifier.TestAttributeIsNonNegative<double>(received_rtp.jitter);
+ verifier.TestAttributeIsDefined(received_rtp.packets_lost);
}
bool VerifyRTCRemoteInboundRtpStreamStats(
const RTCRemoteInboundRtpStreamStats& remote_inbound_stream) {
RTCStatsVerifier verifier(report_.get(), &remote_inbound_stream);
VerifyRTCReceivedRtpStreamStats(remote_inbound_stream, verifier);
- verifier.TestMemberIsDefined(remote_inbound_stream.fraction_lost);
- verifier.TestMemberIsIDReference(remote_inbound_stream.local_id,
- RTCOutboundRtpStreamStats::kType);
- verifier.TestMemberIsNonNegative<double>(
+ verifier.TestAttributeIsDefined(remote_inbound_stream.fraction_lost);
+ verifier.TestAttributeIsIDReference(remote_inbound_stream.local_id,
+ RTCOutboundRtpStreamStats::kType);
+ verifier.TestAttributeIsNonNegative<double>(
remote_inbound_stream.round_trip_time);
- verifier.TestMemberIsNonNegative<double>(
+ verifier.TestAttributeIsNonNegative<double>(
remote_inbound_stream.total_round_trip_time);
- verifier.TestMemberIsNonNegative<int32_t>(
+ verifier.TestAttributeIsNonNegative<int32_t>(
remote_inbound_stream.round_trip_time_measurements);
- return verifier.ExpectAllMembersSuccessfullyTested();
+ return verifier.ExpectAllAttributesSuccessfullyTested();
}
bool VerifyRTCRemoteOutboundRtpStreamStats(
@@ -910,19 +937,19 @@ class RTCStatsReportVerifier {
RTCStatsVerifier verifier(report_.get(), &remote_outbound_stream);
VerifyRTCRtpStreamStats(remote_outbound_stream, verifier);
VerifyRTCSentRtpStreamStats(remote_outbound_stream, verifier);
- verifier.TestMemberIsIDReference(remote_outbound_stream.local_id,
- RTCOutboundRtpStreamStats::kType);
- verifier.TestMemberIsNonNegative<double>(
+ verifier.TestAttributeIsIDReference(remote_outbound_stream.local_id,
+ RTCOutboundRtpStreamStats::kType);
+ verifier.TestAttributeIsNonNegative<double>(
remote_outbound_stream.remote_timestamp);
- verifier.TestMemberIsDefined(remote_outbound_stream.reports_sent);
- return verifier.ExpectAllMembersSuccessfullyTested();
+ verifier.TestAttributeIsDefined(remote_outbound_stream.reports_sent);
+ return verifier.ExpectAllAttributesSuccessfullyTested();
}
void VerifyRTCMediaSourceStats(const RTCMediaSourceStats& media_source,
RTCStatsVerifier* verifier) {
- verifier->TestMemberIsDefined(media_source.track_identifier);
- verifier->TestMemberIsDefined(media_source.kind);
- if (media_source.kind.is_defined()) {
+ verifier->TestAttributeIsDefined(media_source.track_identifier);
+ verifier->TestAttributeIsDefined(media_source.kind);
+ if (media_source.kind.has_value()) {
EXPECT_TRUE((*media_source.kind == "audio" &&
media_source.type() == RTCAudioSourceStats::kType) ||
(*media_source.kind == "video" &&
@@ -936,16 +963,18 @@ class RTCStatsReportVerifier {
// Audio level, unlike audio energy, only gets updated at a certain
// frequency, so we don't require that one to be positive to avoid a race
// (https://crbug.com/webrtc/10962).
- verifier.TestMemberIsNonNegative<double>(audio_source.audio_level);
- verifier.TestMemberIsPositive<double>(audio_source.total_audio_energy);
- verifier.TestMemberIsPositive<double>(audio_source.total_samples_duration);
+ verifier.TestAttributeIsNonNegative<double>(audio_source.audio_level);
+ verifier.TestAttributeIsPositive<double>(audio_source.total_audio_energy);
+ verifier.TestAttributeIsPositive<double>(
+ audio_source.total_samples_duration);
// TODO(hbos): `echo_return_loss` and `echo_return_loss_enhancement` are
// flaky on msan bot (sometimes defined, sometimes undefined). Should the
// test run until available or is there a way to have it always be
// defined? crbug.com/627816
- verifier.MarkMemberTested(audio_source.echo_return_loss, true);
- verifier.MarkMemberTested(audio_source.echo_return_loss_enhancement, true);
- return verifier.ExpectAllMembersSuccessfullyTested();
+ verifier.MarkAttributeTested(audio_source.echo_return_loss, true);
+ verifier.MarkAttributeTested(audio_source.echo_return_loss_enhancement,
+ true);
+ return verifier.ExpectAllAttributesSuccessfullyTested();
}
bool VerifyRTCVideoSourceStats(const RTCVideoSourceStats& video_source) {
@@ -953,58 +982,59 @@ class RTCStatsReportVerifier {
VerifyRTCMediaSourceStats(video_source, &verifier);
// TODO(hbos): This integration test uses fakes that doesn't support
// VideoTrackSourceInterface::Stats. When this is fixed we should
- // TestMemberIsNonNegative<uint32_t>() for `width` and `height` instead to
- // reflect real code.
- verifier.TestMemberIsUndefined(video_source.width);
- verifier.TestMemberIsUndefined(video_source.height);
- verifier.TestMemberIsNonNegative<uint32_t>(video_source.frames);
- verifier.TestMemberIsNonNegative<double>(video_source.frames_per_second);
- return verifier.ExpectAllMembersSuccessfullyTested();
+ // TestAttributeIsNonNegative<uint32_t>() for `width` and `height` instead
+ // to reflect real code.
+ verifier.TestAttributeIsUndefined(video_source.width);
+ verifier.TestAttributeIsUndefined(video_source.height);
+ verifier.TestAttributeIsNonNegative<uint32_t>(video_source.frames);
+ verifier.TestAttributeIsNonNegative<double>(video_source.frames_per_second);
+ return verifier.ExpectAllAttributesSuccessfullyTested();
}
bool VerifyRTCTransportStats(const RTCTransportStats& transport) {
RTCStatsVerifier verifier(report_.get(), &transport);
- verifier.TestMemberIsNonNegative<uint64_t>(transport.bytes_sent);
- verifier.TestMemberIsNonNegative<uint64_t>(transport.packets_sent);
- verifier.TestMemberIsNonNegative<uint64_t>(transport.bytes_received);
- verifier.TestMemberIsNonNegative<uint64_t>(transport.packets_received);
- verifier.TestMemberIsOptionalIDReference(transport.rtcp_transport_stats_id,
- RTCTransportStats::kType);
- verifier.TestMemberIsDefined(transport.dtls_state);
- verifier.TestMemberIsIDReference(transport.selected_candidate_pair_id,
- RTCIceCandidatePairStats::kType);
- verifier.TestMemberIsIDReference(transport.local_certificate_id,
- RTCCertificateStats::kType);
- verifier.TestMemberIsIDReference(transport.remote_certificate_id,
- RTCCertificateStats::kType);
- verifier.TestMemberIsDefined(transport.tls_version);
- verifier.TestMemberIsDefined(transport.dtls_cipher);
- verifier.TestMemberIsDefined(transport.dtls_role);
- verifier.TestMemberIsDefined(transport.srtp_cipher);
- verifier.TestMemberIsPositive<uint32_t>(
+ verifier.TestAttributeIsNonNegative<uint64_t>(transport.bytes_sent);
+ verifier.TestAttributeIsNonNegative<uint64_t>(transport.packets_sent);
+ verifier.TestAttributeIsNonNegative<uint64_t>(transport.bytes_received);
+ verifier.TestAttributeIsNonNegative<uint64_t>(transport.packets_received);
+ verifier.TestAttributeIsOptionalIDReference(
+ transport.rtcp_transport_stats_id, RTCTransportStats::kType);
+ verifier.TestAttributeIsDefined(transport.dtls_state);
+ verifier.TestAttributeIsIDReference(transport.selected_candidate_pair_id,
+ RTCIceCandidatePairStats::kType);
+ verifier.TestAttributeIsIDReference(transport.local_certificate_id,
+ RTCCertificateStats::kType);
+ verifier.TestAttributeIsIDReference(transport.remote_certificate_id,
+ RTCCertificateStats::kType);
+ verifier.TestAttributeIsDefined(transport.tls_version);
+ verifier.TestAttributeIsDefined(transport.dtls_cipher);
+ verifier.TestAttributeIsDefined(transport.dtls_role);
+ verifier.TestAttributeIsDefined(transport.srtp_cipher);
+ verifier.TestAttributeIsPositive<uint32_t>(
transport.selected_candidate_pair_changes);
- verifier.TestMemberIsDefined(transport.ice_role);
- verifier.TestMemberIsDefined(transport.ice_local_username_fragment);
- verifier.TestMemberIsDefined(transport.ice_state);
- return verifier.ExpectAllMembersSuccessfullyTested();
+ verifier.TestAttributeIsDefined(transport.ice_role);
+ verifier.TestAttributeIsDefined(transport.ice_local_username_fragment);
+ verifier.TestAttributeIsDefined(transport.ice_state);
+ return verifier.ExpectAllAttributesSuccessfullyTested();
}
bool VerifyRTCAudioPlayoutStats(const RTCAudioPlayoutStats& audio_playout) {
RTCStatsVerifier verifier(report_.get(), &audio_playout);
- verifier.TestMemberIsDefined(audio_playout.kind);
- if (audio_playout.kind.is_defined()) {
+ verifier.TestAttributeIsDefined(audio_playout.kind);
+ if (audio_playout.kind.has_value()) {
EXPECT_EQ(*audio_playout.kind, "audio");
}
- verifier.TestMemberIsNonNegative<uint64_t>(
+ verifier.TestAttributeIsNonNegative<uint64_t>(
audio_playout.synthesized_samples_events);
- verifier.TestMemberIsNonNegative<double>(
+ verifier.TestAttributeIsNonNegative<double>(
audio_playout.synthesized_samples_duration);
- verifier.TestMemberIsNonNegative<uint64_t>(
+ verifier.TestAttributeIsNonNegative<uint64_t>(
audio_playout.total_samples_count);
- verifier.TestMemberIsNonNegative<double>(
+ verifier.TestAttributeIsNonNegative<double>(
audio_playout.total_samples_duration);
- verifier.TestMemberIsNonNegative<double>(audio_playout.total_playout_delay);
- return verifier.ExpectAllMembersSuccessfullyTested();
+ verifier.TestAttributeIsNonNegative<double>(
+ audio_playout.total_playout_delay);
+ return verifier.ExpectAllAttributesSuccessfullyTested();
}
private:
@@ -1034,8 +1064,8 @@ TEST_F(RTCStatsIntegrationTest, GetStatsFromCallee) {
auto inbound_stats =
report->GetStatsOfType<RTCRemoteInboundRtpStreamStats>();
return !inbound_stats.empty() &&
- inbound_stats.front()->round_trip_time.is_defined() &&
- inbound_stats.front()->round_trip_time_measurements.is_defined();
+ inbound_stats.front()->round_trip_time.has_value() &&
+ inbound_stats.front()->round_trip_time_measurements.has_value();
};
EXPECT_TRUE_WAIT(GetStatsReportAndReturnTrueIfRttIsDefined(), kMaxWaitMs);
RTCStatsReportVerifier(report.get()).VerifyReport({});
@@ -1142,32 +1172,31 @@ TEST_F(RTCStatsIntegrationTest, GetsStatsWhileClosingPeerConnection) {
}
// GetStatsReferencedIds() is optimized to recognize what is or isn't a
-// referenced ID based on dictionary type information and knowing what members
-// are used as references, as opposed to iterating all members to find the ones
-// with the "Id" or "Ids" suffix. As such, GetStatsReferencedIds() is tested as
-// an integration test instead of a unit test in order to guard against adding
-// new references and forgetting to update GetStatsReferencedIds().
+// referenced ID based on dictionary type information and knowing what
+// attributes are used as references, as opposed to iterating all attributes to
+// find the ones with the "Id" or "Ids" suffix. As such, GetStatsReferencedIds()
+// is tested as an integration test instead of a unit test in order to guard
+// against adding new references and forgetting to update
+// GetStatsReferencedIds().
TEST_F(RTCStatsIntegrationTest, GetStatsReferencedIds) {
StartCall();
rtc::scoped_refptr<const RTCStatsReport> report = GetStatsFromCallee();
for (const RTCStats& stats : *report) {
- // Find all references by looking at all string members with the "Id" or
+ // Find all references by looking at all string attributes with the "Id" or
// "Ids" suffix.
std::set<const std::string*> expected_ids;
- for (const auto* member : stats.Members()) {
- if (!member->is_defined())
+ for (const auto& attribute : stats.Attributes()) {
+ if (!attribute.has_value())
continue;
- if (member->type() == RTCStatsMemberInterface::kString) {
- if (absl::EndsWith(member->name(), "Id")) {
- const auto& id = member->cast_to<const RTCStatsMember<std::string>>();
- expected_ids.insert(&(*id));
+ if (attribute.holds_alternative<std::string>()) {
+ if (absl::EndsWith(attribute.name(), "Id")) {
+ expected_ids.insert(&attribute.get<std::string>());
}
- } else if (member->type() == RTCStatsMemberInterface::kSequenceString) {
- if (absl::EndsWith(member->name(), "Ids")) {
- const auto& ids =
- member->cast_to<const RTCStatsMember<std::vector<std::string>>>();
- for (const std::string& id : *ids)
+ } else if (attribute.holds_alternative<std::vector<std::string>>()) {
+ if (absl::EndsWith(attribute.name(), "Ids")) {
+ for (const std::string& id :
+ attribute.get<std::vector<std::string>>())
expected_ids.insert(&id);
}
}
@@ -1184,16 +1213,17 @@ TEST_F(RTCStatsIntegrationTest, GetStatsReferencedIds) {
}
}
-TEST_F(RTCStatsIntegrationTest, GetStatsContainsNoDuplicateMembers) {
+TEST_F(RTCStatsIntegrationTest, GetStatsContainsNoDuplicateAttributes) {
StartCall();
rtc::scoped_refptr<const RTCStatsReport> report = GetStatsFromCallee();
for (const RTCStats& stats : *report) {
- std::set<std::string> member_names;
- for (const auto* member : stats.Members()) {
- EXPECT_TRUE(member_names.find(member->name()) == member_names.end())
- << member->name() << " is a duplicate!";
- member_names.insert(member->name());
+ std::set<std::string> attribute_names;
+ for (const auto& attribute : stats.Attributes()) {
+ EXPECT_TRUE(attribute_names.find(attribute.name()) ==
+ attribute_names.end())
+ << attribute.name() << " is a duplicate!";
+ attribute_names.insert(attribute.name());
}
}
}
diff --git a/third_party/libwebrtc/pc/rtc_stats_traversal.cc b/third_party/libwebrtc/pc/rtc_stats_traversal.cc
index 04de55028c..dfd0570b8f 100644
--- a/third_party/libwebrtc/pc/rtc_stats_traversal.cc
+++ b/third_party/libwebrtc/pc/rtc_stats_traversal.cc
@@ -44,7 +44,7 @@ void TraverseAndTakeVisitedStats(RTCStatsReport* report,
void AddIdIfDefined(const RTCStatsMember<std::string>& id,
std::vector<const std::string*>* neighbor_ids) {
- if (id.is_defined())
+ if (id.has_value())
neighbor_ids->push_back(&(*id));
}
diff --git a/third_party/libwebrtc/pc/rtp_transceiver.cc b/third_party/libwebrtc/pc/rtp_transceiver.cc
index ca626cc94b..34d744a3bb 100644
--- a/third_party/libwebrtc/pc/rtp_transceiver.cc
+++ b/third_party/libwebrtc/pc/rtp_transceiver.cc
@@ -51,9 +51,7 @@ RTCError VerifyCodecPreferences(
// transceiver.direction.
if (!absl::c_any_of(codecs, [&recv_codecs](const RtpCodecCapability& codec) {
- return codec.name != cricket::kRtxCodecName &&
- codec.name != cricket::kRedCodecName &&
- codec.name != cricket::kFlexfecCodecName &&
+ return codec.IsMediaCodec() &&
absl::c_any_of(recv_codecs,
[&codec](const cricket::Codec& recv_codec) {
return recv_codec.MatchesRtpCodec(codec);
@@ -65,9 +63,7 @@ RTCError VerifyCodecPreferences(
}
if (!absl::c_any_of(codecs, [&send_codecs](const RtpCodecCapability& codec) {
- return codec.name != cricket::kRtxCodecName &&
- codec.name != cricket::kRedCodecName &&
- codec.name != cricket::kFlexfecCodecName &&
+ return codec.IsMediaCodec() &&
absl::c_any_of(send_codecs,
[&codec](const cricket::Codec& send_codec) {
return send_codec.MatchesRtpCodec(codec);
@@ -101,11 +97,9 @@ RTCError VerifyCodecPreferences(
}
}
- // Check we have a real codec (not just rtx, red or fec)
+ // Check we have a real codec (not just rtx, red, fec or CN)
if (absl::c_all_of(codecs, [](const RtpCodecCapability& codec) {
- return codec.name == cricket::kRtxCodecName ||
- codec.name == cricket::kRedCodecName ||
- codec.name == cricket::kUlpfecCodecName;
+ return !codec.IsMediaCodec();
})) {
LOG_AND_RETURN_ERROR(
RTCErrorType::INVALID_MODIFICATION,
diff --git a/third_party/libwebrtc/pc/sctp_utils_unittest.cc b/third_party/libwebrtc/pc/sctp_utils_unittest.cc
index 9ef2068a05..64ad257983 100644
--- a/third_party/libwebrtc/pc/sctp_utils_unittest.cc
+++ b/third_party/libwebrtc/pc/sctp_utils_unittest.cc
@@ -72,11 +72,11 @@ class SctpUtilsTest : public ::testing::Test {
EXPECT_EQ(label.size(), label_length);
EXPECT_EQ(config.protocol.size(), protocol_length);
- std::string label_output;
- ASSERT_TRUE(buffer.ReadString(&label_output, label_length));
+ absl::string_view label_output;
+ ASSERT_TRUE(buffer.ReadStringView(&label_output, label_length));
EXPECT_EQ(label, label_output);
- std::string protocol_output;
- ASSERT_TRUE(buffer.ReadString(&protocol_output, protocol_length));
+ absl::string_view protocol_output;
+ ASSERT_TRUE(buffer.ReadStringView(&protocol_output, protocol_length));
EXPECT_EQ(config.protocol, protocol_output);
}
};
diff --git a/third_party/libwebrtc/pc/sdp_offer_answer.cc b/third_party/libwebrtc/pc/sdp_offer_answer.cc
index 1e43833a0b..67c8d10241 100644
--- a/third_party/libwebrtc/pc/sdp_offer_answer.cc
+++ b/third_party/libwebrtc/pc/sdp_offer_answer.cc
@@ -77,11 +77,6 @@ using cricket::SimulcastLayerList;
using cricket::StreamParams;
using cricket::TransportInfo;
-using cricket::LOCAL_PORT_TYPE;
-using cricket::PRFLX_PORT_TYPE;
-using cricket::RELAY_PORT_TYPE;
-using cricket::STUN_PORT_TYPE;
-
namespace webrtc {
namespace {
@@ -2081,24 +2076,16 @@ void SdpOfferAnswerHandler::ApplyRemoteDescription(
if (operation->unified_plan()) {
ApplyRemoteDescriptionUpdateTransceiverState(operation->type());
}
-
- const cricket::AudioContentDescription* audio_desc =
- GetFirstAudioContentDescription(remote_description()->description());
- const cricket::VideoContentDescription* video_desc =
- GetFirstVideoContentDescription(remote_description()->description());
-
- // Check if the descriptions include streams, just in case the peer supports
- // MSID, but doesn't indicate so with "a=msid-semantic".
- if (remote_description()->description()->msid_supported() ||
- (audio_desc && !audio_desc->streams().empty()) ||
- (video_desc && !video_desc->streams().empty())) {
- remote_peer_supports_msid_ = true;
- }
+ remote_peer_supports_msid_ =
+ remote_description()->description()->msid_signaling() !=
+ cricket::kMsidSignalingNotUsed;
if (!operation->unified_plan()) {
PlanBUpdateSendersAndReceivers(
- GetFirstAudioContent(remote_description()->description()), audio_desc,
- GetFirstVideoContent(remote_description()->description()), video_desc);
+ GetFirstAudioContent(remote_description()->description()),
+ GetFirstAudioContentDescription(remote_description()->description()),
+ GetFirstVideoContent(remote_description()->description()),
+ GetFirstVideoContentDescription(remote_description()->description()));
}
if (operation->type() == SdpType::kAnswer) {
diff --git a/third_party/libwebrtc/pc/sdp_offer_answer_unittest.cc b/third_party/libwebrtc/pc/sdp_offer_answer_unittest.cc
index f158febac7..f4c35bfd20 100644
--- a/third_party/libwebrtc/pc/sdp_offer_answer_unittest.cc
+++ b/third_party/libwebrtc/pc/sdp_offer_answer_unittest.cc
@@ -1005,6 +1005,53 @@ TEST_F(SdpOfferAnswerTest, SdpMungingWithInvalidPayloadTypeIsRejected) {
}
}
+TEST_F(SdpOfferAnswerTest, MsidSignalingInSubsequentOfferAnswer) {
+ auto pc = CreatePeerConnection();
+ pc->AddAudioTrack("audio_track", {});
+
+ std::string sdp =
+ "v=0\r\n"
+ "o=- 0 3 IN IP4 127.0.0.1\r\n"
+ "s=-\r\n"
+ "t=0 0\r\n"
+ "a=msid-semantic: WMS\r\n"
+ "a=fingerprint:sha-1 "
+ "4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB\r\n"
+ "a=setup:actpass\r\n"
+ "a=ice-ufrag:ETEn\r\n"
+ "a=ice-pwd:OtSK0WpNtpUjkY4+86js7Z/l\r\n"
+ "m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n"
+ "c=IN IP4 0.0.0.0\r\n"
+ "a=rtcp:9 IN IP4 0.0.0.0\r\n"
+ "a=recvonly\r\n"
+ "a=rtcp-mux\r\n"
+ "a=rtpmap:111 opus/48000/2\r\n";
+
+ auto offer = CreateSessionDescription(SdpType::kOffer, sdp);
+ EXPECT_TRUE(pc->SetRemoteDescription(std::move(offer)));
+
+ // Check the generated SDP.
+ auto answer = pc->CreateAnswer();
+ answer->ToString(&sdp);
+ EXPECT_NE(std::string::npos, sdp.find("a=msid:- audio_track\r\n"));
+
+ EXPECT_TRUE(pc->SetLocalDescription(std::move(answer)));
+
+ // Check the local description object.
+ auto local_description = pc->pc()->local_description();
+ ASSERT_EQ(local_description->description()->contents().size(), 1u);
+ auto streams = local_description->description()
+ ->contents()[0]
+ .media_description()
+ ->streams();
+ ASSERT_EQ(streams.size(), 1u);
+ EXPECT_EQ(streams[0].id, "audio_track");
+
+ // Check the serialization of the local description.
+ local_description->ToString(&sdp);
+ EXPECT_NE(std::string::npos, sdp.find("a=msid:- audio_track\r\n"));
+}
+
// Test variant with boolean order for audio-video and video-audio.
class SdpOfferAnswerShuffleMediaTypes
: public SdpOfferAnswerTest,
@@ -1096,4 +1143,76 @@ INSTANTIATE_TEST_SUITE_P(SdpOfferAnswerShuffleMediaTypes,
SdpOfferAnswerShuffleMediaTypes,
::testing::Values(true, false));
+TEST_F(SdpOfferAnswerTest, OfferWithNoCompatibleCodecsIsRejectedInAnswer) {
+ auto pc = CreatePeerConnection();
+ // An offer with no common codecs. This should reject both contents
+ // in the answer without throwing an error.
+ std::string sdp =
+ "v=0\r\n"
+ "o=- 0 3 IN IP4 127.0.0.1\r\n"
+ "s=-\r\n"
+ "t=0 0\r\n"
+ "a=fingerprint:sha-1 "
+ "4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB\r\n"
+ "a=setup:actpass\r\n"
+ "a=ice-ufrag:ETEn\r\n"
+ "a=ice-pwd:OtSK0WpNtpUjkY4+86js7Z/l\r\n"
+ "m=audio 9 RTP/SAVPF 97\r\n"
+ "c=IN IP4 0.0.0.0\r\n"
+ "a=sendrecv\r\n"
+ "a=rtpmap:97 x-unknown/90000\r\n"
+ "a=rtcp-mux\r\n"
+ "m=video 9 RTP/SAVPF 98\r\n"
+ "c=IN IP4 0.0.0.0\r\n"
+ "a=sendrecv\r\n"
+ "a=rtpmap:98 H263-1998/90000\r\n"
+ "a=fmtp:98 CIF=1;QCIF=1\r\n"
+ "a=rtcp-mux\r\n";
+
+ auto desc = CreateSessionDescription(SdpType::kOffer, sdp);
+ ASSERT_NE(desc, nullptr);
+ RTCError error;
+ pc->SetRemoteDescription(std::move(desc), &error);
+ EXPECT_TRUE(error.ok());
+
+ auto answer = pc->CreateAnswer();
+ auto answer_contents = answer->description()->contents();
+ ASSERT_EQ(answer_contents.size(), 2u);
+ EXPECT_EQ(answer_contents[0].rejected, true);
+ EXPECT_EQ(answer_contents[1].rejected, true);
+}
+
+TEST_F(SdpOfferAnswerTest,
+ OfferWithNoMsidSemanticYieldsAnswerWithoutMsidSemantic) {
+ auto pc = CreatePeerConnection();
+ // An offer with no msid-semantic line. The answer should not add one.
+ std::string sdp =
+ "v=0\r\n"
+ "o=- 0 3 IN IP4 127.0.0.1\r\n"
+ "s=-\r\n"
+ "t=0 0\r\n"
+ "a=fingerprint:sha-1 "
+ "4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB\r\n"
+ "a=setup:actpass\r\n"
+ "a=ice-ufrag:ETEn\r\n"
+ "a=ice-pwd:OtSK0WpNtpUjkY4+86js7Z/l\r\n"
+ "m=audio 9 RTP/SAVPF 111\r\n"
+ "c=IN IP4 0.0.0.0\r\n"
+ "a=sendrecv\r\n"
+ "a=rtpmap:111 opus/48000/2\r\n"
+ "a=rtcp-mux\r\n";
+
+ auto desc = CreateSessionDescription(SdpType::kOffer, sdp);
+ ASSERT_NE(desc, nullptr);
+ EXPECT_EQ(desc->description()->msid_signaling(),
+ cricket::kMsidSignalingNotUsed);
+ RTCError error;
+ pc->SetRemoteDescription(std::move(desc), &error);
+ EXPECT_TRUE(error.ok());
+
+ auto answer = pc->CreateAnswer();
+ EXPECT_EQ(answer->description()->msid_signaling(),
+ cricket::kMsidSignalingNotUsed);
+}
+
} // namespace webrtc
diff --git a/third_party/libwebrtc/pc/session_description.h b/third_party/libwebrtc/pc/session_description.h
index 403e46529f..6ef9c316e1 100644
--- a/third_party/libwebrtc/pc/session_description.h
+++ b/third_party/libwebrtc/pc/session_description.h
@@ -468,13 +468,23 @@ const ContentInfo* FindContentInfoByName(const ContentInfos& contents,
const ContentInfo* FindContentInfoByType(const ContentInfos& contents,
const std::string& type);
-// Determines how the MSID will be signaled in the SDP. These can be used as
-// flags to indicate both or none.
+// Determines how the MSID will be signaled in the SDP.
+// These can be used as bit flags to indicate both or the special value none.
enum MsidSignaling {
- // Signal MSID with one a=msid line in the media section.
+ // MSID is not signaled. This is not a bit flag and must be compared for
+ // equality.
+ kMsidSignalingNotUsed = 0x0,
+ // Signal MSID with at least one a=msid line in the media section.
+ // This requires unified plan.
kMsidSignalingMediaSection = 0x1,
// Signal MSID with a=ssrc: msid lines in the media section.
- kMsidSignalingSsrcAttribute = 0x2
+ // This should only be used with plan-b but is signalled in
+ // offers for backward compability reasons.
+ kMsidSignalingSsrcAttribute = 0x2,
+ // Signal MSID with a=msid-semantic: WMS in the session section.
+ // This is deprecated but signalled for backward compability reasons.
+ // It is typically combined with 0x1 or 0x2.
+ kMsidSignalingSemantic = 0x4
};
// Describes a collection of contents, each with its own name and
@@ -548,9 +558,6 @@ class SessionDescription {
void RemoveGroupByName(const std::string& name);
// Global attributes.
- void set_msid_supported(bool supported) { msid_supported_ = supported; }
- bool msid_supported() const { return msid_supported_; }
-
// Determines how the MSIDs were/will be signaled. Flag value composed of
// MsidSignaling bits (see enum above).
void set_msid_signaling(int msid_signaling) {
@@ -582,10 +589,7 @@ class SessionDescription {
ContentInfos contents_;
TransportInfos transport_infos_;
ContentGroups content_groups_;
- bool msid_supported_ = true;
- // Default to what Plan B would do.
- // TODO(bugs.webrtc.org/8530): Change default to kMsidSignalingMediaSection.
- int msid_signaling_ = kMsidSignalingSsrcAttribute;
+ int msid_signaling_ = kMsidSignalingMediaSection | kMsidSignalingSemantic;
bool extmap_allow_mixed_ = true;
};
diff --git a/third_party/libwebrtc/pc/test/integration_test_helpers.cc b/third_party/libwebrtc/pc/test/integration_test_helpers.cc
index 64d8debc09..1f603ad561 100644
--- a/third_party/libwebrtc/pc/test/integration_test_helpers.cc
+++ b/third_party/libwebrtc/pc/test/integration_test_helpers.cc
@@ -22,7 +22,6 @@ void RemoveSsrcsAndMsids(cricket::SessionDescription* desc) {
for (ContentInfo& content : desc->contents()) {
content.media_description()->mutable_streams().clear();
}
- desc->set_msid_supported(false);
desc->set_msid_signaling(0);
}
diff --git a/third_party/libwebrtc/pc/test/integration_test_helpers.h b/third_party/libwebrtc/pc/test/integration_test_helpers.h
index fb719e7ddd..7b3f11905d 100644
--- a/third_party/libwebrtc/pc/test/integration_test_helpers.h
+++ b/third_party/libwebrtc/pc/test/integration_test_helpers.h
@@ -649,8 +649,8 @@ class PeerConnectionIntegrationWrapper : public PeerConnectionObserver,
auto received_stats = NewGetStats();
auto rtp_stats =
received_stats->GetStatsOfType<RTCInboundRtpStreamStats>()[0];
- ASSERT_TRUE(rtp_stats->relative_packet_arrival_delay.is_defined());
- ASSERT_TRUE(rtp_stats->packets_received.is_defined());
+ ASSERT_TRUE(rtp_stats->relative_packet_arrival_delay.has_value());
+ ASSERT_TRUE(rtp_stats->packets_received.has_value());
rtp_stats_id_ = rtp_stats->id();
audio_packets_stat_ = *rtp_stats->packets_received;
audio_delay_stat_ = *rtp_stats->relative_packet_arrival_delay;
@@ -773,7 +773,7 @@ class PeerConnectionIntegrationWrapper : public PeerConnectionObserver,
pc_factory_dependencies.task_queue_factory =
CreateDefaultTaskQueueFactory();
pc_factory_dependencies.trials = std::make_unique<FieldTrialBasedConfig>();
- pc_factory_dependencies.metronome =
+ pc_factory_dependencies.decode_metronome =
std::make_unique<TaskQueueMetronome>(TimeDelta::Millis(8));
pc_factory_dependencies.adm = fake_audio_capture_module_;
@@ -800,8 +800,7 @@ class PeerConnectionIntegrationWrapper : public PeerConnectionObserver,
pc_factory_dependencies.event_log_factory = std::move(event_log_factory);
} else {
pc_factory_dependencies.event_log_factory =
- std::make_unique<RtcEventLogFactory>(
- pc_factory_dependencies.task_queue_factory.get());
+ std::make_unique<RtcEventLogFactory>();
}
peer_connection_factory_ =
CreateModularPeerConnectionFactory(std::move(pc_factory_dependencies));
@@ -1116,7 +1115,7 @@ class PeerConnectionIntegrationWrapper : public PeerConnectionObserver,
if (remote_async_dns_resolver_) {
const auto& local_candidate = candidate->candidate();
if (local_candidate.address().IsUnresolvedIP()) {
- RTC_DCHECK(local_candidate.type() == cricket::LOCAL_PORT_TYPE);
+ RTC_DCHECK(local_candidate.is_local());
const auto resolved_ip = mdns_responder_->GetMappedAddressForName(
local_candidate.address().hostname());
RTC_DCHECK(!resolved_ip.IsNil());
diff --git a/third_party/libwebrtc/pc/test/svc_e2e_tests.cc b/third_party/libwebrtc/pc/test/svc_e2e_tests.cc
index 3fde5a44e0..b2382d700f 100644
--- a/third_party/libwebrtc/pc/test/svc_e2e_tests.cc
+++ b/third_party/libwebrtc/pc/test/svc_e2e_tests.cc
@@ -210,7 +210,7 @@ class SvcVideoQualityAnalyzer : public DefaultVideoQualityAnalyzer {
// Extract the scalability mode reported in the stats.
auto outbound_stats = report->GetStatsOfType<RTCOutboundRtpStreamStats>();
for (const auto& stat : outbound_stats) {
- if (stat->scalability_mode.is_defined()) {
+ if (stat->scalability_mode.has_value()) {
reported_scalability_mode_ = *stat->scalability_mode;
}
}
@@ -336,10 +336,9 @@ TEST_P(SvcTest, ScalabilityModeSupported) {
RtpEncodingParameters parameters;
parameters.scalability_mode = SvcTestParameters().scalability_mode;
video.encoding_params.push_back(parameters);
- alice->AddVideoConfig(
- std::move(video),
- CreateScreenShareFrameGenerator(
- video, ScreenShareConfig(TimeDelta::Seconds(5))));
+ auto generator = CreateScreenShareFrameGenerator(
+ video, ScreenShareConfig(TimeDelta::Seconds(5)));
+ alice->AddVideoConfig(std::move(video), std::move(generator));
alice->SetVideoCodecs({video_codec_config});
},
[](PeerConfigurer* bob) {}, std::move(analyzer));
diff --git a/third_party/libwebrtc/pc/webrtc_sdp.cc b/third_party/libwebrtc/pc/webrtc_sdp.cc
index da024eab81..88f1ce0d1b 100644
--- a/third_party/libwebrtc/pc/webrtc_sdp.cc
+++ b/third_party/libwebrtc/pc/webrtc_sdp.cc
@@ -722,7 +722,7 @@ void CreateTracksFromSsrcInfos(const SsrcInfoVec& ssrc_infos,
// This is the case with Plan B SDP msid signaling.
stream_ids.push_back(ssrc_info.stream_id);
track_id = ssrc_info.track_id;
- } else {
+ } else if (msid_signaling == cricket::kMsidSignalingNotUsed) {
// Since no media streams isn't supported with older SDP signaling, we
// use a default stream id.
stream_ids.push_back(kDefaultMsid);
@@ -762,29 +762,6 @@ void GetMediaStreamIds(const ContentInfo* content,
}
}
-// RFC 5245
-// It is RECOMMENDED that default candidates be chosen based on the
-// likelihood of those candidates to work with the peer that is being
-// contacted. It is RECOMMENDED that relayed > reflexive > host.
-static const int kPreferenceUnknown = 0;
-static const int kPreferenceHost = 1;
-static const int kPreferenceReflexive = 2;
-static const int kPreferenceRelayed = 3;
-
-static int GetCandidatePreferenceFromType(absl::string_view type) {
- int preference = kPreferenceUnknown;
- if (type == cricket::LOCAL_PORT_TYPE) {
- preference = kPreferenceHost;
- } else if (type == cricket::STUN_PORT_TYPE) {
- preference = kPreferenceReflexive;
- } else if (type == cricket::RELAY_PORT_TYPE) {
- preference = kPreferenceRelayed;
- } else {
- RTC_DCHECK_NOTREACHED();
- }
- return preference;
-}
-
// Get ip and port of the default destination from the `candidates` with the
// given value of `component_id`. The default candidate should be the one most
// likely to work, typically IPv4 relay.
@@ -800,7 +777,7 @@ static void GetDefaultDestination(const std::vector<Candidate>& candidates,
*addr_type = kConnectionIpv4Addrtype;
*port = kDummyPort;
*ip = kDummyAddress;
- int current_preference = kPreferenceUnknown;
+ int current_preference = 0; // Start with lowest preference
int current_family = AF_UNSPEC;
for (const Candidate& candidate : candidates) {
if (candidate.component() != component_id) {
@@ -810,7 +787,7 @@ static void GetDefaultDestination(const std::vector<Candidate>& candidates,
if (candidate.protocol() != cricket::UDP_PROTOCOL_NAME) {
continue;
}
- const int preference = GetCandidatePreferenceFromType(candidate.type());
+ const int preference = candidate.type_preference();
const int family = candidate.address().ipaddr().family();
// See if this candidate is more preferable then the current one if it's the
// same family. Or if the current family is IPv4 already so we could safely
@@ -920,23 +897,31 @@ std::string SdpSerialize(const JsepSessionDescription& jdesc) {
AddLine(os.str(), &message);
}
- // MediaStream semantics
- InitAttrLine(kAttributeMsidSemantics, &os);
- os << kSdpDelimiterColon << " " << kMediaStreamSemantic;
+ // MediaStream semantics.
+ // TODO(bugs.webrtc.org/10421): Change to & cricket::kMsidSignalingSemantic
+ // when we think it's safe to do so, so that we gradually fade out this old
+ // line that was removed from the specification.
+ if (desc->msid_signaling() != cricket::kMsidSignalingNotUsed) {
+ InitAttrLine(kAttributeMsidSemantics, &os);
+ os << kSdpDelimiterColon << " " << kMediaStreamSemantic;
- std::set<std::string> media_stream_ids;
- const ContentInfo* audio_content = GetFirstAudioContent(desc);
- if (audio_content)
- GetMediaStreamIds(audio_content, &media_stream_ids);
+ // TODO(bugs.webrtc.org/10421): this code only looks at the first
+ // audio/video content. Fixing that might result in much larger SDP and the
+ // msid-semantic line should eventually go away so this is not worth fixing.
+ std::set<std::string> media_stream_ids;
+ const ContentInfo* audio_content = GetFirstAudioContent(desc);
+ if (audio_content)
+ GetMediaStreamIds(audio_content, &media_stream_ids);
- const ContentInfo* video_content = GetFirstVideoContent(desc);
- if (video_content)
- GetMediaStreamIds(video_content, &media_stream_ids);
+ const ContentInfo* video_content = GetFirstVideoContent(desc);
+ if (video_content)
+ GetMediaStreamIds(video_content, &media_stream_ids);
- for (const std::string& id : media_stream_ids) {
- os << " " << id;
+ for (const std::string& id : media_stream_ids) {
+ os << " " << id;
+ }
+ AddLine(os.str(), &message);
}
- AddLine(os.str(), &message);
// a=ice-lite
//
@@ -1839,7 +1824,7 @@ bool IsFmtpParam(absl::string_view name) {
return name != kCodecParamPTime && name != kCodecParamMaxPTime;
}
-bool WriteFmtpParameters(const cricket::CodecParameterMap& parameters,
+bool WriteFmtpParameters(const webrtc::CodecParameterMap& parameters,
rtc::StringBuilder* os) {
bool empty = true;
const char* delimiter = ""; // No delimiter before first parameter.
@@ -1902,7 +1887,7 @@ bool GetMinValue(const std::vector<int>& values, int* value) {
}
bool GetParameter(const std::string& name,
- const cricket::CodecParameterMap& params,
+ const webrtc::CodecParameterMap& params,
int* value) {
std::map<std::string, std::string>::const_iterator found = params.find(name);
if (found == params.end()) {
@@ -1997,13 +1982,13 @@ void BuildCandidate(const std::vector<Candidate>& candidates,
// *(SP extension-att-name SP extension-att-value)
std::string type;
// Map the cricket candidate type to "host" / "srflx" / "prflx" / "relay"
- if (candidate.type() == cricket::LOCAL_PORT_TYPE) {
+ if (candidate.is_local()) {
type = kCandidateHost;
- } else if (candidate.type() == cricket::STUN_PORT_TYPE) {
+ } else if (candidate.is_stun()) {
type = kCandidateSrflx;
- } else if (candidate.type() == cricket::RELAY_PORT_TYPE) {
+ } else if (candidate.is_relay()) {
type = kCandidateRelay;
- } else if (candidate.type() == cricket::PRFLX_PORT_TYPE) {
+ } else if (candidate.is_prflx()) {
type = kCandidatePrflx;
// Peer reflexive candidate may be signaled for being removed.
} else {
@@ -2131,7 +2116,7 @@ bool ParseSessionDescription(absl::string_view message,
SdpParseError* error) {
absl::optional<absl::string_view> line;
- desc->set_msid_supported(false);
+ desc->set_msid_signaling(cricket::kMsidSignalingNotUsed);
desc->set_extmap_allow_mixed(false);
// RFC 4566
// v= (protocol version)
@@ -2273,8 +2258,9 @@ bool ParseSessionDescription(absl::string_view message,
if (!GetValue(*aline, kAttributeMsidSemantics, &semantics, error)) {
return false;
}
- desc->set_msid_supported(
- CaseInsensitiveFind(semantics, kMediaStreamSemantic));
+ if (CaseInsensitiveFind(semantics, kMediaStreamSemantic)) {
+ desc->set_msid_signaling(cricket::kMsidSignalingSemantic);
+ }
} else if (HasAttribute(*aline, kAttributeExtmapAllowMixed)) {
desc->set_extmap_allow_mixed(true);
} else if (HasAttribute(*aline, kAttributeExtmap)) {
@@ -2614,6 +2600,25 @@ void MaybeCreateStaticPayloadAudioCodecs(const std::vector<int>& fmts,
}
}
+static void BackfillCodecParameters(std::vector<cricket::Codec>& codecs) {
+ for (auto& codec : codecs) {
+ std::string unused_value;
+ if (absl::EqualsIgnoreCase(cricket::kVp9CodecName, codec.name)) {
+ // https://datatracker.ietf.org/doc/html/draft-ietf-payload-vp9#section-6
+ // profile-id defaults to "0"
+ if (!codec.GetParam(cricket::kVP9ProfileId, &unused_value)) {
+ codec.SetParam(cricket::kVP9ProfileId, "0");
+ }
+ } else if (absl::EqualsIgnoreCase(cricket::kH264CodecName, codec.name)) {
+ // https://www.rfc-editor.org/rfc/rfc6184#section-6.2
+ // packetization-mode defaults to "0"
+ if (!codec.GetParam(cricket::kH264FmtpPacketizationMode, &unused_value)) {
+ codec.SetParam(cricket::kH264FmtpPacketizationMode, "0");
+ }
+ }
+ }
+}
+
static std::unique_ptr<MediaContentDescription> ParseContentDescription(
absl::string_view message,
const cricket::MediaType media_type,
@@ -2657,6 +2662,9 @@ static std::unique_ptr<MediaContentDescription> ParseContentDescription(
const cricket::Codec& b) {
return payload_type_preferences[a.id] > payload_type_preferences[b.id];
});
+ // Backfill any default parameters.
+ BackfillCodecParameters(codecs);
+
media_desc->set_codecs(codecs);
return media_desc;
}
@@ -2672,7 +2680,7 @@ bool ParseMediaDescription(
SdpParseError* error) {
RTC_DCHECK(desc != NULL);
int mline_index = -1;
- int msid_signaling = 0;
+ int msid_signaling = desc->msid_signaling();
// Zero or more media descriptions
// RFC 4566
@@ -2724,7 +2732,7 @@ bool ParseMediaDescription(
std::unique_ptr<MediaContentDescription> content;
std::string content_name;
bool bundle_only = false;
- int section_msid_signaling = 0;
+ int section_msid_signaling = cricket::kMsidSignalingNotUsed;
absl::string_view media_type = fields[0];
if ((media_type == kMediaTypeVideo || media_type == kMediaTypeAudio) &&
!cricket::IsRtpProtocol(protocol)) {
@@ -2854,7 +2862,7 @@ bool ParseMediaDescription(
return true;
}
-void AddParameters(const cricket::CodecParameterMap& parameters,
+void AddParameters(const webrtc::CodecParameterMap& parameters,
cricket::Codec* codec) {
for (const auto& entry : parameters) {
const std::string& key = entry.first;
@@ -2917,7 +2925,7 @@ void AddOrReplaceCodec(MediaContentDescription* content_desc,
// to `parameters`.
void UpdateCodec(MediaContentDescription* content_desc,
int payload_type,
- const cricket::CodecParameterMap& parameters) {
+ const webrtc::CodecParameterMap& parameters) {
// Codec might already have been populated (from rtpmap).
cricket::Codec new_codec = GetCodecWithPayloadType(
content_desc->type(), content_desc->codecs(), payload_type);
@@ -3740,7 +3748,7 @@ bool ParseFmtpAttributes(absl::string_view line,
}
// Parse out format specific parameters.
- cricket::CodecParameterMap codec_params;
+ webrtc::CodecParameterMap codec_params;
for (absl::string_view param :
rtc::split(line_params, kSdpDelimiterSemicolonChar)) {
std::string name;
diff --git a/third_party/libwebrtc/pc/webrtc_sdp.h b/third_party/libwebrtc/pc/webrtc_sdp.h
index f7759bd139..052ed546c8 100644
--- a/third_party/libwebrtc/pc/webrtc_sdp.h
+++ b/third_party/libwebrtc/pc/webrtc_sdp.h
@@ -109,7 +109,7 @@ RTC_EXPORT bool ParseCandidate(absl::string_view message,
// parameters are not considered to be part of the FMTP line, see the function
// IsFmtpParam(). Returns true if the set of FMTP parameters is nonempty, false
// otherwise.
-bool WriteFmtpParameters(const cricket::CodecParameterMap& parameters,
+bool WriteFmtpParameters(const webrtc::CodecParameterMap& parameters,
rtc::StringBuilder* os);
} // namespace webrtc
diff --git a/third_party/libwebrtc/pc/webrtc_sdp_unittest.cc b/third_party/libwebrtc/pc/webrtc_sdp_unittest.cc
index ae26ba0ce2..eb9bc729c6 100644
--- a/third_party/libwebrtc/pc/webrtc_sdp_unittest.cc
+++ b/third_party/libwebrtc/pc/webrtc_sdp_unittest.cc
@@ -173,6 +173,7 @@ static const char kSdpFullString[] =
"a=ice-ufrag:ufrag_voice\r\na=ice-pwd:pwd_voice\r\n"
"a=mid:audio_content_name\r\n"
"a=sendrecv\r\n"
+ "a=msid:local_stream_1 audio_track_id_1\r\n"
"a=rtcp-mux\r\n"
"a=rtcp-rsize\r\n"
"a=crypto:1 AES_CM_128_HMAC_SHA1_32 "
@@ -182,7 +183,6 @@ static const char kSdpFullString[] =
"a=rtpmap:103 ISAC/16000\r\n"
"a=rtpmap:104 ISAC/32000\r\n"
"a=ssrc:1 cname:stream_1_cname\r\n"
- "a=ssrc:1 msid:local_stream_1 audio_track_id_1\r\n"
"m=video 3457 RTP/SAVPF 120\r\n"
"c=IN IP4 74.125.224.39\r\n"
"a=rtcp:3456 IN IP4 74.125.224.39\r\n"
@@ -201,14 +201,13 @@ static const char kSdpFullString[] =
"a=ice-ufrag:ufrag_video\r\na=ice-pwd:pwd_video\r\n"
"a=mid:video_content_name\r\n"
"a=sendrecv\r\n"
+ "a=msid:local_stream_1 video_track_id_1\r\n"
"a=crypto:1 AES_CM_128_HMAC_SHA1_80 "
"inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32\r\n"
"a=rtpmap:120 VP8/90000\r\n"
"a=ssrc-group:FEC 2 3\r\n"
"a=ssrc:2 cname:stream_1_cname\r\n"
- "a=ssrc:2 msid:local_stream_1 video_track_id_1\r\n"
- "a=ssrc:3 cname:stream_1_cname\r\n"
- "a=ssrc:3 msid:local_stream_1 video_track_id_1\r\n";
+ "a=ssrc:3 cname:stream_1_cname\r\n";
// SDP reference string without the candidates.
static const char kSdpString[] =
@@ -224,6 +223,7 @@ static const char kSdpString[] =
"a=ice-ufrag:ufrag_voice\r\na=ice-pwd:pwd_voice\r\n"
"a=mid:audio_content_name\r\n"
"a=sendrecv\r\n"
+ "a=msid:local_stream_1 audio_track_id_1\r\n"
"a=rtcp-mux\r\n"
"a=rtcp-rsize\r\n"
"a=crypto:1 AES_CM_128_HMAC_SHA1_32 "
@@ -233,21 +233,19 @@ static const char kSdpString[] =
"a=rtpmap:103 ISAC/16000\r\n"
"a=rtpmap:104 ISAC/32000\r\n"
"a=ssrc:1 cname:stream_1_cname\r\n"
- "a=ssrc:1 msid:local_stream_1 audio_track_id_1\r\n"
"m=video 9 RTP/SAVPF 120\r\n"
"c=IN IP4 0.0.0.0\r\n"
"a=rtcp:9 IN IP4 0.0.0.0\r\n"
"a=ice-ufrag:ufrag_video\r\na=ice-pwd:pwd_video\r\n"
"a=mid:video_content_name\r\n"
"a=sendrecv\r\n"
+ "a=msid:local_stream_1 video_track_id_1\r\n"
"a=crypto:1 AES_CM_128_HMAC_SHA1_80 "
"inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32\r\n"
"a=rtpmap:120 VP8/90000\r\n"
"a=ssrc-group:FEC 2 3\r\n"
"a=ssrc:2 cname:stream_1_cname\r\n"
- "a=ssrc:2 msid:local_stream_1 video_track_id_1\r\n"
- "a=ssrc:3 cname:stream_1_cname\r\n"
- "a=ssrc:3 msid:local_stream_1 video_track_id_1\r\n";
+ "a=ssrc:3 cname:stream_1_cname\r\n";
// draft-ietf-mmusic-sctp-sdp-03
static const char kSdpSctpDataChannelString[] =
@@ -363,6 +361,7 @@ static const char kBundleOnlySdpFullString[] =
"generation 2\r\n"
"a=ice-ufrag:ufrag_voice\r\na=ice-pwd:pwd_voice\r\n"
"a=mid:audio_content_name\r\n"
+ "a=msid:local_stream_1 audio_track_id_1\r\n"
"a=sendrecv\r\n"
"a=rtcp-mux\r\n"
"a=rtcp-rsize\r\n"
@@ -373,21 +372,19 @@ static const char kBundleOnlySdpFullString[] =
"a=rtpmap:103 ISAC/16000\r\n"
"a=rtpmap:104 ISAC/32000\r\n"
"a=ssrc:1 cname:stream_1_cname\r\n"
- "a=ssrc:1 msid:local_stream_1 audio_track_id_1\r\n"
"m=video 0 RTP/SAVPF 120\r\n"
"c=IN IP4 0.0.0.0\r\n"
"a=rtcp:9 IN IP4 0.0.0.0\r\n"
"a=bundle-only\r\n"
"a=mid:video_content_name\r\n"
+ "a=msid:local_stream_1 video_track_id_1\r\n"
"a=sendrecv\r\n"
"a=crypto:1 AES_CM_128_HMAC_SHA1_80 "
"inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32\r\n"
"a=rtpmap:120 VP8/90000\r\n"
"a=ssrc-group:FEC 2 3\r\n"
"a=ssrc:2 cname:stream_1_cname\r\n"
- "a=ssrc:2 msid:local_stream_1 video_track_id_1\r\n"
- "a=ssrc:3 cname:stream_1_cname\r\n"
- "a=ssrc:3 msid:local_stream_1 video_track_id_1\r\n";
+ "a=ssrc:3 cname:stream_1_cname\r\n";
// Plan B SDP reference string, with 2 streams, 2 audio tracks and 3 video
// tracks.
@@ -1168,7 +1165,8 @@ class WebRtcSdpTest : public ::testing::Test {
absl::WrapUnique(audio_desc_));
desc_.AddContent(kVideoContentName, MediaProtocolType::kRtp,
absl::WrapUnique(video_desc_));
-
+ desc_.set_msid_signaling(cricket::kMsidSignalingSsrcAttribute |
+ cricket::kMsidSignalingSemantic);
ASSERT_TRUE(jdesc_.Initialize(desc_.Clone(), jdesc_.session_id(),
jdesc_.session_version()));
}
@@ -1219,7 +1217,8 @@ class WebRtcSdpTest : public ::testing::Test {
absl::WrapUnique(video_desc_3));
desc_.AddTransportInfo(TransportInfo(
kVideoContentName3, TransportDescription(kUfragVideo3, kPwdVideo3)));
- desc_.set_msid_signaling(cricket::kMsidSignalingMediaSection);
+ desc_.set_msid_signaling(cricket::kMsidSignalingMediaSection |
+ cricket::kMsidSignalingSemantic);
ASSERT_TRUE(jdesc_.Initialize(desc_.Clone(), jdesc_.session_id(),
jdesc_.session_version()));
@@ -1299,7 +1298,8 @@ class WebRtcSdpTest : public ::testing::Test {
absl::WrapUnique(audio_desc));
// Enable signaling a=msid lines.
- desc_.set_msid_signaling(cricket::kMsidSignalingMediaSection);
+ desc_.set_msid_signaling(cricket::kMsidSignalingMediaSection |
+ cricket::kMsidSignalingSemantic);
ASSERT_TRUE(jdesc_.Initialize(desc_.Clone(), jdesc_.session_id(),
jdesc_.session_version()));
}
@@ -1508,7 +1508,7 @@ class WebRtcSdpTest : public ::testing::Test {
}
// global attributes
- EXPECT_EQ(desc1.msid_supported(), desc2.msid_supported());
+ EXPECT_EQ(desc1.msid_signaling(), desc2.msid_signaling());
EXPECT_EQ(desc1.extmap_allow_mixed(), desc2.extmap_allow_mixed());
}
@@ -1815,10 +1815,10 @@ class WebRtcSdpTest : public ::testing::Test {
}
}
- void VerifyCodecParameter(const cricket::CodecParameterMap& params,
+ void VerifyCodecParameter(const webrtc::CodecParameterMap& params,
const std::string& name,
int expected_value) {
- cricket::CodecParameterMap::const_iterator found = params.find(name);
+ webrtc::CodecParameterMap::const_iterator found = params.find(name);
ASSERT_TRUE(found != params.end());
EXPECT_EQ(found->second, rtc::ToString(expected_value));
}
@@ -2449,7 +2449,7 @@ TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithoutRtpmapButWithFmtp) {
EXPECT_EQ("G729", g729.name);
EXPECT_EQ(8000, g729.clockrate);
EXPECT_EQ(18, g729.id);
- cricket::CodecParameterMap::iterator found = g729.params.find("annexb");
+ webrtc::CodecParameterMap::iterator found = g729.params.find("annexb");
ASSERT_TRUE(found != g729.params.end());
EXPECT_EQ(found->second, "yes");
@@ -3035,7 +3035,7 @@ TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithoutEndLineBreak) {
// Deserialize
SdpParseError error;
EXPECT_FALSE(webrtc::SdpDeserialize(sdp, &jdesc, &error));
- const std::string lastline = "a=ssrc:3 msid:local_stream_1 video_track_id_1";
+ const std::string lastline = "a=ssrc:3 cname:stream_1_cname";
EXPECT_EQ(lastline, error.line);
EXPECT_EQ("Invalid SDP line.", error.description);
}
@@ -3292,7 +3292,7 @@ TEST_F(WebRtcSdpTest, DeserializeVideoFmtp) {
cricket::VideoCodec vp8 = vcd->codecs()[0];
EXPECT_EQ("VP8", vp8.name);
EXPECT_EQ(120, vp8.id);
- cricket::CodecParameterMap::iterator found =
+ webrtc::CodecParameterMap::iterator found =
vp8.params.find("x-google-min-bitrate");
ASSERT_TRUE(found != vp8.params.end());
EXPECT_EQ(found->second, "10");
@@ -3326,7 +3326,7 @@ TEST_F(WebRtcSdpTest, DeserializeVideoFmtpWithSprops) {
cricket::VideoCodec h264 = vcd->codecs()[0];
EXPECT_EQ("H264", h264.name);
EXPECT_EQ(98, h264.id);
- cricket::CodecParameterMap::const_iterator found =
+ webrtc::CodecParameterMap::const_iterator found =
h264.params.find("profile-level-id");
ASSERT_TRUE(found != h264.params.end());
EXPECT_EQ(found->second, "42A01E");
@@ -3359,7 +3359,7 @@ TEST_F(WebRtcSdpTest, DeserializeVideoFmtpWithSpace) {
cricket::VideoCodec vp8 = vcd->codecs()[0];
EXPECT_EQ("VP8", vp8.name);
EXPECT_EQ(120, vp8.id);
- cricket::CodecParameterMap::iterator found =
+ webrtc::CodecParameterMap::iterator found =
vp8.params.find("x-google-min-bitrate");
ASSERT_TRUE(found != vp8.params.end());
EXPECT_EQ(found->second, "10");
@@ -3751,7 +3751,7 @@ TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionSpecialMsid) {
// Create both msid lines for Plan B and Unified Plan support.
MakeUnifiedPlanDescriptionMultipleStreamIds(
cricket::kMsidSignalingMediaSection |
- cricket::kMsidSignalingSsrcAttribute);
+ cricket::kMsidSignalingSsrcAttribute | cricket::kMsidSignalingSemantic);
JsepSessionDescription deserialized_description(kDummyType);
EXPECT_TRUE(SdpDeserialize(kUnifiedPlanSdpFullStringWithSpecialMsid,
@@ -3759,7 +3759,8 @@ TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionSpecialMsid) {
EXPECT_TRUE(CompareSessionDescription(jdesc_, deserialized_description));
EXPECT_EQ(cricket::kMsidSignalingMediaSection |
- cricket::kMsidSignalingSsrcAttribute,
+ cricket::kMsidSignalingSsrcAttribute |
+ cricket::kMsidSignalingSemantic,
deserialized_description.description()->msid_signaling());
}
@@ -3771,7 +3772,7 @@ TEST_F(WebRtcSdpTest, SerializeSessionDescriptionSpecialMsid) {
// Create both msid lines for Plan B and Unified Plan support.
MakeUnifiedPlanDescriptionMultipleStreamIds(
cricket::kMsidSignalingMediaSection |
- cricket::kMsidSignalingSsrcAttribute);
+ cricket::kMsidSignalingSsrcAttribute | cricket::kMsidSignalingSemantic);
std::string serialized_sdp = webrtc::SdpSerialize(jdesc_);
// We explicitly test that the serialized SDP string is equal to the hard
// coded SDP string. This is necessary, because in the parser "a=msid" lines
@@ -3787,7 +3788,7 @@ TEST_F(WebRtcSdpTest, SerializeSessionDescriptionSpecialMsid) {
TEST_F(WebRtcSdpTest, UnifiedPlanDeserializeSessionDescriptionSpecialMsid) {
// Only create a=msid lines for strictly Unified Plan stream ID support.
MakeUnifiedPlanDescriptionMultipleStreamIds(
- cricket::kMsidSignalingMediaSection);
+ cricket::kMsidSignalingMediaSection | cricket::kMsidSignalingSemantic);
JsepSessionDescription deserialized_description(kDummyType);
std::string unified_plan_sdp_string =
@@ -3805,7 +3806,7 @@ TEST_F(WebRtcSdpTest, UnifiedPlanDeserializeSessionDescriptionSpecialMsid) {
TEST_F(WebRtcSdpTest, UnifiedPlanSerializeSessionDescriptionSpecialMsid) {
// Only create a=msid lines for strictly Unified Plan stream ID support.
MakeUnifiedPlanDescriptionMultipleStreamIds(
- cricket::kMsidSignalingMediaSection);
+ cricket::kMsidSignalingMediaSection | cricket::kMsidSignalingSemantic);
TestSerialize(jdesc_);
}
@@ -3837,7 +3838,8 @@ TEST_F(WebRtcSdpTest, SerializeUnifiedPlanSessionDescriptionNoSsrcSignaling) {
TEST_F(WebRtcSdpTest, EmptyDescriptionHasNoMsidSignaling) {
JsepSessionDescription jsep_desc(kDummyType);
ASSERT_TRUE(SdpDeserialize(kSdpSessionString, &jsep_desc));
- EXPECT_EQ(0, jsep_desc.description()->msid_signaling());
+ EXPECT_EQ(cricket::kMsidSignalingSemantic,
+ jsep_desc.description()->msid_signaling());
}
TEST_F(WebRtcSdpTest, DataChannelOnlyHasNoMsidSignaling) {
@@ -3845,21 +3847,24 @@ TEST_F(WebRtcSdpTest, DataChannelOnlyHasNoMsidSignaling) {
std::string sdp = kSdpSessionString;
sdp += kSdpSctpDataChannelString;
ASSERT_TRUE(SdpDeserialize(sdp, &jsep_desc));
- EXPECT_EQ(0, jsep_desc.description()->msid_signaling());
+ EXPECT_EQ(cricket::kMsidSignalingSemantic,
+ jsep_desc.description()->msid_signaling());
}
TEST_F(WebRtcSdpTest, PlanBHasSsrcAttributeMsidSignaling) {
JsepSessionDescription jsep_desc(kDummyType);
ASSERT_TRUE(SdpDeserialize(kPlanBSdpFullString, &jsep_desc));
- EXPECT_EQ(cricket::kMsidSignalingSsrcAttribute,
- jsep_desc.description()->msid_signaling());
+ EXPECT_EQ(
+ cricket::kMsidSignalingSsrcAttribute | cricket::kMsidSignalingSemantic,
+ jsep_desc.description()->msid_signaling());
}
TEST_F(WebRtcSdpTest, UnifiedPlanHasMediaSectionMsidSignaling) {
JsepSessionDescription jsep_desc(kDummyType);
ASSERT_TRUE(SdpDeserialize(kUnifiedPlanSdpFullString, &jsep_desc));
- EXPECT_EQ(cricket::kMsidSignalingMediaSection,
- jsep_desc.description()->msid_signaling());
+ EXPECT_EQ(
+ cricket::kMsidSignalingMediaSection | cricket::kMsidSignalingSemantic,
+ jsep_desc.description()->msid_signaling());
}
const char kMediaSectionMsidLine[] = "a=msid:local_stream_1 audio_track_id_1";
@@ -3893,6 +3898,13 @@ TEST_F(WebRtcSdpTest, SerializeBothMediaSectionAndSsrcAttributeMsid) {
EXPECT_NE(std::string::npos, sdp.find(kSsrcAttributeMsidLine));
}
+TEST_F(WebRtcSdpTest, SerializeWithoutMsidSemantics) {
+ jdesc_.description()->set_msid_signaling(cricket::kMsidSignalingNotUsed);
+ std::string sdp = webrtc::SdpSerialize(jdesc_);
+
+ EXPECT_EQ(std::string::npos, sdp.find("a=msid-semantic:"));
+}
+
// Regression test for integer overflow bug:
// https://bugs.chromium.org/p/chromium/issues/detail?id=648071
TEST_F(WebRtcSdpTest, DeserializeLargeBandwidthLimit) {
@@ -4459,16 +4471,15 @@ TEST_F(WebRtcSdpTest, DeserializeEmptySessionName) {
// Simulcast malformed input test for invalid format.
TEST_F(WebRtcSdpTest, DeserializeSimulcastNegative_EmptyAttribute) {
- ExpectParseFailureWithNewLines(
- "a=ssrc:3 msid:local_stream_1 video_track_id_1\r\n", "a=simulcast:\r\n",
- "a=simulcast:");
+ ExpectParseFailureWithNewLines("a=ssrc:3 cname:stream_1_cname\r\n",
+ "a=simulcast:\r\n", "a=simulcast:");
}
// Tests that duplicate simulcast entries in the SDP triggers a parse failure.
TEST_F(WebRtcSdpTest, DeserializeSimulcastNegative_DuplicateAttribute) {
- ExpectParseFailureWithNewLines(
- "a=ssrc:3 msid:local_stream_1 video_track_id_1\r\n",
- "a=simulcast:send 1\r\na=simulcast:recv 2\r\n", "a=simulcast:");
+ ExpectParseFailureWithNewLines("a=ssrc:3 cname:stream_1_cname\r\n",
+ "a=simulcast:send 1\r\na=simulcast:recv 2\r\n",
+ "a=simulcast:");
}
// Validates that deserialization uses the a=simulcast: attribute
@@ -4802,6 +4813,7 @@ TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithoutCname) {
EXPECT_TRUE(SdpDeserialize(sdp_without_cname, &new_jdesc));
audio_desc_->mutable_streams()[0].cname = "";
+ audio_desc_->mutable_streams()[0].ssrcs = {};
ASSERT_TRUE(jdesc_.Initialize(desc_.Clone(), jdesc_.session_id(),
jdesc_.session_version()));
EXPECT_TRUE(CompareSessionDescription(jdesc_, new_jdesc));
@@ -5096,3 +5108,42 @@ TEST_F(WebRtcSdpTest, IgnoresUnknownAttributeLines) {
JsepSessionDescription jdesc(kDummyType);
EXPECT_TRUE(SdpDeserialize(sdp, &jdesc));
}
+
+TEST_F(WebRtcSdpTest, BackfillsDefaultFmtpValues) {
+ std::string sdp =
+ "v=0\r\n"
+ "o=- 0 3 IN IP4 127.0.0.1\r\n"
+ "s=-\r\n"
+ "t=0 0\r\n"
+ "a=group:BUNDLE 0\r\n"
+ "a=fingerprint:sha-1 "
+ "4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB\r\n"
+ "a=setup:actpass\r\n"
+ "a=ice-ufrag:ETEn\r\n"
+ "a=ice-pwd:OtSK0WpNtpUjkY4+86js7Z/l\r\n"
+ "m=video 9 UDP/TLS/RTP/SAVPF 96 97\r\n"
+ "c=IN IP4 0.0.0.0\r\n"
+ "a=rtcp-mux\r\n"
+ "a=sendonly\r\n"
+ "a=mid:0\r\n"
+ "a=rtpmap:96 H264/90000\r\n"
+ "a=rtpmap:97 VP9/90000\r\n"
+ "a=ssrc:1234 cname:test\r\n";
+ JsepSessionDescription jdesc(kDummyType);
+ EXPECT_TRUE(SdpDeserialize(sdp, &jdesc));
+ ASSERT_EQ(1u, jdesc.description()->contents().size());
+ const auto content = jdesc.description()->contents()[0];
+ const auto* description = content.media_description();
+ ASSERT_NE(description, nullptr);
+ const std::vector<cricket::Codec> codecs = description->codecs();
+ ASSERT_EQ(codecs.size(), 2u);
+ std::string value;
+
+ EXPECT_EQ(codecs[0].name, "H264");
+ EXPECT_TRUE(codecs[0].GetParam("packetization-mode", &value));
+ EXPECT_EQ(value, "0");
+
+ EXPECT_EQ(codecs[1].name, "VP9");
+ EXPECT_TRUE(codecs[1].GetParam("profile-id", &value));
+ EXPECT_EQ(value, "0");
+}
diff --git a/third_party/libwebrtc/rtc_base/BUILD.gn b/third_party/libwebrtc/rtc_base/BUILD.gn
index 5392e5f472..57a9c11f01 100644
--- a/third_party/libwebrtc/rtc_base/BUILD.gn
+++ b/third_party/libwebrtc/rtc_base/BUILD.gn
@@ -1119,13 +1119,17 @@ rtc_library("socket") {
"socket.h",
]
deps = [
+ ":buffer",
":macromagic",
":socket_address",
+ "../api/units:timestamp",
+ "system:rtc_export",
"third_party/sigslot",
]
if (is_win) {
deps += [ ":win32" ]
}
+ absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
}
rtc_source_set("network_constants") {
@@ -1367,7 +1371,9 @@ if (!build_with_mozilla) {
":socket_factory",
":timeutils",
"../api:sequence_checker",
+ "../api/units:time_delta",
"../system_wrappers:field_trial",
+ "network:received_packet",
"network:sent_packet",
"system:no_unique_address",
]
@@ -1664,10 +1670,7 @@ rtc_source_set("gtest_prod") {
rtc_library("gunit_helpers") {
testonly = true
- sources = [
- "gunit.cc",
- "gunit.h",
- ]
+ sources = [ "gunit.h" ]
deps = [
":logging",
":rtc_base_tests_utils",
@@ -1806,9 +1809,7 @@ rtc_library("task_queue_for_test") {
]
deps = [
":checks",
- ":macromagic",
":rtc_event",
- ":rtc_task_queue",
"../api:function_view",
"../api/task_queue",
"../api/task_queue:default_task_queue_factory",
@@ -2020,6 +2021,7 @@ if (rtc_include_tests) {
"containers:flat_map",
"containers:unittests",
"memory:unittests",
+ "network:received_packet",
"synchronization:mutex",
"task_utils:repeating_task",
"third_party/base64",
@@ -2046,7 +2048,6 @@ if (rtc_include_tests) {
":gunit_helpers",
":rtc_base_tests_utils",
":rtc_event",
- ":rtc_task_queue",
":task_queue_for_test",
":timeutils",
"../api/units:time_delta",
@@ -2181,6 +2182,7 @@ if (rtc_include_tests) {
"../test:test_main",
"../test:test_support",
"memory:fifo_buffer",
+ "network:received_packet",
"synchronization:mutex",
"third_party/sigslot",
]
diff --git a/third_party/libwebrtc/rtc_base/async_packet_socket.cc b/third_party/libwebrtc/rtc_base/async_packet_socket.cc
index 3721366099..e00bd03e3b 100644
--- a/third_party/libwebrtc/rtc_base/async_packet_socket.cc
+++ b/third_party/libwebrtc/rtc_base/async_packet_socket.cc
@@ -45,13 +45,11 @@ void AsyncPacketSocket::RegisterReceivedPacketCallback(
received_packet_callback) {
RTC_DCHECK_RUN_ON(&network_checker_);
RTC_CHECK(!received_packet_callback_);
- SignalReadPacket.connect(this, &AsyncPacketSocket::NotifyPacketReceived);
received_packet_callback_ = std::move(received_packet_callback);
}
void AsyncPacketSocket::DeregisterReceivedPacketCallback() {
RTC_DCHECK_RUN_ON(&network_checker_);
- SignalReadPacket.disconnect(this);
received_packet_callback_ = nullptr;
}
@@ -62,17 +60,6 @@ void AsyncPacketSocket::NotifyPacketReceived(
received_packet_callback_(this, packet);
return;
}
- if (SignalReadPacket.is_empty()) {
- RTC_DCHECK_NOTREACHED() << " No listener registered";
- return;
- }
- // TODO(bugs.webrtc.org:15368): Remove. This code path is only used if
- // SignalReadyPacket is used by clients to get notification of received
- // packets but actual socket implementation use NotifyPacketReceived to
- // trigger the notification.
- SignalReadPacket(this, reinterpret_cast<const char*>(packet.payload().data()),
- packet.payload().size(), packet.source_address(),
- packet.arrival_time() ? packet.arrival_time()->us() : -1);
}
void CopySocketInformationToPacketInfo(size_t packet_size_bytes,
diff --git a/third_party/libwebrtc/rtc_base/async_packet_socket.h b/third_party/libwebrtc/rtc_base/async_packet_socket.h
index 768fcd446b..740c0bb61f 100644
--- a/third_party/libwebrtc/rtc_base/async_packet_socket.h
+++ b/third_party/libwebrtc/rtc_base/async_packet_socket.h
@@ -122,18 +122,6 @@ class RTC_EXPORT AsyncPacketSocket : public sigslot::has_slots<> {
received_packet_callback);
void DeregisterReceivedPacketCallback();
- // Emitted each time a packet is read. Used only for UDP and
- // connected TCP sockets.
- // TODO(bugs.webrtc.org:15368): Deprecate and remove.
- sigslot::signal5<AsyncPacketSocket*,
- const char*,
- size_t,
- const SocketAddress&,
- // TODO(bugs.webrtc.org/9584): Change to passing the int64_t
- // timestamp by value.
- const int64_t&>
- SignalReadPacket;
-
// Emitted each time a packet is sent.
sigslot::signal2<AsyncPacketSocket*, const SentPacket&> SignalSentPacket;
diff --git a/third_party/libwebrtc/rtc_base/async_packet_socket_unittest.cc b/third_party/libwebrtc/rtc_base/async_packet_socket_unittest.cc
index 6cd4f09459..1d66821958 100644
--- a/third_party/libwebrtc/rtc_base/async_packet_socket_unittest.cc
+++ b/third_party/libwebrtc/rtc_base/async_packet_socket_unittest.cc
@@ -63,48 +63,5 @@ TEST(AsyncPacketSocket, RegisteredCallbackReceivePacketsFromNotify) {
mock_socket.NotifyPacketReceived();
}
-TEST(AsyncPacketSocket, RegisteredCallbackReceivePacketsFromSignalReadPacket) {
- MockAsyncPacketSocket mock_socket;
- MockFunction<void(AsyncPacketSocket*, const rtc::ReceivedPacket&)>
- received_packet;
-
- EXPECT_CALL(received_packet, Call);
- mock_socket.RegisterReceivedPacketCallback(received_packet.AsStdFunction());
- char data[1] = {'a'};
- mock_socket.SignalReadPacket(&mock_socket, data, 1, SocketAddress(), -1);
-}
-
-TEST(AsyncPacketSocket, SignalReadPacketTriggeredByNotifyPacketReceived) {
- class SigslotPacketReceiver : public sigslot::has_slots<> {
- public:
- explicit SigslotPacketReceiver(rtc::AsyncPacketSocket& socket) {
- socket.SignalReadPacket.connect(this,
- &SigslotPacketReceiver::OnPacketReceived);
- }
-
- bool packet_received() const { return packet_received_; }
-
- private:
- void OnPacketReceived(AsyncPacketSocket*,
- const char*,
- size_t,
- const SocketAddress&,
- // TODO(bugs.webrtc.org/9584): Change to passing the
- // int64_t timestamp by value.
- const int64_t&) {
- packet_received_ = true;
- }
-
- bool packet_received_ = false;
- };
-
- MockAsyncPacketSocket mock_socket;
- SigslotPacketReceiver receiver(mock_socket);
- ASSERT_FALSE(receiver.packet_received());
-
- mock_socket.NotifyPacketReceived();
- EXPECT_TRUE(receiver.packet_received());
-}
-
} // namespace
} // namespace rtc
diff --git a/third_party/libwebrtc/rtc_base/async_udp_socket.cc b/third_party/libwebrtc/rtc_base/async_udp_socket.cc
index 358420a5de..3d258bcb26 100644
--- a/third_party/libwebrtc/rtc_base/async_udp_socket.cc
+++ b/third_party/libwebrtc/rtc_base/async_udp_socket.cc
@@ -10,9 +10,11 @@
#include "rtc_base/async_udp_socket.h"
-
+#include "absl/types/optional.h"
+#include "api/units/time_delta.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
+#include "rtc_base/network/received_packet.h"
#include "rtc_base/network/sent_packet.h"
#include "rtc_base/time_utils.h"
#include "system_wrappers/include/field_trial.h"
@@ -109,10 +111,8 @@ void AsyncUDPSocket::OnReadEvent(Socket* socket) {
RTC_DCHECK(socket_.get() == socket);
RTC_DCHECK_RUN_ON(&sequence_checker_);
- SocketAddress remote_addr;
- int64_t timestamp = -1;
- int len = socket_->RecvFrom(buf_, BUF_SIZE, &remote_addr, &timestamp);
-
+ Socket::ReceiveBuffer receive_buffer(buffer_);
+ int len = socket_->RecvFrom(receive_buffer);
if (len < 0) {
// An error here typically means we got an ICMP error in response to our
// send datagram, indicating the remote address was unreachable.
@@ -123,21 +123,31 @@ void AsyncUDPSocket::OnReadEvent(Socket* socket) {
<< "] receive failed with error " << socket_->GetError();
return;
}
- if (timestamp == -1) {
+ if (len == 0) {
+ // Spurios wakeup.
+ return;
+ }
+
+ if (!receive_buffer.arrival_time) {
// Timestamp from socket is not available.
- timestamp = TimeMicros();
+ receive_buffer.arrival_time = webrtc::Timestamp::Micros(rtc::TimeMicros());
} else {
if (!socket_time_offset_) {
- socket_time_offset_ =
- !IsScmTimeStampExperimentDisabled() ? TimeMicros() - timestamp : 0;
+ // Estimate timestamp offset from first packet arrival time unless
+ // disabled
+ bool estimate_time_offset = !IsScmTimeStampExperimentDisabled();
+ if (estimate_time_offset) {
+ socket_time_offset_ = webrtc::Timestamp::Micros(rtc::TimeMicros()) -
+ *receive_buffer.arrival_time;
+ } else {
+ socket_time_offset_ = webrtc::TimeDelta::Micros(0);
+ }
}
- timestamp += *socket_time_offset_;
+ *receive_buffer.arrival_time += *socket_time_offset_;
}
-
- // TODO: Make sure that we got all of the packet.
- // If we did not, then we should resize our buffer to be large enough.
- NotifyPacketReceived(
- rtc::ReceivedPacket::CreateFromLegacy(buf_, len, timestamp, remote_addr));
+ NotifyPacketReceived(ReceivedPacket(receive_buffer.payload,
+ receive_buffer.source_address,
+ receive_buffer.arrival_time));
}
void AsyncUDPSocket::OnWriteEvent(Socket* socket) {
diff --git a/third_party/libwebrtc/rtc_base/async_udp_socket.h b/third_party/libwebrtc/rtc_base/async_udp_socket.h
index 4198b25c4d..af361b98ea 100644
--- a/third_party/libwebrtc/rtc_base/async_udp_socket.h
+++ b/third_party/libwebrtc/rtc_base/async_udp_socket.h
@@ -18,6 +18,7 @@
#include "absl/types/optional.h"
#include "api/sequence_checker.h"
+#include "api/units/time_delta.h"
#include "rtc_base/async_packet_socket.h"
#include "rtc_base/socket.h"
#include "rtc_base/socket_address.h"
@@ -68,9 +69,9 @@ class AsyncUDPSocket : public AsyncPacketSocket {
RTC_NO_UNIQUE_ADDRESS webrtc::SequenceChecker sequence_checker_;
std::unique_ptr<Socket> socket_;
- static constexpr int BUF_SIZE = 64 * 1024;
- char buf_[BUF_SIZE] RTC_GUARDED_BY(sequence_checker_);
- absl::optional<int64_t> socket_time_offset_ RTC_GUARDED_BY(sequence_checker_);
+ rtc::Buffer buffer_ RTC_GUARDED_BY(sequence_checker_);
+ absl::optional<webrtc::TimeDelta> socket_time_offset_
+ RTC_GUARDED_BY(sequence_checker_);
};
} // namespace rtc
diff --git a/third_party/libwebrtc/rtc_base/bitstream_reader.h b/third_party/libwebrtc/rtc_base/bitstream_reader.h
index c367b9dc9f..62b0ba5ebf 100644
--- a/third_party/libwebrtc/rtc_base/bitstream_reader.h
+++ b/third_party/libwebrtc/rtc_base/bitstream_reader.h
@@ -124,11 +124,12 @@ class BitstreamReader {
};
inline BitstreamReader::BitstreamReader(rtc::ArrayView<const uint8_t> bytes)
- : bytes_(bytes.data()), remaining_bits_(bytes.size() * 8) {}
+ : bytes_(bytes.data()),
+ remaining_bits_(rtc::checked_cast<int>(bytes.size() * 8)) {}
inline BitstreamReader::BitstreamReader(absl::string_view bytes)
: bytes_(reinterpret_cast<const uint8_t*>(bytes.data())),
- remaining_bits_(bytes.size() * 8) {}
+ remaining_bits_(rtc::checked_cast<int>(bytes.size() * 8)) {}
inline BitstreamReader::~BitstreamReader() {
RTC_DCHECK(last_read_is_verified_) << "Latest calls to Read or ConsumeBit "
diff --git a/third_party/libwebrtc/rtc_base/byte_buffer.cc b/third_party/libwebrtc/rtc_base/byte_buffer.cc
index a076f46ecb..5674d54e21 100644
--- a/third_party/libwebrtc/rtc_base/byte_buffer.cc
+++ b/third_party/libwebrtc/rtc_base/byte_buffer.cc
@@ -16,21 +16,13 @@ namespace rtc {
ByteBufferWriter::ByteBufferWriter() : ByteBufferWriterT() {}
-ByteBufferWriter::ByteBufferWriter(const char* bytes, size_t len)
+ByteBufferWriter::ByteBufferWriter(const uint8_t* bytes, size_t len)
: ByteBufferWriterT(bytes, len) {}
ByteBufferReader::ByteBufferReader(rtc::ArrayView<const uint8_t> bytes) {
Construct(bytes.data(), bytes.size());
}
-ByteBufferReader::ByteBufferReader(const char* bytes, size_t len) {
- Construct(reinterpret_cast<const uint8_t*>(bytes), len);
-}
-
-ByteBufferReader::ByteBufferReader(const char* bytes) {
- Construct(reinterpret_cast<const uint8_t*>(bytes), strlen(bytes));
-}
-
ByteBufferReader::ByteBufferReader(const ByteBufferWriter& buf) {
Construct(reinterpret_cast<const uint8_t*>(buf.Data()), buf.Length());
}
@@ -140,6 +132,14 @@ bool ByteBufferReader::ReadString(std::string* val, size_t len) {
}
}
+bool ByteBufferReader::ReadStringView(absl::string_view* val, size_t len) {
+ if (!val || len > Length())
+ return false;
+ *val = absl::string_view(reinterpret_cast<const char*>(bytes_ + start_), len);
+ start_ += len;
+ return true;
+}
+
bool ByteBufferReader::ReadBytes(rtc::ArrayView<uint8_t> val) {
if (val.size() == 0) {
return true;
@@ -147,10 +147,6 @@ bool ByteBufferReader::ReadBytes(rtc::ArrayView<uint8_t> val) {
return ReadBytes(val.data(), val.size());
}
-bool ByteBufferReader::ReadBytes(char* val, size_t len) {
- return ReadBytes(reinterpret_cast<uint8_t*>(val), len);
-}
-
// Private function supporting the other Read* functions.
bool ByteBufferReader::ReadBytes(uint8_t* val, size_t len) {
if (len > Length()) {
diff --git a/third_party/libwebrtc/rtc_base/byte_buffer.h b/third_party/libwebrtc/rtc_base/byte_buffer.h
index c15773779e..1508bd6ead 100644
--- a/third_party/libwebrtc/rtc_base/byte_buffer.h
+++ b/third_party/libwebrtc/rtc_base/byte_buffer.h
@@ -51,30 +51,32 @@ class ByteBufferWriterT {
absl::string_view DataAsStringView() const {
return absl::string_view(reinterpret_cast<const char*>(Data()), Length());
}
- char* DataAsCharPointer() const { return reinterpret_cast<char*>(Data()); }
+ const char* DataAsCharPointer() const {
+ return reinterpret_cast<const char*>(Data());
+ }
// Write value to the buffer. Resizes the buffer when it is
// neccessary.
void WriteUInt8(uint8_t val) {
- WriteBytes(reinterpret_cast<const value_type*>(&val), 1);
+ WriteBytesInternal(reinterpret_cast<const value_type*>(&val), 1);
}
void WriteUInt16(uint16_t val) {
uint16_t v = HostToNetwork16(val);
- WriteBytes(reinterpret_cast<const value_type*>(&v), 2);
+ WriteBytesInternal(reinterpret_cast<const value_type*>(&v), 2);
}
void WriteUInt24(uint32_t val) {
uint32_t v = HostToNetwork32(val);
value_type* start = reinterpret_cast<value_type*>(&v);
++start;
- WriteBytes(start, 3);
+ WriteBytesInternal(start, 3);
}
void WriteUInt32(uint32_t val) {
uint32_t v = HostToNetwork32(val);
- WriteBytes(reinterpret_cast<const value_type*>(&v), 4);
+ WriteBytesInternal(reinterpret_cast<const value_type*>(&v), 4);
}
void WriteUInt64(uint64_t val) {
uint64_t v = HostToNetwork64(val);
- WriteBytes(reinterpret_cast<const value_type*>(&v), 8);
+ WriteBytesInternal(reinterpret_cast<const value_type*>(&v), 8);
}
// Serializes an unsigned varint in the format described by
// https://developers.google.com/protocol-buffers/docs/encoding#varints
@@ -84,17 +86,19 @@ class ByteBufferWriterT {
// Write 7 bits at a time, then set the msb to a continuation byte
// (msb=1).
value_type byte = static_cast<value_type>(val) | 0x80;
- WriteBytes(&byte, 1);
+ WriteBytesInternal(&byte, 1);
val >>= 7;
}
value_type last_byte = static_cast<value_type>(val);
- WriteBytes(&last_byte, 1);
+ WriteBytesInternal(&last_byte, 1);
}
void WriteString(absl::string_view val) {
- WriteBytes(val.data(), val.size());
+ WriteBytesInternal(reinterpret_cast<const value_type*>(val.data()),
+ val.size());
}
- void WriteBytes(const value_type* val, size_t len) {
- buffer_.AppendData(val, len);
+ // Write an array of bytes (uint8_t)
+ void WriteBytes(const uint8_t* val, size_t len) {
+ WriteBytesInternal(reinterpret_cast<const value_type*>(val), len);
}
// Reserves the given number of bytes and returns a value_type* that can be
@@ -122,16 +126,20 @@ class ByteBufferWriterT {
}
}
+ void WriteBytesInternal(const value_type* val, size_t len) {
+ buffer_.AppendData(val, len);
+ }
+
BufferClassT buffer_;
// There are sensible ways to define these, but they aren't needed in our code
// base.
};
-class ByteBufferWriter : public ByteBufferWriterT<BufferT<char>> {
+class ByteBufferWriter : public ByteBufferWriterT<BufferT<uint8_t>> {
public:
ByteBufferWriter();
- ByteBufferWriter(const char* bytes, size_t len);
+ ByteBufferWriter(const uint8_t* bytes, size_t len);
ByteBufferWriter(const ByteBufferWriter&) = delete;
ByteBufferWriter& operator=(const ByteBufferWriter&) = delete;
@@ -141,28 +149,18 @@ class ByteBufferWriter : public ByteBufferWriterT<BufferT<char>> {
// valid during the lifetime of the reader.
class ByteBufferReader {
public:
- [[deprecated("Use ArrayView<uint8_t>")]] ByteBufferReader(const char* bytes,
- size_t len);
-
explicit ByteBufferReader(
rtc::ArrayView<const uint8_t> bytes ABSL_ATTRIBUTE_LIFETIME_BOUND);
- // Initializes buffer from a zero-terminated string.
- explicit ByteBufferReader(const char* bytes);
-
explicit ByteBufferReader(const ByteBufferWriter& buf);
ByteBufferReader(const ByteBufferReader&) = delete;
ByteBufferReader& operator=(const ByteBufferReader&) = delete;
- // Returns start of unprocessed data.
- // TODO(bugs.webrtc.org/15661): Deprecate and remove.
- const char* Data() const {
- return reinterpret_cast<const char*>(bytes_ + start_);
- }
+ const uint8_t* Data() const { return bytes_ + start_; }
// Returns number of unprocessed bytes.
size_t Length() const { return end_ - start_; }
- // Returns a view of the unprocessed data.
+ // Returns a view of the unprocessed data. Does not move current position.
rtc::ArrayView<const uint8_t> DataView() const {
return rtc::ArrayView<const uint8_t>(bytes_ + start_, end_ - start_);
}
@@ -175,14 +173,14 @@ class ByteBufferReader {
bool ReadUInt32(uint32_t* val);
bool ReadUInt64(uint64_t* val);
bool ReadUVarint(uint64_t* val);
+ // Copies the val.size() next bytes into val.data().
bool ReadBytes(rtc::ArrayView<uint8_t> val);
- // For backwards compatibility.
- // TODO(bugs.webrtc.org/15661): Deprecate and remove.
- [[deprecated("Read using ArrayView")]] bool ReadBytes(char* val, size_t len);
-
// Appends next `len` bytes from the buffer to `val`. Returns false
// if there is less than `len` bytes left.
bool ReadString(std::string* val, size_t len);
+ // Same as `ReadString` except that the returned string_view will point into
+ // the internal buffer (no additional buffer allocation).
+ bool ReadStringView(absl::string_view* val, size_t len);
// Moves current position `size` bytes forward. Returns false if
// there is less than `size` bytes left in the buffer. Consume doesn't
diff --git a/third_party/libwebrtc/rtc_base/byte_buffer_unittest.cc b/third_party/libwebrtc/rtc_base/byte_buffer_unittest.cc
index f65299e639..520845d40b 100644
--- a/third_party/libwebrtc/rtc_base/byte_buffer_unittest.cc
+++ b/third_party/libwebrtc/rtc_base/byte_buffer_unittest.cc
@@ -16,10 +16,26 @@
#include "rtc_base/arraysize.h"
#include "rtc_base/byte_order.h"
+#include "test/gmock.h"
#include "test/gtest.h"
namespace rtc {
+using ::testing::ElementsAre;
+
+TEST(ByteBufferTest, WriterAccessors) {
+ // To be changed into ByteBufferWriter when base type is converted.
+ ByteBufferWriterT<BufferT<uint8_t>> buffer;
+ buffer.WriteString("abc");
+ EXPECT_EQ(buffer.Length(), 3U);
+ EXPECT_THAT(buffer.DataView(), ElementsAre('a', 'b', 'c'));
+ EXPECT_EQ(absl::string_view("abc"), buffer.DataAsStringView());
+
+ buffer.WriteUInt8(0);
+ EXPECT_STREQ(buffer.DataAsCharPointer(), "abc");
+ EXPECT_STREQ(reinterpret_cast<const char*>(buffer.Data()), "abc");
+}
+
TEST(ByteBufferTest, TestByteOrder) {
uint16_t n16 = 1;
uint32_t n32 = 1;
@@ -150,7 +166,7 @@ TEST(ByteBufferTest, TestReadWriteBuffer) {
buffer.Clear();
// Write and read bytes
- char write_bytes[] = "foo";
+ uint8_t write_bytes[] = "foo";
buffer.WriteBytes(write_bytes, 3);
ByteBufferReader read_buf7(buffer);
uint8_t read_bytes[3];
@@ -162,7 +178,7 @@ TEST(ByteBufferTest, TestReadWriteBuffer) {
buffer.Clear();
// Write and read reserved buffer space
- char* write_dst = buffer.ReserveWriteBuffer(3);
+ uint8_t* write_dst = buffer.ReserveWriteBuffer(3);
memcpy(write_dst, write_bytes, 3);
ByteBufferReader read_buf8(buffer);
memset(read_bytes, 0, 3);
@@ -194,6 +210,27 @@ TEST(ByteBufferTest, TestReadWriteBuffer) {
buffer.Clear();
}
+TEST(ByteBufferTest, TestReadStringView) {
+ const absl::string_view tests[] = {"hello", " ", "string_view"};
+ std::string buffer;
+ for (const auto& test : tests)
+ buffer += test;
+
+ rtc::ArrayView<const uint8_t> bytes(
+ reinterpret_cast<const uint8_t*>(&buffer[0]), buffer.size());
+
+ ByteBufferReader read_buf(bytes);
+ size_t consumed = 0;
+ for (const auto& test : tests) {
+ absl::string_view sv;
+ EXPECT_TRUE(read_buf.ReadStringView(&sv, test.length()));
+ EXPECT_EQ(sv.compare(test), 0);
+ // The returned string view should point directly into the original string.
+ EXPECT_EQ(&sv[0], &buffer[0 + consumed]);
+ consumed += sv.size();
+ }
+}
+
TEST(ByteBufferTest, TestReadWriteUVarint) {
ByteBufferWriter write_buffer;
size_t size = 0;
diff --git a/third_party/libwebrtc/rtc_base/experiments/BUILD.gn b/third_party/libwebrtc/rtc_base/experiments/BUILD.gn
index 185d5931f7..d44eefd4fc 100644
--- a/third_party/libwebrtc/rtc_base/experiments/BUILD.gn
+++ b/third_party/libwebrtc/rtc_base/experiments/BUILD.gn
@@ -16,10 +16,9 @@ rtc_library("alr_experiment") {
deps = [
"..:logging",
"../../api:field_trials_view",
- "../../api/transport:field_trial_based_config",
]
absl_deps = [
- "//third_party/abseil-cpp/absl/strings:strings",
+ "//third_party/abseil-cpp/absl/strings:string_view",
"//third_party/abseil-cpp/absl/types:optional",
]
}
diff --git a/third_party/libwebrtc/rtc_base/experiments/alr_experiment.cc b/third_party/libwebrtc/rtc_base/experiments/alr_experiment.cc
index f5d36f6867..5370de5452 100644
--- a/third_party/libwebrtc/rtc_base/experiments/alr_experiment.cc
+++ b/third_party/libwebrtc/rtc_base/experiments/alr_experiment.cc
@@ -16,21 +16,16 @@
#include <string>
#include "absl/strings/string_view.h"
-#include "api/transport/field_trial_based_config.h"
+#include "api/field_trials_view.h"
#include "rtc_base/logging.h"
namespace webrtc {
+namespace {
-const char AlrExperimentSettings::kScreenshareProbingBweExperimentName[] =
- "WebRTC-ProbingScreenshareBwe";
-const char AlrExperimentSettings::kStrictPacingAndProbingExperimentName[] =
- "WebRTC-StrictPacingAndProbing";
-const char kDefaultProbingScreenshareBweSettings[] = "1.0,2875,80,40,-60,3";
+constexpr absl::string_view kDefaultProbingScreenshareBweSettings =
+ "1.0,2875,80,40,-60,3";
-bool AlrExperimentSettings::MaxOneFieldTrialEnabled() {
- return AlrExperimentSettings::MaxOneFieldTrialEnabled(
- FieldTrialBasedConfig());
-}
+} // namespace
bool AlrExperimentSettings::MaxOneFieldTrialEnabled(
const FieldTrialsView& key_value_config) {
@@ -40,12 +35,6 @@ bool AlrExperimentSettings::MaxOneFieldTrialEnabled(
}
absl::optional<AlrExperimentSettings>
-AlrExperimentSettings::CreateFromFieldTrial(absl::string_view experiment_name) {
- return AlrExperimentSettings::CreateFromFieldTrial(FieldTrialBasedConfig(),
- experiment_name);
-}
-
-absl::optional<AlrExperimentSettings>
AlrExperimentSettings::CreateFromFieldTrial(
const FieldTrialsView& key_value_config,
absl::string_view experiment_name) {
diff --git a/third_party/libwebrtc/rtc_base/experiments/alr_experiment.h b/third_party/libwebrtc/rtc_base/experiments/alr_experiment.h
index 048fd90cab..9914828827 100644
--- a/third_party/libwebrtc/rtc_base/experiments/alr_experiment.h
+++ b/third_party/libwebrtc/rtc_base/experiments/alr_experiment.h
@@ -30,14 +30,14 @@ struct AlrExperimentSettings {
// reserved value to indicate absence of experiment.
int group_id;
- static const char kScreenshareProbingBweExperimentName[];
- static const char kStrictPacingAndProbingExperimentName[];
- static absl::optional<AlrExperimentSettings> CreateFromFieldTrial(
- absl::string_view experiment_name);
+ static constexpr absl::string_view kScreenshareProbingBweExperimentName =
+ "WebRTC-ProbingScreenshareBwe";
+ static constexpr absl::string_view kStrictPacingAndProbingExperimentName =
+ "WebRTC-StrictPacingAndProbing";
+
static absl::optional<AlrExperimentSettings> CreateFromFieldTrial(
const FieldTrialsView& key_value_config,
absl::string_view experiment_name);
- static bool MaxOneFieldTrialEnabled();
static bool MaxOneFieldTrialEnabled(const FieldTrialsView& key_value_config);
private:
diff --git a/third_party/libwebrtc/rtc_base/gunit.cc b/third_party/libwebrtc/rtc_base/gunit.cc
deleted file mode 100644
index 7cd60fe9ee..0000000000
--- a/third_party/libwebrtc/rtc_base/gunit.cc
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2018 The WebRTC Project Authors. All rights reserved.
- *
- * Use of this source code is governed by a BSD-style license
- * that can be found in the LICENSE file in the root of the source
- * tree. An additional intellectual property rights grant can be found
- * in the file PATENTS. All contributing project authors may
- * be found in the AUTHORS file in the root of the source tree.
- */
-
-#include "rtc_base/gunit.h"
-
-#include <string>
-
-#include "absl/strings/match.h"
-#include "absl/strings/string_view.h"
-
-::testing::AssertionResult AssertStartsWith(const char* text_expr,
- const char* prefix_expr,
- absl::string_view text,
- absl::string_view prefix) {
- if (absl::StartsWith(text, prefix)) {
- return ::testing::AssertionSuccess();
- } else {
- return ::testing::AssertionFailure()
- << text_expr << "\nwhich is\n\"" << text
- << "\"\ndoes not start with\n"
- << prefix_expr << "\nwhich is\n\"" << prefix << "\"";
- }
-}
-
-::testing::AssertionResult AssertStringContains(const char* str_expr,
- const char* substr_expr,
- absl::string_view str,
- absl::string_view substr) {
- if (str.find(substr) != absl::string_view::npos) {
- return ::testing::AssertionSuccess();
- } else {
- return ::testing::AssertionFailure()
- << str_expr << "\nwhich is\n\"" << str << "\"\ndoes not contain\n"
- << substr_expr << "\nwhich is\n\"" << substr << "\"";
- }
-}
diff --git a/third_party/libwebrtc/rtc_base/gunit.h b/third_party/libwebrtc/rtc_base/gunit.h
index 6bc1419729..759b377aa2 100644
--- a/third_party/libwebrtc/rtc_base/gunit.h
+++ b/third_party/libwebrtc/rtc_base/gunit.h
@@ -154,16 +154,4 @@
} else \
GTEST_CONCAT_TOKEN_(gunit_label_, __LINE__) : ASSERT_EQ(v1, v2)
-// Usage: EXPECT_PRED_FORMAT2(AssertStartsWith, text, "prefix");
-testing::AssertionResult AssertStartsWith(const char* text_expr,
- const char* prefix_expr,
- absl::string_view text,
- absl::string_view prefix);
-
-// Usage: EXPECT_PRED_FORMAT2(AssertStringContains, str, "substring");
-testing::AssertionResult AssertStringContains(const char* str_expr,
- const char* substr_expr,
- absl::string_view str,
- absl::string_view substr);
-
#endif // RTC_BASE_GUNIT_H_
diff --git a/third_party/libwebrtc/rtc_base/nat_server.cc b/third_party/libwebrtc/rtc_base/nat_server.cc
index b818685efb..c274cedf18 100644
--- a/third_party/libwebrtc/rtc_base/nat_server.cc
+++ b/third_party/libwebrtc/rtc_base/nat_server.cc
@@ -10,12 +10,15 @@
#include "rtc_base/nat_server.h"
+#include <cstddef>
#include <memory>
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/nat_socket_factory.h"
+#include "rtc_base/network/received_packet.h"
#include "rtc_base/socket_adapters.h"
+#include "rtc_base/socket_address.h"
namespace rtc {
@@ -125,17 +128,27 @@ class NATProxyServer : public ProxyServer {
};
NATServer::NATServer(NATType type,
+ rtc::Thread& internal_socket_thread,
SocketFactory* internal,
const SocketAddress& internal_udp_addr,
const SocketAddress& internal_tcp_addr,
+ rtc::Thread& external_socket_thread,
SocketFactory* external,
const SocketAddress& external_ip)
- : external_(external), external_ip_(external_ip.ipaddr(), 0) {
+ : internal_socket_thread_(internal_socket_thread),
+ external_socket_thread_(external_socket_thread),
+ external_(external),
+ external_ip_(external_ip.ipaddr(), 0) {
nat_ = NAT::Create(type);
- udp_server_socket_ = AsyncUDPSocket::Create(internal, internal_udp_addr);
- udp_server_socket_->SignalReadPacket.connect(this,
- &NATServer::OnInternalUDPPacket);
+ internal_socket_thread_.BlockingCall([&] {
+ udp_server_socket_ = AsyncUDPSocket::Create(internal, internal_udp_addr);
+ udp_server_socket_->RegisterReceivedPacketCallback(
+ [&](rtc::AsyncPacketSocket* socket, const rtc::ReceivedPacket& packet) {
+ OnInternalUDPPacket(socket, packet);
+ });
+ });
+
tcp_proxy_server_ =
new NATProxyServer(internal, internal_tcp_addr, external, external_ip);
@@ -156,10 +169,11 @@ NATServer::~NATServer() {
}
void NATServer::OnInternalUDPPacket(AsyncPacketSocket* socket,
- const char* buf,
- size_t size,
- const SocketAddress& addr,
- const int64_t& /* packet_time_us */) {
+ const rtc::ReceivedPacket& packet) {
+ RTC_DCHECK(internal_socket_thread_.IsCurrent());
+ const char* buf = reinterpret_cast<const char*>(packet.payload().data());
+ size_t size = packet.payload().size();
+ const SocketAddress& addr = packet.source_address();
// Read the intended destination from the wire.
SocketAddress dest_addr;
size_t length = UnpackAddressFromNAT(buf, size, &dest_addr);
@@ -182,10 +196,8 @@ void NATServer::OnInternalUDPPacket(AsyncPacketSocket* socket,
}
void NATServer::OnExternalUDPPacket(AsyncPacketSocket* socket,
- const char* buf,
- size_t size,
- const SocketAddress& remote_addr,
- const int64_t& /* packet_time_us */) {
+ const rtc::ReceivedPacket& packet) {
+ RTC_DCHECK(external_socket_thread_.IsCurrent());
SocketAddress local_addr = socket->GetLocalAddress();
// Find the translation for this addresses.
@@ -193,36 +205,46 @@ void NATServer::OnExternalUDPPacket(AsyncPacketSocket* socket,
RTC_DCHECK(iter != ext_map_->end());
// Allow the NAT to reject this packet.
- if (ShouldFilterOut(iter->second, remote_addr)) {
- RTC_LOG(LS_INFO) << "Packet from " << remote_addr.ToSensitiveString()
+ if (ShouldFilterOut(iter->second, packet.source_address())) {
+ RTC_LOG(LS_INFO) << "Packet from "
+ << packet.source_address().ToSensitiveString()
<< " was filtered out by the NAT.";
return;
}
// Forward this packet to the internal address.
// First prepend the address in a quasi-STUN format.
- std::unique_ptr<char[]> real_buf(new char[size + kNATEncodedIPv6AddressSize]);
+ std::unique_ptr<char[]> real_buf(
+ new char[packet.payload().size() + kNATEncodedIPv6AddressSize]);
size_t addrlength = PackAddressForNAT(
- real_buf.get(), size + kNATEncodedIPv6AddressSize, remote_addr);
+ real_buf.get(), packet.payload().size() + kNATEncodedIPv6AddressSize,
+ packet.source_address());
// Copy the data part after the address.
rtc::PacketOptions options;
- memcpy(real_buf.get() + addrlength, buf, size);
- udp_server_socket_->SendTo(real_buf.get(), size + addrlength,
+ memcpy(real_buf.get() + addrlength, packet.payload().data(),
+ packet.payload().size());
+ udp_server_socket_->SendTo(real_buf.get(),
+ packet.payload().size() + addrlength,
iter->second->route.source(), options);
}
void NATServer::Translate(const SocketAddressPair& route) {
- AsyncUDPSocket* socket = AsyncUDPSocket::Create(external_, external_ip_);
+ external_socket_thread_.BlockingCall([&] {
+ AsyncUDPSocket* socket = AsyncUDPSocket::Create(external_, external_ip_);
- if (!socket) {
- RTC_LOG(LS_ERROR) << "Couldn't find a free port!";
- return;
- }
+ if (!socket) {
+ RTC_LOG(LS_ERROR) << "Couldn't find a free port!";
+ return;
+ }
- TransEntry* entry = new TransEntry(route, socket, nat_);
- (*int_map_)[route] = entry;
- (*ext_map_)[socket->GetLocalAddress()] = entry;
- socket->SignalReadPacket.connect(this, &NATServer::OnExternalUDPPacket);
+ TransEntry* entry = new TransEntry(route, socket, nat_);
+ (*int_map_)[route] = entry;
+ (*ext_map_)[socket->GetLocalAddress()] = entry;
+ socket->RegisterReceivedPacketCallback(
+ [&](rtc::AsyncPacketSocket* socket, const rtc::ReceivedPacket& packet) {
+ OnExternalUDPPacket(socket, packet);
+ });
+ });
}
bool NATServer::ShouldFilterOut(TransEntry* entry,
diff --git a/third_party/libwebrtc/rtc_base/nat_server.h b/third_party/libwebrtc/rtc_base/nat_server.h
index acbd62a092..d179efa567 100644
--- a/third_party/libwebrtc/rtc_base/nat_server.h
+++ b/third_party/libwebrtc/rtc_base/nat_server.h
@@ -58,15 +58,17 @@ struct AddrCmp {
const int NAT_SERVER_UDP_PORT = 4237;
const int NAT_SERVER_TCP_PORT = 4238;
-class NATServer : public sigslot::has_slots<> {
+class NATServer {
public:
NATServer(NATType type,
+ rtc::Thread& internal_socket_thread,
SocketFactory* internal,
const SocketAddress& internal_udp_addr,
const SocketAddress& internal_tcp_addr,
+ rtc::Thread& external_socket_thread,
SocketFactory* external,
const SocketAddress& external_ip);
- ~NATServer() override;
+ ~NATServer();
NATServer(const NATServer&) = delete;
NATServer& operator=(const NATServer&) = delete;
@@ -81,15 +83,9 @@ class NATServer : public sigslot::has_slots<> {
// Packets received on one of the networks.
void OnInternalUDPPacket(AsyncPacketSocket* socket,
- const char* buf,
- size_t size,
- const SocketAddress& addr,
- const int64_t& packet_time_us);
+ const rtc::ReceivedPacket& packet);
void OnExternalUDPPacket(AsyncPacketSocket* socket,
- const char* buf,
- size_t size,
- const SocketAddress& remote_addr,
- const int64_t& packet_time_us);
+ const rtc::ReceivedPacket& packet);
private:
typedef std::set<SocketAddress, AddrCmp> AddressSet;
@@ -118,6 +114,8 @@ class NATServer : public sigslot::has_slots<> {
bool ShouldFilterOut(TransEntry* entry, const SocketAddress& ext_addr);
NAT* nat_;
+ rtc::Thread& internal_socket_thread_;
+ rtc::Thread& external_socket_thread_;
SocketFactory* external_;
SocketAddress external_ip_;
AsyncUDPSocket* udp_server_socket_;
diff --git a/third_party/libwebrtc/rtc_base/nat_socket_factory.cc b/third_party/libwebrtc/rtc_base/nat_socket_factory.cc
index fe021b95ff..83ec2bc327 100644
--- a/third_party/libwebrtc/rtc_base/nat_socket_factory.cc
+++ b/third_party/libwebrtc/rtc_base/nat_socket_factory.cc
@@ -368,7 +368,8 @@ NATSocketServer::Translator* NATSocketServer::AddTranslator(
if (nats_.Get(ext_ip))
return nullptr;
- return nats_.Add(ext_ip, new Translator(this, type, int_ip, server_, ext_ip));
+ return nats_.Add(
+ ext_ip, new Translator(this, type, int_ip, *msg_queue_, server_, ext_ip));
}
void NATSocketServer::RemoveTranslator(const SocketAddress& ext_ip) {
@@ -413,6 +414,7 @@ Socket* NATSocketServer::CreateInternalSocket(int family,
NATSocketServer::Translator::Translator(NATSocketServer* server,
NATType type,
const SocketAddress& int_ip,
+ Thread& external_socket_thread,
SocketFactory* ext_factory,
const SocketAddress& ext_ip)
: server_(server) {
@@ -422,7 +424,8 @@ NATSocketServer::Translator::Translator(NATSocketServer* server,
internal_server_ = std::make_unique<VirtualSocketServer>();
internal_server_->SetMessageQueue(server_->queue());
nat_server_ = std::make_unique<NATServer>(
- type, internal_server_.get(), int_ip, int_ip, ext_factory, ext_ip);
+ type, *server->queue(), internal_server_.get(), int_ip, int_ip,
+ external_socket_thread, ext_factory, ext_ip);
}
NATSocketServer::Translator::~Translator() {
@@ -443,8 +446,8 @@ NATSocketServer::Translator* NATSocketServer::Translator::AddTranslator(
return nullptr;
AddClient(ext_ip);
- return nats_.Add(ext_ip,
- new Translator(server_, type, int_ip, server_, ext_ip));
+ return nats_.Add(ext_ip, new Translator(server_, type, int_ip,
+ *server_->queue(), server_, ext_ip));
}
void NATSocketServer::Translator::RemoveTranslator(
const SocketAddress& ext_ip) {
diff --git a/third_party/libwebrtc/rtc_base/nat_socket_factory.h b/third_party/libwebrtc/rtc_base/nat_socket_factory.h
index 0b301b5844..f803496b05 100644
--- a/third_party/libwebrtc/rtc_base/nat_socket_factory.h
+++ b/third_party/libwebrtc/rtc_base/nat_socket_factory.h
@@ -102,6 +102,7 @@ class NATSocketServer : public SocketServer, public NATInternalSocketFactory {
Translator(NATSocketServer* server,
NATType type,
const SocketAddress& int_addr,
+ Thread& external_socket_thread,
SocketFactory* ext_factory,
const SocketAddress& ext_addr);
~Translator();
diff --git a/third_party/libwebrtc/rtc_base/nat_unittest.cc b/third_party/libwebrtc/rtc_base/nat_unittest.cc
index 432985d283..742e0d6ee7 100644
--- a/third_party/libwebrtc/rtc_base/nat_unittest.cc
+++ b/third_party/libwebrtc/rtc_base/nat_unittest.cc
@@ -76,16 +76,17 @@ void TestSend(SocketServer* internal,
Thread th_int(internal);
Thread th_ext(external);
+ th_int.Start();
+ th_ext.Start();
+
SocketAddress server_addr = internal_addr;
server_addr.SetPort(0); // Auto-select a port
- NATServer* nat = new NATServer(nat_type, internal, server_addr, server_addr,
- external, external_addrs[0]);
+ NATServer* nat =
+ new NATServer(nat_type, th_int, internal, server_addr, server_addr,
+ th_ext, external, external_addrs[0]);
NATSocketFactory* natsf = new NATSocketFactory(
internal, nat->internal_udp_address(), nat->internal_tcp_address());
- th_int.Start();
- th_ext.Start();
-
TestClient* in;
th_int.BlockingCall([&] { in = CreateTestClient(natsf, internal_addr); });
@@ -139,13 +140,13 @@ void TestRecv(SocketServer* internal,
SocketAddress server_addr = internal_addr;
server_addr.SetPort(0); // Auto-select a port
- NATServer* nat = new NATServer(nat_type, internal, server_addr, server_addr,
- external, external_addrs[0]);
- NATSocketFactory* natsf = new NATSocketFactory(
- internal, nat->internal_udp_address(), nat->internal_tcp_address());
-
th_int.Start();
th_ext.Start();
+ NATServer* nat =
+ new NATServer(nat_type, th_int, internal, server_addr, server_addr,
+ th_ext, external, external_addrs[0]);
+ NATSocketFactory* natsf = new NATSocketFactory(
+ internal, nat->internal_udp_address(), nat->internal_tcp_address());
TestClient* in = nullptr;
th_int.BlockingCall([&] { in = CreateTestClient(natsf, internal_addr); });
@@ -355,9 +356,11 @@ class NatTcpTest : public ::testing::Test, public sigslot::has_slots<> {
int_thread_(new Thread(int_vss_.get())),
ext_thread_(new Thread(ext_vss_.get())),
nat_(new NATServer(NAT_OPEN_CONE,
+ *int_thread_,
int_vss_.get(),
int_addr_,
int_addr_,
+ *ext_thread_,
ext_vss_.get(),
ext_addr_)),
natsf_(new NATSocketFactory(int_vss_.get(),
diff --git a/third_party/libwebrtc/rtc_base/network/BUILD.gn b/third_party/libwebrtc/rtc_base/network/BUILD.gn
index 7e9cf7ab68..2be484e1e0 100644
--- a/third_party/libwebrtc/rtc_base/network/BUILD.gn
+++ b/third_party/libwebrtc/rtc_base/network/BUILD.gn
@@ -18,6 +18,7 @@ rtc_library("sent_packet") {
}
rtc_library("received_packet") {
+ visibility = [ "*" ]
sources = [
"received_packet.cc",
"received_packet.h",
diff --git a/third_party/libwebrtc/rtc_base/network/received_packet.cc b/third_party/libwebrtc/rtc_base/network/received_packet.cc
index 40d6e1142c..95f5e22d3b 100644
--- a/third_party/libwebrtc/rtc_base/network/received_packet.cc
+++ b/third_party/libwebrtc/rtc_base/network/received_packet.cc
@@ -25,14 +25,12 @@ ReceivedPacket::ReceivedPacket(rtc::ArrayView<const uint8_t> payload,
// static
ReceivedPacket ReceivedPacket::CreateFromLegacy(
- const char* data,
+ const uint8_t* data,
size_t size,
int64_t packet_time_us,
const rtc::SocketAddress& source_address) {
RTC_DCHECK(packet_time_us == -1 || packet_time_us >= 0);
- return ReceivedPacket(rtc::reinterpret_array_view<const uint8_t>(
- rtc::MakeArrayView(data, size)),
- source_address,
+ return ReceivedPacket(rtc::MakeArrayView(data, size), source_address,
(packet_time_us >= 0)
? absl::optional<webrtc::Timestamp>(
webrtc::Timestamp::Micros(packet_time_us))
diff --git a/third_party/libwebrtc/rtc_base/network/received_packet.h b/third_party/libwebrtc/rtc_base/network/received_packet.h
index e33361ca29..d898ccb2e9 100644
--- a/third_party/libwebrtc/rtc_base/network/received_packet.h
+++ b/third_party/libwebrtc/rtc_base/network/received_packet.h
@@ -47,6 +47,15 @@ class RTC_EXPORT ReceivedPacket {
const char* data,
size_t size,
int64_t packet_time_us,
+ const rtc::SocketAddress& addr = rtc::SocketAddress()) {
+ return CreateFromLegacy(reinterpret_cast<const uint8_t*>(data), size,
+ packet_time_us, addr);
+ }
+
+ static ReceivedPacket CreateFromLegacy(
+ const uint8_t* data,
+ size_t size,
+ int64_t packet_time_us,
const rtc::SocketAddress& = rtc::SocketAddress());
private:
diff --git a/third_party/libwebrtc/rtc_base/server_socket_adapters.cc b/third_party/libwebrtc/rtc_base/server_socket_adapters.cc
index 47c19cbed9..0bef752f1e 100644
--- a/third_party/libwebrtc/rtc_base/server_socket_adapters.cc
+++ b/third_party/libwebrtc/rtc_base/server_socket_adapters.cc
@@ -75,7 +75,9 @@ void AsyncSocksProxyServerSocket::ProcessInput(char* data, size_t* len) {
// Consume parsed data
*len = response.Length();
- memmove(data, response.Data(), *len);
+ if (response.Length() > 0) {
+ memmove(data, response.DataView().data(), *len);
+ }
}
void AsyncSocksProxyServerSocket::DirectSend(const ByteBufferWriter& buf) {
diff --git a/third_party/libwebrtc/rtc_base/socket.cc b/third_party/libwebrtc/rtc_base/socket.cc
index bcd62ad2a4..0908c2991f 100644
--- a/third_party/libwebrtc/rtc_base/socket.cc
+++ b/third_party/libwebrtc/rtc_base/socket.cc
@@ -10,4 +10,24 @@
#include "rtc_base/socket.h"
-namespace rtc {} // namespace rtc
+#include <cstdint>
+
+#include "rtc_base/buffer.h"
+
+namespace rtc {
+
+int Socket::RecvFrom(ReceiveBuffer& buffer) {
+ static constexpr int BUF_SIZE = 64 * 1024;
+ int64_t timestamp = -1;
+ buffer.payload.EnsureCapacity(BUF_SIZE);
+ int len = RecvFrom(buffer.payload.data(), buffer.payload.capacity(),
+ &buffer.source_address, &timestamp);
+ buffer.payload.SetSize(len > 0 ? len : 0);
+ if (len > 0 && timestamp != -1) {
+ buffer.arrival_time = webrtc::Timestamp::Micros(timestamp);
+ }
+
+ return len;
+}
+
+} // namespace rtc
diff --git a/third_party/libwebrtc/rtc_base/socket.h b/third_party/libwebrtc/rtc_base/socket.h
index 0ed3a7fa6a..fac75aca94 100644
--- a/third_party/libwebrtc/rtc_base/socket.h
+++ b/third_party/libwebrtc/rtc_base/socket.h
@@ -13,6 +13,8 @@
#include <errno.h>
+#include "absl/types/optional.h"
+
#if defined(WEBRTC_POSIX)
#include <arpa/inet.h>
#include <netinet/in.h>
@@ -25,7 +27,10 @@
#include "rtc_base/win32.h"
#endif
+#include "api/units/timestamp.h"
+#include "rtc_base/buffer.h"
#include "rtc_base/socket_address.h"
+#include "rtc_base/system/rtc_export.h"
#include "rtc_base/third_party/sigslot/sigslot.h"
// Rather than converting errors into a private namespace,
@@ -78,8 +83,15 @@ inline bool IsBlockingError(int e) {
// General interface for the socket implementations of various networks. The
// methods match those of normal UNIX sockets very closely.
-class Socket {
+class RTC_EXPORT Socket {
public:
+ struct ReceiveBuffer {
+ ReceiveBuffer(rtc::Buffer& payload) : payload(payload) {}
+
+ absl::optional<webrtc::Timestamp> arrival_time;
+ SocketAddress source_address;
+ rtc::Buffer& payload;
+ };
virtual ~Socket() {}
Socket(const Socket&) = delete;
@@ -103,6 +115,10 @@ class Socket {
size_t cb,
SocketAddress* paddr,
int64_t* timestamp) = 0;
+ // Intended to replace RecvFrom(void* ...).
+ // Default implementation calls RecvFrom(void* ...) with 64Kbyte buffer.
+ // Returns number of bytes received or a negative value on error.
+ virtual int RecvFrom(ReceiveBuffer& buffer);
virtual int Listen(int backlog) = 0;
virtual Socket* Accept(SocketAddress* paddr) = 0;
virtual int Close() = 0;
diff --git a/third_party/libwebrtc/rtc_base/socket_adapters.cc b/third_party/libwebrtc/rtc_base/socket_adapters.cc
index f628929a46..a1eee5bd67 100644
--- a/third_party/libwebrtc/rtc_base/socket_adapters.cc
+++ b/third_party/libwebrtc/rtc_base/socket_adapters.cc
@@ -629,7 +629,7 @@ void AsyncSocksProxySocket::SendAuth() {
size_t len = pass_.GetLength() + 1;
char* sensitive = new char[len];
pass_.CopyTo(sensitive, true);
- request.WriteBytes(sensitive, pass_.GetLength()); // Password
+ request.WriteString(std::string(sensitive, pass_.GetLength())); // Password
ExplicitZeroMemory(sensitive, len);
delete[] sensitive;
DirectSend(request.Data(), request.Length());
diff --git a/third_party/libwebrtc/rtc_base/task_queue_for_test.cc b/third_party/libwebrtc/rtc_base/task_queue_for_test.cc
index cb6b23ceae..e8993edcd1 100644
--- a/third_party/libwebrtc/rtc_base/task_queue_for_test.cc
+++ b/third_party/libwebrtc/rtc_base/task_queue_for_test.cc
@@ -10,12 +10,28 @@
#include "rtc_base/task_queue_for_test.h"
+#include <memory>
+#include <utility>
+
#include "api/task_queue/default_task_queue_factory.h"
+#include "api/task_queue/task_queue_base.h"
namespace webrtc {
-TaskQueueForTest::TaskQueueForTest(absl::string_view name, Priority priority)
- : TaskQueue(
- CreateDefaultTaskQueueFactory()->CreateTaskQueue(name, priority)) {}
+TaskQueueForTest::TaskQueueForTest(
+ std::unique_ptr<TaskQueueBase, TaskQueueDeleter> task_queue)
+ : impl_(std::move(task_queue)) {}
+
+TaskQueueForTest::TaskQueueForTest(absl::string_view name,
+ TaskQueueFactory::Priority priority)
+ : impl_(CreateDefaultTaskQueueFactory()->CreateTaskQueue(name, priority)) {}
+
+TaskQueueForTest::~TaskQueueForTest() {
+ // Stop the TaskQueue before invalidating impl_ pointer so that tasks that
+ // race with the TaskQueueForTest destructor could still use TaskQueueForTest
+ // functions like 'IsCurrent'.
+ impl_.get_deleter()(impl_.get());
+ impl_.release();
+}
} // namespace webrtc
diff --git a/third_party/libwebrtc/rtc_base/task_queue_for_test.h b/third_party/libwebrtc/rtc_base/task_queue_for_test.h
index 4c7f842abe..b54b1daefa 100644
--- a/third_party/libwebrtc/rtc_base/task_queue_for_test.h
+++ b/third_party/libwebrtc/rtc_base/task_queue_for_test.h
@@ -17,10 +17,9 @@
#include "absl/strings/string_view.h"
#include "api/function_view.h"
#include "api/task_queue/task_queue_base.h"
+#include "api/task_queue/task_queue_factory.h"
#include "rtc_base/checks.h"
#include "rtc_base/event.h"
-#include "rtc_base/task_queue.h"
-#include "rtc_base/thread_annotations.h"
namespace webrtc {
@@ -38,14 +37,39 @@ inline void SendTask(TaskQueueBase* task_queue,
/*warn_after=*/TimeDelta::Seconds(10)));
}
-class RTC_LOCKABLE TaskQueueForTest : public rtc::TaskQueue {
+class TaskQueueForTest {
public:
- using rtc::TaskQueue::TaskQueue;
- explicit TaskQueueForTest(absl::string_view name = "TestQueue",
- Priority priority = Priority::NORMAL);
+ explicit TaskQueueForTest(
+ std::unique_ptr<TaskQueueBase, TaskQueueDeleter> task_queue);
+ explicit TaskQueueForTest(
+ absl::string_view name = "TestQueue",
+ TaskQueueFactory::Priority priority = TaskQueueFactory::Priority::NORMAL);
TaskQueueForTest(const TaskQueueForTest&) = delete;
TaskQueueForTest& operator=(const TaskQueueForTest&) = delete;
- ~TaskQueueForTest() = default;
+ ~TaskQueueForTest();
+
+ bool IsCurrent() const { return impl_->IsCurrent(); }
+
+ // Returns non-owning pointer to the task queue implementation.
+ TaskQueueBase* Get() { return impl_.get(); }
+
+ void PostTask(
+ absl::AnyInvocable<void() &&> task,
+ const webrtc::Location& location = webrtc::Location::Current()) {
+ impl_->PostTask(std::move(task), location);
+ }
+ void PostDelayedTask(
+ absl::AnyInvocable<void() &&> task,
+ webrtc::TimeDelta delay,
+ const webrtc::Location& location = webrtc::Location::Current()) {
+ impl_->PostDelayedTask(std::move(task), delay, location);
+ }
+ void PostDelayedHighPrecisionTask(
+ absl::AnyInvocable<void() &&> task,
+ webrtc::TimeDelta delay,
+ const webrtc::Location& location = webrtc::Location::Current()) {
+ impl_->PostDelayedHighPrecisionTask(std::move(task), delay, location);
+ }
// A convenience, test-only method that blocks the current thread while
// a task executes on the task queue.
@@ -61,6 +85,9 @@ class RTC_LOCKABLE TaskQueueForTest : public rtc::TaskQueue {
// that all already posted tasks on the queue get executed.
SendTask([]() {});
}
+
+ private:
+ std::unique_ptr<TaskQueueBase, TaskQueueDeleter> impl_;
};
} // namespace webrtc
diff --git a/third_party/libwebrtc/rtc_base/task_queue_unittest.cc b/third_party/libwebrtc/rtc_base/task_queue_unittest.cc
index 579dc3cced..eb5c5b16fb 100644
--- a/third_party/libwebrtc/rtc_base/task_queue_unittest.cc
+++ b/third_party/libwebrtc/rtc_base/task_queue_unittest.cc
@@ -28,10 +28,10 @@
#include "rtc_base/time_utils.h"
#include "test/gtest.h"
-namespace rtc {
+namespace webrtc {
namespace {
-using ::webrtc::TimeDelta;
+
// Noop on all platforms except Windows, where it turns on high precision
// multimedia timers which increases the precision of TimeMillis() while in
// scope.
@@ -51,12 +51,6 @@ class EnableHighResTimers {
#endif
};
-void CheckCurrent(Event* signal, TaskQueue* queue) {
- EXPECT_TRUE(queue->IsCurrent());
- if (signal)
- signal->Set();
-}
-
} // namespace
// This task needs to be run manually due to the slowness of some of our bots.
@@ -65,14 +59,18 @@ TEST(TaskQueueTest, DISABLED_PostDelayedHighRes) {
EnableHighResTimers high_res_scope;
static const char kQueueName[] = "PostDelayedHighRes";
- Event event;
- webrtc::TaskQueueForTest queue(kQueueName, TaskQueue::Priority::HIGH);
+ rtc::Event event;
+ TaskQueueForTest queue(kQueueName, TaskQueueFactory::Priority::HIGH);
- uint32_t start = Time();
- queue.PostDelayedTask([&event, &queue] { CheckCurrent(&event, &queue); },
- TimeDelta::Millis(3));
- EXPECT_TRUE(event.Wait(webrtc::TimeDelta::Seconds(1)));
- uint32_t end = TimeMillis();
+ uint32_t start = rtc::TimeMillis();
+ queue.PostDelayedTask(
+ [&event, &queue] {
+ EXPECT_TRUE(queue.IsCurrent());
+ event.Set();
+ },
+ TimeDelta::Millis(3));
+ EXPECT_TRUE(event.Wait(TimeDelta::Seconds(1)));
+ uint32_t end = rtc::TimeMillis();
// These tests are a little relaxed due to how "powerful" our test bots can
// be. Most recently we've seen windows bots fire the callback after 94-99ms,
// which is why we have a little bit of leeway backwards as well.
@@ -80,4 +78,4 @@ TEST(TaskQueueTest, DISABLED_PostDelayedHighRes) {
EXPECT_NEAR(end - start, 3, 3u);
}
-} // namespace rtc
+} // namespace webrtc
diff --git a/third_party/libwebrtc/rtc_base/thread_unittest.cc b/third_party/libwebrtc/rtc_base/thread_unittest.cc
index cd733db2cd..11ee2abc9f 100644
--- a/third_party/libwebrtc/rtc_base/thread_unittest.cc
+++ b/third_party/libwebrtc/rtc_base/thread_unittest.cc
@@ -22,6 +22,7 @@
#include "rtc_base/fake_clock.h"
#include "rtc_base/gunit.h"
#include "rtc_base/internal/default_socket_server.h"
+#include "rtc_base/network/received_packet.h"
#include "rtc_base/null_socket_server.h"
#include "rtc_base/physical_socket_server.h"
#include "rtc_base/ref_counted_object.h"
@@ -84,20 +85,20 @@ class SocketClient : public TestGenerator, public sigslot::has_slots<> {
: socket_(AsyncUDPSocket::Create(socket, addr)),
post_thread_(post_thread),
post_handler_(phandler) {
- socket_->SignalReadPacket.connect(this, &SocketClient::OnPacket);
+ socket_->RegisterReceivedPacketCallback(
+ [&](rtc::AsyncPacketSocket* socket, const rtc::ReceivedPacket& packet) {
+ OnPacket(socket, packet);
+ });
}
~SocketClient() override { delete socket_; }
SocketAddress address() const { return socket_->GetLocalAddress(); }
- void OnPacket(AsyncPacketSocket* socket,
- const char* buf,
- size_t size,
- const SocketAddress& remote_addr,
- const int64_t& packet_time_us) {
- EXPECT_EQ(size, sizeof(uint32_t));
- uint32_t prev = reinterpret_cast<const uint32_t*>(buf)[0];
+ void OnPacket(AsyncPacketSocket* socket, const rtc::ReceivedPacket& packet) {
+ EXPECT_EQ(packet.payload().size(), sizeof(uint32_t));
+ uint32_t prev =
+ reinterpret_cast<const uint32_t*>(packet.payload().data())[0];
uint32_t result = Next(prev);
post_thread_->PostDelayedTask([post_handler_ = post_handler_,
diff --git a/third_party/libwebrtc/rtc_base/virtual_socket_unittest.cc b/third_party/libwebrtc/rtc_base/virtual_socket_unittest.cc
index 67585b1fcd..8efc9d8223 100644
--- a/third_party/libwebrtc/rtc_base/virtual_socket_unittest.cc
+++ b/third_party/libwebrtc/rtc_base/virtual_socket_unittest.cc
@@ -13,6 +13,8 @@
#include <stdlib.h>
#include <string.h>
#include <time.h>
+
+#include "rtc_base/network/received_packet.h"
#if defined(WEBRTC_POSIX)
#include <netinet/in.h>
#endif
@@ -101,7 +103,10 @@ struct Receiver : public sigslot::has_slots<> {
sum(0),
sum_sq(0),
samples(0) {
- socket->SignalReadPacket.connect(this, &Receiver::OnReadPacket);
+ socket->RegisterReceivedPacketCallback(
+ [&](rtc::AsyncPacketSocket* socket, const rtc::ReceivedPacket& packet) {
+ OnReadPacket(socket, packet);
+ });
periodic = RepeatingTaskHandle::DelayedStart(
thread, TimeDelta::Seconds(1), [this] {
// It is always possible for us to receive more than expected because
@@ -116,18 +121,15 @@ struct Receiver : public sigslot::has_slots<> {
~Receiver() override { periodic.Stop(); }
- void OnReadPacket(AsyncPacketSocket* s,
- const char* data,
- size_t size,
- const SocketAddress& remote_addr,
- const int64_t& /* packet_time_us */) {
+ void OnReadPacket(AsyncPacketSocket* s, const rtc::ReceivedPacket& packet) {
ASSERT_EQ(socket.get(), s);
- ASSERT_GE(size, 4U);
+ ASSERT_GE(packet.payload().size(), 4U);
- count += size;
- sec_count += size;
+ count += packet.payload().size();
+ sec_count += packet.payload().size();
- uint32_t send_time = *reinterpret_cast<const uint32_t*>(data);
+ uint32_t send_time =
+ *reinterpret_cast<const uint32_t*>(packet.payload().data());
uint32_t recv_time = rtc::TimeMillis();
uint32_t delay = recv_time - send_time;
sum += delay;
diff --git a/third_party/libwebrtc/rtc_tools/BUILD.gn b/third_party/libwebrtc/rtc_tools/BUILD.gn
index df3c55fec8..6637d143fe 100644
--- a/third_party/libwebrtc/rtc_tools/BUILD.gn
+++ b/third_party/libwebrtc/rtc_tools/BUILD.gn
@@ -258,6 +258,7 @@ if (!is_component_build) {
"../api:rtp_parameters",
"../api/environment",
"../api/environment:environment_factory",
+ "../api/task_queue",
"../api/test/video:function_video_factory",
"../api/transport:field_trial_based_config",
"../api/units:timestamp",
diff --git a/third_party/libwebrtc/rtc_tools/network_tester/BUILD.gn b/third_party/libwebrtc/rtc_tools/network_tester/BUILD.gn
index 5930431ab9..e44681441a 100644
--- a/third_party/libwebrtc/rtc_tools/network_tester/BUILD.gn
+++ b/third_party/libwebrtc/rtc_tools/network_tester/BUILD.gn
@@ -43,6 +43,7 @@ if (rtc_enable_protobuf) {
"../../api/task_queue",
"../../api/task_queue:default_task_queue_factory",
"../../api/task_queue:pending_task_safety_flag",
+ "../../api/units:timestamp",
"../../p2p:rtc_p2p",
"../../rtc_base:async_packet_socket",
"../../rtc_base:checks",
@@ -55,9 +56,9 @@ if (rtc_enable_protobuf) {
"../../rtc_base:socket_server",
"../../rtc_base:threading",
"../../rtc_base:timeutils",
+ "../../rtc_base/network:received_packet",
"../../rtc_base/synchronization:mutex",
"../../rtc_base/system:no_unique_address",
- "../../rtc_base/third_party/sigslot",
]
absl_deps = [
"//third_party/abseil-cpp/absl/functional:any_invocable",
diff --git a/third_party/libwebrtc/rtc_tools/network_tester/test_controller.cc b/third_party/libwebrtc/rtc_tools/network_tester/test_controller.cc
index 3d9af380f1..f8641aacb6 100644
--- a/third_party/libwebrtc/rtc_tools/network_tester/test_controller.cc
+++ b/third_party/libwebrtc/rtc_tools/network_tester/test_controller.cc
@@ -13,10 +13,12 @@
#include <limits>
#include "absl/types/optional.h"
+#include "api/units/timestamp.h"
#include "rtc_base/checks.h"
#include "rtc_base/internal/default_socket_server.h"
#include "rtc_base/ip_address.h"
#include "rtc_base/logging.h"
+#include "rtc_base/network/received_packet.h"
#include "rtc_base/thread.h"
namespace webrtc {
@@ -44,7 +46,10 @@ TestController::TestController(int min_port,
std::unique_ptr<rtc::AsyncPacketSocket>(socket_factory_.CreateUdpSocket(
rtc::SocketAddress(rtc::GetAnyIP(AF_INET), 0), min_port, max_port));
RTC_CHECK(udp_socket_ != nullptr);
- udp_socket_->SignalReadPacket.connect(this, &TestController::OnReadPacket);
+ udp_socket_->RegisterReceivedPacketCallback(
+ [&](rtc::AsyncPacketSocket* socket, const rtc::ReceivedPacket& packet) {
+ OnReadPacket(socket, packet);
+ });
});
}
@@ -103,14 +108,13 @@ bool TestController::IsTestDone() {
}
void TestController::OnReadPacket(rtc::AsyncPacketSocket* socket,
- const char* data,
- size_t len,
- const rtc::SocketAddress& remote_addr,
- const int64_t& packet_time_us) {
+ const rtc::ReceivedPacket& received_packet) {
RTC_DCHECK_RUN_ON(packet_sender_thread_.get());
RTC_LOG(LS_VERBOSE) << "OnReadPacket";
- size_t packet_size = data[0];
- std::string receive_data(&data[1], packet_size);
+ size_t packet_size = received_packet.payload()[0];
+ std::string receive_data(
+ reinterpret_cast<const char*>(&received_packet.payload()[1]),
+ packet_size);
NetworkTesterPacket packet;
packet.ParseFromString(receive_data);
RTC_CHECK(packet.has_type());
@@ -118,7 +122,7 @@ void TestController::OnReadPacket(rtc::AsyncPacketSocket* socket,
case NetworkTesterPacket::HAND_SHAKING: {
NetworkTesterPacket packet;
packet.set_type(NetworkTesterPacket::TEST_START);
- remote_address_ = remote_addr;
+ remote_address_ = received_packet.source_address();
SendData(packet, absl::nullopt);
packet_sender_.reset(new PacketSender(this, packet_sender_thread_.get(),
task_safety_flag_,
@@ -140,8 +144,9 @@ void TestController::OnReadPacket(rtc::AsyncPacketSocket* socket,
break;
}
case NetworkTesterPacket::TEST_DATA: {
- packet.set_arrival_timestamp(packet_time_us);
- packet.set_packet_size(len);
+ packet.set_arrival_timestamp(
+ received_packet.arrival_time().value_or(Timestamp::Zero()).us());
+ packet.set_packet_size(received_packet.payload().size());
packet_logger_.LogPacket(packet);
break;
}
diff --git a/third_party/libwebrtc/rtc_tools/network_tester/test_controller.h b/third_party/libwebrtc/rtc_tools/network_tester/test_controller.h
index 3638c75af1..423eb08d0c 100644
--- a/third_party/libwebrtc/rtc_tools/network_tester/test_controller.h
+++ b/third_party/libwebrtc/rtc_tools/network_tester/test_controller.h
@@ -22,11 +22,11 @@
#include "api/sequence_checker.h"
#include "p2p/base/basic_packet_socket_factory.h"
#include "rtc_base/async_packet_socket.h"
+#include "rtc_base/network/received_packet.h"
#include "rtc_base/socket_address.h"
#include "rtc_base/socket_server.h"
#include "rtc_base/synchronization/mutex.h"
#include "rtc_base/system/no_unique_address.h"
-#include "rtc_base/third_party/sigslot/sigslot.h"
#include "rtc_base/thread.h"
#include "rtc_base/thread_annotations.h"
#include "rtc_tools/network_tester/packet_logger.h"
@@ -43,13 +43,13 @@ namespace webrtc {
constexpr size_t kEthernetMtu = 1500;
-class TestController : public sigslot::has_slots<> {
+class TestController {
public:
TestController(int min_port,
int max_port,
const std::string& config_file_path,
const std::string& log_file_path);
- ~TestController() override;
+ ~TestController();
TestController(const TestController&) = delete;
TestController& operator=(const TestController&) = delete;
@@ -65,10 +65,7 @@ class TestController : public sigslot::has_slots<> {
private:
void OnReadPacket(rtc::AsyncPacketSocket* socket,
- const char* data,
- size_t len,
- const rtc::SocketAddress& remote_addr,
- const int64_t& packet_time_us);
+ const rtc::ReceivedPacket& received_packet);
RTC_NO_UNIQUE_ADDRESS SequenceChecker test_controller_thread_checker_;
std::unique_ptr<rtc::SocketServer> socket_server_;
std::unique_ptr<rtc::Thread> packet_sender_thread_;
diff --git a/third_party/libwebrtc/rtc_tools/video_replay.cc b/third_party/libwebrtc/rtc_tools/video_replay.cc
index 52c8d68048..243919ca94 100644
--- a/third_party/libwebrtc/rtc_tools/video_replay.cc
+++ b/third_party/libwebrtc/rtc_tools/video_replay.cc
@@ -20,6 +20,7 @@
#include "api/environment/environment_factory.h"
#include "api/field_trials.h"
#include "api/media_types.h"
+#include "api/task_queue/task_queue_base.h"
#include "api/test/video/function_video_decoder_factory.h"
#include "api/transport/field_trial_based_config.h"
#include "api/units/timestamp.h"
@@ -486,9 +487,8 @@ class RtpReplayer final {
time_sim_ ? time_sim_->GetTaskQueueFactory() : nullptr,
time_sim_ ? time_sim_->GetClock() : nullptr)),
rtp_reader_(CreateRtpReader(rtp_dump_path_)) {
- worker_thread_ = std::make_unique<rtc::TaskQueue>(
- env_.task_queue_factory().CreateTaskQueue(
- "worker_thread", TaskQueueFactory::Priority::NORMAL));
+ worker_thread_ = env_.task_queue_factory().CreateTaskQueue(
+ "worker_thread", TaskQueueFactory::Priority::NORMAL);
rtc::Event event;
worker_thread_->PostTask([&]() {
call_ = Call::Create(CallConfig(env_));
@@ -663,7 +663,7 @@ class RtpReplayer final {
const std::string rtp_dump_path_;
std::unique_ptr<GlobalSimulatedTimeController> time_sim_;
Environment env_;
- std::unique_ptr<rtc::TaskQueue> worker_thread_;
+ std::unique_ptr<TaskQueueBase, TaskQueueDeleter> worker_thread_;
std::unique_ptr<Call> call_;
std::unique_ptr<test::RtpFileReader> rtp_reader_;
std::unique_ptr<StreamState> stream_state_;
diff --git a/third_party/libwebrtc/sdk/objc/api/peerconnection/RTCPeerConnectionFactory.mm b/third_party/libwebrtc/sdk/objc/api/peerconnection/RTCPeerConnectionFactory.mm
index 15f9eb9ee4..445006f0d0 100644
--- a/third_party/libwebrtc/sdk/objc/api/peerconnection/RTCPeerConnectionFactory.mm
+++ b/third_party/libwebrtc/sdk/objc/api/peerconnection/RTCPeerConnectionFactory.mm
@@ -206,8 +206,7 @@
dependencies.audio_processing = webrtc::AudioProcessingBuilder().Create();
}
webrtc::EnableMedia(dependencies);
- dependencies.event_log_factory =
- std::make_unique<webrtc::RtcEventLogFactory>(dependencies.task_queue_factory.get());
+ dependencies.event_log_factory = std::make_unique<webrtc::RtcEventLogFactory>();
dependencies.network_controller_factory = std::move(networkControllerFactory);
_nativeFactory = webrtc::CreateModularPeerConnectionFactory(std::move(dependencies));
NSAssert(_nativeFactory, @"Failed to initialize PeerConnectionFactory!");
diff --git a/third_party/libwebrtc/sdk/objc/api/peerconnection/RTCStatisticsReport.mm b/third_party/libwebrtc/sdk/objc/api/peerconnection/RTCStatisticsReport.mm
index bfe2424553..eaf2097cce 100644
--- a/third_party/libwebrtc/sdk/objc/api/peerconnection/RTCStatisticsReport.mm
+++ b/third_party/libwebrtc/sdk/objc/api/peerconnection/RTCStatisticsReport.mm
@@ -17,105 +17,91 @@ namespace webrtc {
/** Converts a single value to a suitable NSNumber, NSString or NSArray containing NSNumbers
or NSStrings, or NSDictionary of NSString keys to NSNumber values.*/
-NSObject *ValueFromStatsMember(const RTCStatsMemberInterface *member) {
- if (member->is_defined()) {
- switch (member->type()) {
- case RTCStatsMemberInterface::kBool:
- return [NSNumber numberWithBool:*member->cast_to<RTCStatsMember<bool>>()];
- case RTCStatsMemberInterface::kInt32:
- return [NSNumber numberWithInt:*member->cast_to<RTCStatsMember<int32_t>>()];
- case RTCStatsMemberInterface::kUint32:
- return [NSNumber numberWithUnsignedInt:*member->cast_to<RTCStatsMember<uint32_t>>()];
- case RTCStatsMemberInterface::kInt64:
- return [NSNumber numberWithLong:*member->cast_to<RTCStatsMember<int64_t>>()];
- case RTCStatsMemberInterface::kUint64:
- return [NSNumber numberWithUnsignedLong:*member->cast_to<RTCStatsMember<uint64_t>>()];
- case RTCStatsMemberInterface::kDouble:
- return [NSNumber numberWithDouble:*member->cast_to<RTCStatsMember<double>>()];
- case RTCStatsMemberInterface::kString:
- return [NSString stringForStdString:*member->cast_to<RTCStatsMember<std::string>>()];
- case RTCStatsMemberInterface::kSequenceBool: {
- std::vector<bool> sequence = *member->cast_to<RTCStatsMember<std::vector<bool>>>();
- NSMutableArray *array = [NSMutableArray arrayWithCapacity:sequence.size()];
- for (auto item : sequence) {
- [array addObject:[NSNumber numberWithBool:item]];
- }
- return [array copy];
- }
- case RTCStatsMemberInterface::kSequenceInt32: {
- std::vector<int32_t> sequence = *member->cast_to<RTCStatsMember<std::vector<int32_t>>>();
- NSMutableArray<NSNumber *> *array = [NSMutableArray arrayWithCapacity:sequence.size()];
- for (const auto &item : sequence) {
- [array addObject:[NSNumber numberWithInt:item]];
- }
- return [array copy];
- }
- case RTCStatsMemberInterface::kSequenceUint32: {
- std::vector<uint32_t> sequence = *member->cast_to<RTCStatsMember<std::vector<uint32_t>>>();
- NSMutableArray<NSNumber *> *array = [NSMutableArray arrayWithCapacity:sequence.size()];
- for (const auto &item : sequence) {
- [array addObject:[NSNumber numberWithUnsignedInt:item]];
- }
- return [array copy];
- }
- case RTCStatsMemberInterface::kSequenceInt64: {
- std::vector<int64_t> sequence = *member->cast_to<RTCStatsMember<std::vector<int64_t>>>();
- NSMutableArray<NSNumber *> *array = [NSMutableArray arrayWithCapacity:sequence.size()];
- for (const auto &item : sequence) {
- [array addObject:[NSNumber numberWithLong:item]];
- }
- return [array copy];
- }
- case RTCStatsMemberInterface::kSequenceUint64: {
- std::vector<uint64_t> sequence = *member->cast_to<RTCStatsMember<std::vector<uint64_t>>>();
- NSMutableArray<NSNumber *> *array = [NSMutableArray arrayWithCapacity:sequence.size()];
- for (const auto &item : sequence) {
- [array addObject:[NSNumber numberWithUnsignedLong:item]];
- }
- return [array copy];
- }
- case RTCStatsMemberInterface::kSequenceDouble: {
- std::vector<double> sequence = *member->cast_to<RTCStatsMember<std::vector<double>>>();
- NSMutableArray<NSNumber *> *array = [NSMutableArray arrayWithCapacity:sequence.size()];
- for (const auto &item : sequence) {
- [array addObject:[NSNumber numberWithDouble:item]];
- }
- return [array copy];
- }
- case RTCStatsMemberInterface::kSequenceString: {
- std::vector<std::string> sequence =
- *member->cast_to<RTCStatsMember<std::vector<std::string>>>();
- NSMutableArray<NSString *> *array = [NSMutableArray arrayWithCapacity:sequence.size()];
- for (const auto &item : sequence) {
- [array addObject:[NSString stringForStdString:item]];
- }
- return [array copy];
- }
- case RTCStatsMemberInterface::kMapStringUint64: {
- std::map<std::string, uint64_t> map =
- *member->cast_to<RTCStatsMember<std::map<std::string, uint64_t>>>();
- NSMutableDictionary<NSString *, NSNumber *> *dictionary =
- [NSMutableDictionary dictionaryWithCapacity:map.size()];
- for (const auto &item : map) {
- dictionary[[NSString stringForStdString:item.first]] = @(item.second);
- }
- return [dictionary copy];
- }
- case RTCStatsMemberInterface::kMapStringDouble: {
- std::map<std::string, double> map =
- *member->cast_to<RTCStatsMember<std::map<std::string, double>>>();
- NSMutableDictionary<NSString *, NSNumber *> *dictionary =
- [NSMutableDictionary dictionaryWithCapacity:map.size()];
- for (const auto &item : map) {
- dictionary[[NSString stringForStdString:item.first]] = @(item.second);
- }
- return [dictionary copy];
- }
- default:
- RTC_DCHECK_NOTREACHED();
+NSObject *ValueFromStatsAttribute(const Attribute &attribute) {
+ if (!attribute.has_value()) {
+ return nil;
+ }
+ if (attribute.holds_alternative<bool>()) {
+ return [NSNumber numberWithBool:attribute.get<bool>()];
+ } else if (attribute.holds_alternative<int32_t>()) {
+ return [NSNumber numberWithInt:attribute.get<int32_t>()];
+ } else if (attribute.holds_alternative<uint32_t>()) {
+ return [NSNumber numberWithUnsignedInt:attribute.get<uint32_t>()];
+ } else if (attribute.holds_alternative<int64_t>()) {
+ return [NSNumber numberWithLong:attribute.get<int64_t>()];
+ } else if (attribute.holds_alternative<uint64_t>()) {
+ return [NSNumber numberWithUnsignedLong:attribute.get<uint64_t>()];
+ } else if (attribute.holds_alternative<double>()) {
+ return [NSNumber numberWithDouble:attribute.get<double>()];
+ } else if (attribute.holds_alternative<std::string>()) {
+ return [NSString stringForStdString:attribute.get<std::string>()];
+ } else if (attribute.holds_alternative<std::vector<bool>>()) {
+ std::vector<bool> sequence = attribute.get<std::vector<bool>>();
+ NSMutableArray *array = [NSMutableArray arrayWithCapacity:sequence.size()];
+ for (auto item : sequence) {
+ [array addObject:[NSNumber numberWithBool:item]];
}
+ return [array copy];
+ } else if (attribute.holds_alternative<std::vector<int32_t>>()) {
+ std::vector<int32_t> sequence = attribute.get<std::vector<int32_t>>();
+ NSMutableArray<NSNumber *> *array = [NSMutableArray arrayWithCapacity:sequence.size()];
+ for (const auto &item : sequence) {
+ [array addObject:[NSNumber numberWithInt:item]];
+ }
+ return [array copy];
+ } else if (attribute.holds_alternative<std::vector<uint32_t>>()) {
+ std::vector<uint32_t> sequence = attribute.get<std::vector<uint32_t>>();
+ NSMutableArray<NSNumber *> *array = [NSMutableArray arrayWithCapacity:sequence.size()];
+ for (const auto &item : sequence) {
+ [array addObject:[NSNumber numberWithUnsignedInt:item]];
+ }
+ return [array copy];
+ } else if (attribute.holds_alternative<std::vector<int64_t>>()) {
+ std::vector<int64_t> sequence = attribute.get<std::vector<int64_t>>();
+ NSMutableArray<NSNumber *> *array = [NSMutableArray arrayWithCapacity:sequence.size()];
+ for (const auto &item : sequence) {
+ [array addObject:[NSNumber numberWithLong:item]];
+ }
+ return [array copy];
+ } else if (attribute.holds_alternative<std::vector<uint64_t>>()) {
+ std::vector<uint64_t> sequence = attribute.get<std::vector<uint64_t>>();
+ NSMutableArray<NSNumber *> *array = [NSMutableArray arrayWithCapacity:sequence.size()];
+ for (const auto &item : sequence) {
+ [array addObject:[NSNumber numberWithUnsignedLong:item]];
+ }
+ return [array copy];
+ } else if (attribute.holds_alternative<std::vector<double>>()) {
+ std::vector<double> sequence = attribute.get<std::vector<double>>();
+ NSMutableArray<NSNumber *> *array = [NSMutableArray arrayWithCapacity:sequence.size()];
+ for (const auto &item : sequence) {
+ [array addObject:[NSNumber numberWithDouble:item]];
+ }
+ return [array copy];
+ } else if (attribute.holds_alternative<std::vector<std::string>>()) {
+ std::vector<std::string> sequence = attribute.get<std::vector<std::string>>();
+ NSMutableArray<NSString *> *array = [NSMutableArray arrayWithCapacity:sequence.size()];
+ for (const auto &item : sequence) {
+ [array addObject:[NSString stringForStdString:item]];
+ }
+ return [array copy];
+ } else if (attribute.holds_alternative<std::map<std::string, uint64_t>>()) {
+ std::map<std::string, uint64_t> map = attribute.get<std::map<std::string, uint64_t>>();
+ NSMutableDictionary<NSString *, NSNumber *> *dictionary =
+ [NSMutableDictionary dictionaryWithCapacity:map.size()];
+ for (const auto &item : map) {
+ dictionary[[NSString stringForStdString:item.first]] = @(item.second);
+ }
+ return [dictionary copy];
+ } else if (attribute.holds_alternative<std::map<std::string, double>>()) {
+ std::map<std::string, double> map = attribute.get<std::map<std::string, double>>();
+ NSMutableDictionary<NSString *, NSNumber *> *dictionary =
+ [NSMutableDictionary dictionaryWithCapacity:map.size()];
+ for (const auto &item : map) {
+ dictionary[[NSString stringForStdString:item.first]] = @(item.second);
+ }
+ return [dictionary copy];
}
-
+ RTC_DCHECK_NOTREACHED();
return nil;
}
} // namespace webrtc
@@ -134,10 +120,11 @@ NSObject *ValueFromStatsMember(const RTCStatsMemberInterface *member) {
_type = [NSString stringWithCString:statistics.type() encoding:NSUTF8StringEncoding];
NSMutableDictionary<NSString *, NSObject *> *values = [NSMutableDictionary dictionary];
- for (const webrtc::RTCStatsMemberInterface *member : statistics.Members()) {
- NSObject *value = ValueFromStatsMember(member);
+ for (const auto &attribute : statistics.Attributes()) {
+ NSObject *value = ValueFromStatsAttribute(attribute);
if (value) {
- NSString *name = [NSString stringWithCString:member->name() encoding:NSUTF8StringEncoding];
+ NSString *name = [NSString stringWithCString:attribute.name()
+ encoding:NSUTF8StringEncoding];
RTC_DCHECK(name.length > 0);
RTC_DCHECK(!values[name]);
values[name] = value;
diff --git a/third_party/libwebrtc/sdk/objc/native/src/audio/audio_device_ios.h b/third_party/libwebrtc/sdk/objc/native/src/audio/audio_device_ios.h
index a86acb56fe..4ef4d0b5df 100644
--- a/third_party/libwebrtc/sdk/objc/native/src/audio/audio_device_ios.h
+++ b/third_party/libwebrtc/sdk/objc/native/src/audio/audio_device_ios.h
@@ -299,6 +299,10 @@ class AudioDeviceIOS : public AudioDeviceGeneric,
// Avoids running pending task after `this` is Terminated.
rtc::scoped_refptr<PendingTaskSafetyFlag> safety_ =
PendingTaskSafetyFlag::Create();
+
+ // Ratio between mach tick units and nanosecond. Used to change mach tick
+ // units to nanoseconds.
+ double machTickUnitsToNanoseconds_;
};
} // namespace ios_adm
} // namespace webrtc
diff --git a/third_party/libwebrtc/sdk/objc/native/src/audio/audio_device_ios.mm b/third_party/libwebrtc/sdk/objc/native/src/audio/audio_device_ios.mm
index dd2c11bdd2..78420ec232 100644
--- a/third_party/libwebrtc/sdk/objc/native/src/audio/audio_device_ios.mm
+++ b/third_party/libwebrtc/sdk/objc/native/src/audio/audio_device_ios.mm
@@ -13,6 +13,7 @@
#include "audio_device_ios.h"
+#include <mach/mach_time.h>
#include <cmath>
#include "api/array_view.h"
@@ -110,6 +111,9 @@ AudioDeviceIOS::AudioDeviceIOS(bool bypass_voice_processing)
thread_ = rtc::Thread::Current();
audio_session_observer_ = [[RTCNativeAudioSessionDelegateAdapter alloc] initWithObserver:this];
+ mach_timebase_info_data_t tinfo;
+ mach_timebase_info(&tinfo);
+ machTickUnitsToNanoseconds_ = (double)tinfo.numer / tinfo.denom;
}
AudioDeviceIOS::~AudioDeviceIOS() {
@@ -376,6 +380,11 @@ OSStatus AudioDeviceIOS::OnDeliverRecordedData(AudioUnitRenderActionFlags* flags
record_audio_buffer_.Clear();
record_audio_buffer_.SetSize(num_frames);
+ // Get audio timestamp for the audio.
+ // The timestamp will not have NTP time epoch, but that will be addressed by
+ // the TimeStampAligner in AudioDeviceBuffer::SetRecordedBuffer().
+ SInt64 capture_timestamp_ns = time_stamp->mHostTime * machTickUnitsToNanoseconds_;
+
// Allocate AudioBuffers to be used as storage for the received audio.
// The AudioBufferList structure works as a placeholder for the
// AudioBuffer structure, which holds a pointer to the actual data buffer
@@ -404,7 +413,8 @@ OSStatus AudioDeviceIOS::OnDeliverRecordedData(AudioUnitRenderActionFlags* flags
// Get a pointer to the recorded audio and send it to the WebRTC ADB.
// Use the FineAudioBuffer instance to convert between native buffer size
// and the 10ms buffer size used by WebRTC.
- fine_audio_buffer_->DeliverRecordedData(record_audio_buffer_, kFixedRecordDelayEstimate);
+ fine_audio_buffer_->DeliverRecordedData(
+ record_audio_buffer_, kFixedRecordDelayEstimate, capture_timestamp_ns);
return noErr;
}
diff --git a/third_party/libwebrtc/stats/BUILD.gn b/third_party/libwebrtc/stats/BUILD.gn
index b2a0c20473..8993272921 100644
--- a/third_party/libwebrtc/stats/BUILD.gn
+++ b/third_party/libwebrtc/stats/BUILD.gn
@@ -16,7 +16,9 @@ rtc_library("rtc_stats") {
visibility = [ "*" ]
cflags = []
sources = [
+ "attribute.cc",
"rtc_stats.cc",
+ "rtc_stats_member.cc",
"rtc_stats_report.cc",
"rtcstats_objects.cc",
]
@@ -27,6 +29,7 @@ rtc_library("rtc_stats") {
"../rtc_base:macromagic",
"../rtc_base:stringutils",
]
+ absl_deps = [ "//third_party/abseil-cpp/absl/types:variant" ]
}
rtc_library("rtc_stats_test_utils") {
diff --git a/third_party/libwebrtc/stats/attribute.cc b/third_party/libwebrtc/stats/attribute.cc
new file mode 100644
index 0000000000..fab948b1bd
--- /dev/null
+++ b/third_party/libwebrtc/stats/attribute.cc
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2024 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "api/stats/attribute.h"
+
+#include <string>
+
+#include "absl/types/variant.h"
+#include "rtc_base/arraysize.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/string_encode.h"
+#include "rtc_base/strings/string_builder.h"
+
+namespace webrtc {
+
+namespace {
+
+struct VisitIsSequence {
+ // Any type of vector is a sequence.
+ template <typename T>
+ bool operator()(const RTCStatsMember<std::vector<T>>* attribute) {
+ return true;
+ }
+ // Any other type is not.
+ template <typename T>
+ bool operator()(const RTCStatsMember<T>* attribute) {
+ return false;
+ }
+};
+
+// Converts the attribute to string in a JSON-compatible way.
+struct VisitToString {
+ template <typename T,
+ typename std::enable_if_t<
+ std::is_same_v<T, int32_t> || std::is_same_v<T, uint32_t> ||
+ std::is_same_v<T, bool> || std::is_same_v<T, std::string>,
+ bool> = true>
+ std::string ValueToString(const T& value) {
+ return rtc::ToString(value);
+ }
+ // Convert 64-bit integers to doubles before converting to string because JSON
+ // represents all numbers as floating points with ~15 digits of precision.
+ template <typename T,
+ typename std::enable_if_t<std::is_same_v<T, int64_t> ||
+ std::is_same_v<T, uint64_t> ||
+ std::is_same_v<T, double>,
+ bool> = true>
+ std::string ValueToString(const T& value) {
+ char buf[32];
+ const int len = std::snprintf(&buf[0], arraysize(buf), "%.16g",
+ static_cast<double>(value));
+ RTC_DCHECK_LE(len, arraysize(buf));
+ return std::string(&buf[0], len);
+ }
+
+ // Vector attributes.
+ template <typename T>
+ std::string operator()(const RTCStatsMember<std::vector<T>>* attribute) {
+ rtc::StringBuilder sb;
+ sb << "[";
+ const char* separator = "";
+ constexpr bool element_is_string = std::is_same<T, std::string>::value;
+ for (const T& element : attribute->value()) {
+ sb << separator;
+ if (element_is_string) {
+ sb << "\"";
+ }
+ sb << ValueToString(element);
+ if (element_is_string) {
+ sb << "\"";
+ }
+ separator = ",";
+ }
+ sb << "]";
+ return sb.Release();
+ }
+ // Map attributes.
+ template <typename T>
+ std::string operator()(
+ const RTCStatsMember<std::map<std::string, T>>* attribute) {
+ rtc::StringBuilder sb;
+ sb << "{";
+ const char* separator = "";
+ constexpr bool element_is_string = std::is_same<T, std::string>::value;
+ for (const auto& pair : attribute->value()) {
+ sb << separator;
+ sb << "\"" << pair.first << "\":";
+ if (element_is_string) {
+ sb << "\"";
+ }
+ sb << ValueToString(pair.second);
+ if (element_is_string) {
+ sb << "\"";
+ }
+ separator = ",";
+ }
+ sb << "}";
+ return sb.Release();
+ }
+ // Simple attributes.
+ template <typename T>
+ std::string operator()(const RTCStatsMember<T>* attribute) {
+ return ValueToString(attribute->value());
+ }
+};
+
+struct VisitIsEqual {
+ template <typename T>
+ bool operator()(const RTCStatsMember<T>* attribute) {
+ if (!other.holds_alternative<T>()) {
+ return false;
+ }
+ absl::optional<T> attribute_as_optional =
+ attribute->has_value() ? absl::optional<T>(attribute->value())
+ : absl::nullopt;
+ return attribute_as_optional == other.as_optional<T>();
+ }
+
+ const Attribute& other;
+};
+
+} // namespace
+
+const char* Attribute::name() const {
+ return name_;
+}
+
+const Attribute::StatVariant& Attribute::as_variant() const {
+ return attribute_;
+}
+
+bool Attribute::has_value() const {
+ return absl::visit([](const auto* attr) { return attr->has_value(); },
+ attribute_);
+}
+
+bool Attribute::is_sequence() const {
+ return absl::visit(VisitIsSequence(), attribute_);
+}
+
+bool Attribute::is_string() const {
+ return absl::holds_alternative<const RTCStatsMember<std::string>*>(
+ attribute_);
+}
+
+std::string Attribute::ToString() const {
+ if (!has_value()) {
+ return "null";
+ }
+ return absl::visit(VisitToString(), attribute_);
+}
+
+bool Attribute::operator==(const Attribute& other) const {
+ return absl::visit(VisitIsEqual{.other = other}, attribute_);
+}
+
+bool Attribute::operator!=(const Attribute& other) const {
+ return !(*this == other);
+}
+
+AttributeInit::AttributeInit(const char* name,
+ const Attribute::StatVariant& variant)
+ : name(name), variant(variant) {}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/stats/g3doc/stats.md b/third_party/libwebrtc/stats/g3doc/stats.md
index 25127dc36e..7fb89340b8 100644
--- a/third_party/libwebrtc/stats/g3doc/stats.md
+++ b/third_party/libwebrtc/stats/g3doc/stats.md
@@ -1,18 +1,9 @@
-<!-- go/cmark -->
-<!--* freshness: {owner: 'hta' reviewed: '2022-10-01'} *-->
-
-# getStats in WebRTC
-
-The WebRTC getStats API is specified in
- https://w3c.github.io/webrtc-stats/
-and allows querying information about the current state of a RTCPeerConnection
-API and some of its member objects.
-
-## Adding new statistics to Chrome
-
-When adding a new standardized `RTCStatsMember` it is necessary to add
-it to the Chrome allowlist
- chrome/test/data/webrtc/peerconnection_getstats.js
-before landing the WebRTC change. This mechanism prevents the accidential
-addition and exposure of non-standard attributes and is not required for
-`RTCNonStandardStatsMember` which is not exposed to the web API. \ No newline at end of file
+<!-- go/cmark -->
+<!--* freshness: {owner: 'hta' reviewed: '2024-01-08'} *-->
+
+# getStats in WebRTC
+
+The WebRTC getStats API is specified in
+ https://w3c.github.io/webrtc-stats/
+and allows querying information about the current state of a RTCPeerConnection
+API and some of its member objects.
diff --git a/third_party/libwebrtc/stats/rtc_stats.cc b/third_party/libwebrtc/stats/rtc_stats.cc
index 25bde289c2..e7d72ee3a3 100644
--- a/third_party/libwebrtc/stats/rtc_stats.cc
+++ b/third_party/libwebrtc/stats/rtc_stats.cc
@@ -12,125 +12,25 @@
#include <cstdio>
-#include "rtc_base/arraysize.h"
-#include "rtc_base/string_encode.h"
#include "rtc_base/strings/string_builder.h"
namespace webrtc {
-namespace {
+RTCStats::RTCStats(const RTCStats& other)
+ : RTCStats(other.id_, other.timestamp_) {}
-// Produces "[a,b,c]". Works for non-vector `RTCStatsMemberInterface::Type`
-// types.
-template <typename T>
-std::string VectorToString(const std::vector<T>& vector) {
- rtc::StringBuilder sb;
- sb << "[";
- const char* separator = "";
- for (const T& element : vector) {
- sb << separator << rtc::ToString(element);
- separator = ",";
- }
- sb << "]";
- return sb.Release();
-}
-
-// This overload is required because std::vector<bool> range loops don't
-// return references but objects, causing -Wrange-loop-analysis diagnostics.
-std::string VectorToString(const std::vector<bool>& vector) {
- rtc::StringBuilder sb;
- sb << "[";
- const char* separator = "";
- for (bool element : vector) {
- sb << separator << rtc::ToString(element);
- separator = ",";
- }
- sb << "]";
- return sb.Release();
-}
-
-// Produces "[\"a\",\"b\",\"c\"]". Works for vectors of both const char* and
-// std::string element types.
-template <typename T>
-std::string VectorOfStringsToString(const std::vector<T>& strings) {
- rtc::StringBuilder sb;
- sb << "[";
- const char* separator = "";
- for (const T& element : strings) {
- sb << separator << "\"" << rtc::ToString(element) << "\"";
- separator = ",";
- }
- sb << "]";
- return sb.Release();
-}
-
-template <typename T>
-std::string MapToString(const std::map<std::string, T>& map) {
- rtc::StringBuilder sb;
- sb << "{";
- const char* separator = "";
- for (const auto& element : map) {
- sb << separator << rtc::ToString(element.first) << ":"
- << rtc::ToString(element.second);
- separator = ",";
- }
- sb << "}";
- return sb.Release();
-}
-
-template <typename T>
-std::string ToStringAsDouble(const T value) {
- // JSON represents numbers as floating point numbers with about 15 decimal
- // digits of precision.
- char buf[32];
- const int len = std::snprintf(&buf[0], arraysize(buf), "%.16g",
- static_cast<double>(value));
- RTC_DCHECK_LE(len, arraysize(buf));
- return std::string(&buf[0], len);
-}
-
-template <typename T>
-std::string VectorToStringAsDouble(const std::vector<T>& vector) {
- rtc::StringBuilder sb;
- sb << "[";
- const char* separator = "";
- for (const T& element : vector) {
- sb << separator << ToStringAsDouble<T>(element);
- separator = ",";
- }
- sb << "]";
- return sb.Release();
-}
-
-template <typename T>
-std::string MapToStringAsDouble(const std::map<std::string, T>& map) {
- rtc::StringBuilder sb;
- sb << "{";
- const char* separator = "";
- for (const auto& element : map) {
- sb << separator << "\"" << rtc::ToString(element.first)
- << "\":" << ToStringAsDouble(element.second);
- separator = ",";
- }
- sb << "}";
- return sb.Release();
-}
-
-} // namespace
+RTCStats::~RTCStats() {}
bool RTCStats::operator==(const RTCStats& other) const {
if (type() != other.type() || id() != other.id())
return false;
- std::vector<const RTCStatsMemberInterface*> members = Members();
- std::vector<const RTCStatsMemberInterface*> other_members = other.Members();
- RTC_DCHECK_EQ(members.size(), other_members.size());
- for (size_t i = 0; i < members.size(); ++i) {
- const RTCStatsMemberInterface* member = members[i];
- const RTCStatsMemberInterface* other_member = other_members[i];
- RTC_DCHECK_EQ(member->type(), other_member->type());
- RTC_DCHECK_EQ(member->name(), other_member->name());
- if (*member != *other_member)
+ std::vector<Attribute> attributes = Attributes();
+ std::vector<Attribute> other_attributes = other.Attributes();
+ RTC_DCHECK_EQ(attributes.size(), other_attributes.size());
+ for (size_t i = 0; i < attributes.size(); ++i) {
+ if (attributes[i] != other_attributes[i]) {
return false;
+ }
}
return true;
}
@@ -148,150 +48,31 @@ std::string RTCStats::ToJson() const {
<< "\","
"\"timestamp\":"
<< timestamp_.us();
- for (const RTCStatsMemberInterface* member : Members()) {
- if (member->is_defined()) {
- sb << ",\"" << member->name() << "\":";
- if (member->is_string())
- sb << "\"" << member->ValueToJson() << "\"";
- else
- sb << member->ValueToJson();
+ for (const Attribute& attribute : Attributes()) {
+ if (attribute.has_value()) {
+ sb << ",\"" << attribute.name() << "\":";
+ if (attribute.holds_alternative<std::string>()) {
+ sb << "\"";
+ }
+ sb << attribute.ToString();
+ if (attribute.holds_alternative<std::string>()) {
+ sb << "\"";
+ }
}
}
sb << "}";
return sb.Release();
}
-std::vector<const RTCStatsMemberInterface*> RTCStats::Members() const {
- return MembersOfThisObjectAndAncestors(0);
+std::vector<Attribute> RTCStats::Attributes() const {
+ return AttributesImpl(0);
}
-std::vector<const RTCStatsMemberInterface*>
-RTCStats::MembersOfThisObjectAndAncestors(size_t additional_capacity) const {
- std::vector<const RTCStatsMemberInterface*> members;
- members.reserve(additional_capacity);
- return members;
+std::vector<Attribute> RTCStats::AttributesImpl(
+ size_t additional_capacity) const {
+ std::vector<Attribute> attributes;
+ attributes.reserve(additional_capacity);
+ return attributes;
}
-#define WEBRTC_DEFINE_RTCSTATSMEMBER(T, type, is_seq, is_str, to_str, to_json) \
- template <> \
- RTCStatsMemberInterface::Type RTCStatsMember<T>::StaticType() { \
- return type; \
- } \
- template <> \
- bool RTCStatsMember<T>::is_sequence() const { \
- return is_seq; \
- } \
- template <> \
- bool RTCStatsMember<T>::is_string() const { \
- return is_str; \
- } \
- template <> \
- std::string RTCStatsMember<T>::ValueToString() const { \
- RTC_DCHECK(value_.has_value()); \
- return to_str; \
- } \
- template <> \
- std::string RTCStatsMember<T>::ValueToJson() const { \
- RTC_DCHECK(value_.has_value()); \
- return to_json; \
- } \
- template class RTC_EXPORT_TEMPLATE_DEFINE(RTC_EXPORT) RTCStatsMember<T>
-
-WEBRTC_DEFINE_RTCSTATSMEMBER(bool,
- kBool,
- false,
- false,
- rtc::ToString(*value_),
- rtc::ToString(*value_));
-WEBRTC_DEFINE_RTCSTATSMEMBER(int32_t,
- kInt32,
- false,
- false,
- rtc::ToString(*value_),
- rtc::ToString(*value_));
-WEBRTC_DEFINE_RTCSTATSMEMBER(uint32_t,
- kUint32,
- false,
- false,
- rtc::ToString(*value_),
- rtc::ToString(*value_));
-WEBRTC_DEFINE_RTCSTATSMEMBER(int64_t,
- kInt64,
- false,
- false,
- rtc::ToString(*value_),
- ToStringAsDouble(*value_));
-WEBRTC_DEFINE_RTCSTATSMEMBER(uint64_t,
- kUint64,
- false,
- false,
- rtc::ToString(*value_),
- ToStringAsDouble(*value_));
-WEBRTC_DEFINE_RTCSTATSMEMBER(double,
- kDouble,
- false,
- false,
- rtc::ToString(*value_),
- ToStringAsDouble(*value_));
-WEBRTC_DEFINE_RTCSTATSMEMBER(std::string,
- kString,
- false,
- true,
- *value_,
- *value_);
-WEBRTC_DEFINE_RTCSTATSMEMBER(std::vector<bool>,
- kSequenceBool,
- true,
- false,
- VectorToString(*value_),
- VectorToString(*value_));
-WEBRTC_DEFINE_RTCSTATSMEMBER(std::vector<int32_t>,
- kSequenceInt32,
- true,
- false,
- VectorToString(*value_),
- VectorToString(*value_));
-WEBRTC_DEFINE_RTCSTATSMEMBER(std::vector<uint32_t>,
- kSequenceUint32,
- true,
- false,
- VectorToString(*value_),
- VectorToString(*value_));
-WEBRTC_DEFINE_RTCSTATSMEMBER(std::vector<int64_t>,
- kSequenceInt64,
- true,
- false,
- VectorToString(*value_),
- VectorToStringAsDouble(*value_));
-WEBRTC_DEFINE_RTCSTATSMEMBER(std::vector<uint64_t>,
- kSequenceUint64,
- true,
- false,
- VectorToString(*value_),
- VectorToStringAsDouble(*value_));
-WEBRTC_DEFINE_RTCSTATSMEMBER(std::vector<double>,
- kSequenceDouble,
- true,
- false,
- VectorToString(*value_),
- VectorToStringAsDouble(*value_));
-WEBRTC_DEFINE_RTCSTATSMEMBER(std::vector<std::string>,
- kSequenceString,
- true,
- false,
- VectorOfStringsToString(*value_),
- VectorOfStringsToString(*value_));
-WEBRTC_DEFINE_RTCSTATSMEMBER(rtc_stats_internal::MapStringUint64,
- kMapStringUint64,
- false,
- false,
- MapToString(*value_),
- MapToStringAsDouble(*value_));
-WEBRTC_DEFINE_RTCSTATSMEMBER(rtc_stats_internal::MapStringDouble,
- kMapStringDouble,
- false,
- false,
- MapToString(*value_),
- MapToStringAsDouble(*value_));
-
} // namespace webrtc
diff --git a/third_party/libwebrtc/stats/rtc_stats_member.cc b/third_party/libwebrtc/stats/rtc_stats_member.cc
new file mode 100644
index 0000000000..3f91988ed0
--- /dev/null
+++ b/third_party/libwebrtc/stats/rtc_stats_member.cc
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2023 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "api/stats/rtc_stats_member.h"
+
+namespace webrtc {
+
+#define WEBRTC_DEFINE_RTCSTATSMEMBER(T, type, is_seq, is_str) \
+ template <> \
+ RTCStatsMemberInterface::Type RTCStatsMember<T>::StaticType() { \
+ return type; \
+ } \
+ template <> \
+ bool RTCStatsMember<T>::is_sequence() const { \
+ return is_seq; \
+ } \
+ template <> \
+ bool RTCStatsMember<T>::is_string() const { \
+ return is_str; \
+ } \
+ template class RTC_EXPORT_TEMPLATE_DEFINE(RTC_EXPORT) RTCStatsMember<T>
+
+WEBRTC_DEFINE_RTCSTATSMEMBER(bool, kBool, false, false);
+WEBRTC_DEFINE_RTCSTATSMEMBER(int32_t, kInt32, false, false);
+WEBRTC_DEFINE_RTCSTATSMEMBER(uint32_t, kUint32, false, false);
+WEBRTC_DEFINE_RTCSTATSMEMBER(int64_t, kInt64, false, false);
+WEBRTC_DEFINE_RTCSTATSMEMBER(uint64_t, kUint64, false, false);
+WEBRTC_DEFINE_RTCSTATSMEMBER(double, kDouble, false, false);
+WEBRTC_DEFINE_RTCSTATSMEMBER(std::string, kString, false, true);
+WEBRTC_DEFINE_RTCSTATSMEMBER(std::vector<bool>, kSequenceBool, true, false);
+WEBRTC_DEFINE_RTCSTATSMEMBER(std::vector<int32_t>, kSequenceInt32, true, false);
+WEBRTC_DEFINE_RTCSTATSMEMBER(std::vector<uint32_t>,
+ kSequenceUint32,
+ true,
+ false);
+WEBRTC_DEFINE_RTCSTATSMEMBER(std::vector<int64_t>, kSequenceInt64, true, false);
+WEBRTC_DEFINE_RTCSTATSMEMBER(std::vector<uint64_t>,
+ kSequenceUint64,
+ true,
+ false);
+WEBRTC_DEFINE_RTCSTATSMEMBER(std::vector<double>, kSequenceDouble, true, false);
+WEBRTC_DEFINE_RTCSTATSMEMBER(std::vector<std::string>,
+ kSequenceString,
+ true,
+ false);
+WEBRTC_DEFINE_RTCSTATSMEMBER(rtc_stats_internal::MapStringUint64,
+ kMapStringUint64,
+ false,
+ false);
+WEBRTC_DEFINE_RTCSTATSMEMBER(rtc_stats_internal::MapStringDouble,
+ kMapStringDouble,
+ false,
+ false);
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/stats/rtc_stats_report_unittest.cc b/third_party/libwebrtc/stats/rtc_stats_report_unittest.cc
index ded0c27609..b3ac0a2db4 100644
--- a/third_party/libwebrtc/stats/rtc_stats_report_unittest.cc
+++ b/third_party/libwebrtc/stats/rtc_stats_report_unittest.cc
@@ -10,6 +10,7 @@
#include "api/stats/rtc_stats_report.h"
+#include "api/stats/attribute.h"
#include "api/stats/rtc_stats.h"
#include "rtc_base/checks.h"
#include "test/gtest.h"
@@ -21,36 +22,45 @@ class RTCTestStats1 : public RTCStats {
WEBRTC_RTCSTATS_DECL();
RTCTestStats1(const std::string& id, Timestamp timestamp)
- : RTCStats(id, timestamp), integer("integer") {}
+ : RTCStats(id, timestamp) {}
RTCStatsMember<int32_t> integer;
};
-WEBRTC_RTCSTATS_IMPL(RTCTestStats1, RTCStats, "test-stats-1", &integer)
+WEBRTC_RTCSTATS_IMPL(RTCTestStats1,
+ RTCStats,
+ "test-stats-1",
+ AttributeInit("integer", &integer))
class RTCTestStats2 : public RTCStats {
public:
WEBRTC_RTCSTATS_DECL();
RTCTestStats2(const std::string& id, Timestamp timestamp)
- : RTCStats(id, timestamp), number("number") {}
+ : RTCStats(id, timestamp) {}
RTCStatsMember<double> number;
};
-WEBRTC_RTCSTATS_IMPL(RTCTestStats2, RTCStats, "test-stats-2", &number)
+WEBRTC_RTCSTATS_IMPL(RTCTestStats2,
+ RTCStats,
+ "test-stats-2",
+ AttributeInit("number", &number))
class RTCTestStats3 : public RTCStats {
public:
WEBRTC_RTCSTATS_DECL();
RTCTestStats3(const std::string& id, Timestamp timestamp)
- : RTCStats(id, timestamp), string("string") {}
+ : RTCStats(id, timestamp) {}
RTCStatsMember<std::string> string;
};
-WEBRTC_RTCSTATS_IMPL(RTCTestStats3, RTCStats, "test-stats-3", &string)
+WEBRTC_RTCSTATS_IMPL(RTCTestStats3,
+ RTCStats,
+ "test-stats-3",
+ AttributeInit("string", &string))
TEST(RTCStatsReport, AddAndGetStats) {
rtc::scoped_refptr<RTCStatsReport> report =
diff --git a/third_party/libwebrtc/stats/rtc_stats_unittest.cc b/third_party/libwebrtc/stats/rtc_stats_unittest.cc
index 249491effd..fd90692875 100644
--- a/third_party/libwebrtc/stats/rtc_stats_unittest.cc
+++ b/third_party/libwebrtc/stats/rtc_stats_unittest.cc
@@ -44,19 +44,22 @@ class RTCChildStats : public RTCStats {
WEBRTC_RTCSTATS_DECL();
RTCChildStats(const std::string& id, Timestamp timestamp)
- : RTCStats(id, timestamp), child_int("childInt") {}
+ : RTCStats(id, timestamp) {}
RTCStatsMember<int32_t> child_int;
};
-WEBRTC_RTCSTATS_IMPL(RTCChildStats, RTCStats, "child-stats", &child_int)
+WEBRTC_RTCSTATS_IMPL(RTCChildStats,
+ RTCStats,
+ "child-stats",
+ AttributeInit("childInt", &child_int))
class RTCGrandChildStats : public RTCChildStats {
public:
WEBRTC_RTCSTATS_DECL();
RTCGrandChildStats(const std::string& id, Timestamp timestamp)
- : RTCChildStats(id, timestamp), grandchild_int("grandchildInt") {}
+ : RTCChildStats(id, timestamp) {}
RTCStatsMember<int32_t> grandchild_int;
};
@@ -64,16 +67,16 @@ class RTCGrandChildStats : public RTCChildStats {
WEBRTC_RTCSTATS_IMPL(RTCGrandChildStats,
RTCChildStats,
"grandchild-stats",
- &grandchild_int)
+ AttributeInit("grandchildInt", &grandchild_int))
-TEST(RTCStatsTest, RTCStatsAndMembers) {
+TEST(RTCStatsTest, RTCStatsAndAttributes) {
RTCTestStats stats("testId", Timestamp::Micros(42));
EXPECT_EQ(stats.id(), "testId");
EXPECT_EQ(stats.timestamp().us(), static_cast<int64_t>(42));
- std::vector<const RTCStatsMemberInterface*> members = stats.Members();
- EXPECT_EQ(members.size(), static_cast<size_t>(16));
- for (const RTCStatsMemberInterface* member : members) {
- EXPECT_FALSE(member->is_defined());
+ std::vector<Attribute> attributes = stats.Attributes();
+ EXPECT_EQ(attributes.size(), static_cast<size_t>(16));
+ for (const auto& attribute : attributes) {
+ EXPECT_FALSE(attribute.has_value());
}
stats.m_bool = true;
stats.m_int32 = 123;
@@ -104,15 +107,15 @@ TEST(RTCStatsTest, RTCStatsAndMembers) {
stats.m_sequence_bool = sequence_bool;
stats.m_sequence_int32 = sequence_int32;
stats.m_sequence_uint32 = sequence_uint32;
- EXPECT_FALSE(stats.m_sequence_int64.is_defined());
+ EXPECT_FALSE(stats.m_sequence_int64.has_value());
stats.m_sequence_int64 = sequence_int64;
stats.m_sequence_uint64 = sequence_uint64;
stats.m_sequence_double = sequence_double;
stats.m_sequence_string = sequence_string;
stats.m_map_string_uint64 = map_string_uint64;
stats.m_map_string_double = map_string_double;
- for (const RTCStatsMemberInterface* member : members) {
- EXPECT_TRUE(member->is_defined());
+ for (const auto& attribute : attributes) {
+ EXPECT_TRUE(attribute.has_value());
}
EXPECT_EQ(*stats.m_bool, true);
EXPECT_EQ(*stats.m_int32, static_cast<int32_t>(123));
@@ -217,8 +220,8 @@ TEST(RTCStatsTest, RTCStatsGrandChild) {
stats.child_int = 1;
stats.grandchild_int = 2;
int32_t sum = 0;
- for (const RTCStatsMemberInterface* member : stats.Members()) {
- sum += *member->cast_to<const RTCStatsMember<int32_t>>();
+ for (const auto& attribute : stats.Attributes()) {
+ sum += attribute.get<int32_t>();
}
EXPECT_EQ(sum, static_cast<int32_t>(3));
@@ -379,8 +382,8 @@ TEST(RTCStatsTest, RTCStatsPrintsValidJson) {
// "mUint32" should not be part of the generated JSON object.
int m_uint32;
int m_uint64;
- EXPECT_FALSE(stats.m_uint32.is_defined());
- EXPECT_FALSE(stats.m_uint64.is_defined());
+ EXPECT_FALSE(stats.m_uint32.has_value());
+ EXPECT_FALSE(stats.m_uint64.has_value());
EXPECT_FALSE(rtc::GetIntFromJsonObject(json_output, "mUint32", &m_uint32));
EXPECT_FALSE(rtc::GetIntFromJsonObject(json_output, "mUint64", &m_uint64));
@@ -456,45 +459,50 @@ TEST(RTCStatsTest, IsString) {
EXPECT_FALSE(stats.m_map_string_double.is_string());
}
-TEST(RTCStatsTest, ValueToString) {
+TEST(RTCStatsTest, AttributeToString) {
RTCTestStats stats("statsId", Timestamp::Micros(42));
stats.m_bool = true;
- EXPECT_EQ("true", stats.m_bool.ValueToString());
+ EXPECT_EQ("true", stats.GetAttribute(stats.m_bool).ToString());
stats.m_string = "foo";
- EXPECT_EQ("foo", stats.m_string.ValueToString());
+ EXPECT_EQ("foo", stats.GetAttribute(stats.m_string).ToString());
stats.m_int32 = -32;
- EXPECT_EQ("-32", stats.m_int32.ValueToString());
+ EXPECT_EQ("-32", stats.GetAttribute(stats.m_int32).ToString());
stats.m_uint32 = 32;
- EXPECT_EQ("32", stats.m_uint32.ValueToString());
+ EXPECT_EQ("32", stats.GetAttribute(stats.m_uint32).ToString());
stats.m_int64 = -64;
- EXPECT_EQ("-64", stats.m_int64.ValueToString());
+ EXPECT_EQ("-64", stats.GetAttribute(stats.m_int64).ToString());
stats.m_uint64 = 64;
- EXPECT_EQ("64", stats.m_uint64.ValueToString());
+ EXPECT_EQ("64", stats.GetAttribute(stats.m_uint64).ToString());
stats.m_double = 0.5;
- EXPECT_EQ("0.5", stats.m_double.ValueToString());
+ EXPECT_EQ("0.5", stats.GetAttribute(stats.m_double).ToString());
stats.m_sequence_bool = {true, false};
- EXPECT_EQ("[true,false]", stats.m_sequence_bool.ValueToString());
+ EXPECT_EQ("[true,false]",
+ stats.GetAttribute(stats.m_sequence_bool).ToString());
stats.m_sequence_int32 = {-32, 32};
- EXPECT_EQ("[-32,32]", stats.m_sequence_int32.ValueToString());
+ EXPECT_EQ("[-32,32]", stats.GetAttribute(stats.m_sequence_int32).ToString());
stats.m_sequence_uint32 = {64, 32};
- EXPECT_EQ("[64,32]", stats.m_sequence_uint32.ValueToString());
+ EXPECT_EQ("[64,32]", stats.GetAttribute(stats.m_sequence_uint32).ToString());
stats.m_sequence_int64 = {-64, 32};
- EXPECT_EQ("[-64,32]", stats.m_sequence_int64.ValueToString());
+ EXPECT_EQ("[-64,32]", stats.GetAttribute(stats.m_sequence_int64).ToString());
stats.m_sequence_uint64 = {16, 32};
- EXPECT_EQ("[16,32]", stats.m_sequence_uint64.ValueToString());
+ EXPECT_EQ("[16,32]", stats.GetAttribute(stats.m_sequence_uint64).ToString());
stats.m_sequence_double = {0.5, 0.25};
- EXPECT_EQ("[0.5,0.25]", stats.m_sequence_double.ValueToString());
+ EXPECT_EQ("[0.5,0.25]",
+ stats.GetAttribute(stats.m_sequence_double).ToString());
stats.m_sequence_string = {"foo", "bar"};
- EXPECT_EQ("[\"foo\",\"bar\"]", stats.m_sequence_string.ValueToString());
+ EXPECT_EQ("[\"foo\",\"bar\"]",
+ stats.GetAttribute(stats.m_sequence_string).ToString());
stats.m_map_string_uint64 = std::map<std::string, uint64_t>();
stats.m_map_string_uint64->emplace("foo", 32);
stats.m_map_string_uint64->emplace("bar", 64);
- EXPECT_EQ("{bar:64,foo:32}", stats.m_map_string_uint64.ValueToString());
+ EXPECT_EQ("{\"bar\":64,\"foo\":32}",
+ stats.GetAttribute(stats.m_map_string_uint64).ToString());
stats.m_map_string_double = std::map<std::string, double>();
stats.m_map_string_double->emplace("foo", 0.5);
stats.m_map_string_double->emplace("bar", 0.25);
- EXPECT_EQ("{bar:0.25,foo:0.5}", stats.m_map_string_double.ValueToString());
+ EXPECT_EQ("{\"bar\":0.25,\"foo\":0.5}",
+ stats.GetAttribute(stats.m_map_string_double).ToString());
}
// Death tests.
@@ -504,7 +512,7 @@ TEST(RTCStatsTest, ValueToString) {
TEST(RTCStatsDeathTest, ValueOfUndefinedMember) {
RTCTestStats stats("testId", Timestamp::Micros(0));
- EXPECT_FALSE(stats.m_int32.is_defined());
+ EXPECT_FALSE(stats.m_int32.has_value());
EXPECT_DEATH(*stats.m_int32, "");
}
diff --git a/third_party/libwebrtc/stats/rtcstats_objects.cc b/third_party/libwebrtc/stats/rtcstats_objects.cc
index 77feaf87ba..4c58f45f02 100644
--- a/third_party/libwebrtc/stats/rtcstats_objects.cc
+++ b/third_party/libwebrtc/stats/rtcstats_objects.cc
@@ -12,6 +12,7 @@
#include <utility>
+#include "api/stats/attribute.h"
#include "api/stats/rtc_stats.h"
#include "rtc_base/checks.h"
@@ -19,182 +20,110 @@ namespace webrtc {
// clang-format off
WEBRTC_RTCSTATS_IMPL(RTCCertificateStats, RTCStats, "certificate",
- &fingerprint,
- &fingerprint_algorithm,
- &base64_certificate,
- &issuer_certificate_id)
+ AttributeInit("fingerprint", &fingerprint),
+ AttributeInit("fingerprintAlgorithm", &fingerprint_algorithm),
+ AttributeInit("base64Certificate", &base64_certificate),
+ AttributeInit("issuerCertificateId", &issuer_certificate_id))
// clang-format on
RTCCertificateStats::RTCCertificateStats(std::string id, Timestamp timestamp)
- : RTCStats(std::move(id), timestamp),
- fingerprint("fingerprint"),
- fingerprint_algorithm("fingerprintAlgorithm"),
- base64_certificate("base64Certificate"),
- issuer_certificate_id("issuerCertificateId") {}
-
-RTCCertificateStats::RTCCertificateStats(const RTCCertificateStats& other) =
- default;
+ : RTCStats(std::move(id), timestamp) {}
+
RTCCertificateStats::~RTCCertificateStats() {}
// clang-format off
WEBRTC_RTCSTATS_IMPL(RTCCodecStats, RTCStats, "codec",
- &transport_id,
- &payload_type,
- &mime_type,
- &clock_rate,
- &channels,
- &sdp_fmtp_line)
+ AttributeInit("transportId", &transport_id),
+ AttributeInit("payloadType", &payload_type),
+ AttributeInit("mimeType", &mime_type),
+ AttributeInit("clockRate", &clock_rate),
+ AttributeInit("channels", &channels),
+ AttributeInit("sdpFmtpLine", &sdp_fmtp_line))
// clang-format on
RTCCodecStats::RTCCodecStats(std::string id, Timestamp timestamp)
- : RTCStats(std::move(id), timestamp),
- transport_id("transportId"),
- payload_type("payloadType"),
- mime_type("mimeType"),
- clock_rate("clockRate"),
- channels("channels"),
- sdp_fmtp_line("sdpFmtpLine") {}
-
-RTCCodecStats::RTCCodecStats(const RTCCodecStats& other) = default;
+ : RTCStats(std::move(id), timestamp) {}
RTCCodecStats::~RTCCodecStats() {}
// clang-format off
WEBRTC_RTCSTATS_IMPL(RTCDataChannelStats, RTCStats, "data-channel",
- &label,
- &protocol,
- &data_channel_identifier,
- &state,
- &messages_sent,
- &bytes_sent,
- &messages_received,
- &bytes_received)
+ AttributeInit("label", &label),
+ AttributeInit("protocol", &protocol),
+ AttributeInit("dataChannelIdentifier", &data_channel_identifier),
+ AttributeInit("state", &state),
+ AttributeInit("messagesSent", &messages_sent),
+ AttributeInit("bytesSent", &bytes_sent),
+ AttributeInit("messagesReceived", &messages_received),
+ AttributeInit("bytesReceived", &bytes_received))
// clang-format on
RTCDataChannelStats::RTCDataChannelStats(std::string id, Timestamp timestamp)
- : RTCStats(std::move(id), timestamp),
- label("label"),
- protocol("protocol"),
- data_channel_identifier("dataChannelIdentifier"),
- state("state"),
- messages_sent("messagesSent"),
- bytes_sent("bytesSent"),
- messages_received("messagesReceived"),
- bytes_received("bytesReceived") {}
-
-RTCDataChannelStats::RTCDataChannelStats(const RTCDataChannelStats& other) =
- default;
+ : RTCStats(std::move(id), timestamp) {}
RTCDataChannelStats::~RTCDataChannelStats() {}
// clang-format off
WEBRTC_RTCSTATS_IMPL(RTCIceCandidatePairStats, RTCStats, "candidate-pair",
- &transport_id,
- &local_candidate_id,
- &remote_candidate_id,
- &state,
- &priority,
- &nominated,
- &writable,
- &packets_sent,
- &packets_received,
- &bytes_sent,
- &bytes_received,
- &total_round_trip_time,
- &current_round_trip_time,
- &available_outgoing_bitrate,
- &available_incoming_bitrate,
- &requests_received,
- &requests_sent,
- &responses_received,
- &responses_sent,
- &consent_requests_sent,
- &packets_discarded_on_send,
- &bytes_discarded_on_send,
- &last_packet_received_timestamp,
- &last_packet_sent_timestamp)
+ AttributeInit("transportId", &transport_id),
+ AttributeInit("localCandidateId", &local_candidate_id),
+ AttributeInit("remoteCandidateId", &remote_candidate_id),
+ AttributeInit("state", &state),
+ AttributeInit("priority", &priority),
+ AttributeInit("nominated", &nominated),
+ AttributeInit("writable", &writable),
+ AttributeInit("packetsSent", &packets_sent),
+ AttributeInit("packetsReceived", &packets_received),
+ AttributeInit("bytesSent", &bytes_sent),
+ AttributeInit("bytesReceived", &bytes_received),
+ AttributeInit("totalRoundTripTime", &total_round_trip_time),
+ AttributeInit("currentRoundTripTime", &current_round_trip_time),
+ AttributeInit("availableOutgoingBitrate", &available_outgoing_bitrate),
+ AttributeInit("availableIncomingBitrate", &available_incoming_bitrate),
+ AttributeInit("requestsReceived", &requests_received),
+ AttributeInit("requestsSent", &requests_sent),
+ AttributeInit("responsesReceived", &responses_received),
+ AttributeInit("responsesSent", &responses_sent),
+ AttributeInit("consentRequestsSent", &consent_requests_sent),
+ AttributeInit("packetsDiscardedOnSend", &packets_discarded_on_send),
+ AttributeInit("bytesDiscardedOnSend", &bytes_discarded_on_send),
+ AttributeInit("lastPacketReceivedTimestamp",
+ &last_packet_received_timestamp),
+ AttributeInit("lastPacketSentTimestamp", &last_packet_sent_timestamp))
// clang-format on
RTCIceCandidatePairStats::RTCIceCandidatePairStats(std::string id,
Timestamp timestamp)
- : RTCStats(std::move(id), timestamp),
- transport_id("transportId"),
- local_candidate_id("localCandidateId"),
- remote_candidate_id("remoteCandidateId"),
- state("state"),
- priority("priority"),
- nominated("nominated"),
- writable("writable"),
- packets_sent("packetsSent"),
- packets_received("packetsReceived"),
- bytes_sent("bytesSent"),
- bytes_received("bytesReceived"),
- total_round_trip_time("totalRoundTripTime"),
- current_round_trip_time("currentRoundTripTime"),
- available_outgoing_bitrate("availableOutgoingBitrate"),
- available_incoming_bitrate("availableIncomingBitrate"),
- requests_received("requestsReceived"),
- requests_sent("requestsSent"),
- responses_received("responsesReceived"),
- responses_sent("responsesSent"),
- consent_requests_sent("consentRequestsSent"),
- packets_discarded_on_send("packetsDiscardedOnSend"),
- bytes_discarded_on_send("bytesDiscardedOnSend"),
- last_packet_received_timestamp("lastPacketReceivedTimestamp"),
- last_packet_sent_timestamp("lastPacketSentTimestamp") {}
-
-RTCIceCandidatePairStats::RTCIceCandidatePairStats(
- const RTCIceCandidatePairStats& other) = default;
+ : RTCStats(std::move(id), timestamp) {}
RTCIceCandidatePairStats::~RTCIceCandidatePairStats() {}
// clang-format off
WEBRTC_RTCSTATS_IMPL(RTCIceCandidateStats, RTCStats, "abstract-ice-candidate",
- &transport_id,
- &is_remote,
- &network_type,
- &ip,
- &address,
- &port,
- &protocol,
- &relay_protocol,
- &candidate_type,
- &priority,
- &url,
- &foundation,
- &related_address,
- &related_port,
- &username_fragment,
- &tcp_type,
- &vpn,
- &network_adapter_type)
+ AttributeInit("transportId", &transport_id),
+ AttributeInit("isRemote", &is_remote),
+ AttributeInit("networkType", &network_type),
+ AttributeInit("ip", &ip),
+ AttributeInit("address", &address),
+ AttributeInit("port", &port),
+ AttributeInit("protocol", &protocol),
+ AttributeInit("relayProtocol", &relay_protocol),
+ AttributeInit("candidateType", &candidate_type),
+ AttributeInit("priority", &priority),
+ AttributeInit("url", &url),
+ AttributeInit("foundation", &foundation),
+ AttributeInit("relatedAddress", &related_address),
+ AttributeInit("relatedPort", &related_port),
+ AttributeInit("usernameFragment", &username_fragment),
+ AttributeInit("tcpType", &tcp_type),
+ AttributeInit("vpn", &vpn),
+ AttributeInit("networkAdapterType", &network_adapter_type))
// clang-format on
RTCIceCandidateStats::RTCIceCandidateStats(std::string id,
Timestamp timestamp,
bool is_remote)
- : RTCStats(std::move(id), timestamp),
- transport_id("transportId"),
- is_remote("isRemote", is_remote),
- network_type("networkType"),
- ip("ip"),
- address("address"),
- port("port"),
- protocol("protocol"),
- relay_protocol("relayProtocol"),
- candidate_type("candidateType"),
- priority("priority"),
- url("url"),
- foundation("foundation"),
- related_address("relatedAddress"),
- related_port("relatedPort"),
- username_fragment("usernameFragment"),
- tcp_type("tcpType"),
- vpn("vpn"),
- network_adapter_type("networkAdapterType") {}
-
-RTCIceCandidateStats::RTCIceCandidateStats(const RTCIceCandidateStats& other) =
- default;
+ : RTCStats(std::move(id), timestamp), is_remote(is_remote) {}
RTCIceCandidateStats::~RTCIceCandidateStats() {}
@@ -228,286 +157,172 @@ const char* RTCRemoteIceCandidateStats::type() const {
// clang-format off
WEBRTC_RTCSTATS_IMPL(RTCPeerConnectionStats, RTCStats, "peer-connection",
- &data_channels_opened,
- &data_channels_closed)
+ AttributeInit("dataChannelsOpened", &data_channels_opened),
+ AttributeInit("dataChannelsClosed", &data_channels_closed))
// clang-format on
RTCPeerConnectionStats::RTCPeerConnectionStats(std::string id,
Timestamp timestamp)
- : RTCStats(std::move(id), timestamp),
- data_channels_opened("dataChannelsOpened"),
- data_channels_closed("dataChannelsClosed") {}
-
-RTCPeerConnectionStats::RTCPeerConnectionStats(
- const RTCPeerConnectionStats& other) = default;
+ : RTCStats(std::move(id), timestamp) {}
RTCPeerConnectionStats::~RTCPeerConnectionStats() {}
// clang-format off
WEBRTC_RTCSTATS_IMPL(RTCRtpStreamStats, RTCStats, "rtp",
- &ssrc,
- &kind,
- &transport_id,
- &codec_id)
+ AttributeInit("ssrc", &ssrc),
+ AttributeInit("kind", &kind),
+ AttributeInit("transportId", &transport_id),
+ AttributeInit("codecId", &codec_id))
// clang-format on
RTCRtpStreamStats::RTCRtpStreamStats(std::string id, Timestamp timestamp)
- : RTCStats(std::move(id), timestamp),
- ssrc("ssrc"),
- kind("kind"),
- transport_id("transportId"),
- codec_id("codecId") {}
-
-RTCRtpStreamStats::RTCRtpStreamStats(const RTCRtpStreamStats& other) = default;
+ : RTCStats(std::move(id), timestamp) {}
RTCRtpStreamStats::~RTCRtpStreamStats() {}
// clang-format off
WEBRTC_RTCSTATS_IMPL(
RTCReceivedRtpStreamStats, RTCRtpStreamStats, "received-rtp",
- &jitter,
- &packets_lost)
+ AttributeInit("jitter", &jitter),
+ AttributeInit("packetsLost", &packets_lost))
// clang-format on
RTCReceivedRtpStreamStats::RTCReceivedRtpStreamStats(std::string id,
Timestamp timestamp)
- : RTCRtpStreamStats(std::move(id), timestamp),
- jitter("jitter"),
- packets_lost("packetsLost") {}
-
-RTCReceivedRtpStreamStats::RTCReceivedRtpStreamStats(
- const RTCReceivedRtpStreamStats& other) = default;
+ : RTCRtpStreamStats(std::move(id), timestamp) {}
RTCReceivedRtpStreamStats::~RTCReceivedRtpStreamStats() {}
// clang-format off
-WEBRTC_RTCSTATS_IMPL(
- RTCSentRtpStreamStats, RTCRtpStreamStats, "sent-rtp",
- &packets_sent,
- &bytes_sent)
+WEBRTC_RTCSTATS_IMPL(RTCSentRtpStreamStats, RTCRtpStreamStats, "sent-rtp",
+ AttributeInit("packetsSent", &packets_sent),
+ AttributeInit("bytesSent", &bytes_sent))
// clang-format on
RTCSentRtpStreamStats::RTCSentRtpStreamStats(std::string id,
Timestamp timestamp)
- : RTCRtpStreamStats(std::move(id), timestamp),
- packets_sent("packetsSent"),
- bytes_sent("bytesSent") {}
-
-RTCSentRtpStreamStats::RTCSentRtpStreamStats(
- const RTCSentRtpStreamStats& other) = default;
+ : RTCRtpStreamStats(std::move(id), timestamp) {}
RTCSentRtpStreamStats::~RTCSentRtpStreamStats() {}
// clang-format off
WEBRTC_RTCSTATS_IMPL(
RTCInboundRtpStreamStats, RTCReceivedRtpStreamStats, "inbound-rtp",
- &track_identifier,
- &mid,
- &remote_id,
- &packets_received,
- &packets_discarded,
- &fec_packets_received,
- &fec_bytes_received,
- &fec_packets_discarded,
- &fec_ssrc,
- &bytes_received,
- &header_bytes_received,
- &retransmitted_packets_received,
- &retransmitted_bytes_received,
- &rtx_ssrc,
- &last_packet_received_timestamp,
- &jitter_buffer_delay,
- &jitter_buffer_target_delay,
- &jitter_buffer_minimum_delay,
- &jitter_buffer_emitted_count,
- &total_samples_received,
- &concealed_samples,
- &silent_concealed_samples,
- &concealment_events,
- &inserted_samples_for_deceleration,
- &removed_samples_for_acceleration,
- &audio_level,
- &total_audio_energy,
- &total_samples_duration,
- &playout_id,
- &frames_received,
- &frame_width,
- &frame_height,
- &frames_per_second,
- &frames_decoded,
- &key_frames_decoded,
- &frames_dropped,
- &total_decode_time,
- &total_processing_delay,
- &total_assembly_time,
- &frames_assembled_from_multiple_packets,
- &total_inter_frame_delay,
- &total_squared_inter_frame_delay,
- &pause_count,
- &total_pauses_duration,
- &freeze_count,
- &total_freezes_duration,
- &content_type,
- &estimated_playout_timestamp,
- &decoder_implementation,
- &fir_count,
- &pli_count,
- &nack_count,
- &qp_sum,
- &goog_timing_frame_info,
- &power_efficient_decoder,
- &jitter_buffer_flushes,
- &delayed_packet_outage_samples,
- &relative_packet_arrival_delay,
- &interruption_count,
- &total_interruption_duration,
- &min_playout_delay)
+ AttributeInit("playoutId", &playout_id),
+ AttributeInit("trackIdentifier", &track_identifier),
+ AttributeInit("mid", &mid),
+ AttributeInit("remoteId", &remote_id),
+ AttributeInit("packetsReceived", &packets_received),
+ AttributeInit("packetsDiscarded", &packets_discarded),
+ AttributeInit("fecPacketsReceived", &fec_packets_received),
+ AttributeInit("fecBytesReceived", &fec_bytes_received),
+ AttributeInit("fecPacketsDiscarded", &fec_packets_discarded),
+ AttributeInit("fecSsrc", &fec_ssrc),
+ AttributeInit("bytesReceived", &bytes_received),
+ AttributeInit("headerBytesReceived", &header_bytes_received),
+ AttributeInit("retransmittedPacketsReceived",
+ &retransmitted_packets_received),
+ AttributeInit("retransmittedBytesReceived", &retransmitted_bytes_received),
+ AttributeInit("rtxSsrc", &rtx_ssrc),
+ AttributeInit("lastPacketReceivedTimestamp",
+ &last_packet_received_timestamp),
+ AttributeInit("jitterBufferDelay", &jitter_buffer_delay),
+ AttributeInit("jitterBufferTargetDelay", &jitter_buffer_target_delay),
+ AttributeInit("jitterBufferMinimumDelay", &jitter_buffer_minimum_delay),
+ AttributeInit("jitterBufferEmittedCount", &jitter_buffer_emitted_count),
+ AttributeInit("totalSamplesReceived", &total_samples_received),
+ AttributeInit("concealedSamples", &concealed_samples),
+ AttributeInit("silentConcealedSamples", &silent_concealed_samples),
+ AttributeInit("concealmentEvents", &concealment_events),
+ AttributeInit("insertedSamplesForDeceleration",
+ &inserted_samples_for_deceleration),
+ AttributeInit("removedSamplesForAcceleration",
+ &removed_samples_for_acceleration),
+ AttributeInit("audioLevel", &audio_level),
+ AttributeInit("totalAudioEnergy", &total_audio_energy),
+ AttributeInit("totalSamplesDuration", &total_samples_duration),
+ AttributeInit("framesReceived", &frames_received),
+ AttributeInit("frameWidth", &frame_width),
+ AttributeInit("frameHeight", &frame_height),
+ AttributeInit("framesPerSecond", &frames_per_second),
+ AttributeInit("framesDecoded", &frames_decoded),
+ AttributeInit("keyFramesDecoded", &key_frames_decoded),
+ AttributeInit("framesDropped", &frames_dropped),
+ AttributeInit("totalDecodeTime", &total_decode_time),
+ AttributeInit("totalProcessingDelay", &total_processing_delay),
+ AttributeInit("totalAssemblyTime", &total_assembly_time),
+ AttributeInit("framesAssembledFromMultiplePackets",
+ &frames_assembled_from_multiple_packets),
+ AttributeInit("totalInterFrameDelay", &total_inter_frame_delay),
+ AttributeInit("totalSquaredInterFrameDelay",
+ &total_squared_inter_frame_delay),
+ AttributeInit("pauseCount", &pause_count),
+ AttributeInit("totalPausesDuration", &total_pauses_duration),
+ AttributeInit("freezeCount", &freeze_count),
+ AttributeInit("totalFreezesDuration", &total_freezes_duration),
+ AttributeInit("contentType", &content_type),
+ AttributeInit("estimatedPlayoutTimestamp", &estimated_playout_timestamp),
+ AttributeInit("decoderImplementation", &decoder_implementation),
+ AttributeInit("firCount", &fir_count),
+ AttributeInit("pliCount", &pli_count),
+ AttributeInit("nackCount", &nack_count),
+ AttributeInit("qpSum", &qp_sum),
+ AttributeInit("googTimingFrameInfo", &goog_timing_frame_info),
+ AttributeInit("powerEfficientDecoder", &power_efficient_decoder),
+ AttributeInit("jitterBufferFlushes", &jitter_buffer_flushes),
+ AttributeInit("delayedPacketOutageSamples", &delayed_packet_outage_samples),
+ AttributeInit("relativePacketArrivalDelay", &relative_packet_arrival_delay),
+ AttributeInit("interruptionCount", &interruption_count),
+ AttributeInit("totalInterruptionDuration", &total_interruption_duration),
+ AttributeInit("minPlayoutDelay", &min_playout_delay))
// clang-format on
RTCInboundRtpStreamStats::RTCInboundRtpStreamStats(std::string id,
Timestamp timestamp)
- : RTCReceivedRtpStreamStats(std::move(id), timestamp),
- playout_id("playoutId"),
- track_identifier("trackIdentifier"),
- mid("mid"),
- remote_id("remoteId"),
- packets_received("packetsReceived"),
- packets_discarded("packetsDiscarded"),
- fec_packets_received("fecPacketsReceived"),
- fec_bytes_received("fecBytesReceived"),
- fec_packets_discarded("fecPacketsDiscarded"),
- fec_ssrc("fecSsrc"),
- bytes_received("bytesReceived"),
- header_bytes_received("headerBytesReceived"),
- retransmitted_packets_received("retransmittedPacketsReceived"),
- retransmitted_bytes_received("retransmittedBytesReceived"),
- rtx_ssrc("rtxSsrc"),
- last_packet_received_timestamp("lastPacketReceivedTimestamp"),
- jitter_buffer_delay("jitterBufferDelay"),
- jitter_buffer_target_delay("jitterBufferTargetDelay"),
- jitter_buffer_minimum_delay("jitterBufferMinimumDelay"),
- jitter_buffer_emitted_count("jitterBufferEmittedCount"),
- total_samples_received("totalSamplesReceived"),
- concealed_samples("concealedSamples"),
- silent_concealed_samples("silentConcealedSamples"),
- concealment_events("concealmentEvents"),
- inserted_samples_for_deceleration("insertedSamplesForDeceleration"),
- removed_samples_for_acceleration("removedSamplesForAcceleration"),
- audio_level("audioLevel"),
- total_audio_energy("totalAudioEnergy"),
- total_samples_duration("totalSamplesDuration"),
- frames_received("framesReceived"),
- frame_width("frameWidth"),
- frame_height("frameHeight"),
- frames_per_second("framesPerSecond"),
- frames_decoded("framesDecoded"),
- key_frames_decoded("keyFramesDecoded"),
- frames_dropped("framesDropped"),
- total_decode_time("totalDecodeTime"),
- total_processing_delay("totalProcessingDelay"),
- total_assembly_time("totalAssemblyTime"),
- frames_assembled_from_multiple_packets(
- "framesAssembledFromMultiplePackets"),
- total_inter_frame_delay("totalInterFrameDelay"),
- total_squared_inter_frame_delay("totalSquaredInterFrameDelay"),
- pause_count("pauseCount"),
- total_pauses_duration("totalPausesDuration"),
- freeze_count("freezeCount"),
- total_freezes_duration("totalFreezesDuration"),
- content_type("contentType"),
- estimated_playout_timestamp("estimatedPlayoutTimestamp"),
- decoder_implementation("decoderImplementation"),
- fir_count("firCount"),
- pli_count("pliCount"),
- nack_count("nackCount"),
- qp_sum("qpSum"),
- goog_timing_frame_info("googTimingFrameInfo"),
- power_efficient_decoder("powerEfficientDecoder"),
- jitter_buffer_flushes("jitterBufferFlushes"),
- delayed_packet_outage_samples("delayedPacketOutageSamples"),
- relative_packet_arrival_delay("relativePacketArrivalDelay"),
- interruption_count("interruptionCount"),
- total_interruption_duration("totalInterruptionDuration"),
- min_playout_delay("minPlayoutDelay") {}
-
-RTCInboundRtpStreamStats::RTCInboundRtpStreamStats(
- const RTCInboundRtpStreamStats& other) = default;
+ : RTCReceivedRtpStreamStats(std::move(id), timestamp) {}
+
RTCInboundRtpStreamStats::~RTCInboundRtpStreamStats() {}
// clang-format off
WEBRTC_RTCSTATS_IMPL(
RTCOutboundRtpStreamStats, RTCSentRtpStreamStats, "outbound-rtp",
- &media_source_id,
- &remote_id,
- &mid,
- &rid,
- &retransmitted_packets_sent,
- &header_bytes_sent,
- &retransmitted_bytes_sent,
- &target_bitrate,
- &frames_encoded,
- &key_frames_encoded,
- &total_encode_time,
- &total_encoded_bytes_target,
- &frame_width,
- &frame_height,
- &frames_per_second,
- &frames_sent,
- &huge_frames_sent,
- &total_packet_send_delay,
- &quality_limitation_reason,
- &quality_limitation_durations,
- &quality_limitation_resolution_changes,
- &content_type,
- &encoder_implementation,
- &fir_count,
- &pli_count,
- &nack_count,
- &qp_sum,
- &active,
- &power_efficient_encoder,
- &scalability_mode,
- &rtx_ssrc)
+ AttributeInit("mediaSourceId", &media_source_id),
+ AttributeInit("remoteId", &remote_id),
+ AttributeInit("mid", &mid),
+ AttributeInit("rid", &rid),
+ AttributeInit("retransmittedPacketsSent", &retransmitted_packets_sent),
+ AttributeInit("headerBytesSent", &header_bytes_sent),
+ AttributeInit("retransmittedBytesSent", &retransmitted_bytes_sent),
+ AttributeInit("targetBitrate", &target_bitrate),
+ AttributeInit("framesEncoded", &frames_encoded),
+ AttributeInit("keyFramesEncoded", &key_frames_encoded),
+ AttributeInit("totalEncodeTime", &total_encode_time),
+ AttributeInit("totalEncodedBytesTarget", &total_encoded_bytes_target),
+ AttributeInit("frameWidth", &frame_width),
+ AttributeInit("frameHeight", &frame_height),
+ AttributeInit("framesPerSecond", &frames_per_second),
+ AttributeInit("framesSent", &frames_sent),
+ AttributeInit("hugeFramesSent", &huge_frames_sent),
+ AttributeInit("totalPacketSendDelay", &total_packet_send_delay),
+ AttributeInit("qualityLimitationReason", &quality_limitation_reason),
+ AttributeInit("qualityLimitationDurations", &quality_limitation_durations),
+ AttributeInit("qualityLimitationResolutionChanges",
+ &quality_limitation_resolution_changes),
+ AttributeInit("contentType", &content_type),
+ AttributeInit("encoderImplementation", &encoder_implementation),
+ AttributeInit("firCount", &fir_count),
+ AttributeInit("pliCount", &pli_count),
+ AttributeInit("nackCount", &nack_count),
+ AttributeInit("qpSum", &qp_sum),
+ AttributeInit("active", &active),
+ AttributeInit("powerEfficientEncoder", &power_efficient_encoder),
+ AttributeInit("scalabilityMode", &scalability_mode),
+ AttributeInit("rtxSsrc", &rtx_ssrc))
// clang-format on
RTCOutboundRtpStreamStats::RTCOutboundRtpStreamStats(std::string id,
Timestamp timestamp)
- : RTCSentRtpStreamStats(std::move(id), timestamp),
- media_source_id("mediaSourceId"),
- remote_id("remoteId"),
- mid("mid"),
- rid("rid"),
- retransmitted_packets_sent("retransmittedPacketsSent"),
- header_bytes_sent("headerBytesSent"),
- retransmitted_bytes_sent("retransmittedBytesSent"),
- target_bitrate("targetBitrate"),
- frames_encoded("framesEncoded"),
- key_frames_encoded("keyFramesEncoded"),
- total_encode_time("totalEncodeTime"),
- total_encoded_bytes_target("totalEncodedBytesTarget"),
- frame_width("frameWidth"),
- frame_height("frameHeight"),
- frames_per_second("framesPerSecond"),
- frames_sent("framesSent"),
- huge_frames_sent("hugeFramesSent"),
- total_packet_send_delay("totalPacketSendDelay"),
- quality_limitation_reason("qualityLimitationReason"),
- quality_limitation_durations("qualityLimitationDurations"),
- quality_limitation_resolution_changes(
- "qualityLimitationResolutionChanges"),
- content_type("contentType"),
- encoder_implementation("encoderImplementation"),
- fir_count("firCount"),
- pli_count("pliCount"),
- nack_count("nackCount"),
- qp_sum("qpSum"),
- active("active"),
- power_efficient_encoder("powerEfficientEncoder"),
- scalability_mode("scalabilityMode"),
- rtx_ssrc("rtxSsrc") {}
-
-RTCOutboundRtpStreamStats::RTCOutboundRtpStreamStats(
- const RTCOutboundRtpStreamStats& other) = default;
+ : RTCSentRtpStreamStats(std::move(id), timestamp) {}
RTCOutboundRtpStreamStats::~RTCOutboundRtpStreamStats() {}
@@ -515,25 +330,17 @@ RTCOutboundRtpStreamStats::~RTCOutboundRtpStreamStats() {}
WEBRTC_RTCSTATS_IMPL(
RTCRemoteInboundRtpStreamStats, RTCReceivedRtpStreamStats,
"remote-inbound-rtp",
- &local_id,
- &round_trip_time,
- &fraction_lost,
- &total_round_trip_time,
- &round_trip_time_measurements)
+ AttributeInit("localId", &local_id),
+ AttributeInit("roundTripTime", &round_trip_time),
+ AttributeInit("fractionLost", &fraction_lost),
+ AttributeInit("totalRoundTripTime", &total_round_trip_time),
+ AttributeInit("roundTripTimeMeasurements", &round_trip_time_measurements))
// clang-format on
RTCRemoteInboundRtpStreamStats::RTCRemoteInboundRtpStreamStats(
std::string id,
Timestamp timestamp)
- : RTCReceivedRtpStreamStats(std::move(id), timestamp),
- local_id("localId"),
- round_trip_time("roundTripTime"),
- fraction_lost("fractionLost"),
- total_round_trip_time("totalRoundTripTime"),
- round_trip_time_measurements("roundTripTimeMeasurements") {}
-
-RTCRemoteInboundRtpStreamStats::RTCRemoteInboundRtpStreamStats(
- const RTCRemoteInboundRtpStreamStats& other) = default;
+ : RTCReceivedRtpStreamStats(std::move(id), timestamp) {}
RTCRemoteInboundRtpStreamStats::~RTCRemoteInboundRtpStreamStats() {}
@@ -541,156 +348,100 @@ RTCRemoteInboundRtpStreamStats::~RTCRemoteInboundRtpStreamStats() {}
WEBRTC_RTCSTATS_IMPL(
RTCRemoteOutboundRtpStreamStats, RTCSentRtpStreamStats,
"remote-outbound-rtp",
- &local_id,
- &remote_timestamp,
- &reports_sent,
- &round_trip_time,
- &round_trip_time_measurements,
- &total_round_trip_time)
+ AttributeInit("localId", &local_id),
+ AttributeInit("remoteTimestamp", &remote_timestamp),
+ AttributeInit("reportsSent", &reports_sent),
+ AttributeInit("roundTripTime", &round_trip_time),
+ AttributeInit("roundTripTimeMeasurements", &round_trip_time_measurements),
+ AttributeInit("totalRoundTripTime", &total_round_trip_time))
// clang-format on
RTCRemoteOutboundRtpStreamStats::RTCRemoteOutboundRtpStreamStats(
std::string id,
Timestamp timestamp)
- : RTCSentRtpStreamStats(std::move(id), timestamp),
- local_id("localId"),
- remote_timestamp("remoteTimestamp"),
- reports_sent("reportsSent"),
- round_trip_time("roundTripTime"),
- round_trip_time_measurements("roundTripTimeMeasurements"),
- total_round_trip_time("totalRoundTripTime") {}
-
-RTCRemoteOutboundRtpStreamStats::RTCRemoteOutboundRtpStreamStats(
- const RTCRemoteOutboundRtpStreamStats& other) = default;
+ : RTCSentRtpStreamStats(std::move(id), timestamp) {}
RTCRemoteOutboundRtpStreamStats::~RTCRemoteOutboundRtpStreamStats() {}
// clang-format off
WEBRTC_RTCSTATS_IMPL(RTCMediaSourceStats, RTCStats, "parent-media-source",
- &track_identifier,
- &kind)
+ AttributeInit("trackIdentifier", &track_identifier),
+ AttributeInit("kind", &kind))
// clang-format on
RTCMediaSourceStats::RTCMediaSourceStats(std::string id, Timestamp timestamp)
- : RTCStats(std::move(id), timestamp),
- track_identifier("trackIdentifier"),
- kind("kind") {}
-
-RTCMediaSourceStats::RTCMediaSourceStats(const RTCMediaSourceStats& other) =
- default;
+ : RTCStats(std::move(id), timestamp) {}
RTCMediaSourceStats::~RTCMediaSourceStats() {}
// clang-format off
WEBRTC_RTCSTATS_IMPL(RTCAudioSourceStats, RTCMediaSourceStats, "media-source",
- &audio_level,
- &total_audio_energy,
- &total_samples_duration,
- &echo_return_loss,
- &echo_return_loss_enhancement)
+ AttributeInit("audioLevel", &audio_level),
+ AttributeInit("totalAudioEnergy", &total_audio_energy),
+ AttributeInit("totalSamplesDuration", &total_samples_duration),
+ AttributeInit("echoReturnLoss", &echo_return_loss),
+ AttributeInit("echoReturnLossEnhancement", &echo_return_loss_enhancement))
// clang-format on
RTCAudioSourceStats::RTCAudioSourceStats(std::string id, Timestamp timestamp)
- : RTCMediaSourceStats(std::move(id), timestamp),
- audio_level("audioLevel"),
- total_audio_energy("totalAudioEnergy"),
- total_samples_duration("totalSamplesDuration"),
- echo_return_loss("echoReturnLoss"),
- echo_return_loss_enhancement("echoReturnLossEnhancement") {}
-
-RTCAudioSourceStats::RTCAudioSourceStats(const RTCAudioSourceStats& other) =
- default;
+ : RTCMediaSourceStats(std::move(id), timestamp) {}
RTCAudioSourceStats::~RTCAudioSourceStats() {}
// clang-format off
WEBRTC_RTCSTATS_IMPL(RTCVideoSourceStats, RTCMediaSourceStats, "media-source",
- &width,
- &height,
- &frames,
- &frames_per_second)
+ AttributeInit("width", &width),
+ AttributeInit("height", &height),
+ AttributeInit("frames", &frames),
+ AttributeInit("framesPerSecond", &frames_per_second))
// clang-format on
RTCVideoSourceStats::RTCVideoSourceStats(std::string id, Timestamp timestamp)
- : RTCMediaSourceStats(std::move(id), timestamp),
- width("width"),
- height("height"),
- frames("frames"),
- frames_per_second("framesPerSecond") {}
-
-RTCVideoSourceStats::RTCVideoSourceStats(const RTCVideoSourceStats& other) =
- default;
+ : RTCMediaSourceStats(std::move(id), timestamp) {}
RTCVideoSourceStats::~RTCVideoSourceStats() {}
// clang-format off
WEBRTC_RTCSTATS_IMPL(RTCTransportStats, RTCStats, "transport",
- &bytes_sent,
- &packets_sent,
- &bytes_received,
- &packets_received,
- &rtcp_transport_stats_id,
- &dtls_state,
- &selected_candidate_pair_id,
- &local_certificate_id,
- &remote_certificate_id,
- &tls_version,
- &dtls_cipher,
- &dtls_role,
- &srtp_cipher,
- &selected_candidate_pair_changes,
- &ice_role,
- &ice_local_username_fragment,
- &ice_state)
+ AttributeInit("bytesSent", &bytes_sent),
+ AttributeInit("packetsSent", &packets_sent),
+ AttributeInit("bytesReceived", &bytes_received),
+ AttributeInit("packetsReceived", &packets_received),
+ AttributeInit("rtcpTransportStatsId", &rtcp_transport_stats_id),
+ AttributeInit("dtlsState", &dtls_state),
+ AttributeInit("selectedCandidatePairId", &selected_candidate_pair_id),
+ AttributeInit("localCertificateId", &local_certificate_id),
+ AttributeInit("remoteCertificateId", &remote_certificate_id),
+ AttributeInit("tlsVersion", &tls_version),
+ AttributeInit("dtlsCipher", &dtls_cipher),
+ AttributeInit("dtlsRole", &dtls_role),
+ AttributeInit("srtpCipher", &srtp_cipher),
+ AttributeInit("selectedCandidatePairChanges",
+ &selected_candidate_pair_changes),
+ AttributeInit("iceRole", &ice_role),
+ AttributeInit("iceLocalUsernameFragment", &ice_local_username_fragment),
+ AttributeInit("iceState", &ice_state))
// clang-format on
RTCTransportStats::RTCTransportStats(std::string id, Timestamp timestamp)
- : RTCStats(std::move(id), timestamp),
- bytes_sent("bytesSent"),
- packets_sent("packetsSent"),
- bytes_received("bytesReceived"),
- packets_received("packetsReceived"),
- rtcp_transport_stats_id("rtcpTransportStatsId"),
- dtls_state("dtlsState"),
- selected_candidate_pair_id("selectedCandidatePairId"),
- local_certificate_id("localCertificateId"),
- remote_certificate_id("remoteCertificateId"),
- tls_version("tlsVersion"),
- dtls_cipher("dtlsCipher"),
- dtls_role("dtlsRole"),
- srtp_cipher("srtpCipher"),
- selected_candidate_pair_changes("selectedCandidatePairChanges"),
- ice_role("iceRole"),
- ice_local_username_fragment("iceLocalUsernameFragment"),
- ice_state("iceState") {}
-
-RTCTransportStats::RTCTransportStats(const RTCTransportStats& other) = default;
+ : RTCStats(std::move(id), timestamp) {}
RTCTransportStats::~RTCTransportStats() {}
+// clang-format off
+WEBRTC_RTCSTATS_IMPL(RTCAudioPlayoutStats, RTCStats, "media-playout",
+ AttributeInit("kind", &kind),
+ AttributeInit("synthesizedSamplesDuration", &synthesized_samples_duration),
+ AttributeInit("synthesizedSamplesEvents", &synthesized_samples_events),
+ AttributeInit("totalSamplesDuration", &total_samples_duration),
+ AttributeInit("totalPlayoutDelay", &total_playout_delay),
+ AttributeInit("totalSamplesCount", &total_samples_count))
+// clang-format on
+
RTCAudioPlayoutStats::RTCAudioPlayoutStats(const std::string& id,
Timestamp timestamp)
- : RTCStats(std::move(id), timestamp),
- kind("kind", "audio"),
- synthesized_samples_duration("synthesizedSamplesDuration"),
- synthesized_samples_events("synthesizedSamplesEvents"),
- total_samples_duration("totalSamplesDuration"),
- total_playout_delay("totalPlayoutDelay"),
- total_samples_count("totalSamplesCount") {}
-
-RTCAudioPlayoutStats::RTCAudioPlayoutStats(const RTCAudioPlayoutStats& other) =
- default;
+ : RTCStats(std::move(id), timestamp), kind("audio") {}
RTCAudioPlayoutStats::~RTCAudioPlayoutStats() {}
-// clang-format off
-WEBRTC_RTCSTATS_IMPL(RTCAudioPlayoutStats, RTCStats, "media-playout",
- &kind,
- &synthesized_samples_duration,
- &synthesized_samples_events,
- &total_samples_duration,
- &total_playout_delay,
- &total_samples_count)
-// clang-format on
-
} // namespace webrtc
diff --git a/third_party/libwebrtc/stats/test/rtc_test_stats.cc b/third_party/libwebrtc/stats/test/rtc_test_stats.cc
index a83fa24178..834daeef72 100644
--- a/third_party/libwebrtc/stats/test/rtc_test_stats.cc
+++ b/third_party/libwebrtc/stats/test/rtc_test_stats.cc
@@ -10,6 +10,7 @@
#include "stats/test/rtc_test_stats.h"
+#include "api/stats/attribute.h"
#include "rtc_base/checks.h"
namespace webrtc {
@@ -17,60 +18,25 @@ namespace webrtc {
WEBRTC_RTCSTATS_IMPL(RTCTestStats,
RTCStats,
"test-stats",
- &m_bool,
- &m_int32,
- &m_uint32,
- &m_int64,
- &m_uint64,
- &m_double,
- &m_string,
- &m_sequence_bool,
- &m_sequence_int32,
- &m_sequence_uint32,
- &m_sequence_int64,
- &m_sequence_uint64,
- &m_sequence_double,
- &m_sequence_string,
- &m_map_string_uint64,
- &m_map_string_double)
+ AttributeInit("mBool", &m_bool),
+ AttributeInit("mInt32", &m_int32),
+ AttributeInit("mUint32", &m_uint32),
+ AttributeInit("mInt64", &m_int64),
+ AttributeInit("mUint64", &m_uint64),
+ AttributeInit("mDouble", &m_double),
+ AttributeInit("mString", &m_string),
+ AttributeInit("mSequenceBool", &m_sequence_bool),
+ AttributeInit("mSequenceInt32", &m_sequence_int32),
+ AttributeInit("mSequenceUint32", &m_sequence_uint32),
+ AttributeInit("mSequenceInt64", &m_sequence_int64),
+ AttributeInit("mSequenceUint64", &m_sequence_uint64),
+ AttributeInit("mSequenceDouble", &m_sequence_double),
+ AttributeInit("mSequenceString", &m_sequence_string),
+ AttributeInit("mMapStringUint64", &m_map_string_uint64),
+ AttributeInit("mMapStringDouble", &m_map_string_double))
RTCTestStats::RTCTestStats(const std::string& id, Timestamp timestamp)
- : RTCStats(id, timestamp),
- m_bool("mBool"),
- m_int32("mInt32"),
- m_uint32("mUint32"),
- m_int64("mInt64"),
- m_uint64("mUint64"),
- m_double("mDouble"),
- m_string("mString"),
- m_sequence_bool("mSequenceBool"),
- m_sequence_int32("mSequenceInt32"),
- m_sequence_uint32("mSequenceUint32"),
- m_sequence_int64("mSequenceInt64"),
- m_sequence_uint64("mSequenceUint64"),
- m_sequence_double("mSequenceDouble"),
- m_sequence_string("mSequenceString"),
- m_map_string_uint64("mMapStringUint64"),
- m_map_string_double("mMapStringDouble") {}
-
-RTCTestStats::RTCTestStats(const RTCTestStats& other)
- : RTCStats(other.id(), other.timestamp()),
- m_bool(other.m_bool),
- m_int32(other.m_int32),
- m_uint32(other.m_uint32),
- m_int64(other.m_int64),
- m_uint64(other.m_uint64),
- m_double(other.m_double),
- m_string(other.m_string),
- m_sequence_bool(other.m_sequence_bool),
- m_sequence_int32(other.m_sequence_int32),
- m_sequence_uint32(other.m_sequence_uint32),
- m_sequence_int64(other.m_sequence_int64),
- m_sequence_uint64(other.m_sequence_uint64),
- m_sequence_double(other.m_sequence_double),
- m_sequence_string(other.m_sequence_string),
- m_map_string_uint64(other.m_map_string_uint64),
- m_map_string_double(other.m_map_string_double) {}
+ : RTCStats(id, timestamp) {}
RTCTestStats::~RTCTestStats() {}
diff --git a/third_party/libwebrtc/stats/test/rtc_test_stats.h b/third_party/libwebrtc/stats/test/rtc_test_stats.h
index 0247c0cc01..05c0904c02 100644
--- a/third_party/libwebrtc/stats/test/rtc_test_stats.h
+++ b/third_party/libwebrtc/stats/test/rtc_test_stats.h
@@ -24,9 +24,7 @@ namespace webrtc {
class RTC_EXPORT RTCTestStats : public RTCStats {
public:
WEBRTC_RTCSTATS_DECL();
-
RTCTestStats(const std::string& id, Timestamp timestamp);
- RTCTestStats(const RTCTestStats& other);
~RTCTestStats() override;
RTCStatsMember<bool> m_bool;
diff --git a/third_party/libwebrtc/test/BUILD.gn b/third_party/libwebrtc/test/BUILD.gn
index 854530c01e..75d8d9f3a8 100644
--- a/third_party/libwebrtc/test/BUILD.gn
+++ b/third_party/libwebrtc/test/BUILD.gn
@@ -167,7 +167,6 @@ rtc_library("frame_generator_capturer") {
"../rtc_base:checks",
"../rtc_base:logging",
"../rtc_base:macromagic",
- "../rtc_base:rtc_task_queue",
"../rtc_base/synchronization:mutex",
"../rtc_base/task_utils:repeating_task",
"../system_wrappers",
@@ -215,7 +214,6 @@ rtc_library("video_test_common") {
"../rtc_base:criticalsection",
"../rtc_base:logging",
"../rtc_base:refcount",
- "../rtc_base:rtc_task_queue",
"../rtc_base:timeutils",
"../rtc_base/synchronization:mutex",
"../rtc_base/task_utils:repeating_task",
@@ -737,13 +735,17 @@ if (rtc_include_tests) {
"../api:mock_video_encoder",
"../api:scoped_refptr",
"../api:simulcast_test_fixture_api",
+ "../api/task_queue",
"../api/task_queue:task_queue_test",
"../api/test/video:function_video_factory",
"../api/test/video:video_frame_writer",
"../api/units:data_rate",
+ "../api/units:data_size",
+ "../api/units:frequency",
"../api/units:time_delta",
"../api/video:encoded_image",
"../api/video:video_frame",
+ "../api/video_codecs:scalability_mode",
"../api/video_codecs:video_codecs_api",
"../call:video_stream_api",
"../common_video",
@@ -760,7 +762,6 @@ if (rtc_include_tests) {
"../modules/video_coding/svc:scalability_mode_util",
"../rtc_base:criticalsection",
"../rtc_base:rtc_event",
- "../rtc_base:rtc_task_queue",
"../rtc_base/synchronization:mutex",
"../rtc_base/system:file_wrapper",
"jitter:jitter_unittests",
@@ -1022,7 +1023,6 @@ rtc_library("fake_video_codecs") {
"../rtc_base:checks",
"../rtc_base:criticalsection",
"../rtc_base:macromagic",
- "../rtc_base:rtc_task_queue",
"../rtc_base:timeutils",
"../rtc_base/synchronization:mutex",
"../system_wrappers",
@@ -1395,6 +1395,7 @@ rtc_library("video_codec_tester") {
"video_codec_tester.h",
]
deps = [
+ ":scoped_key_value_config",
"../api:array_view",
"../api/numerics:numerics",
"../api/test/metrics:metric",
@@ -1413,6 +1414,7 @@ rtc_library("video_codec_tester") {
"../media:media_constants",
"../modules/video_coding:video_codec_interface",
"../modules/video_coding:video_coding_utility",
+ "../modules/video_coding:webrtc_h264",
"../modules/video_coding:webrtc_vp9_helpers",
"../modules/video_coding/codecs/av1:av1_svc_config",
"../modules/video_coding/svc:scalability_mode_util",
@@ -1426,6 +1428,7 @@ rtc_library("video_codec_tester") {
"../system_wrappers",
"../test:fileutils",
"../test:video_test_support",
+ "../video/config:streams_config",
"//third_party/libyuv",
]
diff --git a/third_party/libwebrtc/test/OWNERS b/third_party/libwebrtc/test/OWNERS
index a1bd812244..f747873741 100644
--- a/third_party/libwebrtc/test/OWNERS
+++ b/third_party/libwebrtc/test/OWNERS
@@ -2,6 +2,5 @@ sprang@webrtc.org
srte@webrtc.org
stefan@webrtc.org
titovartem@webrtc.org
-landrey@webrtc.org
mbonadei@webrtc.org
jleconte@webrtc.org
diff --git a/third_party/libwebrtc/test/call_test.cc b/third_party/libwebrtc/test/call_test.cc
index 09099cccd6..6cdd8da133 100644
--- a/third_party/libwebrtc/test/call_test.cc
+++ b/third_party/libwebrtc/test/call_test.cc
@@ -572,7 +572,7 @@ void CallTest::CreateVideoSendStreams() {
if (fec_controller_factory_.get()) {
video_send_streams_[i] = sender_call_->CreateVideoSendStream(
video_send_configs_[i].Copy(), video_encoder_configs_[i].Copy(),
- fec_controller_factory_->CreateFecController());
+ fec_controller_factory_->CreateFecController(send_env_));
} else {
video_send_streams_[i] = sender_call_->CreateVideoSendStream(
video_send_configs_[i].Copy(), video_encoder_configs_[i].Copy());
diff --git a/third_party/libwebrtc/test/fake_decoder.cc b/third_party/libwebrtc/test/fake_decoder.cc
index 01d95bfeb4..12bff8d36c 100644
--- a/third_party/libwebrtc/test/fake_decoder.cc
+++ b/third_party/libwebrtc/test/fake_decoder.cc
@@ -15,13 +15,13 @@
#include <memory>
#include "api/scoped_refptr.h"
+#include "api/task_queue/task_queue_factory.h"
#include "api/video/i420_buffer.h"
#include "api/video/video_frame.h"
#include "api/video/video_frame_buffer.h"
#include "api/video/video_rotation.h"
#include "modules/video_coding/include/video_error_codes.h"
#include "rtc_base/checks.h"
-#include "rtc_base/task_queue.h"
#include "rtc_base/time_utils.h"
namespace webrtc {
diff --git a/third_party/libwebrtc/test/frame_generator_capturer.cc b/third_party/libwebrtc/test/frame_generator_capturer.cc
index 6ba0807a74..7cdfec2cc2 100644
--- a/third_party/libwebrtc/test/frame_generator_capturer.cc
+++ b/third_party/libwebrtc/test/frame_generator_capturer.cc
@@ -29,7 +29,6 @@
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/synchronization/mutex.h"
-#include "rtc_base/task_queue.h"
#include "rtc_base/task_utils/repeating_task.h"
#include "system_wrappers/include/clock.h"
#include "test/test_video_capturer.h"
@@ -58,6 +57,9 @@ FrameGeneratorCapturer::FrameGeneratorCapturer(
FrameGeneratorCapturer::~FrameGeneratorCapturer() {
Stop();
+ // Deconstruct first as tasks in the TaskQueue access other fields of the
+ // instance of this class.
+ task_queue_ = nullptr;
}
void FrameGeneratorCapturer::SetFakeRotation(VideoRotation rotation) {
@@ -78,7 +80,7 @@ bool FrameGeneratorCapturer::Init() {
return false;
frame_task_ = RepeatingTaskHandle::DelayedStart(
- task_queue_.Get(),
+ task_queue_.get(),
TimeDelta::Seconds(1) / GetCurrentConfiguredFramerate(),
[this] {
InsertFrame();
@@ -131,7 +133,7 @@ void FrameGeneratorCapturer::Start() {
}
if (!frame_task_.Running()) {
frame_task_ = RepeatingTaskHandle::Start(
- task_queue_.Get(),
+ task_queue_.get(),
[this] {
InsertFrame();
return TimeDelta::Seconds(1) / GetCurrentConfiguredFramerate();
@@ -219,7 +221,7 @@ void FrameGeneratorCapturer::UpdateFps(int max_fps) {
void FrameGeneratorCapturer::ForceFrame() {
// One-time non-repeating task,
- task_queue_.PostTask([this] { InsertFrame(); });
+ task_queue_->PostTask([this] { InsertFrame(); });
}
int FrameGeneratorCapturer::GetCurrentConfiguredFramerate() {
diff --git a/third_party/libwebrtc/test/frame_generator_capturer.h b/third_party/libwebrtc/test/frame_generator_capturer.h
index 6824ba681e..bb0c445c53 100644
--- a/third_party/libwebrtc/test/frame_generator_capturer.h
+++ b/third_party/libwebrtc/test/frame_generator_capturer.h
@@ -15,6 +15,7 @@
#include <memory>
#include "absl/types/optional.h"
+#include "api/task_queue/task_queue_base.h"
#include "api/task_queue/task_queue_factory.h"
#include "api/test/frame_generator_interface.h"
#include "api/video/color_space.h"
@@ -23,7 +24,6 @@
#include "api/video/video_sink_interface.h"
#include "api/video/video_source_interface.h"
#include "rtc_base/synchronization/mutex.h"
-#include "rtc_base/task_queue.h"
#include "rtc_base/task_utils/repeating_task.h"
#include "rtc_base/thread_annotations.h"
#include "system_wrappers/include/clock.h"
@@ -106,9 +106,7 @@ class FrameGeneratorCapturer : public TestVideoCapturer {
int64_t first_frame_capture_time_;
- // Must be the last field, so it will be deconstructed first as tasks
- // in the TaskQueue access other fields of the instance of this class.
- rtc::TaskQueue task_queue_;
+ std::unique_ptr<TaskQueueBase, TaskQueueDeleter> task_queue_;
};
} // namespace test
} // namespace webrtc
diff --git a/third_party/libwebrtc/test/fuzzers/BUILD.gn b/third_party/libwebrtc/test/fuzzers/BUILD.gn
index 43e9a5e922..083c20c6f4 100644
--- a/third_party/libwebrtc/test/fuzzers/BUILD.gn
+++ b/third_party/libwebrtc/test/fuzzers/BUILD.gn
@@ -238,6 +238,36 @@ webrtc_fuzzer_test("rtp_packetizer_av1_fuzzer") {
]
}
+webrtc_fuzzer_test("rtp_format_h264_fuzzer") {
+ sources = [ "rtp_format_h264_fuzzer.cc" ]
+ deps = [
+ "../../api/video:video_frame_type",
+ "../../modules/rtp_rtcp:rtp_rtcp",
+ "../../modules/rtp_rtcp:rtp_rtcp_format",
+ "../../rtc_base:checks",
+ ]
+}
+
+webrtc_fuzzer_test("rtp_format_vp8_fuzzer") {
+ sources = [ "rtp_format_vp8_fuzzer.cc" ]
+ deps = [
+ "../../api/video:video_frame_type",
+ "../../modules/rtp_rtcp:rtp_rtcp",
+ "../../modules/rtp_rtcp:rtp_rtcp_format",
+ "../../rtc_base:checks",
+ ]
+}
+
+webrtc_fuzzer_test("rtp_format_vp9_fuzzer") {
+ sources = [ "rtp_format_vp9_fuzzer.cc" ]
+ deps = [
+ "../../api/video:video_frame_type",
+ "../../modules/rtp_rtcp:rtp_rtcp",
+ "../../modules/rtp_rtcp:rtp_rtcp_format",
+ "../../rtc_base:checks",
+ ]
+}
+
webrtc_fuzzer_test("receive_side_congestion_controller_fuzzer") {
sources = [ "receive_side_congestion_controller_fuzzer.cc" ]
deps = [
@@ -248,6 +278,7 @@ webrtc_fuzzer_test("receive_side_congestion_controller_fuzzer") {
"../../modules/rtp_rtcp:rtp_rtcp_format",
"../../system_wrappers",
]
+ seed_corpus = "corpora/receive-side-cc"
}
rtc_library("audio_decoder_fuzzer") {
@@ -469,6 +500,7 @@ webrtc_fuzzer_test("audio_processing_fuzzer") {
"../../api:scoped_refptr",
"../../api/audio:aec3_factory",
"../../api/audio:echo_detector_creator",
+ "../../api/task_queue",
"../../api/task_queue:default_task_queue_factory",
"../../modules/audio_processing",
"../../modules/audio_processing:api",
@@ -478,11 +510,13 @@ webrtc_fuzzer_test("audio_processing_fuzzer") {
"../../modules/audio_processing/aec_dump",
"../../modules/audio_processing/aec_dump:aec_dump_impl",
"../../rtc_base:macromagic",
- "../../rtc_base:rtc_task_queue",
"../../rtc_base:safe_minmax",
"../../system_wrappers:field_trial",
]
- absl_deps = [ "//third_party/abseil-cpp/absl/memory" ]
+ absl_deps = [
+ "//third_party/abseil-cpp/absl/base:nullability",
+ "//third_party/abseil-cpp/absl/memory",
+ ]
seed_corpus = "corpora/audio_processing-corpus"
}
diff --git a/third_party/libwebrtc/test/fuzzers/audio_processing_configs_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/audio_processing_configs_fuzzer.cc
index 331a373f4e..93bce2f2e7 100644
--- a/third_party/libwebrtc/test/fuzzers/audio_processing_configs_fuzzer.cc
+++ b/third_party/libwebrtc/test/fuzzers/audio_processing_configs_fuzzer.cc
@@ -11,16 +11,17 @@
#include <bitset>
#include <string>
+#include "absl/base/nullability.h"
#include "absl/memory/memory.h"
#include "api/audio/echo_canceller3_factory.h"
#include "api/audio/echo_detector_creator.h"
#include "api/task_queue/default_task_queue_factory.h"
+#include "api/task_queue/task_queue_base.h"
#include "modules/audio_processing/aec_dump/aec_dump_factory.h"
#include "modules/audio_processing/include/audio_processing.h"
#include "modules/audio_processing/test/audio_processing_builder_for_testing.h"
#include "rtc_base/arraysize.h"
#include "rtc_base/numerics/safe_minmax.h"
-#include "rtc_base/task_queue.h"
#include "system_wrappers/include/field_trial.h"
#include "test/fuzzers/audio_processing_fuzzer_helper.h"
#include "test/fuzzers/fuzz_data_helper.h"
@@ -33,9 +34,10 @@ const std::string kFieldTrialNames[] = {
"WebRTC-Aec3ShortHeadroomKillSwitch",
};
-rtc::scoped_refptr<AudioProcessing> CreateApm(test::FuzzDataHelper* fuzz_data,
- std::string* field_trial_string,
- rtc::TaskQueue* worker_queue) {
+rtc::scoped_refptr<AudioProcessing> CreateApm(
+ test::FuzzDataHelper* fuzz_data,
+ std::string* field_trial_string,
+ absl::Nonnull<TaskQueueBase*> worker_queue) {
// Parse boolean values for optionally enabling different
// configurable public components of APM.
bool use_ts = fuzz_data->ReadOrDefaultValue(true);
@@ -134,9 +136,10 @@ void FuzzOneInput(const uint8_t* data, size_t size) {
// for field_trial.h. Hence it's created here and not in CreateApm.
std::string field_trial_string = "";
- rtc::TaskQueue worker_queue(GetTaskQueueFactory()->CreateTaskQueue(
- "rtc-low-prio", rtc::TaskQueue::Priority::LOW));
- auto apm = CreateApm(&fuzz_data, &field_trial_string, &worker_queue);
+ std::unique_ptr<TaskQueueBase, TaskQueueDeleter> worker_queue =
+ GetTaskQueueFactory()->CreateTaskQueue("rtc-low-prio",
+ TaskQueueFactory::Priority::LOW);
+ auto apm = CreateApm(&fuzz_data, &field_trial_string, worker_queue.get());
if (apm) {
FuzzAudioProcessing(&fuzz_data, std::move(apm));
diff --git a/third_party/libwebrtc/test/fuzzers/corpora/receive-side-cc/testcase-5414098152390656 b/third_party/libwebrtc/test/fuzzers/corpora/receive-side-cc/testcase-5414098152390656
new file mode 100644
index 0000000000..98c423cdc2
--- /dev/null
+++ b/third_party/libwebrtc/test/fuzzers/corpora/receive-side-cc/testcase-5414098152390656
Binary files differ
diff --git a/third_party/libwebrtc/test/fuzzers/rtp_format_h264_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/rtp_format_h264_fuzzer.cc
new file mode 100644
index 0000000000..ddf2ca9d3d
--- /dev/null
+++ b/third_party/libwebrtc/test/fuzzers/rtp_format_h264_fuzzer.cc
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2024 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+#include <stddef.h>
+#include <stdint.h>
+
+#include "api/video/video_frame_type.h"
+#include "modules/rtp_rtcp/source/rtp_format.h"
+#include "modules/rtp_rtcp/source/rtp_format_h264.h"
+#include "modules/rtp_rtcp/source/rtp_packet_to_send.h"
+#include "rtc_base/checks.h"
+#include "test/fuzzers/fuzz_data_helper.h"
+
+namespace webrtc {
+void FuzzOneInput(const uint8_t* data, size_t size) {
+ test::FuzzDataHelper fuzz_input(rtc::MakeArrayView(data, size));
+
+ RtpPacketizer::PayloadSizeLimits limits;
+ limits.max_payload_len = 1200;
+ // Read uint8_t to be sure reduction_lens are much smaller than
+ // max_payload_len and thus limits structure is valid.
+ limits.first_packet_reduction_len = fuzz_input.ReadOrDefaultValue<uint8_t>(0);
+ limits.last_packet_reduction_len = fuzz_input.ReadOrDefaultValue<uint8_t>(0);
+ limits.single_packet_reduction_len =
+ fuzz_input.ReadOrDefaultValue<uint8_t>(0);
+ const H264PacketizationMode kPacketizationModes[] = {
+ H264PacketizationMode::NonInterleaved,
+ H264PacketizationMode::SingleNalUnit};
+
+ H264PacketizationMode packetization_mode =
+ fuzz_input.SelectOneOf(kPacketizationModes);
+
+ // Main function under test: RtpPacketizerH264's constructor.
+ RtpPacketizerH264 packetizer(fuzz_input.ReadByteArray(fuzz_input.BytesLeft()),
+ limits, packetization_mode);
+
+ size_t num_packets = packetizer.NumPackets();
+ if (num_packets == 0) {
+ return;
+ }
+ // When packetization was successful, validate NextPacket function too.
+ // While at it, check that packets respect the payload size limits.
+ RtpPacketToSend rtp_packet(nullptr);
+ // Single packet.
+ if (num_packets == 1) {
+ RTC_CHECK(packetizer.NextPacket(&rtp_packet));
+ RTC_CHECK_LE(rtp_packet.payload_size(),
+ limits.max_payload_len - limits.single_packet_reduction_len);
+ return;
+ }
+ // First packet.
+ RTC_CHECK(packetizer.NextPacket(&rtp_packet));
+ RTC_CHECK_LE(rtp_packet.payload_size(),
+ limits.max_payload_len - limits.first_packet_reduction_len);
+ // Middle packets.
+ for (size_t i = 1; i < num_packets - 1; ++i) {
+ rtp_packet.Clear();
+ RTC_CHECK(packetizer.NextPacket(&rtp_packet))
+ << "Failed to get packet#" << i;
+ RTC_CHECK_LE(rtp_packet.payload_size(), limits.max_payload_len)
+ << "Packet #" << i << " exceeds it's limit";
+ }
+ // Last packet.
+ rtp_packet.Clear();
+ RTC_CHECK(packetizer.NextPacket(&rtp_packet));
+ RTC_CHECK_LE(rtp_packet.payload_size(),
+ limits.max_payload_len - limits.last_packet_reduction_len);
+}
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/fuzzers/rtp_format_vp8_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/rtp_format_vp8_fuzzer.cc
new file mode 100644
index 0000000000..c3c055de0f
--- /dev/null
+++ b/third_party/libwebrtc/test/fuzzers/rtp_format_vp8_fuzzer.cc
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2024 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+#include <stddef.h>
+#include <stdint.h>
+
+#include "api/video/video_frame_type.h"
+#include "modules/rtp_rtcp/source/rtp_format.h"
+#include "modules/rtp_rtcp/source/rtp_format_vp8.h"
+#include "modules/rtp_rtcp/source/rtp_packet_to_send.h"
+#include "rtc_base/checks.h"
+#include "test/fuzzers/fuzz_data_helper.h"
+
+namespace webrtc {
+void FuzzOneInput(const uint8_t* data, size_t size) {
+ test::FuzzDataHelper fuzz_input(rtc::MakeArrayView(data, size));
+
+ RtpPacketizer::PayloadSizeLimits limits;
+ limits.max_payload_len = 1200;
+ // Read uint8_t to be sure reduction_lens are much smaller than
+ // max_payload_len and thus limits structure is valid.
+ limits.first_packet_reduction_len = fuzz_input.ReadOrDefaultValue<uint8_t>(0);
+ limits.last_packet_reduction_len = fuzz_input.ReadOrDefaultValue<uint8_t>(0);
+ limits.single_packet_reduction_len =
+ fuzz_input.ReadOrDefaultValue<uint8_t>(0);
+
+ RTPVideoHeaderVP8 hdr_info;
+ hdr_info.InitRTPVideoHeaderVP8();
+ uint16_t picture_id = fuzz_input.ReadOrDefaultValue<uint16_t>(0);
+ hdr_info.pictureId =
+ picture_id >= 0x8000 ? kNoPictureId : picture_id & 0x7fff;
+
+ // Main function under test: RtpPacketizerVp8's constructor.
+ RtpPacketizerVp8 packetizer(fuzz_input.ReadByteArray(fuzz_input.BytesLeft()),
+ limits, hdr_info);
+
+ size_t num_packets = packetizer.NumPackets();
+ if (num_packets == 0) {
+ return;
+ }
+ // When packetization was successful, validate NextPacket function too.
+ // While at it, check that packets respect the payload size limits.
+ RtpPacketToSend rtp_packet(nullptr);
+ // Single packet.
+ if (num_packets == 1) {
+ RTC_CHECK(packetizer.NextPacket(&rtp_packet));
+ RTC_CHECK_LE(rtp_packet.payload_size(),
+ limits.max_payload_len - limits.single_packet_reduction_len);
+ return;
+ }
+ // First packet.
+ RTC_CHECK(packetizer.NextPacket(&rtp_packet));
+ RTC_CHECK_LE(rtp_packet.payload_size(),
+ limits.max_payload_len - limits.first_packet_reduction_len);
+ // Middle packets.
+ for (size_t i = 1; i < num_packets - 1; ++i) {
+ RTC_CHECK(packetizer.NextPacket(&rtp_packet))
+ << "Failed to get packet#" << i;
+ RTC_CHECK_LE(rtp_packet.payload_size(), limits.max_payload_len)
+ << "Packet #" << i << " exceeds it's limit";
+ }
+ // Last packet.
+ RTC_CHECK(packetizer.NextPacket(&rtp_packet));
+ RTC_CHECK_LE(rtp_packet.payload_size(),
+ limits.max_payload_len - limits.last_packet_reduction_len);
+}
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/fuzzers/rtp_format_vp9_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/rtp_format_vp9_fuzzer.cc
new file mode 100644
index 0000000000..3b5e67f697
--- /dev/null
+++ b/third_party/libwebrtc/test/fuzzers/rtp_format_vp9_fuzzer.cc
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2024 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+#include <stddef.h>
+#include <stdint.h>
+
+#include "api/video/video_frame_type.h"
+#include "modules/rtp_rtcp/source/rtp_format.h"
+#include "modules/rtp_rtcp/source/rtp_format_vp9.h"
+#include "modules/rtp_rtcp/source/rtp_packet_to_send.h"
+#include "rtc_base/checks.h"
+#include "test/fuzzers/fuzz_data_helper.h"
+
+namespace webrtc {
+void FuzzOneInput(const uint8_t* data, size_t size) {
+ test::FuzzDataHelper fuzz_input(rtc::MakeArrayView(data, size));
+
+ RtpPacketizer::PayloadSizeLimits limits;
+ limits.max_payload_len = 1200;
+ // Read uint8_t to be sure reduction_lens are much smaller than
+ // max_payload_len and thus limits structure is valid.
+ limits.first_packet_reduction_len = fuzz_input.ReadOrDefaultValue<uint8_t>(0);
+ limits.last_packet_reduction_len = fuzz_input.ReadOrDefaultValue<uint8_t>(0);
+ limits.single_packet_reduction_len =
+ fuzz_input.ReadOrDefaultValue<uint8_t>(0);
+
+ RTPVideoHeaderVP9 hdr_info;
+ hdr_info.InitRTPVideoHeaderVP9();
+ uint16_t picture_id = fuzz_input.ReadOrDefaultValue<uint16_t>(0);
+ hdr_info.picture_id =
+ picture_id >= 0x8000 ? kNoPictureId : picture_id & 0x7fff;
+
+ // Main function under test: RtpPacketizerVp9's constructor.
+ RtpPacketizerVp9 packetizer(fuzz_input.ReadByteArray(fuzz_input.BytesLeft()),
+ limits, hdr_info);
+
+ size_t num_packets = packetizer.NumPackets();
+ if (num_packets == 0) {
+ return;
+ }
+ // When packetization was successful, validate NextPacket function too.
+ // While at it, check that packets respect the payload size limits.
+ RtpPacketToSend rtp_packet(nullptr);
+ // Single packet.
+ if (num_packets == 1) {
+ RTC_CHECK(packetizer.NextPacket(&rtp_packet));
+ RTC_CHECK_LE(rtp_packet.payload_size(),
+ limits.max_payload_len - limits.single_packet_reduction_len);
+ return;
+ }
+ // First packet.
+ RTC_CHECK(packetizer.NextPacket(&rtp_packet));
+ RTC_CHECK_LE(rtp_packet.payload_size(),
+ limits.max_payload_len - limits.first_packet_reduction_len);
+ // Middle packets.
+ for (size_t i = 1; i < num_packets - 1; ++i) {
+ RTC_CHECK(packetizer.NextPacket(&rtp_packet))
+ << "Failed to get packet#" << i;
+ RTC_CHECK_LE(rtp_packet.payload_size(), limits.max_payload_len)
+ << "Packet #" << i << " exceeds it's limit";
+ }
+ // Last packet.
+ RTC_CHECK(packetizer.NextPacket(&rtp_packet));
+ RTC_CHECK_LE(rtp_packet.payload_size(),
+ limits.max_payload_len - limits.last_packet_reduction_len);
+}
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/network/BUILD.gn b/third_party/libwebrtc/test/network/BUILD.gn
index b8255d35fd..6df563d31d 100644
--- a/third_party/libwebrtc/test/network/BUILD.gn
+++ b/third_party/libwebrtc/test/network/BUILD.gn
@@ -47,6 +47,7 @@ rtc_library("emulated_network") {
"../../api:simulated_network_api",
"../../api:time_controller",
"../../api/numerics",
+ "../../api/task_queue",
"../../api/task_queue:pending_task_safety_flag",
"../../api/test/network_emulation",
"../../api/transport:stun_types",
@@ -67,7 +68,6 @@ rtc_library("emulated_network") {
"../../rtc_base:random",
"../../rtc_base:rtc_base_tests_utils",
"../../rtc_base:rtc_event",
- "../../rtc_base:rtc_task_queue",
"../../rtc_base:safe_minmax",
"../../rtc_base:socket",
"../../rtc_base:socket_address",
@@ -87,6 +87,7 @@ rtc_library("emulated_network") {
]
absl_deps = [
"//third_party/abseil-cpp/absl/algorithm:container",
+ "//third_party/abseil-cpp/absl/base:nullability",
"//third_party/abseil-cpp/absl/memory",
"//third_party/abseil-cpp/absl/strings",
"//third_party/abseil-cpp/absl/types:optional",
@@ -117,7 +118,6 @@ if (rtc_include_tests && !build_with_chromium) {
deps = [
":emulated_network",
"../:test_support",
- "../../api:callfactory_api",
"../../api:enable_media_with_defaults",
"../../api:libjingle_peerconnection_api",
"../../api:scoped_refptr",
diff --git a/third_party/libwebrtc/test/network/cross_traffic_unittest.cc b/third_party/libwebrtc/test/network/cross_traffic_unittest.cc
index 36aff67bb2..0f98fc9e72 100644
--- a/third_party/libwebrtc/test/network/cross_traffic_unittest.cc
+++ b/third_party/libwebrtc/test/network/cross_traffic_unittest.cc
@@ -55,7 +55,7 @@ struct TrafficCounterFixture {
EmulatedEndpointConfig(),
EmulatedNetworkStatsGatheringMode::kDefault,
},
- /*is_enabled=*/true, &task_queue_, &clock};
+ /*is_enabled=*/true, task_queue_.Get(), &clock};
};
} // namespace
diff --git a/third_party/libwebrtc/test/network/network_emulation.cc b/third_party/libwebrtc/test/network/network_emulation.cc
index f1c9ca80dd..642bf6fc7a 100644
--- a/third_party/libwebrtc/test/network/network_emulation.cc
+++ b/third_party/libwebrtc/test/network/network_emulation.cc
@@ -15,9 +15,11 @@
#include <memory>
#include <utility>
+#include "absl/base/nullability.h"
#include "absl/types/optional.h"
#include "api/numerics/samples_stats_counter.h"
#include "api/sequence_checker.h"
+#include "api/task_queue/task_queue_base.h"
#include "api/test/network_emulation/network_emulation_interfaces.h"
#include "api/test/network_emulation_manager.h"
#include "api/units/data_size.h"
@@ -330,7 +332,7 @@ void LinkEmulation::OnPacketReceived(EmulatedIpPacket packet) {
return;
Timestamp current_time = clock_->CurrentTime();
process_task_ = RepeatingTaskHandle::DelayedStart(
- task_queue_->Get(),
+ task_queue_,
std::max(TimeDelta::Zero(),
Timestamp::Micros(*next_time_us) - current_time),
[this]() {
@@ -383,7 +385,7 @@ void LinkEmulation::Process(Timestamp at_time) {
}
}
-NetworkRouterNode::NetworkRouterNode(rtc::TaskQueue* task_queue)
+NetworkRouterNode::NetworkRouterNode(absl::Nonnull<TaskQueueBase*> task_queue)
: task_queue_(task_queue) {}
void NetworkRouterNode::OnPacketReceived(EmulatedIpPacket packet) {
@@ -459,7 +461,7 @@ void NetworkRouterNode::SetFilter(
EmulatedNetworkNode::EmulatedNetworkNode(
Clock* clock,
- rtc::TaskQueue* task_queue,
+ absl::Nonnull<TaskQueueBase*> task_queue,
std::unique_ptr<NetworkBehaviorInterface> network_behavior,
EmulatedNetworkStatsGatheringMode stats_gathering_mode)
: router_(task_queue),
@@ -510,10 +512,11 @@ EmulatedEndpointImpl::Options::Options(
config.allow_receive_packets_with_different_dest_ip),
log_name(ip.ToString() + " (" + config.name.value_or("") + ")") {}
-EmulatedEndpointImpl::EmulatedEndpointImpl(const Options& options,
- bool is_enabled,
- rtc::TaskQueue* task_queue,
- Clock* clock)
+EmulatedEndpointImpl::EmulatedEndpointImpl(
+ const Options& options,
+ bool is_enabled,
+ absl::Nonnull<TaskQueueBase*> task_queue,
+ Clock* clock)
: options_(options),
is_enabled_(is_enabled),
clock_(clock),
diff --git a/third_party/libwebrtc/test/network/network_emulation.h b/third_party/libwebrtc/test/network/network_emulation.h
index dffabafa7c..20705197be 100644
--- a/third_party/libwebrtc/test/network/network_emulation.h
+++ b/third_party/libwebrtc/test/network/network_emulation.h
@@ -19,10 +19,12 @@
#include <utility>
#include <vector>
+#include "absl/base/nullability.h"
#include "absl/types/optional.h"
#include "api/array_view.h"
#include "api/numerics/samples_stats_counter.h"
#include "api/sequence_checker.h"
+#include "api/task_queue/task_queue_base.h"
#include "api/test/network_emulation/network_emulation_interfaces.h"
#include "api/test/network_emulation_manager.h"
#include "api/test/simulated_network.h"
@@ -145,7 +147,7 @@ class EmulatedNetworkNodeStatsBuilder {
class LinkEmulation : public EmulatedNetworkReceiverInterface {
public:
LinkEmulation(Clock* clock,
- rtc::TaskQueue* task_queue,
+ absl::Nonnull<TaskQueueBase*> task_queue,
std::unique_ptr<NetworkBehaviorInterface> network_behavior,
EmulatedNetworkReceiverInterface* receiver,
EmulatedNetworkStatsGatheringMode stats_gathering_mode)
@@ -168,7 +170,7 @@ class LinkEmulation : public EmulatedNetworkReceiverInterface {
void Process(Timestamp at_time) RTC_RUN_ON(task_queue_);
Clock* const clock_;
- rtc::TaskQueue* const task_queue_;
+ const absl::Nonnull<TaskQueueBase*> task_queue_;
const std::unique_ptr<NetworkBehaviorInterface> network_behavior_
RTC_GUARDED_BY(task_queue_);
EmulatedNetworkReceiverInterface* const receiver_;
@@ -186,7 +188,7 @@ class LinkEmulation : public EmulatedNetworkReceiverInterface {
// the packet will be silently dropped.
class NetworkRouterNode : public EmulatedNetworkReceiverInterface {
public:
- explicit NetworkRouterNode(rtc::TaskQueue* task_queue);
+ explicit NetworkRouterNode(absl::Nonnull<TaskQueueBase*> task_queue);
void OnPacketReceived(EmulatedIpPacket packet) override;
void SetReceiver(const rtc::IPAddress& dest_ip,
@@ -200,7 +202,7 @@ class NetworkRouterNode : public EmulatedNetworkReceiverInterface {
void SetFilter(std::function<bool(const EmulatedIpPacket&)> filter);
private:
- rtc::TaskQueue* const task_queue_;
+ const absl::Nonnull<TaskQueueBase*> task_queue_;
absl::optional<EmulatedNetworkReceiverInterface*> default_receiver_
RTC_GUARDED_BY(task_queue_);
std::map<rtc::IPAddress, EmulatedNetworkReceiverInterface*> routing_
@@ -224,7 +226,7 @@ class EmulatedNetworkNode : public EmulatedNetworkReceiverInterface {
// they are ready.
EmulatedNetworkNode(
Clock* clock,
- rtc::TaskQueue* task_queue,
+ absl::Nonnull<TaskQueueBase*> task_queue,
std::unique_ptr<NetworkBehaviorInterface> network_behavior,
EmulatedNetworkStatsGatheringMode stats_gathering_mode);
~EmulatedNetworkNode() override;
@@ -283,7 +285,7 @@ class EmulatedEndpointImpl : public EmulatedEndpoint {
EmulatedEndpointImpl(const Options& options,
bool is_enabled,
- rtc::TaskQueue* task_queue,
+ absl::Nonnull<TaskQueueBase*> task_queue,
Clock* clock);
~EmulatedEndpointImpl() override;
@@ -341,7 +343,7 @@ class EmulatedEndpointImpl : public EmulatedEndpoint {
const Options options_;
bool is_enabled_ RTC_GUARDED_BY(enabled_state_checker_);
Clock* const clock_;
- rtc::TaskQueue* const task_queue_;
+ const absl::Nonnull<TaskQueueBase*> task_queue_;
std::unique_ptr<rtc::Network> network_;
NetworkRouterNode router_;
diff --git a/third_party/libwebrtc/test/network/network_emulation_manager.cc b/third_party/libwebrtc/test/network/network_emulation_manager.cc
index 97c0bc1ba8..dd0e93d8ee 100644
--- a/third_party/libwebrtc/test/network/network_emulation_manager.cc
+++ b/third_party/libwebrtc/test/network/network_emulation_manager.cc
@@ -13,6 +13,7 @@
#include <algorithm>
#include <memory>
+#include "api/field_trials_view.h"
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
#include "call/simulated_network.h"
@@ -30,10 +31,12 @@ constexpr uint32_t kMinIPv4Address = 0xC0A80000;
// uint32_t representation of 192.168.255.255 address
constexpr uint32_t kMaxIPv4Address = 0xC0A8FFFF;
-std::unique_ptr<TimeController> CreateTimeController(TimeMode mode) {
+std::unique_ptr<TimeController> CreateTimeController(
+ TimeMode mode,
+ const FieldTrialsView* field_trials) {
switch (mode) {
case TimeMode::kRealTime:
- return std::make_unique<RealTimeController>();
+ return std::make_unique<RealTimeController>(field_trials);
case TimeMode::kSimulated:
// Using an offset of 100000 to get nice fixed width and readable
// timestamps in typical test scenarios.
@@ -46,10 +49,11 @@ std::unique_ptr<TimeController> CreateTimeController(TimeMode mode) {
NetworkEmulationManagerImpl::NetworkEmulationManagerImpl(
TimeMode mode,
- EmulatedNetworkStatsGatheringMode stats_gathering_mode)
+ EmulatedNetworkStatsGatheringMode stats_gathering_mode,
+ const FieldTrialsView* field_trials)
: time_mode_(mode),
stats_gathering_mode_(stats_gathering_mode),
- time_controller_(CreateTimeController(mode)),
+ time_controller_(CreateTimeController(mode, field_trials)),
clock_(time_controller_->GetClock()),
next_node_id_(1),
next_ip4_address_(kMinIPv4Address),
@@ -75,8 +79,9 @@ EmulatedNetworkNode* NetworkEmulationManagerImpl::CreateEmulatedNode(
EmulatedNetworkNode* NetworkEmulationManagerImpl::CreateEmulatedNode(
std::unique_ptr<NetworkBehaviorInterface> network_behavior) {
- auto node = std::make_unique<EmulatedNetworkNode>(
- clock_, &task_queue_, std::move(network_behavior), stats_gathering_mode_);
+ auto node = std::make_unique<EmulatedNetworkNode>(clock_, task_queue_.Get(),
+ std::move(network_behavior),
+ stats_gathering_mode_);
EmulatedNetworkNode* out = node.get();
task_queue_.PostTask([this, node = std::move(node)]() mutable {
network_nodes_.push_back(std::move(node));
@@ -111,7 +116,7 @@ EmulatedEndpointImpl* NetworkEmulationManagerImpl::CreateEndpoint(
auto node = std::make_unique<EmulatedEndpointImpl>(
EmulatedEndpointImpl::Options(next_node_id_++, *ip, config,
stats_gathering_mode_),
- config.start_as_enabled, &task_queue_, clock_);
+ config.start_as_enabled, task_queue_.Get(), clock_);
EmulatedEndpointImpl* out = node.get();
endpoints_.push_back(std::move(node));
return out;
diff --git a/third_party/libwebrtc/test/network/network_emulation_manager.h b/third_party/libwebrtc/test/network/network_emulation_manager.h
index 29debca693..4b0a76494f 100644
--- a/third_party/libwebrtc/test/network/network_emulation_manager.h
+++ b/third_party/libwebrtc/test/network/network_emulation_manager.h
@@ -18,6 +18,7 @@
#include <vector>
#include "api/array_view.h"
+#include "api/field_trials_view.h"
#include "api/test/network_emulation_manager.h"
#include "api/test/simulated_network.h"
#include "api/test/time_controller.h"
@@ -38,7 +39,8 @@ class NetworkEmulationManagerImpl : public NetworkEmulationManager {
public:
NetworkEmulationManagerImpl(
TimeMode mode,
- EmulatedNetworkStatsGatheringMode stats_gathering_mode);
+ EmulatedNetworkStatsGatheringMode stats_gathering_mode,
+ const FieldTrialsView* field_trials = nullptr);
~NetworkEmulationManagerImpl();
EmulatedNetworkNode* CreateEmulatedNode(BuiltInNetworkBehaviorConfig config,
diff --git a/third_party/libwebrtc/test/network/network_emulation_pc_unittest.cc b/third_party/libwebrtc/test/network/network_emulation_pc_unittest.cc
index 09d3946747..73ac54e7ef 100644
--- a/third_party/libwebrtc/test/network/network_emulation_pc_unittest.cc
+++ b/third_party/libwebrtc/test/network/network_emulation_pc_unittest.cc
@@ -56,8 +56,7 @@ rtc::scoped_refptr<PeerConnectionFactoryInterface> CreatePeerConnectionFactory(
rtc::Thread* network_thread) {
PeerConnectionFactoryDependencies pcf_deps;
pcf_deps.task_queue_factory = CreateDefaultTaskQueueFactory();
- pcf_deps.event_log_factory =
- std::make_unique<RtcEventLogFactory>(pcf_deps.task_queue_factory.get());
+ pcf_deps.event_log_factory = std::make_unique<RtcEventLogFactory>();
pcf_deps.network_thread = network_thread;
pcf_deps.signaling_thread = signaling_thread;
pcf_deps.trials = std::make_unique<FieldTrialBasedConfig>();
diff --git a/third_party/libwebrtc/test/pc/e2e/BUILD.gn b/third_party/libwebrtc/test/pc/e2e/BUILD.gn
index 9d1c1d437f..0eb7aa2c68 100644
--- a/third_party/libwebrtc/test/pc/e2e/BUILD.gn
+++ b/third_party/libwebrtc/test/pc/e2e/BUILD.gn
@@ -99,6 +99,7 @@ if (!build_with_chromium) {
"../../../api:enable_media_with_defaults",
"../../../api:time_controller",
"../../../api/rtc_event_log:rtc_event_log_factory",
+ "../../../api/task_queue",
"../../../api/task_queue:default_task_queue_factory",
"../../../api/test/pclf:media_configuration",
"../../../api/test/pclf:media_quality_test_params",
@@ -109,7 +110,6 @@ if (!build_with_chromium) {
"../../../modules/audio_device:test_audio_device_module",
"../../../modules/audio_processing/aec_dump",
"../../../p2p:rtc_p2p",
- "../../../rtc_base:rtc_task_queue",
"../../../rtc_base:threading",
"analyzer/video:quality_analyzing_video_encoder",
"analyzer/video:video_quality_analyzer_injection_helper",
@@ -286,7 +286,6 @@ if (!build_with_chromium) {
":default_audio_quality_analyzer",
":network_quality_metrics_reporter",
":stats_based_network_quality_metrics_reporter",
- "../../../api:callfactory_api",
"../../../api:create_network_emulation_manager",
"../../../api:create_peer_connection_quality_test_frame_generator",
"../../../api:create_peerconnection_quality_test_fixture",
diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/audio/default_audio_quality_analyzer.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/audio/default_audio_quality_analyzer.cc
index bca52d9bfc..93d8906d6a 100644
--- a/third_party/libwebrtc/test/pc/e2e/analyzer/audio/default_audio_quality_analyzer.cc
+++ b/third_party/libwebrtc/test/pc/e2e/analyzer/audio/default_audio_quality_analyzer.cc
@@ -42,29 +42,27 @@ void DefaultAudioQualityAnalyzer::OnStatsReports(
auto stats = report->GetStatsOfType<RTCInboundRtpStreamStats>();
for (auto& stat : stats) {
- if (!stat->kind.is_defined() || !(*stat->kind == "audio")) {
+ if (!stat->kind.has_value() || !(*stat->kind == "audio")) {
continue;
}
StatsSample sample;
- sample.total_samples_received =
- stat->total_samples_received.ValueOrDefault(0ul);
- sample.concealed_samples = stat->concealed_samples.ValueOrDefault(0ul);
+ sample.total_samples_received = stat->total_samples_received.value_or(0ul);
+ sample.concealed_samples = stat->concealed_samples.value_or(0ul);
sample.removed_samples_for_acceleration =
- stat->removed_samples_for_acceleration.ValueOrDefault(0ul);
+ stat->removed_samples_for_acceleration.value_or(0ul);
sample.inserted_samples_for_deceleration =
- stat->inserted_samples_for_deceleration.ValueOrDefault(0ul);
+ stat->inserted_samples_for_deceleration.value_or(0ul);
sample.silent_concealed_samples =
- stat->silent_concealed_samples.ValueOrDefault(0ul);
+ stat->silent_concealed_samples.value_or(0ul);
sample.jitter_buffer_delay =
- TimeDelta::Seconds(stat->jitter_buffer_delay.ValueOrDefault(0.));
+ TimeDelta::Seconds(stat->jitter_buffer_delay.value_or(0.));
sample.jitter_buffer_target_delay =
- TimeDelta::Seconds(stat->jitter_buffer_target_delay.ValueOrDefault(0.));
+ TimeDelta::Seconds(stat->jitter_buffer_target_delay.value_or(0.));
sample.jitter_buffer_emitted_count =
- stat->jitter_buffer_emitted_count.ValueOrDefault(0ul);
- sample.total_samples_duration =
- stat->total_samples_duration.ValueOrDefault(0.);
- sample.total_audio_energy = stat->total_audio_energy.ValueOrDefault(0.);
+ stat->jitter_buffer_emitted_count.value_or(0ul);
+ sample.total_samples_duration = stat->total_samples_duration.value_or(0.);
+ sample.total_audio_energy = stat->total_audio_energy.value_or(0.);
TrackIdStreamInfoMap::StreamInfo stream_info =
analyzer_helper_->GetStreamInfoFromTrackId(*stat->track_identifier);
diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_quality_metrics_reporter.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_quality_metrics_reporter.cc
index 817b3caad0..87f1590cb0 100644
--- a/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_quality_metrics_reporter.cc
+++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_quality_metrics_reporter.cc
@@ -58,12 +58,14 @@ void VideoQualityMetricsReporter::OnStatsReports(
auto transport_stats = report->GetStatsOfType<RTCTransportStats>();
if (transport_stats.size() == 0u ||
- !transport_stats[0]->selected_candidate_pair_id.is_defined()) {
+ !transport_stats[0]->selected_candidate_pair_id.has_value()) {
return;
}
RTC_DCHECK_EQ(transport_stats.size(), 1);
std::string selected_ice_id =
- transport_stats[0]->selected_candidate_pair_id.ValueToString();
+ transport_stats[0]
+ ->GetAttribute(transport_stats[0]->selected_candidate_pair_id)
+ .ToString();
// Use the selected ICE candidate pair ID to get the appropriate ICE stats.
const RTCIceCandidatePairStats ice_candidate_pair_stats =
report->Get(selected_ice_id)->cast_to<const RTCIceCandidatePairStats>();
@@ -71,7 +73,7 @@ void VideoQualityMetricsReporter::OnStatsReports(
auto outbound_rtp_stats = report->GetStatsOfType<RTCOutboundRtpStreamStats>();
StatsSample sample;
for (auto& s : outbound_rtp_stats) {
- if (!s->kind.is_defined()) {
+ if (!s->kind.has_value()) {
continue;
}
if (!(*s->kind == "video")) {
@@ -81,15 +83,15 @@ void VideoQualityMetricsReporter::OnStatsReports(
sample.sample_time = s->timestamp();
}
sample.retransmitted_bytes_sent +=
- DataSize::Bytes(s->retransmitted_bytes_sent.ValueOrDefault(0ul));
- sample.bytes_sent += DataSize::Bytes(s->bytes_sent.ValueOrDefault(0ul));
+ DataSize::Bytes(s->retransmitted_bytes_sent.value_or(0ul));
+ sample.bytes_sent += DataSize::Bytes(s->bytes_sent.value_or(0ul));
sample.header_bytes_sent +=
- DataSize::Bytes(s->header_bytes_sent.ValueOrDefault(0ul));
+ DataSize::Bytes(s->header_bytes_sent.value_or(0ul));
}
MutexLock lock(&video_bwe_stats_lock_);
VideoBweStats& video_bwe_stats = video_bwe_stats_[std::string(pc_label)];
- if (ice_candidate_pair_stats.available_outgoing_bitrate.is_defined()) {
+ if (ice_candidate_pair_stats.available_outgoing_bitrate.has_value()) {
video_bwe_stats.available_send_bandwidth.AddSample(
DataRate::BitsPerSec(
*ice_candidate_pair_stats.available_outgoing_bitrate)
diff --git a/third_party/libwebrtc/test/pc/e2e/cross_media_metrics_reporter.cc b/third_party/libwebrtc/test/pc/e2e/cross_media_metrics_reporter.cc
index aad5946c9f..c1536018d2 100644
--- a/third_party/libwebrtc/test/pc/e2e/cross_media_metrics_reporter.cc
+++ b/third_party/libwebrtc/test/pc/e2e/cross_media_metrics_reporter.cc
@@ -47,8 +47,8 @@ void CrossMediaMetricsReporter::OnStatsReports(
std::map<std::string, std::vector<const RTCInboundRtpStreamStats*>>
sync_group_stats;
for (const auto& stat : inbound_stats) {
- if (stat->estimated_playout_timestamp.ValueOrDefault(0.) > 0 &&
- stat->track_identifier.is_defined()) {
+ if (stat->estimated_playout_timestamp.value_or(0.) > 0 &&
+ stat->track_identifier.has_value()) {
sync_group_stats[reporter_helper_
->GetStreamInfoFromTrackId(*stat->track_identifier)
.sync_group]
@@ -66,8 +66,8 @@ void CrossMediaMetricsReporter::OnStatsReports(
const RTCInboundRtpStreamStats* audio_stat = pair.second[0];
const RTCInboundRtpStreamStats* video_stat = pair.second[1];
- RTC_CHECK(pair.second.size() == 2 && audio_stat->kind.is_defined() &&
- video_stat->kind.is_defined() &&
+ RTC_CHECK(pair.second.size() == 2 && audio_stat->kind.has_value() &&
+ video_stat->kind.has_value() &&
*audio_stat->kind != *video_stat->kind)
<< "Sync group should consist of one audio and one video stream.";
@@ -77,8 +77,8 @@ void CrossMediaMetricsReporter::OnStatsReports(
// Stream labels of a sync group are same for all polls, so we need it add
// it only once.
if (stats_info_.find(sync_group) == stats_info_.end()) {
- RTC_CHECK(audio_stat->track_identifier.is_defined());
- RTC_CHECK(video_stat->track_identifier.is_defined());
+ RTC_CHECK(audio_stat->track_identifier.has_value());
+ RTC_CHECK(video_stat->track_identifier.has_value());
stats_info_[sync_group].audio_stream_info =
reporter_helper_->GetStreamInfoFromTrackId(
*audio_stat->track_identifier);
diff --git a/third_party/libwebrtc/test/pc/e2e/network_quality_metrics_reporter.cc b/third_party/libwebrtc/test/pc/e2e/network_quality_metrics_reporter.cc
index 2d6aa597ce..257fecf309 100644
--- a/third_party/libwebrtc/test/pc/e2e/network_quality_metrics_reporter.cc
+++ b/third_party/libwebrtc/test/pc/e2e/network_quality_metrics_reporter.cc
@@ -79,15 +79,14 @@ void NetworkQualityMetricsReporter::OnStatsReports(
auto inbound_stats = report->GetStatsOfType<RTCInboundRtpStreamStats>();
for (const auto& stat : inbound_stats) {
payload_received +=
- DataSize::Bytes(stat->bytes_received.ValueOrDefault(0ul) +
- stat->header_bytes_received.ValueOrDefault(0ul));
+ DataSize::Bytes(stat->bytes_received.value_or(0ul) +
+ stat->header_bytes_received.value_or(0ul));
}
auto outbound_stats = report->GetStatsOfType<RTCOutboundRtpStreamStats>();
for (const auto& stat : outbound_stats) {
- payload_sent +=
- DataSize::Bytes(stat->bytes_sent.ValueOrDefault(0ul) +
- stat->header_bytes_sent.ValueOrDefault(0ul));
+ payload_sent += DataSize::Bytes(stat->bytes_sent.value_or(0ul) +
+ stat->header_bytes_sent.value_or(0ul));
}
MutexLock lock(&lock_);
diff --git a/third_party/libwebrtc/test/pc/e2e/peer_connection_quality_test.cc b/third_party/libwebrtc/test/pc/e2e/peer_connection_quality_test.cc
index 5eb47b4682..90f201facd 100644
--- a/third_party/libwebrtc/test/pc/e2e/peer_connection_quality_test.cc
+++ b/third_party/libwebrtc/test/pc/e2e/peer_connection_quality_test.cc
@@ -277,7 +277,7 @@ void PeerConnectionE2EQualityTest::Run(RunParams run_params) {
TestPeerFactory test_peer_factory(
signaling_thread.get(), time_controller_,
- video_quality_analyzer_injection_helper_.get(), task_queue_.get());
+ video_quality_analyzer_injection_helper_.get(), task_queue_->Get());
alice_ = test_peer_factory.CreateTestPeer(
std::move(alice_configurer),
std::make_unique<FixturePeerConnectionObserver>(
diff --git a/third_party/libwebrtc/test/pc/e2e/stats_based_network_quality_metrics_reporter.cc b/third_party/libwebrtc/test/pc/e2e/stats_based_network_quality_metrics_reporter.cc
index eb5f29287e..b965a7acd8 100644
--- a/third_party/libwebrtc/test/pc/e2e/stats_based_network_quality_metrics_reporter.cc
+++ b/third_party/libwebrtc/test/pc/e2e/stats_based_network_quality_metrics_reporter.cc
@@ -299,25 +299,23 @@ void StatsBasedNetworkQualityMetricsReporter::OnStatsReports(
auto inbound_stats = report->GetStatsOfType<RTCInboundRtpStreamStats>();
for (const auto& stat : inbound_stats) {
cur_stats.payload_received +=
- DataSize::Bytes(stat->bytes_received.ValueOrDefault(0ul) +
- stat->header_bytes_received.ValueOrDefault(0ul));
+ DataSize::Bytes(stat->bytes_received.value_or(0ul) +
+ stat->header_bytes_received.value_or(0ul));
}
auto outbound_stats = report->GetStatsOfType<RTCOutboundRtpStreamStats>();
for (const auto& stat : outbound_stats) {
- cur_stats.payload_sent +=
- DataSize::Bytes(stat->bytes_sent.ValueOrDefault(0ul) +
- stat->header_bytes_sent.ValueOrDefault(0ul));
+ cur_stats.payload_sent += DataSize::Bytes(
+ stat->bytes_sent.value_or(0ul) + stat->header_bytes_sent.value_or(0ul));
}
auto candidate_pairs_stats = report->GetStatsOfType<RTCTransportStats>();
for (const auto& stat : candidate_pairs_stats) {
cur_stats.total_received +=
- DataSize::Bytes(stat->bytes_received.ValueOrDefault(0ul));
- cur_stats.total_sent +=
- DataSize::Bytes(stat->bytes_sent.ValueOrDefault(0ul));
- cur_stats.packets_received += stat->packets_received.ValueOrDefault(0ul);
- cur_stats.packets_sent += stat->packets_sent.ValueOrDefault(0ul);
+ DataSize::Bytes(stat->bytes_received.value_or(0ul));
+ cur_stats.total_sent += DataSize::Bytes(stat->bytes_sent.value_or(0ul));
+ cur_stats.packets_received += stat->packets_received.value_or(0ul);
+ cur_stats.packets_sent += stat->packets_sent.value_or(0ul);
}
MutexLock lock(&mutex_);
diff --git a/third_party/libwebrtc/test/pc/e2e/test_peer_factory.cc b/third_party/libwebrtc/test/pc/e2e/test_peer_factory.cc
index dd900027ee..a184c5db3c 100644
--- a/third_party/libwebrtc/test/pc/e2e/test_peer_factory.cc
+++ b/third_party/libwebrtc/test/pc/e2e/test_peer_factory.cc
@@ -43,16 +43,14 @@ constexpr int kDefaultSamplingFrequencyInHz = 48000;
// and `pc_dependencies` if they are omitted. Also setup required
// dependencies, that won't be specially provided by factory and will be just
// transferred to peer connection creation code.
-void SetMandatoryEntities(InjectableComponents* components,
- TimeController& time_controller) {
+void SetMandatoryEntities(InjectableComponents* components) {
RTC_DCHECK(components->pcf_dependencies);
RTC_DCHECK(components->pc_dependencies);
// Setup required peer connection factory dependencies.
if (components->pcf_dependencies->event_log_factory == nullptr) {
components->pcf_dependencies->event_log_factory =
- std::make_unique<RtcEventLogFactory>(
- time_controller.GetTaskQueueFactory());
+ std::make_unique<RtcEventLogFactory>();
}
if (!components->pcf_dependencies->trials) {
components->pcf_dependencies->trials =
@@ -286,7 +284,7 @@ std::unique_ptr<TestPeer> TestPeerFactory::CreateTestPeer(
RTC_DCHECK(configurable_params);
RTC_DCHECK_EQ(configurable_params->video_configs.size(),
video_sources.size());
- SetMandatoryEntities(components.get(), time_controller_);
+ SetMandatoryEntities(components.get());
params->rtc_configuration.sdp_semantics = SdpSemantics::kUnifiedPlan;
// Create peer connection factory.
@@ -329,6 +327,7 @@ std::unique_ptr<TestPeer> TestPeerFactory::CreateTestPeer(
components->worker_thread, components->network_thread);
rtc::scoped_refptr<PeerConnectionFactoryInterface> peer_connection_factory =
CreateModularPeerConnectionFactory(std::move(pcf_deps));
+ peer_connection_factory->SetOptions(params->peer_connection_factory_options);
// Create peer connection.
PeerConnectionDependencies pc_deps =
diff --git a/third_party/libwebrtc/test/pc/e2e/test_peer_factory.h b/third_party/libwebrtc/test/pc/e2e/test_peer_factory.h
index f2698e2a15..cc61b04ae1 100644
--- a/third_party/libwebrtc/test/pc/e2e/test_peer_factory.h
+++ b/third_party/libwebrtc/test/pc/e2e/test_peer_factory.h
@@ -18,12 +18,12 @@
#include "absl/strings/string_view.h"
#include "api/rtc_event_log/rtc_event_log_factory.h"
+#include "api/task_queue/task_queue_base.h"
#include "api/test/pclf/media_configuration.h"
#include "api/test/pclf/media_quality_test_params.h"
#include "api/test/pclf/peer_configurer.h"
#include "api/test/time_controller.h"
#include "modules/audio_device/include/test_audio_device.h"
-#include "rtc_base/task_queue.h"
#include "test/pc/e2e/analyzer/video/video_quality_analyzer_injection_helper.h"
#include "test/pc/e2e/test_peer.h"
@@ -55,7 +55,7 @@ class TestPeerFactory {
TestPeerFactory(rtc::Thread* signaling_thread,
TimeController& time_controller,
VideoQualityAnalyzerInjectionHelper* video_analyzer_helper,
- rtc::TaskQueue* task_queue)
+ TaskQueueBase* task_queue)
: signaling_thread_(signaling_thread),
time_controller_(time_controller),
video_analyzer_helper_(video_analyzer_helper),
@@ -75,7 +75,7 @@ class TestPeerFactory {
rtc::Thread* signaling_thread_;
TimeController& time_controller_;
VideoQualityAnalyzerInjectionHelper* video_analyzer_helper_;
- rtc::TaskQueue* task_queue_;
+ TaskQueueBase* const task_queue_;
};
} // namespace webrtc_pc_e2e
diff --git a/third_party/libwebrtc/test/peer_scenario/peer_scenario_client.cc b/third_party/libwebrtc/test/peer_scenario/peer_scenario_client.cc
index 60f2ea7f2e..1397b32fe3 100644
--- a/third_party/libwebrtc/test/peer_scenario/peer_scenario_client.cc
+++ b/third_party/libwebrtc/test/peer_scenario/peer_scenario_client.cc
@@ -248,8 +248,7 @@ PeerScenarioClient::PeerScenarioClient(
pcf_deps.worker_thread = worker_thread_.get();
pcf_deps.task_queue_factory =
net->time_controller()->CreateTaskQueueFactory();
- pcf_deps.event_log_factory =
- std::make_unique<RtcEventLogFactory>(task_queue_factory_);
+ pcf_deps.event_log_factory = std::make_unique<RtcEventLogFactory>();
pcf_deps.trials = std::make_unique<FieldTrialBasedConfig>();
pcf_deps.adm = TestAudioDeviceModule::Create(
diff --git a/third_party/libwebrtc/test/run_loop_unittest.cc b/third_party/libwebrtc/test/run_loop_unittest.cc
index 80f0bcbdcc..e6c747ac4f 100644
--- a/third_party/libwebrtc/test/run_loop_unittest.cc
+++ b/third_party/libwebrtc/test/run_loop_unittest.cc
@@ -10,8 +10,8 @@
#include "test/run_loop.h"
+#include "api/task_queue/task_queue_base.h"
#include "api/units/time_delta.h"
-#include "rtc_base/task_queue.h"
#include "test/gtest.h"
namespace webrtc {
diff --git a/third_party/libwebrtc/test/scenario/audio_stream.cc b/third_party/libwebrtc/test/scenario/audio_stream.cc
index 5f7db7acdf..232f7382bd 100644
--- a/third_party/libwebrtc/test/scenario/audio_stream.cc
+++ b/third_party/libwebrtc/test/scenario/audio_stream.cc
@@ -89,7 +89,7 @@ SendAudioStream::SendAudioStream(
AudioSendStream::Config send_config(send_transport);
ssrc_ = sender->GetNextAudioSsrc();
send_config.rtp.ssrc = ssrc_;
- SdpAudioFormat::Parameters sdp_params;
+ CodecParameterMap sdp_params;
if (config.source.channels == 2)
sdp_params["stereo"] = "1";
if (config.encoder.initial_frame_length != TimeDelta::Millis(20))
diff --git a/third_party/libwebrtc/test/scenario/video_stream.cc b/third_party/libwebrtc/test/scenario/video_stream.cc
index eb20f8dbc7..654aed7c6c 100644
--- a/third_party/libwebrtc/test/scenario/video_stream.cc
+++ b/third_party/libwebrtc/test/scenario/video_stream.cc
@@ -430,7 +430,8 @@ SendVideoStream::SendVideoStream(CallClient* sender,
if (config.stream.fec_controller_factory) {
send_stream_ = sender_->call_->CreateVideoSendStream(
std::move(send_config), std::move(encoder_config),
- config.stream.fec_controller_factory->CreateFecController());
+ config.stream.fec_controller_factory->CreateFecController(
+ sender_->env_));
} else {
send_stream_ = sender_->call_->CreateVideoSendStream(
std::move(send_config), std::move(encoder_config));
diff --git a/third_party/libwebrtc/test/time_controller/BUILD.gn b/third_party/libwebrtc/test/time_controller/BUILD.gn
index b4b368a42a..6686528345 100644
--- a/third_party/libwebrtc/test/time_controller/BUILD.gn
+++ b/third_party/libwebrtc/test/time_controller/BUILD.gn
@@ -24,6 +24,7 @@ rtc_library("time_controller") {
]
deps = [
+ "../../api:field_trials_view",
"../../api:sequence_checker",
"../../api:time_controller",
"../../api/task_queue",
@@ -57,10 +58,10 @@ if (rtc_include_tests) {
":time_controller",
"../:test_support",
"../../api:time_controller",
+ "../../api/task_queue",
"../../api/units:time_delta",
"../../rtc_base:macromagic",
"../../rtc_base:rtc_event",
- "../../rtc_base:rtc_task_queue",
"../../rtc_base:task_queue_for_test",
"../../rtc_base:threading",
"../../rtc_base/synchronization:mutex",
diff --git a/third_party/libwebrtc/test/time_controller/external_time_controller_unittest.cc b/third_party/libwebrtc/test/time_controller/external_time_controller_unittest.cc
index 13d63fe8ed..302055999a 100644
--- a/third_party/libwebrtc/test/time_controller/external_time_controller_unittest.cc
+++ b/third_party/libwebrtc/test/time_controller/external_time_controller_unittest.cc
@@ -14,8 +14,8 @@
#include <memory>
#include <utility>
+#include "api/task_queue/task_queue_base.h"
#include "rtc_base/event.h"
-#include "rtc_base/task_queue.h"
#include "rtc_base/task_utils/repeating_task.h"
#include "test/gmock.h"
#include "test/gtest.h"
@@ -88,11 +88,11 @@ TEST(ExternalTimeControllerTest, TaskIsStoppedOnStop) {
const int kMargin = 1;
FakeAlarm alarm(kStartTime);
ExternalTimeController time_simulation(&alarm);
- rtc::TaskQueue task_queue(
+ std::unique_ptr<TaskQueueBase, TaskQueueDeleter> task_queue =
time_simulation.GetTaskQueueFactory()->CreateTaskQueue(
- "TestQueue", TaskQueueFactory::Priority::NORMAL));
+ "TestQueue", TaskQueueFactory::Priority::NORMAL);
std::atomic_int counter(0);
- auto handle = RepeatingTaskHandle::Start(task_queue.Get(), [&] {
+ auto handle = RepeatingTaskHandle::Start(task_queue.get(), [&] {
if (++counter >= kShortIntervalCount)
return kLongInterval;
return kShortInterval;
@@ -101,7 +101,7 @@ TEST(ExternalTimeControllerTest, TaskIsStoppedOnStop) {
time_simulation.AdvanceTime(kShortInterval * (kShortIntervalCount + kMargin));
EXPECT_EQ(counter.load(), kShortIntervalCount);
- task_queue.PostTask(
+ task_queue->PostTask(
[handle = std::move(handle)]() mutable { handle.Stop(); });
// Sleep long enough that the task would run at least once more if not
@@ -114,13 +114,13 @@ TEST(ExternalTimeControllerTest, TaskCanStopItself) {
std::atomic_int counter(0);
FakeAlarm alarm(kStartTime);
ExternalTimeController time_simulation(&alarm);
- rtc::TaskQueue task_queue(
+ std::unique_ptr<TaskQueueBase, TaskQueueDeleter> task_queue =
time_simulation.GetTaskQueueFactory()->CreateTaskQueue(
- "TestQueue", TaskQueueFactory::Priority::NORMAL));
+ "TestQueue", TaskQueueFactory::Priority::NORMAL);
RepeatingTaskHandle handle;
- task_queue.PostTask([&] {
- handle = RepeatingTaskHandle::Start(task_queue.Get(), [&] {
+ task_queue->PostTask([&] {
+ handle = RepeatingTaskHandle::Start(task_queue.get(), [&] {
++counter;
handle.Stop();
return TimeDelta::Millis(2);
@@ -134,12 +134,12 @@ TEST(ExternalTimeControllerTest, YieldForTask) {
FakeAlarm alarm(kStartTime);
ExternalTimeController time_simulation(&alarm);
- rtc::TaskQueue task_queue(
+ std::unique_ptr<TaskQueueBase, TaskQueueDeleter> task_queue =
time_simulation.GetTaskQueueFactory()->CreateTaskQueue(
- "TestQueue", TaskQueueFactory::Priority::NORMAL));
+ "TestQueue", TaskQueueFactory::Priority::NORMAL);
rtc::Event event;
- task_queue.PostTask([&] { event.Set(); });
+ task_queue->PostTask([&] { event.Set(); });
EXPECT_TRUE(event.Wait(TimeDelta::Millis(200)));
}
@@ -147,16 +147,16 @@ TEST(ExternalTimeControllerTest, TasksYieldToEachOther) {
FakeAlarm alarm(kStartTime);
ExternalTimeController time_simulation(&alarm);
- rtc::TaskQueue task_queue(
+ std::unique_ptr<TaskQueueBase, TaskQueueDeleter> task_queue =
time_simulation.GetTaskQueueFactory()->CreateTaskQueue(
- "TestQueue", TaskQueueFactory::Priority::NORMAL));
- rtc::TaskQueue other_queue(
+ "TestQueue", TaskQueueFactory::Priority::NORMAL);
+ std::unique_ptr<TaskQueueBase, TaskQueueDeleter> other_queue =
time_simulation.GetTaskQueueFactory()->CreateTaskQueue(
- "OtherQueue", TaskQueueFactory::Priority::NORMAL));
+ "OtherQueue", TaskQueueFactory::Priority::NORMAL);
- task_queue.PostTask([&] {
+ task_queue->PostTask([&] {
rtc::Event event;
- other_queue.PostTask([&] { event.Set(); });
+ other_queue->PostTask([&] { event.Set(); });
EXPECT_TRUE(event.Wait(TimeDelta::Millis(200)));
});
@@ -167,11 +167,11 @@ TEST(ExternalTimeControllerTest, CurrentTaskQueue) {
FakeAlarm alarm(kStartTime);
ExternalTimeController time_simulation(&alarm);
- rtc::TaskQueue task_queue(
+ std::unique_ptr<TaskQueueBase, TaskQueueDeleter> task_queue =
time_simulation.GetTaskQueueFactory()->CreateTaskQueue(
- "TestQueue", TaskQueueFactory::Priority::NORMAL));
+ "TestQueue", TaskQueueFactory::Priority::NORMAL);
- task_queue.PostTask([&] { EXPECT_TRUE(task_queue.IsCurrent()); });
+ task_queue->PostTask([&] { EXPECT_TRUE(task_queue->IsCurrent()); });
time_simulation.AdvanceTime(TimeDelta::Millis(10));
}
diff --git a/third_party/libwebrtc/test/time_controller/real_time_controller.cc b/third_party/libwebrtc/test/time_controller/real_time_controller.cc
index 7cc750d6d4..537532d20f 100644
--- a/third_party/libwebrtc/test/time_controller/real_time_controller.cc
+++ b/third_party/libwebrtc/test/time_controller/real_time_controller.cc
@@ -9,6 +9,7 @@
*/
#include "test/time_controller/real_time_controller.h"
+#include "api/field_trials_view.h"
#include "api/task_queue/default_task_queue_factory.h"
#include "rtc_base/null_socket_server.h"
@@ -30,8 +31,8 @@ class MainThread : public rtc::Thread {
CurrentThreadSetter current_setter_;
};
} // namespace
-RealTimeController::RealTimeController()
- : task_queue_factory_(CreateDefaultTaskQueueFactory()),
+RealTimeController::RealTimeController(const FieldTrialsView* field_trials)
+ : task_queue_factory_(CreateDefaultTaskQueueFactory(field_trials)),
main_thread_(std::make_unique<MainThread>()) {
main_thread_->SetName("Main", this);
}
diff --git a/third_party/libwebrtc/test/time_controller/real_time_controller.h b/third_party/libwebrtc/test/time_controller/real_time_controller.h
index 5f02eaf85f..0085732e63 100644
--- a/third_party/libwebrtc/test/time_controller/real_time_controller.h
+++ b/third_party/libwebrtc/test/time_controller/real_time_controller.h
@@ -13,6 +13,7 @@
#include <functional>
#include <memory>
+#include "api/field_trials_view.h"
#include "api/task_queue/task_queue_factory.h"
#include "api/test/time_controller.h"
#include "api/units/time_delta.h"
@@ -21,7 +22,7 @@
namespace webrtc {
class RealTimeController : public TimeController {
public:
- RealTimeController();
+ RealTimeController(const FieldTrialsView* field_trials = nullptr);
Clock* GetClock() override;
TaskQueueFactory* GetTaskQueueFactory() override;
diff --git a/third_party/libwebrtc/test/time_controller/simulated_time_controller.cc b/third_party/libwebrtc/test/time_controller/simulated_time_controller.cc
index dbb36fdfcc..ce666c4bf5 100644
--- a/third_party/libwebrtc/test/time_controller/simulated_time_controller.cc
+++ b/third_party/libwebrtc/test/time_controller/simulated_time_controller.cc
@@ -218,9 +218,6 @@ void GlobalSimulatedTimeController::SkipForwardBy(TimeDelta duration) {
impl_.AdvanceTime(target_time);
sim_clock_.AdvanceTimeMicroseconds(duration.us());
global_clock_.AdvanceTime(duration);
-
- // Run tasks that were pending during the skip.
- impl_.RunReadyRunners();
}
void GlobalSimulatedTimeController::Register(
diff --git a/third_party/libwebrtc/test/time_controller/simulated_time_controller.h b/third_party/libwebrtc/test/time_controller/simulated_time_controller.h
index f3f0da9274..df7f866b14 100644
--- a/third_party/libwebrtc/test/time_controller/simulated_time_controller.h
+++ b/third_party/libwebrtc/test/time_controller/simulated_time_controller.h
@@ -139,7 +139,6 @@ class GlobalSimulatedTimeController : public TimeController {
void AdvanceTime(TimeDelta duration) override;
// Advances time by `duration`and do not run delayed tasks in the meantime.
- // Runs any pending tasks at the end.
// Useful for simulating contention on destination queues.
void SkipForwardBy(TimeDelta duration);
diff --git a/third_party/libwebrtc/test/time_controller/simulated_time_controller_unittest.cc b/third_party/libwebrtc/test/time_controller/simulated_time_controller_unittest.cc
index f223ffe85d..c1c0ac2c0e 100644
--- a/third_party/libwebrtc/test/time_controller/simulated_time_controller_unittest.cc
+++ b/third_party/libwebrtc/test/time_controller/simulated_time_controller_unittest.cc
@@ -13,9 +13,9 @@
#include <atomic>
#include <memory>
+#include "api/task_queue/task_queue_base.h"
#include "api/units/time_delta.h"
#include "rtc_base/event.h"
-#include "rtc_base/task_queue.h"
#include "rtc_base/task_queue_for_test.h"
#include "rtc_base/task_utils/repeating_task.h"
#include "test/gmock.h"
@@ -39,11 +39,11 @@ TEST(SimulatedTimeControllerTest, TaskIsStoppedOnStop) {
const int kShortIntervalCount = 4;
const int kMargin = 1;
GlobalSimulatedTimeController time_simulation(kStartTime);
- rtc::TaskQueue task_queue(
+ std::unique_ptr<TaskQueueBase, TaskQueueDeleter> task_queue =
time_simulation.GetTaskQueueFactory()->CreateTaskQueue(
- "TestQueue", TaskQueueFactory::Priority::NORMAL));
+ "TestQueue", TaskQueueFactory::Priority::NORMAL);
std::atomic_int counter(0);
- auto handle = RepeatingTaskHandle::Start(task_queue.Get(), [&] {
+ auto handle = RepeatingTaskHandle::Start(task_queue.get(), [&] {
if (++counter >= kShortIntervalCount)
return kLongInterval;
return kShortInterval;
@@ -52,7 +52,7 @@ TEST(SimulatedTimeControllerTest, TaskIsStoppedOnStop) {
time_simulation.AdvanceTime(kShortInterval * (kShortIntervalCount + kMargin));
EXPECT_EQ(counter.load(), kShortIntervalCount);
- task_queue.PostTask(
+ task_queue->PostTask(
[handle = std::move(handle)]() mutable { handle.Stop(); });
// Sleep long enough that the task would run at least once more if not
@@ -64,13 +64,13 @@ TEST(SimulatedTimeControllerTest, TaskIsStoppedOnStop) {
TEST(SimulatedTimeControllerTest, TaskCanStopItself) {
std::atomic_int counter(0);
GlobalSimulatedTimeController time_simulation(kStartTime);
- rtc::TaskQueue task_queue(
+ std::unique_ptr<TaskQueueBase, TaskQueueDeleter> task_queue =
time_simulation.GetTaskQueueFactory()->CreateTaskQueue(
- "TestQueue", TaskQueueFactory::Priority::NORMAL));
+ "TestQueue", TaskQueueFactory::Priority::NORMAL);
RepeatingTaskHandle handle;
- task_queue.PostTask([&] {
- handle = RepeatingTaskHandle::Start(task_queue.Get(), [&] {
+ task_queue->PostTask([&] {
+ handle = RepeatingTaskHandle::Start(task_queue.get(), [&] {
++counter;
handle.Stop();
return TimeDelta::Millis(2);
@@ -86,29 +86,29 @@ TEST(SimulatedTimeControllerTest, Example) {
void DoPeriodicTask() {}
TimeDelta TimeUntilNextRun() { return TimeDelta::Millis(100); }
void StartPeriodicTask(RepeatingTaskHandle* handle,
- rtc::TaskQueue* task_queue) {
- *handle = RepeatingTaskHandle::Start(task_queue->Get(), [this] {
+ TaskQueueBase* task_queue) {
+ *handle = RepeatingTaskHandle::Start(task_queue, [this] {
DoPeriodicTask();
return TimeUntilNextRun();
});
}
};
GlobalSimulatedTimeController time_simulation(kStartTime);
- rtc::TaskQueue task_queue(
+ std::unique_ptr<TaskQueueBase, TaskQueueDeleter> task_queue =
time_simulation.GetTaskQueueFactory()->CreateTaskQueue(
- "TestQueue", TaskQueueFactory::Priority::NORMAL));
+ "TestQueue", TaskQueueFactory::Priority::NORMAL);
auto object = std::make_unique<ObjectOnTaskQueue>();
// Create and start the periodic task.
RepeatingTaskHandle handle;
- object->StartPeriodicTask(&handle, &task_queue);
+ object->StartPeriodicTask(&handle, task_queue.get());
// Restart the task
- task_queue.PostTask(
+ task_queue->PostTask(
[handle = std::move(handle)]() mutable { handle.Stop(); });
- object->StartPeriodicTask(&handle, &task_queue);
- task_queue.PostTask(
+ object->StartPeriodicTask(&handle, task_queue.get());
+ task_queue->PostTask(
[handle = std::move(handle)]() mutable { handle.Stop(); });
- task_queue.PostTask([object = std::move(object)] {});
+ task_queue->PostTask([object = std::move(object)] {});
}
TEST(SimulatedTimeControllerTest, DelayTaskRunOnTime) {
@@ -159,6 +159,8 @@ TEST(SimulatedTimeControllerTest, SkipsDelayedTaskForward) {
}));
main_thread->PostDelayedTask(fun.AsStdFunction(), shorter_duration);
sim.SkipForwardBy(duration_during_which_nothing_runs);
+ // Run tasks that were pending during the skip.
+ sim.AdvanceTime(TimeDelta::Zero());
}
} // namespace webrtc
diff --git a/third_party/libwebrtc/test/video_codec_tester.cc b/third_party/libwebrtc/test/video_codec_tester.cc
index 9453c3a7ef..f5fdc07a6b 100644
--- a/third_party/libwebrtc/test/video_codec_tester.cc
+++ b/third_party/libwebrtc/test/video_codec_tester.cc
@@ -23,10 +23,13 @@
#include "api/video/video_bitrate_allocator.h"
#include "api/video/video_codec_type.h"
#include "api/video/video_frame.h"
+#include "api/video_codecs/h264_profile_level_id.h"
+#include "api/video_codecs/simulcast_stream.h"
#include "api/video_codecs/video_decoder.h"
#include "api/video_codecs/video_encoder.h"
#include "media/base/media_constants.h"
#include "modules/video_coding/codecs/av1/av1_svc_config.h"
+#include "modules/video_coding/codecs/h264/include/h264.h"
#include "modules/video_coding/codecs/vp9/svc_config.h"
#include "modules/video_coding/include/video_codec_interface.h"
#include "modules/video_coding/include/video_error_codes.h"
@@ -39,10 +42,12 @@
#include "rtc_base/task_queue_for_test.h"
#include "rtc_base/time_utils.h"
#include "system_wrappers/include/sleep.h"
+#include "test/scoped_key_value_config.h"
#include "test/testsupport/file_utils.h"
#include "test/testsupport/frame_reader.h"
#include "test/testsupport/video_frame_writer.h"
#include "third_party/libyuv/include/libyuv/compare.h"
+#include "video/config/simulcast.h"
namespace webrtc {
namespace test {
@@ -260,9 +265,10 @@ class TesterIvfWriter {
task_queue_.SendTask([] {});
}
- void Write(const EncodedImage& encoded_frame) {
- task_queue_.PostTask([this, encoded_frame] {
- int spatial_idx = encoded_frame.SimulcastIndex().value_or(0);
+ void Write(const EncodedImage& encoded_frame, VideoCodecType codec_type) {
+ task_queue_.PostTask([this, encoded_frame, codec_type] {
+ int spatial_idx = encoded_frame.SpatialIndex().value_or(
+ encoded_frame.SimulcastIndex().value_or(0));
if (ivf_file_writers_.find(spatial_idx) == ivf_file_writers_.end()) {
std::string ivf_path =
base_path_ + "-s" + std::to_string(spatial_idx) + ".ivf";
@@ -277,8 +283,7 @@ class TesterIvfWriter {
}
// To play: ffplay -vcodec vp8|vp9|av1|hevc|h264 filename
- ivf_file_writers_.at(spatial_idx)
- ->WriteFrame(encoded_frame, VideoCodecType::kVideoCodecGeneric);
+ ivf_file_writers_.at(spatial_idx)->WriteFrame(encoded_frame, codec_type);
});
}
@@ -344,7 +349,8 @@ class VideoCodecAnalyzer : public VideoCodecTester::VideoCodecStats {
int64_t encode_finished_us = rtc::TimeMicros();
task_queue_.PostTask(
[this, timestamp_rtp = encoded_frame.RtpTimestamp(),
- spatial_idx = encoded_frame.SpatialIndex().value_or(0),
+ spatial_idx = encoded_frame.SpatialIndex().value_or(
+ encoded_frame.SimulcastIndex().value_or(0)),
temporal_idx = encoded_frame.TemporalIndex().value_or(0),
width = encoded_frame._encodedWidth,
height = encoded_frame._encodedHeight,
@@ -378,17 +384,30 @@ class VideoCodecAnalyzer : public VideoCodecTester::VideoCodecStats {
int64_t decode_start_us = rtc::TimeMicros();
task_queue_.PostTask(
[this, timestamp_rtp = encoded_frame.RtpTimestamp(),
- spatial_idx = encoded_frame.SpatialIndex().value_or(0),
+ spatial_idx = encoded_frame.SpatialIndex().value_or(
+ encoded_frame.SimulcastIndex().value_or(0)),
+ temporal_idx = encoded_frame.TemporalIndex().value_or(0),
+ width = encoded_frame._encodedWidth,
+ height = encoded_frame._encodedHeight,
+ frame_type = encoded_frame._frameType, qp = encoded_frame.qp_,
frame_size_bytes = encoded_frame.size(), decode_start_us]() {
- if (frames_.find(timestamp_rtp) == frames_.end() ||
- frames_.at(timestamp_rtp).find(spatial_idx) ==
- frames_.at(timestamp_rtp).end()) {
+ bool decode_only = frames_.find(timestamp_rtp) == frames_.end();
+ if (decode_only || frames_.at(timestamp_rtp).find(spatial_idx) ==
+ frames_.at(timestamp_rtp).end()) {
Frame frame;
frame.timestamp_rtp = timestamp_rtp;
- frame.layer_id = {.spatial_idx = spatial_idx};
- frame.frame_size = DataSize::Bytes(frame_size_bytes);
- frames_.emplace(timestamp_rtp,
- std::map<int, Frame>{{spatial_idx, frame}});
+ frame.layer_id = {.spatial_idx = spatial_idx,
+ .temporal_idx = temporal_idx};
+ frame.width = width;
+ frame.height = height;
+ frame.keyframe = frame_type == VideoFrameType::kVideoFrameKey;
+ frame.qp = qp;
+ if (decode_only) {
+ frame.frame_size = DataSize::Bytes(frame_size_bytes);
+ frames_[timestamp_rtp] = {{spatial_idx, frame}};
+ } else {
+ frames_[timestamp_rtp][spatial_idx] = frame;
+ }
}
Frame& frame = frames_.at(timestamp_rtp).at(spatial_idx);
@@ -485,6 +504,8 @@ class VideoCodecAnalyzer : public VideoCodecTester::VideoCodecStats {
Frame superframe = subframes.back();
for (const Frame& frame :
rtc::ArrayView<Frame>(subframes).subview(0, subframes.size() - 1)) {
+ superframe.decoded |= frame.decoded;
+ superframe.encoded |= frame.encoded;
superframe.frame_size += frame.frame_size;
superframe.keyframe |= frame.keyframe;
superframe.encode_time =
@@ -775,11 +796,13 @@ class Decoder : public DecodedImageCallback {
RTC_CHECK(decoder_) << "Could not create decoder for video format "
<< sdp_video_format.ToString();
- task_queue_.PostTaskAndWait([this, &sdp_video_format] {
+ codec_type_ = PayloadStringToCodecType(sdp_video_format.name);
+
+ task_queue_.PostTaskAndWait([this] {
decoder_->RegisterDecodeCompleteCallback(this);
VideoDecoder::Settings ds;
- ds.set_codec_type(PayloadStringToCodecType(sdp_video_format.name));
+ ds.set_codec_type(*codec_type_);
ds.set_number_of_cores(1);
ds.set_max_render_resolution({1280, 720});
bool result = decoder_->Configure(ds);
@@ -788,6 +811,16 @@ class Decoder : public DecodedImageCallback {
}
void Decode(const EncodedImage& encoded_frame) {
+ int spatial_idx = encoded_frame.SpatialIndex().value_or(
+ encoded_frame.SimulcastIndex().value_or(0));
+ {
+ MutexLock lock(&mutex_);
+ RTC_CHECK_EQ(spatial_idx_.value_or(spatial_idx), spatial_idx)
+ << "Spatial index changed from " << *spatial_idx_ << " to "
+ << spatial_idx;
+ spatial_idx_ = spatial_idx;
+ }
+
Timestamp pts =
Timestamp::Micros((encoded_frame.RtpTimestamp() / k90kHz).us());
@@ -804,7 +837,7 @@ class Decoder : public DecodedImageCallback {
pacer_.Schedule(pts));
if (ivf_writer_) {
- ivf_writer_->Write(encoded_frame);
+ ivf_writer_->Write(encoded_frame, *codec_type_);
}
}
@@ -815,10 +848,16 @@ class Decoder : public DecodedImageCallback {
private:
int Decoded(VideoFrame& decoded_frame) override {
- analyzer_->FinishDecode(decoded_frame, /*spatial_idx=*/0);
+ int spatial_idx;
+ {
+ MutexLock lock(&mutex_);
+ spatial_idx = *spatial_idx_;
+ }
+
+ analyzer_->FinishDecode(decoded_frame, spatial_idx);
if (y4m_writer_) {
- y4m_writer_->Write(decoded_frame, /*spatial_idx=*/0);
+ y4m_writer_->Write(decoded_frame, spatial_idx);
}
return WEBRTC_VIDEO_CODEC_OK;
@@ -831,6 +870,9 @@ class Decoder : public DecodedImageCallback {
LimitedTaskQueue task_queue_;
std::unique_ptr<TesterIvfWriter> ivf_writer_;
std::unique_ptr<TesterY4mWriter> y4m_writer_;
+ absl::optional<VideoCodecType> codec_type_;
+ absl::optional<int> spatial_idx_ RTC_GUARDED_BY(mutex_);
+ Mutex mutex_;
};
class Encoder : public EncodedImageCallback {
@@ -863,6 +905,9 @@ class Encoder : public EncodedImageCallback {
RTC_CHECK(encoder_) << "Could not create encoder for video format "
<< encoding_settings.sdp_video_format.ToString();
+ codec_type_ =
+ PayloadStringToCodecType(encoding_settings.sdp_video_format.name);
+
task_queue_.PostTaskAndWait([this, encoding_settings] {
encoder_->RegisterEncodeCompleteCallback(this);
Configure(encoding_settings);
@@ -888,14 +933,13 @@ class Encoder : public EncodedImageCallback {
!IsSameRate(encoding_settings, *last_encoding_settings_)) {
SetRates(encoding_settings);
}
+ last_encoding_settings_ = encoding_settings;
int error = encoder_->Encode(input_frame, /*frame_types=*/nullptr);
if (error != 0) {
RTC_LOG(LS_WARNING) << "Encode failed with error code " << error
<< " RTP timestamp " << input_frame.timestamp();
}
-
- last_encoding_settings_ = encoding_settings;
},
pacer_.Schedule(pts));
@@ -906,13 +950,54 @@ class Encoder : public EncodedImageCallback {
void Flush() {
task_queue_.PostTaskAndWait([this] { encoder_->Release(); });
+ if (last_superframe_) {
+ int num_spatial_layers =
+ ScalabilityModeToNumSpatialLayers(last_superframe_->scalability_mode);
+ for (int sidx = *last_superframe_->encoded_frame.SpatialIndex() + 1;
+ sidx < num_spatial_layers; ++sidx) {
+ last_superframe_->encoded_frame.SetSpatialIndex(sidx);
+ DeliverEncodedFrame(last_superframe_->encoded_frame);
+ }
+ last_superframe_.reset();
+ }
}
private:
+ struct Superframe {
+ EncodedImage encoded_frame;
+ rtc::scoped_refptr<EncodedImageBuffer> encoded_data;
+ ScalabilityMode scalability_mode;
+ };
+
Result OnEncodedImage(const EncodedImage& encoded_frame,
const CodecSpecificInfo* codec_specific_info) override {
analyzer_->FinishEncode(encoded_frame);
+ if (last_superframe_ && last_superframe_->encoded_frame.RtpTimestamp() !=
+ encoded_frame.RtpTimestamp()) {
+ // New temporal unit. We have frame of previous temporal unit (TU) stored
+ // which means that the previous TU used spatial prediction. If encoder
+ // dropped a frame of layer X in the previous TU, mark the stored frame
+ // as a frame belonging to layer >X and deliver it such that decoders of
+ // layer >X receive encoded lower layers.
+ int num_spatial_layers =
+ ScalabilityModeToNumSpatialLayers(last_superframe_->scalability_mode);
+ for (int sidx = *last_superframe_->encoded_frame.SpatialIndex() + 1;
+ sidx < num_spatial_layers; ++sidx) {
+ last_superframe_->encoded_frame.SetSpatialIndex(sidx);
+ DeliverEncodedFrame(last_superframe_->encoded_frame);
+ }
+ last_superframe_.reset();
+ }
+
+ const EncodedImage& superframe =
+ MakeSuperFrame(encoded_frame, codec_specific_info);
+ DeliverEncodedFrame(superframe);
+
+ return Result(Result::Error::OK);
+ }
+
+ void DeliverEncodedFrame(const EncodedImage& encoded_frame) {
{
MutexLock lock(&mutex_);
auto it = callbacks_.find(encoded_frame.RtpTimestamp());
@@ -922,23 +1007,30 @@ class Encoder : public EncodedImageCallback {
}
if (ivf_writer_ != nullptr) {
- ivf_writer_->Write(encoded_frame);
+ ivf_writer_->Write(encoded_frame, codec_type_);
}
-
- return Result(Result::Error::OK);
}
void Configure(const EncodingSettings& es) {
- const LayerSettings& layer_settings = es.layers_settings.rbegin()->second;
- const DataRate& bitrate = layer_settings.bitrate;
+ const LayerSettings& top_layer_settings =
+ es.layers_settings.rbegin()->second;
+ const int num_spatial_layers =
+ ScalabilityModeToNumSpatialLayers(es.scalability_mode);
+ const int num_temporal_layers =
+ ScalabilityModeToNumTemporalLayers(es.scalability_mode);
+ DataRate total_bitrate = std::accumulate(
+ es.layers_settings.begin(), es.layers_settings.end(), DataRate::Zero(),
+ [](DataRate acc, const std::pair<const LayerId, LayerSettings> layer) {
+ return acc + layer.second.bitrate;
+ });
VideoCodec vc;
- vc.width = layer_settings.resolution.width;
- vc.height = layer_settings.resolution.height;
- vc.startBitrate = bitrate.kbps();
- vc.maxBitrate = bitrate.kbps();
+ vc.width = top_layer_settings.resolution.width;
+ vc.height = top_layer_settings.resolution.height;
+ vc.startBitrate = total_bitrate.kbps();
+ vc.maxBitrate = total_bitrate.kbps();
vc.minBitrate = 0;
- vc.maxFramerate = layer_settings.framerate.hertz<uint32_t>();
+ vc.maxFramerate = top_layer_settings.framerate.hertz<uint32_t>();
vc.active = true;
vc.numberOfSimulcastStreams = 0;
vc.mode = webrtc::VideoCodecMode::kRealtimeVideo;
@@ -950,10 +1042,11 @@ class Encoder : public EncodedImageCallback {
switch (vc.codecType) {
case kVideoCodecVP8:
*(vc.VP8()) = VideoEncoder::GetDefaultVp8Settings();
- vc.VP8()->SetNumberOfTemporalLayers(
- ScalabilityModeToNumTemporalLayers(es.scalability_mode));
+ vc.VP8()->SetNumberOfTemporalLayers(num_temporal_layers);
+ vc.SetScalabilityMode(std::vector<ScalabilityMode>{
+ ScalabilityMode::kL1T1, ScalabilityMode::kL1T2,
+ ScalabilityMode::kL1T3}[num_temporal_layers - 1]);
vc.qpMax = cricket::kDefaultVideoMaxQpVpx;
- // TODO(webrtc:14852): Configure simulcast.
break;
case kVideoCodecVP9:
*(vc.VP9()) = VideoEncoder::GetDefaultVp9Settings();
@@ -966,6 +1059,7 @@ class Encoder : public EncodedImageCallback {
break;
case kVideoCodecH264:
*(vc.H264()) = VideoEncoder::GetDefaultH264Settings();
+ vc.H264()->SetNumberOfTemporalLayers(num_temporal_layers);
vc.qpMax = cricket::kDefaultVideoMaxQpH26x;
break;
case kVideoCodecH265:
@@ -977,6 +1071,36 @@ class Encoder : public EncodedImageCallback {
break;
}
+ bool is_simulcast =
+ num_spatial_layers > 1 &&
+ (vc.codecType == kVideoCodecVP8 || vc.codecType == kVideoCodecH264 ||
+ vc.codecType == kVideoCodecH265);
+ if (is_simulcast) {
+ vc.numberOfSimulcastStreams = num_spatial_layers;
+ for (int sidx = 0; sidx < num_spatial_layers; ++sidx) {
+ auto tl0_settings = es.layers_settings.find(
+ LayerId{.spatial_idx = sidx, .temporal_idx = 0});
+ auto tlx_settings = es.layers_settings.find(LayerId{
+ .spatial_idx = sidx, .temporal_idx = num_temporal_layers - 1});
+ DataRate total_bitrate = std::accumulate(
+ tl0_settings, tlx_settings, DataRate::Zero(),
+ [](DataRate acc,
+ const std::pair<const LayerId, LayerSettings> layer) {
+ return acc + layer.second.bitrate;
+ });
+ SimulcastStream& ss = vc.simulcastStream[sidx];
+ ss.width = tl0_settings->second.resolution.width;
+ ss.height = tl0_settings->second.resolution.height;
+ ss.numberOfTemporalLayers = num_temporal_layers;
+ ss.maxBitrate = total_bitrate.kbps();
+ ss.targetBitrate = total_bitrate.kbps();
+ ss.minBitrate = 0;
+ ss.maxFramerate = vc.maxFramerate;
+ ss.qpMax = vc.qpMax;
+ ss.active = true;
+ }
+ }
+
VideoEncoder::Settings ves(
VideoEncoder::Capabilities(/*loss_notification=*/false),
/*number_of_cores=*/1,
@@ -1021,6 +1145,52 @@ class Encoder : public EncodedImageCallback {
return true;
}
+ static bool IsSvc(const EncodedImage& encoded_frame,
+ const CodecSpecificInfo& codec_specific_info) {
+ if (!codec_specific_info.scalability_mode) {
+ return false;
+ }
+ ScalabilityMode scalability_mode = *codec_specific_info.scalability_mode;
+ return (kFullSvcScalabilityModes.count(scalability_mode) ||
+ (kKeySvcScalabilityModes.count(scalability_mode) &&
+ encoded_frame.FrameType() == VideoFrameType::kVideoFrameKey));
+ }
+
+ const EncodedImage& MakeSuperFrame(
+ const EncodedImage& encoded_frame,
+ const CodecSpecificInfo* codec_specific_info) {
+ if (last_superframe_) {
+ // Append to base spatial layer frame(s).
+ RTC_CHECK_EQ(*encoded_frame.SpatialIndex(),
+ *last_superframe_->encoded_frame.SpatialIndex() + 1)
+ << "Inter-layer frame drops are not supported.";
+ size_t current_size = last_superframe_->encoded_data->size();
+ last_superframe_->encoded_data->Realloc(current_size +
+ encoded_frame.size());
+ memcpy(last_superframe_->encoded_data->data() + current_size,
+ encoded_frame.data(), encoded_frame.size());
+ last_superframe_->encoded_frame.SetEncodedData(
+ last_superframe_->encoded_data);
+ last_superframe_->encoded_frame.SetSpatialIndex(
+ encoded_frame.SpatialIndex());
+ return last_superframe_->encoded_frame;
+ }
+
+ RTC_CHECK(codec_specific_info != nullptr);
+ if (IsSvc(encoded_frame, *codec_specific_info)) {
+ last_superframe_ = Superframe{
+ .encoded_frame = EncodedImage(encoded_frame),
+ .encoded_data = EncodedImageBuffer::Create(encoded_frame.data(),
+ encoded_frame.size()),
+ .scalability_mode = *codec_specific_info->scalability_mode};
+ last_superframe_->encoded_frame.SetEncodedData(
+ last_superframe_->encoded_data);
+ return last_superframe_->encoded_frame;
+ }
+
+ return encoded_frame;
+ }
+
VideoEncoderFactory* const encoder_factory_;
std::unique_ptr<VideoEncoder> encoder_;
VideoCodecAnalyzer* const analyzer_;
@@ -1032,9 +1202,62 @@ class Encoder : public EncodedImageCallback {
std::unique_ptr<TesterIvfWriter> ivf_writer_;
std::map<uint32_t, int> sidx_ RTC_GUARDED_BY(mutex_);
std::map<uint32_t, EncodeCallback> callbacks_ RTC_GUARDED_BY(mutex_);
+ VideoCodecType codec_type_;
+ absl::optional<Superframe> last_superframe_;
Mutex mutex_;
};
+void ConfigureSimulcast(VideoCodec* vc) {
+ int num_spatial_layers =
+ ScalabilityModeToNumSpatialLayers(*vc->GetScalabilityMode());
+ int num_temporal_layers =
+ ScalabilityModeToNumTemporalLayers(*vc->GetScalabilityMode());
+
+ if (num_spatial_layers == 1) {
+ SimulcastStream* ss = &vc->simulcastStream[0];
+ ss->width = vc->width;
+ ss->height = vc->height;
+ ss->numberOfTemporalLayers = num_temporal_layers;
+ ss->maxBitrate = vc->maxBitrate;
+ ss->targetBitrate = vc->maxBitrate;
+ ss->minBitrate = vc->minBitrate;
+ ss->qpMax = vc->qpMax;
+ ss->active = true;
+ return;
+ }
+
+ ScopedKeyValueConfig field_trials((rtc::StringBuilder()
+ << "WebRTC-VP8ConferenceTemporalLayers/"
+ << num_temporal_layers << "/")
+ .str());
+
+ const std::vector<webrtc::VideoStream> streams = cricket::GetSimulcastConfig(
+ /*min_layer=*/1, num_spatial_layers, vc->width, vc->height,
+ /*bitrate_priority=*/1.0, cricket::kDefaultVideoMaxQpVpx,
+ /*is_screenshare=*/false, /*temporal_layers_supported=*/true,
+ field_trials);
+
+ vc->numberOfSimulcastStreams = streams.size();
+ RTC_CHECK_LE(vc->numberOfSimulcastStreams, num_spatial_layers);
+ if (vc->numberOfSimulcastStreams < num_spatial_layers) {
+ vc->SetScalabilityMode(LimitNumSpatialLayers(*vc->GetScalabilityMode(),
+ vc->numberOfSimulcastStreams));
+ }
+
+ for (int i = 0; i < vc->numberOfSimulcastStreams; ++i) {
+ SimulcastStream* ss = &vc->simulcastStream[i];
+ ss->width = streams[i].width;
+ ss->height = streams[i].height;
+ RTC_CHECK_EQ(*streams[i].num_temporal_layers, num_temporal_layers);
+ ss->numberOfTemporalLayers = *streams[i].num_temporal_layers;
+ ss->maxBitrate = streams[i].max_bitrate_bps / 1000;
+ ss->targetBitrate = streams[i].target_bitrate_bps / 1000;
+ ss->minBitrate = streams[i].min_bitrate_bps / 1000;
+ ss->qpMax = streams[i].max_qp;
+ ss->active = true;
+ }
+}
+
std::tuple<std::vector<DataRate>, ScalabilityMode>
SplitBitrateAndUpdateScalabilityMode(std::string codec_type,
ScalabilityMode scalability_mode,
@@ -1075,8 +1298,7 @@ SplitBitrateAndUpdateScalabilityMode(std::string codec_type,
// TODO(webrtc:14852): Configure simulcast.
*(vc.VP8()) = VideoEncoder::GetDefaultVp8Settings();
vc.VP8()->SetNumberOfTemporalLayers(num_temporal_layers);
- vc.simulcastStream[0].width = vc.width;
- vc.simulcastStream[0].height = vc.height;
+ ConfigureSimulcast(&vc);
break;
case kVideoCodecVP9: {
*(vc.VP9()) = VideoEncoder::GetDefaultVp9Settings();
@@ -1095,6 +1317,7 @@ SplitBitrateAndUpdateScalabilityMode(std::string codec_type,
case kVideoCodecH264: {
*(vc.H264()) = VideoEncoder::GetDefaultH264Settings();
vc.H264()->SetNumberOfTemporalLayers(num_temporal_layers);
+ ConfigureSimulcast(&vc);
} break;
case kVideoCodecH265:
break;
@@ -1227,14 +1450,24 @@ std::map<uint32_t, EncodingSettings> VideoCodecTester::CreateEncodingSettings(
}
}
+ SdpVideoFormat sdp_video_format = SdpVideoFormat(codec_type);
+ if (codec_type == "H264") {
+ const std::string packetization_mode =
+ "1"; // H264PacketizationMode::SingleNalUnit
+ sdp_video_format.parameters =
+ CreateH264Format(H264Profile::kProfileConstrainedBaseline,
+ H264Level::kLevel3_1, packetization_mode,
+ /*add_scalability_modes=*/false)
+ .parameters;
+ }
+
std::map<uint32_t, EncodingSettings> frames_settings;
uint32_t timestamp_rtp = first_timestamp_rtp;
for (int frame_num = 0; frame_num < num_frames; ++frame_num) {
frames_settings.emplace(
- timestamp_rtp,
- EncodingSettings{.sdp_video_format = SdpVideoFormat(codec_type),
- .scalability_mode = scalability_mode,
- .layers_settings = layers_settings});
+ timestamp_rtp, EncodingSettings{.sdp_video_format = sdp_video_format,
+ .scalability_mode = scalability_mode,
+ .layers_settings = layers_settings});
timestamp_rtp += k90kHz / Frequency::MilliHertz(1000 * framerate_fps);
}
@@ -1298,10 +1531,19 @@ VideoCodecTester::RunEncodeDecodeTest(
VideoSource video_source(source_settings);
std::unique_ptr<VideoCodecAnalyzer> analyzer =
std::make_unique<VideoCodecAnalyzer>(&video_source);
- Decoder decoder(decoder_factory, decoder_settings, analyzer.get());
+ const EncodingSettings& frame_settings = encoding_settings.begin()->second;
Encoder encoder(encoder_factory, encoder_settings, analyzer.get());
- encoder.Initialize(encoding_settings.begin()->second);
- decoder.Initialize(encoding_settings.begin()->second.sdp_video_format);
+ encoder.Initialize(frame_settings);
+
+ int num_spatial_layers =
+ ScalabilityModeToNumSpatialLayers(frame_settings.scalability_mode);
+ std::vector<std::unique_ptr<Decoder>> decoders;
+ for (int sidx = 0; sidx < num_spatial_layers; ++sidx) {
+ auto decoder = std::make_unique<Decoder>(decoder_factory, decoder_settings,
+ analyzer.get());
+ decoder->Initialize(frame_settings.sdp_video_format);
+ decoders.push_back(std::move(decoder));
+ }
for (const auto& [timestamp_rtp, frame_settings] : encoding_settings) {
const EncodingSettings::LayerSettings& top_layer =
@@ -1309,13 +1551,17 @@ VideoCodecTester::RunEncodeDecodeTest(
VideoFrame source_frame = video_source.PullFrame(
timestamp_rtp, top_layer.resolution, top_layer.framerate);
encoder.Encode(source_frame, frame_settings,
- [&decoder](const EncodedImage& encoded_frame) {
- decoder.Decode(encoded_frame);
+ [&decoders](const EncodedImage& encoded_frame) {
+ int sidx = encoded_frame.SpatialIndex().value_or(
+ encoded_frame.SimulcastIndex().value_or(0));
+ decoders.at(sidx)->Decode(encoded_frame);
});
}
encoder.Flush();
- decoder.Flush();
+ for (auto& decoder : decoders) {
+ decoder->Flush();
+ }
analyzer->Flush();
return std::move(analyzer);
}
diff --git a/third_party/libwebrtc/test/video_codec_tester_unittest.cc b/third_party/libwebrtc/test/video_codec_tester_unittest.cc
index af31fe2c13..df5dca90a2 100644
--- a/third_party/libwebrtc/test/video_codec_tester_unittest.cc
+++ b/third_party/libwebrtc/test/video_codec_tester_unittest.cc
@@ -22,9 +22,14 @@
#include "api/test/mock_video_encoder.h"
#include "api/test/mock_video_encoder_factory.h"
#include "api/units/data_rate.h"
+#include "api/units/data_size.h"
+#include "api/units/frequency.h"
#include "api/units/time_delta.h"
#include "api/video/i420_buffer.h"
#include "api/video/video_frame.h"
+#include "api/video_codecs/scalability_mode.h"
+#include "api/video_codecs/video_decoder.h"
+#include "api/video_codecs/video_encoder.h"
#include "modules/video_coding/include/video_codec_interface.h"
#include "modules/video_coding/svc/scalability_mode_util.h"
#include "test/gmock.h"
@@ -44,6 +49,8 @@ using ::testing::InvokeWithoutArgs;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::SizeIs;
+using ::testing::UnorderedElementsAreArray;
+using ::testing::Values;
using VideoCodecStats = VideoCodecTester::VideoCodecStats;
using VideoSourceSettings = VideoCodecTester::VideoSourceSettings;
@@ -77,14 +84,19 @@ rtc::scoped_refptr<I420Buffer> CreateYuvBuffer(uint8_t y = 0,
return buffer;
}
+// TODO(ssilkin): Wrap this into a class that removes file in dtor.
std::string CreateYuvFile(int width, int height, int num_frames) {
std::string path = webrtc::test::TempFilename(webrtc::test::OutputPath(),
"video_codec_tester_unittest");
FILE* file = fopen(path.c_str(), "wb");
for (int frame_num = 0; frame_num < num_frames; ++frame_num) {
- uint8_t y = (frame_num + 0) & 255;
- uint8_t u = (frame_num + 1) & 255;
- uint8_t v = (frame_num + 2) & 255;
+ // For purposes of testing quality estimation, we need Y, U, V values in
+ // source and decoded video to be unique and deterministic. In source video
+ // we make them functions of frame number. The test decoder makes them
+ // functions of encoded frame size in decoded video.
+ uint8_t y = (frame_num * 3 + 0) & 255;
+ uint8_t u = (frame_num * 3 + 1) & 255;
+ uint8_t v = (frame_num * 3 + 2) & 255;
rtc::scoped_refptr<I420Buffer> buffer = CreateYuvBuffer(y, u, v);
fwrite(buffer->DataY(), 1, width * height, file);
int chroma_size_bytes = (width + 1) / 2 * (height + 1) / 2;
@@ -95,115 +107,161 @@ std::string CreateYuvFile(int width, int height, int num_frames) {
return path;
}
-std::unique_ptr<VideoCodecStats> RunTest(std::vector<std::vector<Frame>> frames,
- ScalabilityMode scalability_mode) {
- int num_frames = static_cast<int>(frames.size());
- std::string source_yuv_path = CreateYuvFile(kWidth, kHeight, num_frames);
- VideoSourceSettings source_settings{
- .file_path = source_yuv_path,
- .resolution = {.width = kWidth, .height = kHeight},
- .framerate = kTargetFramerate};
+class TestVideoEncoder : public MockVideoEncoder {
+ public:
+ TestVideoEncoder(ScalabilityMode scalability_mode,
+ std::vector<std::vector<Frame>> encoded_frames)
+ : scalability_mode_(scalability_mode), encoded_frames_(encoded_frames) {}
+ int32_t Encode(const VideoFrame& input_frame,
+ const std::vector<VideoFrameType>*) override {
+ for (const Frame& frame : encoded_frames_[num_encoded_frames_]) {
+ if (frame.frame_size.IsZero()) {
+ continue; // Frame drop.
+ }
+ EncodedImage encoded_frame;
+ encoded_frame._encodedWidth = frame.width;
+ encoded_frame._encodedHeight = frame.height;
+ encoded_frame.SetFrameType(frame.keyframe
+ ? VideoFrameType::kVideoFrameKey
+ : VideoFrameType::kVideoFrameDelta);
+ encoded_frame.SetRtpTimestamp(input_frame.timestamp());
+ encoded_frame.SetSpatialIndex(frame.layer_id.spatial_idx);
+ encoded_frame.SetTemporalIndex(frame.layer_id.temporal_idx);
+ encoded_frame.SetEncodedData(
+ EncodedImageBuffer::Create(frame.frame_size.bytes()));
+ CodecSpecificInfo codec_specific_info;
+ codec_specific_info.scalability_mode = scalability_mode_;
+ callback_->OnEncodedImage(encoded_frame, &codec_specific_info);
+ }
+ ++num_encoded_frames_;
+ return WEBRTC_VIDEO_CODEC_OK;
+ }
- int num_encoded_frames = 0;
- EncodedImageCallback* encoded_frame_callback;
- NiceMock<MockVideoEncoderFactory> encoder_factory;
- ON_CALL(encoder_factory, CreateVideoEncoder)
- .WillByDefault([&](const SdpVideoFormat&) {
- auto encoder = std::make_unique<NiceMock<MockVideoEncoder>>();
- ON_CALL(*encoder, RegisterEncodeCompleteCallback)
- .WillByDefault([&](EncodedImageCallback* callback) {
- encoded_frame_callback = callback;
- return WEBRTC_VIDEO_CODEC_OK;
- });
- ON_CALL(*encoder, Encode)
- .WillByDefault([&](const VideoFrame& input_frame,
- const std::vector<VideoFrameType>*) {
- for (const Frame& frame : frames[num_encoded_frames]) {
- EncodedImage encoded_frame;
- encoded_frame._encodedWidth = frame.width;
- encoded_frame._encodedHeight = frame.height;
- encoded_frame.SetFrameType(
- frame.keyframe ? VideoFrameType::kVideoFrameKey
- : VideoFrameType::kVideoFrameDelta);
- encoded_frame.SetRtpTimestamp(input_frame.timestamp());
- encoded_frame.SetSpatialIndex(frame.layer_id.spatial_idx);
- encoded_frame.SetTemporalIndex(frame.layer_id.temporal_idx);
- encoded_frame.SetEncodedData(
- EncodedImageBuffer::Create(frame.frame_size.bytes()));
- encoded_frame_callback->OnEncodedImage(
- encoded_frame,
- /*codec_specific_info=*/nullptr);
- }
- ++num_encoded_frames;
- return WEBRTC_VIDEO_CODEC_OK;
- });
- return encoder;
- });
+ int32_t RegisterEncodeCompleteCallback(
+ EncodedImageCallback* callback) override {
+ callback_ = callback;
+ return WEBRTC_VIDEO_CODEC_OK;
+ }
- int num_decoded_frames = 0;
- DecodedImageCallback* decode_callback;
- NiceMock<MockVideoDecoderFactory> decoder_factory;
- ON_CALL(decoder_factory, CreateVideoDecoder)
- .WillByDefault([&](const SdpVideoFormat&) {
- auto decoder = std::make_unique<NiceMock<MockVideoDecoder>>();
- ON_CALL(*decoder, RegisterDecodeCompleteCallback)
- .WillByDefault([&](DecodedImageCallback* callback) {
- decode_callback = callback;
- return WEBRTC_VIDEO_CODEC_OK;
- });
- ON_CALL(*decoder, Decode(_, _))
- .WillByDefault([&](const EncodedImage& encoded_frame, int64_t) {
- // Make values to be different from source YUV generated in
- // `CreateYuvFile`.
- uint8_t y = ((num_decoded_frames + 1) * 2) & 255;
- uint8_t u = ((num_decoded_frames + 2) * 2) & 255;
- uint8_t v = ((num_decoded_frames + 3) * 2) & 255;
- rtc::scoped_refptr<I420Buffer> frame_buffer =
- CreateYuvBuffer(y, u, v);
- VideoFrame decoded_frame =
- VideoFrame::Builder()
- .set_video_frame_buffer(frame_buffer)
- .set_timestamp_rtp(encoded_frame.RtpTimestamp())
- .build();
- decode_callback->Decoded(decoded_frame);
- ++num_decoded_frames;
- return WEBRTC_VIDEO_CODEC_OK;
- });
- return decoder;
- });
+ private:
+ ScalabilityMode scalability_mode_;
+ std::vector<std::vector<Frame>> encoded_frames_;
+ int num_encoded_frames_ = 0;
+ EncodedImageCallback* callback_;
+};
+
+class TestVideoDecoder : public MockVideoDecoder {
+ public:
+ int32_t Decode(const EncodedImage& encoded_frame, int64_t) {
+ uint8_t y = (encoded_frame.size() + 0) & 255;
+ uint8_t u = (encoded_frame.size() + 2) & 255;
+ uint8_t v = (encoded_frame.size() + 4) & 255;
+ rtc::scoped_refptr<I420Buffer> frame_buffer = CreateYuvBuffer(y, u, v);
+ VideoFrame decoded_frame =
+ VideoFrame::Builder()
+ .set_video_frame_buffer(frame_buffer)
+ .set_timestamp_rtp(encoded_frame.RtpTimestamp())
+ .build();
+ callback_->Decoded(decoded_frame);
+ frame_sizes_.push_back(DataSize::Bytes(encoded_frame.size()));
+ return WEBRTC_VIDEO_CODEC_OK;
+ }
- int num_spatial_layers = ScalabilityModeToNumSpatialLayers(scalability_mode);
- int num_temporal_layers =
- ScalabilityModeToNumTemporalLayers(scalability_mode);
+ int32_t RegisterDecodeCompleteCallback(DecodedImageCallback* callback) {
+ callback_ = callback;
+ return WEBRTC_VIDEO_CODEC_OK;
+ }
- std::map<uint32_t, EncodingSettings> encoding_settings;
- for (int frame_num = 0; frame_num < num_frames; ++frame_num) {
- std::map<LayerId, LayerSettings> layers_settings;
- for (int sidx = 0; sidx < num_spatial_layers; ++sidx) {
- for (int tidx = 0; tidx < num_temporal_layers; ++tidx) {
- layers_settings.emplace(
- LayerId{.spatial_idx = sidx, .temporal_idx = tidx},
- LayerSettings{.resolution = {.width = kWidth, .height = kHeight},
- .framerate = kTargetFramerate /
- (1 << (num_temporal_layers - 1 - tidx)),
- .bitrate = kTargetLayerBitrate});
+ const std::vector<DataSize>& frame_sizes() const { return frame_sizes_; }
+
+ private:
+ DecodedImageCallback* callback_;
+ std::vector<DataSize> frame_sizes_;
+};
+
+class VideoCodecTesterTest : public ::testing::Test {
+ public:
+ std::unique_ptr<VideoCodecStats> RunEncodeDecodeTest(
+ std::string codec_type,
+ ScalabilityMode scalability_mode,
+ std::vector<std::vector<Frame>> encoded_frames) {
+ int num_frames = encoded_frames.size();
+ std::string yuv_path = CreateYuvFile(kWidth, kHeight, num_frames);
+ VideoSourceSettings video_source_settings{
+ .file_path = yuv_path,
+ .resolution = {.width = kWidth, .height = kHeight},
+ .framerate = kTargetFramerate};
+
+ NiceMock<MockVideoEncoderFactory> encoder_factory;
+ ON_CALL(encoder_factory, CreateVideoEncoder)
+ .WillByDefault([&](const SdpVideoFormat&) {
+ return std::make_unique<NiceMock<TestVideoEncoder>>(scalability_mode,
+ encoded_frames);
+ });
+
+ NiceMock<MockVideoDecoderFactory> decoder_factory;
+ ON_CALL(decoder_factory, CreateVideoDecoder)
+ .WillByDefault([&](const SdpVideoFormat&) {
+ // Video codec tester destroyes decoder at the end of test. Test
+ // decoder collects stats which we need to access after test. To keep
+ // the decode alive we wrap it into a wrapper and pass the wrapper to
+ // the tester.
+ class DecoderWrapper : public TestVideoDecoder {
+ public:
+ explicit DecoderWrapper(TestVideoDecoder* decoder)
+ : decoder_(decoder) {}
+ int32_t Decode(const EncodedImage& encoded_frame,
+ int64_t render_time_ms) {
+ return decoder_->Decode(encoded_frame, render_time_ms);
+ }
+ int32_t RegisterDecodeCompleteCallback(
+ DecodedImageCallback* callback) {
+ return decoder_->RegisterDecodeCompleteCallback(callback);
+ }
+ TestVideoDecoder* decoder_;
+ };
+ decoders_.push_back(std::make_unique<NiceMock<TestVideoDecoder>>());
+ return std::make_unique<NiceMock<DecoderWrapper>>(
+ decoders_.back().get());
+ });
+
+ int num_spatial_layers =
+ ScalabilityModeToNumSpatialLayers(scalability_mode);
+ int num_temporal_layers =
+ ScalabilityModeToNumTemporalLayers(scalability_mode);
+ std::map<uint32_t, EncodingSettings> encoding_settings;
+ for (int frame_num = 0; frame_num < num_frames; ++frame_num) {
+ std::map<LayerId, LayerSettings> layers_settings;
+ for (int sidx = 0; sidx < num_spatial_layers; ++sidx) {
+ for (int tidx = 0; tidx < num_temporal_layers; ++tidx) {
+ layers_settings.emplace(
+ LayerId{.spatial_idx = sidx, .temporal_idx = tidx},
+ LayerSettings{
+ .resolution = {.width = kWidth, .height = kHeight},
+ .framerate = kTargetFramerate /
+ (1 << (num_temporal_layers - 1 - tidx)),
+ .bitrate = kTargetLayerBitrate});
+ }
}
+ encoding_settings.emplace(
+ encoded_frames[frame_num].front().timestamp_rtp,
+ EncodingSettings{.sdp_video_format = SdpVideoFormat(codec_type),
+ .scalability_mode = scalability_mode,
+ .layers_settings = layers_settings});
}
- encoding_settings.emplace(
- frames[frame_num][0].timestamp_rtp,
- EncodingSettings{.scalability_mode = scalability_mode,
- .layers_settings = layers_settings});
+
+ std::unique_ptr<VideoCodecStats> stats =
+ VideoCodecTester::RunEncodeDecodeTest(
+ video_source_settings, &encoder_factory, &decoder_factory,
+ EncoderSettings{}, DecoderSettings{}, encoding_settings);
+
+ remove(yuv_path.c_str());
+ return stats;
}
- EncoderSettings encoder_settings;
- DecoderSettings decoder_settings;
- std::unique_ptr<VideoCodecStats> stats =
- VideoCodecTester::RunEncodeDecodeTest(
- source_settings, &encoder_factory, &decoder_factory, encoder_settings,
- decoder_settings, encoding_settings);
- remove(source_yuv_path.c_str());
- return stats;
-}
+ protected:
+ std::vector<std::unique_ptr<TestVideoDecoder>> decoders_;
+};
EncodedImage CreateEncodedImage(uint32_t timestamp_rtp) {
EncodedImage encoded_image;
@@ -233,52 +291,63 @@ class MockCodedVideoSource : public CodedVideoSource {
} // namespace
-TEST(VideoCodecTester, Slice) {
- std::unique_ptr<VideoCodecStats> stats = RunTest(
- {{{.timestamp_rtp = 0, .layer_id = {.spatial_idx = 0, .temporal_idx = 0}},
- {.timestamp_rtp = 0,
- .layer_id = {.spatial_idx = 1, .temporal_idx = 0}}},
- {{.timestamp_rtp = 1,
- .layer_id = {.spatial_idx = 0, .temporal_idx = 1}}}},
- ScalabilityMode::kL2T2);
+TEST_F(VideoCodecTesterTest, Slice) {
+ std::unique_ptr<VideoCodecStats> stats =
+ RunEncodeDecodeTest("VP9", ScalabilityMode::kL2T2,
+ {{{.timestamp_rtp = 0,
+ .layer_id = {.spatial_idx = 0, .temporal_idx = 0},
+ .frame_size = DataSize::Bytes(1)},
+ {.timestamp_rtp = 0,
+ .layer_id = {.spatial_idx = 1, .temporal_idx = 0},
+ .frame_size = DataSize::Bytes(2)}},
+ {{.timestamp_rtp = 1,
+ .layer_id = {.spatial_idx = 0, .temporal_idx = 1},
+ .frame_size = DataSize::Bytes(3)}}});
std::vector<Frame> slice = stats->Slice(Filter{}, /*merge=*/false);
- EXPECT_THAT(slice, ElementsAre(Field(&Frame::timestamp_rtp, 0),
- Field(&Frame::timestamp_rtp, 0),
- Field(&Frame::timestamp_rtp, 1)));
+ EXPECT_THAT(slice,
+ ElementsAre(Field(&Frame::frame_size, DataSize::Bytes(1)),
+ Field(&Frame::frame_size, DataSize::Bytes(2)),
+ Field(&Frame::frame_size, DataSize::Bytes(3)),
+ Field(&Frame::frame_size, DataSize::Bytes(0))));
slice = stats->Slice({.min_timestamp_rtp = 1}, /*merge=*/false);
- EXPECT_THAT(slice, ElementsAre(Field(&Frame::timestamp_rtp, 1)));
+ EXPECT_THAT(slice,
+ ElementsAre(Field(&Frame::frame_size, DataSize::Bytes(3)),
+ Field(&Frame::frame_size, DataSize::Bytes(0))));
slice = stats->Slice({.max_timestamp_rtp = 0}, /*merge=*/false);
- EXPECT_THAT(slice, ElementsAre(Field(&Frame::timestamp_rtp, 0),
- Field(&Frame::timestamp_rtp, 0)));
+ EXPECT_THAT(slice,
+ ElementsAre(Field(&Frame::frame_size, DataSize::Bytes(1)),
+ Field(&Frame::frame_size, DataSize::Bytes(2))));
slice = stats->Slice({.layer_id = {{.spatial_idx = 0, .temporal_idx = 0}}},
/*merge=*/false);
- EXPECT_THAT(slice, ElementsAre(Field(&Frame::timestamp_rtp, 0)));
+ EXPECT_THAT(slice,
+ ElementsAre(Field(&Frame::frame_size, DataSize::Bytes(1))));
slice = stats->Slice({.layer_id = {{.spatial_idx = 0, .temporal_idx = 1}}},
/*merge=*/false);
- EXPECT_THAT(slice, ElementsAre(Field(&Frame::timestamp_rtp, 0),
- Field(&Frame::timestamp_rtp, 1)));
+ EXPECT_THAT(slice,
+ ElementsAre(Field(&Frame::frame_size, DataSize::Bytes(1)),
+ Field(&Frame::frame_size, DataSize::Bytes(3))));
}
-TEST(VideoCodecTester, Merge) {
+TEST_F(VideoCodecTesterTest, Merge) {
std::unique_ptr<VideoCodecStats> stats =
- RunTest({{{.timestamp_rtp = 0,
- .layer_id = {.spatial_idx = 0, .temporal_idx = 0},
- .frame_size = DataSize::Bytes(1),
- .keyframe = true},
- {.timestamp_rtp = 0,
- .layer_id = {.spatial_idx = 1, .temporal_idx = 0},
- .frame_size = DataSize::Bytes(2)}},
- {{.timestamp_rtp = 1,
- .layer_id = {.spatial_idx = 0, .temporal_idx = 1},
- .frame_size = DataSize::Bytes(4)},
- {.timestamp_rtp = 1,
- .layer_id = {.spatial_idx = 1, .temporal_idx = 1},
- .frame_size = DataSize::Bytes(8)}}},
- ScalabilityMode::kL2T2_KEY);
+ RunEncodeDecodeTest("VP8", ScalabilityMode::kL2T2_KEY,
+ {{{.timestamp_rtp = 0,
+ .layer_id = {.spatial_idx = 0, .temporal_idx = 0},
+ .frame_size = DataSize::Bytes(1),
+ .keyframe = true},
+ {.timestamp_rtp = 0,
+ .layer_id = {.spatial_idx = 1, .temporal_idx = 0},
+ .frame_size = DataSize::Bytes(2)}},
+ {{.timestamp_rtp = 1,
+ .layer_id = {.spatial_idx = 0, .temporal_idx = 1},
+ .frame_size = DataSize::Bytes(4)},
+ {.timestamp_rtp = 1,
+ .layer_id = {.spatial_idx = 1, .temporal_idx = 1},
+ .frame_size = DataSize::Bytes(8)}}});
std::vector<Frame> slice = stats->Slice(Filter{}, /*merge=*/true);
EXPECT_THAT(
@@ -300,33 +369,34 @@ struct AggregationTestParameters {
};
class VideoCodecTesterTestAggregation
- : public ::testing::TestWithParam<AggregationTestParameters> {};
+ : public VideoCodecTesterTest,
+ public ::testing::WithParamInterface<AggregationTestParameters> {};
TEST_P(VideoCodecTesterTestAggregation, Aggregate) {
AggregationTestParameters test_params = GetParam();
std::unique_ptr<VideoCodecStats> stats =
- RunTest({{// L0T0
- {.timestamp_rtp = 0,
- .layer_id = {.spatial_idx = 0, .temporal_idx = 0},
- .frame_size = DataSize::Bytes(1),
- .keyframe = true},
- // L1T0
- {.timestamp_rtp = 0,
- .layer_id = {.spatial_idx = 1, .temporal_idx = 0},
- .frame_size = DataSize::Bytes(2)}},
- // Emulate frame drop (frame_size = 0).
- {{.timestamp_rtp = 3000,
- .layer_id = {.spatial_idx = 0, .temporal_idx = 0},
- .frame_size = DataSize::Zero()}},
- {// L0T1
- {.timestamp_rtp = 87000,
- .layer_id = {.spatial_idx = 0, .temporal_idx = 1},
- .frame_size = DataSize::Bytes(4)},
- // L1T1
- {.timestamp_rtp = 87000,
- .layer_id = {.spatial_idx = 1, .temporal_idx = 1},
- .frame_size = DataSize::Bytes(8)}}},
- ScalabilityMode::kL2T2_KEY);
+ RunEncodeDecodeTest("VP8", ScalabilityMode::kL2T2_KEY,
+ {{// L0T0
+ {.timestamp_rtp = 0,
+ .layer_id = {.spatial_idx = 0, .temporal_idx = 0},
+ .frame_size = DataSize::Bytes(1),
+ .keyframe = true},
+ // L1T0
+ {.timestamp_rtp = 0,
+ .layer_id = {.spatial_idx = 1, .temporal_idx = 0},
+ .frame_size = DataSize::Bytes(2)}},
+ // Emulate frame drop (frame_size = 0).
+ {{.timestamp_rtp = 3000,
+ .layer_id = {.spatial_idx = 0, .temporal_idx = 0},
+ .frame_size = DataSize::Zero()}},
+ {// L0T1
+ {.timestamp_rtp = 87000,
+ .layer_id = {.spatial_idx = 0, .temporal_idx = 1},
+ .frame_size = DataSize::Bytes(4)},
+ // L1T1
+ {.timestamp_rtp = 87000,
+ .layer_id = {.spatial_idx = 1, .temporal_idx = 1},
+ .frame_size = DataSize::Bytes(8)}}});
Stream stream = stats->Aggregate(test_params.filter);
EXPECT_EQ(stream.keyframe.GetSum(), test_params.expected_keyframe_sum);
@@ -343,7 +413,7 @@ TEST_P(VideoCodecTesterTestAggregation, Aggregate) {
INSTANTIATE_TEST_SUITE_P(
All,
VideoCodecTesterTestAggregation,
- ::testing::Values(
+ Values(
// No filtering.
AggregationTestParameters{
.filter = {},
@@ -400,11 +470,11 @@ INSTANTIATE_TEST_SUITE_P(
.expected_framerate_mismatch_pct =
100 * (2.0 / kTargetFramerate.hertz() - 1)}));
-TEST(VideoCodecTester, Psnr) {
- std::unique_ptr<VideoCodecStats> stats =
- RunTest({{{.timestamp_rtp = 0, .frame_size = DataSize::Bytes(1)}},
- {{.timestamp_rtp = 3000, .frame_size = DataSize::Bytes(1)}}},
- ScalabilityMode::kL1T1);
+TEST_F(VideoCodecTesterTest, Psnr) {
+ std::unique_ptr<VideoCodecStats> stats = RunEncodeDecodeTest(
+ "VP8", ScalabilityMode::kL1T1,
+ {{{.timestamp_rtp = 0, .frame_size = DataSize::Bytes(2)}},
+ {{.timestamp_rtp = 3000, .frame_size = DataSize::Bytes(6)}}});
std::vector<Frame> slice = stats->Slice(Filter{}, /*merge=*/false);
ASSERT_THAT(slice, SizeIs(2));
@@ -418,6 +488,107 @@ TEST(VideoCodecTester, Psnr) {
EXPECT_NEAR(slice[1].psnr->v, 34, 1);
}
+struct ScalabilityTestParameters {
+ std::string codec_type;
+ ScalabilityMode scalability_mode;
+ // Temporal unit -> spatial layer -> frame size.
+ std::vector<std::map<int, DataSize>> encoded_frame_sizes;
+ std::vector<DataSize> expected_decode_frame_sizes;
+};
+
+class VideoCodecTesterTestScalability
+ : public VideoCodecTesterTest,
+ public ::testing::WithParamInterface<ScalabilityTestParameters> {};
+
+TEST_P(VideoCodecTesterTestScalability, EncodeDecode) {
+ ScalabilityTestParameters test_params = GetParam();
+ std::vector<std::vector<Frame>> frames;
+ for (size_t frame_num = 0; frame_num < test_params.encoded_frame_sizes.size();
+ ++frame_num) {
+ std::vector<Frame> temporal_unit;
+ for (auto [sidx, frame_size] : test_params.encoded_frame_sizes[frame_num]) {
+ temporal_unit.push_back(
+ Frame{.timestamp_rtp = static_cast<uint32_t>(3000 * frame_num),
+ .layer_id = {.spatial_idx = sidx, .temporal_idx = 0},
+ .frame_size = frame_size,
+ .keyframe = (frame_num == 0 && sidx == 0)});
+ }
+ frames.push_back(temporal_unit);
+ }
+ RunEncodeDecodeTest(test_params.codec_type, test_params.scalability_mode,
+ frames);
+
+ size_t num_spatial_layers =
+ ScalabilityModeToNumSpatialLayers(test_params.scalability_mode);
+ EXPECT_EQ(num_spatial_layers, decoders_.size());
+
+ // Collect input frame sizes from all decoders.
+ std::vector<DataSize> decode_frame_sizes;
+ for (const auto& decoder : decoders_) {
+ const auto& frame_sizes = decoder->frame_sizes();
+ decode_frame_sizes.insert(decode_frame_sizes.end(), frame_sizes.begin(),
+ frame_sizes.end());
+ }
+ EXPECT_THAT(decode_frame_sizes, UnorderedElementsAreArray(
+ test_params.expected_decode_frame_sizes));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ All,
+ VideoCodecTesterTestScalability,
+ Values(
+ ScalabilityTestParameters{
+ .codec_type = "VP8",
+ .scalability_mode = ScalabilityMode::kS2T1,
+ .encoded_frame_sizes = {{{0, DataSize::Bytes(1)},
+ {1, DataSize::Bytes(2)}},
+ {{0, DataSize::Bytes(4)},
+ // Emulate frame drop.
+ {1, DataSize::Bytes(0)}}},
+ .expected_decode_frame_sizes = {DataSize::Bytes(1),
+ DataSize::Bytes(2),
+ DataSize::Bytes(4)},
+ },
+ ScalabilityTestParameters{
+ .codec_type = "VP9",
+ .scalability_mode = ScalabilityMode::kL2T1,
+ .encoded_frame_sizes =
+ {{{0, DataSize::Bytes(1)}, {1, DataSize::Bytes(2)}},
+ {{0, DataSize::Bytes(4)}, {1, DataSize::Bytes(8)}},
+ {{0, DataSize::Bytes(16)},
+ // Emulate frame drop.
+ {1, DataSize::Bytes(0)}}},
+ .expected_decode_frame_sizes =
+ {DataSize::Bytes(1), DataSize::Bytes(3), DataSize::Bytes(4),
+ DataSize::Bytes(12), DataSize::Bytes(16), DataSize::Bytes(16)},
+ },
+ ScalabilityTestParameters{
+ .codec_type = "VP9",
+ .scalability_mode = ScalabilityMode::kL2T1_KEY,
+ .encoded_frame_sizes =
+ {{{0, DataSize::Bytes(1)}, {1, DataSize::Bytes(2)}},
+ {{0, DataSize::Bytes(4)}, {1, DataSize::Bytes(8)}},
+ {{0, DataSize::Bytes(16)},
+ // Emulate frame drop.
+ {1, DataSize::Bytes(0)}}},
+ .expected_decode_frame_sizes =
+ {DataSize::Bytes(1), DataSize::Bytes(3), DataSize::Bytes(4),
+ DataSize::Bytes(8), DataSize::Bytes(16)},
+ },
+ ScalabilityTestParameters{
+ .codec_type = "VP9",
+ .scalability_mode = ScalabilityMode::kS2T1,
+ .encoded_frame_sizes =
+ {{{0, DataSize::Bytes(1)}, {1, DataSize::Bytes(2)}},
+ {{0, DataSize::Bytes(4)}, {1, DataSize::Bytes(8)}},
+ {{0, DataSize::Bytes(16)},
+ // Emulate frame drop.
+ {1, DataSize::Bytes(0)}}},
+ .expected_decode_frame_sizes =
+ {DataSize::Bytes(1), DataSize::Bytes(2), DataSize::Bytes(4),
+ DataSize::Bytes(8), DataSize::Bytes(16)},
+ }));
+
class VideoCodecTesterTestPacing
: public ::testing::TestWithParam<std::tuple<PacingSettings, int>> {
public:
@@ -428,15 +599,11 @@ class VideoCodecTesterTestPacing
const Frequency kTargetFramerate = Frequency::Hertz(10);
void SetUp() override {
- source_yuv_file_path_ = webrtc::test::TempFilename(
- webrtc::test::OutputPath(), "video_codec_tester_impl_unittest");
- FILE* file = fopen(source_yuv_file_path_.c_str(), "wb");
- for (int i = 0; i < 3 * kSourceWidth * kSourceHeight / 2; ++i) {
- fwrite("x", 1, 1, file);
- }
- fclose(file);
+ source_yuv_file_path_ = CreateYuvFile(kSourceWidth, kSourceHeight, 1);
}
+ void TearDown() override { remove(source_yuv_file_path_.c_str()); }
+
protected:
std::string source_yuv_file_path_;
};
@@ -498,7 +665,7 @@ TEST_P(VideoCodecTesterTestPacing, PaceDecode) {
INSTANTIATE_TEST_SUITE_P(
DISABLED_All,
VideoCodecTesterTestPacing,
- ::testing::Values(
+ Values(
// No pacing.
std::make_tuple(PacingSettings{.mode = PacingMode::kNoPacing},
/*expected_delta_ms=*/0),
diff --git a/third_party/libwebrtc/tools_webrtc/OWNERS b/third_party/libwebrtc/tools_webrtc/OWNERS
index f73dc520b8..90ac9c9cc2 100644
--- a/third_party/libwebrtc/tools_webrtc/OWNERS
+++ b/third_party/libwebrtc/tools_webrtc/OWNERS
@@ -1,5 +1,4 @@
mbonadei@webrtc.org
jansson@webrtc.org
terelius@webrtc.org
-landrey@webrtc.org
jleconte@webrtc.org
diff --git a/third_party/libwebrtc/tools_webrtc/libs/generate_licenses.py b/third_party/libwebrtc/tools_webrtc/libs/generate_licenses.py
index fdc40f5c4c..feff4281f7 100755
--- a/third_party/libwebrtc/tools_webrtc/libs/generate_licenses.py
+++ b/third_party/libwebrtc/tools_webrtc/libs/generate_licenses.py
@@ -77,7 +77,7 @@ LIB_TO_LICENSES_DICT = {
'ooura': ['common_audio/third_party/ooura/LICENSE'],
'spl_sqrt_floor': ['common_audio/third_party/spl_sqrt_floor/LICENSE'],
'kotlin_stdlib': ['third_party/kotlin_stdlib/LICENSE'],
-
+ 'jni_zero': ['third_party/jni_zero/LICENSE'],
# TODO(bugs.webrtc.org/1110): Remove this hack. This is not a lib.
# For some reason it is listed as so in _GetThirdPartyLibraries.
'android_deps': [],
diff --git a/third_party/libwebrtc/tools_webrtc/mb/mb_config.pyl b/third_party/libwebrtc/tools_webrtc/mb/mb_config.pyl
index c0f5130ff0..8862950e0e 100644
--- a/third_party/libwebrtc/tools_webrtc/mb/mb_config.pyl
+++ b/third_party/libwebrtc/tools_webrtc/mb/mb_config.pyl
@@ -146,6 +146,7 @@
'dummy_audio_file_devices_no_protobuf':
'dummy_audio_file_devices_no_protobuf_android_arm',
'rtti_no_sctp': 'rtti_no_sctp_android_arm',
+ 'disable_trace_events': 'disable_trace_events_android_arm',
},
'android_arm_rel': 'android_release_bot_arm',
'android_compile_arm64_dbg': 'android_debug_static_bot_arm64',
@@ -183,6 +184,7 @@
'dummy_audio_file_devices_no_protobuf':
'dummy_audio_file_devices_no_protobuf_x64',
'rtti_no_sctp': 'rtti_no_sctp_x64',
+ 'disable_trace_events': 'disable_trace_events_x64',
},
'linux_msan': 'msan_clang_release_bot_x64',
'linux_rel': 'release_bot_x64',
@@ -202,6 +204,8 @@
'mac_rel_m1': 'release_bot_arm64',
# Windows
+ 'win11_debug': 'win_clang_debug_bot_x64',
+ 'win11_release': 'win_clang_release_bot_x64',
'win_asan': 'win_asan_clang_release_bot_x64',
'win_compile_x64_clang_dbg': 'win_clang_debug_bot_x64',
'win_compile_x64_clang_rel': 'win_clang_release_bot_x64',
@@ -216,6 +220,7 @@
'dummy_audio_file_devices_no_protobuf':
'dummy_audio_file_devices_no_protobuf_x86',
'rtti_no_sctp': 'rtti_no_sctp_no_unicode_win_x86',
+ 'disable_trace_events': 'disable_trace_events_x86',
},
}
},
@@ -300,6 +305,12 @@
'debug_bot_arm64': ['openh264', 'debug_bot', 'arm64', 'h265'],
'debug_bot_x64': ['openh264', 'debug_bot', 'x64', 'h265'],
'debug_bot_x86': ['openh264', 'debug_bot', 'x86', 'h265'],
+ 'disable_trace_events_android_arm':
+ ['android', 'arm', 'disable_trace_events', 'release_bot'],
+ 'disable_trace_events_x64':
+ ['x64', 'disable_trace_events', 'release_bot'],
+ 'disable_trace_events_x86':
+ ['x86', 'disable_trace_events', 'release_bot'],
'dummy_audio_file_devices_no_protobuf_android_arm': [
'android', 'debug_static_bot', 'arm', 'dummy_audio_file_devices',
'no_protobuf'
@@ -449,6 +460,9 @@
'debug_static_bot': {
'mixins': ['debug', 'minimal_symbols', 'reclient', 'strict_field_trials'],
},
+ 'disable_trace_events': {
+ 'gn_args': 'rtc_disable_trace_events=true',
+ },
'dummy_audio_file_devices': {
'gn_args': 'rtc_use_dummy_audio_file_devices=true',
},
diff --git a/third_party/libwebrtc/video/BUILD.gn b/third_party/libwebrtc/video/BUILD.gn
index 0a930053c0..2d6d8ab10c 100644
--- a/third_party/libwebrtc/video/BUILD.gn
+++ b/third_party/libwebrtc/video/BUILD.gn
@@ -65,8 +65,6 @@ rtc_library("video") {
"video_quality_observer2.h",
"video_receive_stream2.cc",
"video_receive_stream2.h",
- "video_send_stream.cc",
- "video_send_stream.h",
"video_send_stream_impl.cc",
"video_send_stream_impl.h",
"video_stream_decoder2.cc",
@@ -82,18 +80,22 @@ rtc_library("video") {
":video_stream_encoder_impl",
":video_stream_encoder_interface",
"../api:array_view",
+ "../api:bitrate_allocation",
"../api:fec_controller_api",
"../api:field_trials_view",
"../api:frame_transformer_interface",
"../api:rtp_parameters",
+ "../api:rtp_sender_interface",
"../api:scoped_refptr",
"../api:sequence_checker",
"../api:transport_api",
+ "../api/adaptation:resource_adaptation_api",
"../api/crypto:frame_decryptor_interface",
"../api/crypto:options",
+ "../api/environment",
+ "../api/metronome",
"../api/task_queue",
"../api/task_queue:pending_task_safety_flag",
- "../api/transport:field_trial_based_config",
"../api/units:data_rate",
"../api/units:frequency",
"../api/units:time_delta",
@@ -104,6 +106,8 @@ rtc_library("video") {
"../api/video:video_bitrate_allocator",
"../api/video:video_codec_constants",
"../api/video:video_frame",
+ "../api/video:video_frame_type",
+ "../api/video:video_layers_allocation",
"../api/video:video_rtp_headers",
"../api/video:video_stream_encoder",
"../api/video_codecs:video_codecs_api",
@@ -141,7 +145,6 @@ rtc_library("video") {
"../rtc_base:rate_tracker",
"../rtc_base:rtc_event",
"../rtc_base:rtc_numerics",
- "../rtc_base:rtc_task_queue",
"../rtc_base:safe_conversions",
"../rtc_base:sample_counter",
"../rtc_base:stringutils",
@@ -230,6 +233,7 @@ rtc_library("frame_cadence_adapter") {
deps = [
"../api:field_trials_view",
"../api:sequence_checker",
+ "../api/metronome",
"../api/task_queue",
"../api/task_queue:pending_task_safety_flag",
"../api/units:time_delta",
@@ -253,6 +257,7 @@ rtc_library("frame_cadence_adapter") {
absl_deps = [
"//third_party/abseil-cpp/absl/algorithm:container",
"//third_party/abseil-cpp/absl/base:core_headers",
+ "//third_party/abseil-cpp/absl/cleanup:cleanup",
]
}
@@ -444,7 +449,6 @@ rtc_library("video_stream_encoder_impl") {
"../rtc_base:refcount",
"../rtc_base:rtc_event",
"../rtc_base:rtc_numerics",
- "../rtc_base:rtc_task_queue",
"../rtc_base:safe_conversions",
"../rtc_base:stringutils",
"../rtc_base:timeutils",
@@ -816,6 +820,8 @@ if (rtc_include_tests) {
":video_stream_buffer_controller",
":video_stream_encoder_impl",
":video_stream_encoder_interface",
+ "../api:array_view",
+ "../api:bitrate_allocation",
"../api:create_frame_generator",
"../api:fake_frame_decryptor",
"../api:fake_frame_encryptor",
@@ -835,6 +841,7 @@ if (rtc_include_tests) {
"../api:time_controller",
"../api:transport_api",
"../api/adaptation:resource_adaptation_api",
+ "../api/adaptation:resource_adaptation_api",
"../api/crypto:options",
"../api/environment",
"../api/environment:environment_factory",
@@ -856,11 +863,13 @@ if (rtc_include_tests) {
"../api/video:video_bitrate_allocation",
"../api/video:video_frame",
"../api/video:video_frame_type",
+ "../api/video:video_layers_allocation",
"../api/video:video_rtp_headers",
"../api/video/test:video_frame_matchers",
"../api/video_codecs:scalability_mode",
"../api/video_codecs:video_codecs_api",
"../api/video_codecs:vp8_temporal_layers_factory",
+ "../call:bitrate_allocator",
"../call:call_interfaces",
"../call:fake_network",
"../call:mock_bitrate_allocator",
@@ -917,7 +926,6 @@ if (rtc_include_tests) {
"../rtc_base:rtc_base_tests_utils",
"../rtc_base:rtc_event",
"../rtc_base:rtc_numerics",
- "../rtc_base:rtc_task_queue",
"../rtc_base:safe_conversions",
"../rtc_base:stringutils",
"../rtc_base:task_queue_for_test",
diff --git a/third_party/libwebrtc/video/config/simulcast.cc b/third_party/libwebrtc/video/config/simulcast.cc
index 2bd4ac04c3..7a78ef8d05 100644
--- a/third_party/libwebrtc/video/config/simulcast.cc
+++ b/third_party/libwebrtc/video/config/simulcast.cc
@@ -350,10 +350,9 @@ std::vector<webrtc::VideoStream> GetNormalSimulcastLayers(
bool base_heavy_tl3_rate_alloc,
const webrtc::FieldTrialsView& trials) {
std::vector<webrtc::VideoStream> layers(layer_count);
-
const bool enable_lowres_bitrate_interpolation =
EnableLowresBitrateInterpolation(trials);
-
+ const int num_temporal_layers = DefaultNumberOfTemporalLayers(trials);
// Format width and height has to be divisible by |2 ^ num_simulcast_layers -
// 1|.
width = NormalizeSimulcastSize(width, layer_count);
@@ -366,7 +365,7 @@ std::vector<webrtc::VideoStream> GetNormalSimulcastLayers(
// TODO(pbos): Fill actual temporal-layer bitrate thresholds.
layers[s].max_qp = max_qp;
layers[s].num_temporal_layers =
- temporal_layers_supported ? DefaultNumberOfTemporalLayers(trials) : 1;
+ temporal_layers_supported ? num_temporal_layers : 1;
layers[s].max_bitrate_bps =
FindSimulcastMaxBitrate(width, height,
enable_lowres_bitrate_interpolation)
@@ -375,7 +374,6 @@ std::vector<webrtc::VideoStream> GetNormalSimulcastLayers(
FindSimulcastTargetBitrate(width, height,
enable_lowres_bitrate_interpolation)
.bps();
- int num_temporal_layers = DefaultNumberOfTemporalLayers(trials);
if (s == 0) {
// If alternative temporal rate allocation is selected, adjust the
// bitrate of the lowest simulcast stream so that absolute bitrate for
diff --git a/third_party/libwebrtc/video/frame_cadence_adapter.cc b/third_party/libwebrtc/video/frame_cadence_adapter.cc
index 2c4acdd6c2..4aea1acec6 100644
--- a/third_party/libwebrtc/video/frame_cadence_adapter.cc
+++ b/third_party/libwebrtc/video/frame_cadence_adapter.cc
@@ -19,6 +19,7 @@
#include "absl/algorithm/container.h"
#include "absl/base/attributes.h"
+#include "absl/cleanup/cleanup.h"
#include "api/sequence_checker.h"
#include "api/task_queue/pending_task_safety_flag.h"
#include "api/task_queue/task_queue_base.h"
@@ -102,7 +103,9 @@ class ZeroHertzAdapterMode : public AdapterMode {
ZeroHertzAdapterMode(TaskQueueBase* queue,
Clock* clock,
FrameCadenceAdapterInterface::Callback* callback,
- double max_fps);
+ double max_fps,
+ std::atomic<int>& frames_scheduled_for_processing,
+ bool zero_hertz_queue_overload);
~ZeroHertzAdapterMode() { refresh_frame_requester_.Stop(); }
// Reconfigures according to parameters.
@@ -189,12 +192,20 @@ class ZeroHertzAdapterMode : public AdapterMode {
// have arrived.
void ProcessRepeatedFrameOnDelayedCadence(int frame_id)
RTC_RUN_ON(sequence_checker_);
- // Sends a frame, updating the timestamp to the current time.
- void SendFrameNow(Timestamp post_time, const VideoFrame& frame) const
- RTC_RUN_ON(sequence_checker_);
+ // Sends a frame, updating the timestamp to the current time. Also updates
+ // `queue_overload_count_` based on the time it takes to encode a frame and
+ // the amount of received frames while encoding. The `queue_overload`
+ // parameter in the OnFrame callback will be true while
+ // `queue_overload_count_` is larger than zero to allow the client to drop
+ // frames and thereby mitigate delay buildups.
+ // Repeated frames are sent with `post_time` set to absl::nullopt.
+ void SendFrameNow(absl::optional<Timestamp> post_time,
+ const VideoFrame& frame) RTC_RUN_ON(sequence_checker_);
// Returns the repeat duration depending on if it's an idle repeat or not.
TimeDelta RepeatDuration(bool idle_repeat) const
RTC_RUN_ON(sequence_checker_);
+ // Returns the frame duration taking potential restrictions into account.
+ TimeDelta FrameDuration() const RTC_RUN_ON(sequence_checker_);
// Unless timer already running, starts repeatedly requesting refresh frames
// after a grace_period. If a frame appears before the grace_period has
// passed, the request is cancelled.
@@ -207,6 +218,14 @@ class ZeroHertzAdapterMode : public AdapterMode {
// The configured max_fps.
// TODO(crbug.com/1255737): support max_fps updates.
const double max_fps_;
+
+ // Number of frames that are currently scheduled for processing on the
+ // `queue_`.
+ const std::atomic<int>& frames_scheduled_for_processing_;
+
+ // Can be used as kill-switch for the queue overload mechanism.
+ const bool zero_hertz_queue_overload_enabled_;
+
// How much the incoming frame sequence is delayed by.
const TimeDelta frame_delay_ = TimeDelta::Seconds(1) / max_fps_;
@@ -230,14 +249,88 @@ class ZeroHertzAdapterMode : public AdapterMode {
// the max frame rate.
absl::optional<TimeDelta> restricted_frame_delay_
RTC_GUARDED_BY(sequence_checker_);
+ // Set in OnSendFrame to reflect how many future frames will be forwarded with
+ // the `queue_overload` flag set to true.
+ int queue_overload_count_ RTC_GUARDED_BY(sequence_checker_) = 0;
ScopedTaskSafety safety_;
};
+// Implements a frame cadence adapter supporting VSync aligned encoding.
+class VSyncEncodeAdapterMode : public AdapterMode {
+ public:
+ VSyncEncodeAdapterMode(
+ Clock* clock,
+ TaskQueueBase* queue,
+ rtc::scoped_refptr<PendingTaskSafetyFlag> queue_safety_flag,
+ Metronome* metronome,
+ TaskQueueBase* worker_queue,
+ FrameCadenceAdapterInterface::Callback* callback)
+ : clock_(clock),
+ queue_(queue),
+ queue_safety_flag_(queue_safety_flag),
+ callback_(callback),
+ metronome_(metronome),
+ worker_queue_(worker_queue) {
+ queue_sequence_checker_.Detach();
+ worker_sequence_checker_.Detach();
+ }
+
+ // Adapter overrides.
+ void OnFrame(Timestamp post_time,
+ bool queue_overload,
+ const VideoFrame& frame) override;
+
+ absl::optional<uint32_t> GetInputFrameRateFps() override {
+ RTC_DCHECK_RUN_ON(&queue_sequence_checker_);
+ return input_framerate_.Rate(clock_->TimeInMilliseconds());
+ }
+
+ void UpdateFrameRate() override {
+ RTC_DCHECK_RUN_ON(&queue_sequence_checker_);
+ input_framerate_.Update(1, clock_->TimeInMilliseconds());
+ }
+
+ void EncodeAllEnqueuedFrames();
+
+ private:
+ // Holds input frames coming from the client ready to be encoded.
+ struct InputFrameRef {
+ InputFrameRef(const VideoFrame& video_frame, Timestamp time_when_posted_us)
+ : time_when_posted_us(time_when_posted_us),
+ video_frame(std::move(video_frame)) {}
+ Timestamp time_when_posted_us;
+ const VideoFrame video_frame;
+ };
+
+ Clock* const clock_;
+ TaskQueueBase* queue_;
+ RTC_NO_UNIQUE_ADDRESS SequenceChecker queue_sequence_checker_;
+ rtc::scoped_refptr<PendingTaskSafetyFlag> queue_safety_flag_;
+ // Input frame rate statistics for use when not in zero-hertz mode.
+ RateStatistics input_framerate_ RTC_GUARDED_BY(queue_sequence_checker_){
+ FrameCadenceAdapterInterface::kFrameRateAveragingWindowSizeMs, 1000};
+ FrameCadenceAdapterInterface::Callback* const callback_;
+
+ Metronome* metronome_;
+ TaskQueueBase* const worker_queue_;
+ RTC_NO_UNIQUE_ADDRESS SequenceChecker worker_sequence_checker_;
+ // `worker_safety_` protects tasks on the worker queue related to `metronome_`
+ // since metronome usage must happen on worker thread.
+ ScopedTaskSafetyDetached worker_safety_;
+ Timestamp expected_next_tick_ RTC_GUARDED_BY(worker_sequence_checker_) =
+ Timestamp::PlusInfinity();
+ // Vector of input frames to be encoded.
+ std::vector<InputFrameRef> input_queue_
+ RTC_GUARDED_BY(worker_sequence_checker_);
+};
+
class FrameCadenceAdapterImpl : public FrameCadenceAdapterInterface {
public:
FrameCadenceAdapterImpl(Clock* clock,
TaskQueueBase* queue,
+ Metronome* metronome,
+ TaskQueueBase* worker_queue,
const FieldTrialsView& field_trials);
~FrameCadenceAdapterImpl();
@@ -273,6 +366,10 @@ class FrameCadenceAdapterImpl : public FrameCadenceAdapterInterface {
// - zero-hertz mode enabled
bool IsZeroHertzScreenshareEnabled() const RTC_RUN_ON(queue_);
+ // Configures current adapter on non-ZeroHertz mode, called when Initialize or
+ // MaybeReconfigureAdapters.
+ void ConfigureCurrentAdapterWithoutZeroHertz();
+
// Handles adapter creation on configuration changes.
void MaybeReconfigureAdapters(bool was_zero_hertz_enabled) RTC_RUN_ON(queue_);
@@ -283,15 +380,24 @@ class FrameCadenceAdapterImpl : public FrameCadenceAdapterInterface {
// 0 Hz.
const bool zero_hertz_screenshare_enabled_;
- // The two possible modes we're under.
+ // Kill-switch for the queue overload mechanism in zero-hertz mode.
+ const bool frame_cadence_adapter_zero_hertz_queue_overload_enabled_;
+
+ // The three possible modes we're under.
absl::optional<PassthroughAdapterMode> passthrough_adapter_;
absl::optional<ZeroHertzAdapterMode> zero_hertz_adapter_;
+ // The `vsync_encode_adapter_` must be destroyed on the worker queue since
+ // VSync metronome needs to happen on worker thread.
+ std::unique_ptr<VSyncEncodeAdapterMode> vsync_encode_adapter_;
// If set, zero-hertz mode has been enabled.
absl::optional<ZeroHertzModeParams> zero_hertz_params_;
- std::atomic<bool> zero_hertz_adapter_is_active_{false};
// Cache for the current adapter mode.
AdapterMode* current_adapter_mode_ = nullptr;
+ // VSync encoding is used when this valid.
+ Metronome* const metronome_;
+ TaskQueueBase* const worker_queue_;
+
// Timestamp for statistics reporting.
absl::optional<Timestamp> zero_hertz_adapter_created_timestamp_
RTC_GUARDED_BY(queue_);
@@ -323,8 +429,15 @@ ZeroHertzAdapterMode::ZeroHertzAdapterMode(
TaskQueueBase* queue,
Clock* clock,
FrameCadenceAdapterInterface::Callback* callback,
- double max_fps)
- : queue_(queue), clock_(clock), callback_(callback), max_fps_(max_fps) {
+ double max_fps,
+ std::atomic<int>& frames_scheduled_for_processing,
+ bool zero_hertz_queue_overload_enabled)
+ : queue_(queue),
+ clock_(clock),
+ callback_(callback),
+ max_fps_(max_fps),
+ frames_scheduled_for_processing_(frames_scheduled_for_processing),
+ zero_hertz_queue_overload_enabled_(zero_hertz_queue_overload_enabled) {
sequence_checker_.Detach();
MaybeStartRefreshFrameRequester();
}
@@ -391,22 +504,13 @@ void ZeroHertzAdapterMode::OnFrame(Timestamp post_time,
// Store the frame in the queue and schedule deferred processing.
queued_frames_.push_back(frame);
- int frame_id = current_frame_id_;
current_frame_id_++;
scheduled_repeat_ = absl::nullopt;
TimeDelta time_spent_since_post = clock_->CurrentTime() - post_time;
- TRACE_EVENT_ASYNC_BEGIN0(TRACE_DISABLED_BY_DEFAULT("webrtc"), "QueueToEncode",
- frame_id);
queue_->PostDelayedHighPrecisionTask(
SafeTask(safety_.flag(),
- [this, post_time, frame_id, frame] {
- RTC_UNUSED(frame_id);
+ [this, post_time] {
RTC_DCHECK_RUN_ON(&sequence_checker_);
- TRACE_EVENT_ASYNC_END0(TRACE_DISABLED_BY_DEFAULT("webrtc"),
- "QueueToEncode", frame_id);
- TRACE_EVENT_ASYNC_END0(TRACE_DISABLED_BY_DEFAULT("webrtc"),
- "OnFrameToEncode",
- frame.video_frame_buffer().get());
ProcessOnDelayedCadence(post_time);
}),
std::max(frame_delay_ - time_spent_since_post, TimeDelta::Zero()));
@@ -582,36 +686,70 @@ void ZeroHertzAdapterMode::ProcessRepeatedFrameOnDelayedCadence(int frame_id) {
// Schedule another repeat before sending the frame off which could take time.
ScheduleRepeat(frame_id, HasQualityConverged());
- // Mark `post_time` with 0 to signal that this is a repeated frame.
- SendFrameNow(Timestamp::Zero(), frame);
+ SendFrameNow(absl::nullopt, frame);
}
-void ZeroHertzAdapterMode::SendFrameNow(Timestamp post_time,
- const VideoFrame& frame) const {
+void ZeroHertzAdapterMode::SendFrameNow(absl::optional<Timestamp> post_time,
+ const VideoFrame& frame) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
TRACE_EVENT0("webrtc", __func__);
- Timestamp now = clock_->CurrentTime();
- // Exclude repeated frames which are marked with zero as post time.
- if (post_time != Timestamp::Zero()) {
- TimeDelta delay = (now - post_time);
+
+ Timestamp encode_start_time = clock_->CurrentTime();
+ if (post_time.has_value()) {
+ TimeDelta delay = (encode_start_time - *post_time);
RTC_HISTOGRAM_COUNTS_10000("WebRTC.Screenshare.ZeroHz.DelayMs", delay.ms());
}
- // TODO(crbug.com/1255737): ensure queue_overload is computed from current
- // conditions on the encoder queue.
- callback_->OnFrame(/*post_time=*/now,
- /*queue_overload=*/false, frame);
+
+ // Forward the frame and set `queue_overload` if is has been detected that it
+ // is not possible to deliver frames at the expected rate due to slow
+ // encoding.
+ callback_->OnFrame(/*post_time=*/encode_start_time, queue_overload_count_ > 0,
+ frame);
+
+ // WebRTC-ZeroHertzQueueOverload kill-switch.
+ if (!zero_hertz_queue_overload_enabled_)
+ return;
+
+ // `queue_overload_count_` determines for how many future frames the
+ // `queue_overload` flag will be set and it is only increased if:
+ // o We are not already in an overload state.
+ // o New frames have been scheduled for processing on the queue while encoding
+ // took place in OnFrame.
+ // o The duration of OnFrame is longer than the current frame duration.
+ // If all these conditions are fulfilled, `queue_overload_count_` is set to
+ // `frames_scheduled_for_processing_` and any pending repeat is canceled since
+ // new frames are available and the repeat is not needed.
+ // If the adapter is already in an overload state, simply decrease
+ // `queue_overload_count_` by one.
+ if (queue_overload_count_ == 0) {
+ const int frames_scheduled_for_processing =
+ frames_scheduled_for_processing_.load(std::memory_order_relaxed);
+ if (frames_scheduled_for_processing > 0) {
+ TimeDelta encode_time = clock_->CurrentTime() - encode_start_time;
+ if (encode_time > FrameDuration()) {
+ queue_overload_count_ = frames_scheduled_for_processing;
+ // Invalidates any outstanding repeat to avoid sending pending repeat
+ // directly after too long encode.
+ current_frame_id_++;
+ }
+ }
+ } else {
+ queue_overload_count_--;
+ }
+ RTC_HISTOGRAM_BOOLEAN("WebRTC.Screenshare.ZeroHz.QueueOverload",
+ queue_overload_count_ > 0);
+}
+
+TimeDelta ZeroHertzAdapterMode::FrameDuration() const {
+ RTC_DCHECK_RUN_ON(&sequence_checker_);
+ return std::max(frame_delay_, restricted_frame_delay_.value_or(frame_delay_));
}
TimeDelta ZeroHertzAdapterMode::RepeatDuration(bool idle_repeat) const {
RTC_DCHECK_RUN_ON(&sequence_checker_);
- // By default use `frame_delay_` in non-idle repeat mode but use the
- // restricted frame delay instead if it is set in
- // UpdateVideoSourceRestrictions.
- TimeDelta frame_delay =
- std::max(frame_delay_, restricted_frame_delay_.value_or(frame_delay_));
return idle_repeat
? FrameCadenceAdapterInterface::kZeroHertzIdleRepeatRatePeriod
- : frame_delay;
+ : FrameDuration();
}
void ZeroHertzAdapterMode::MaybeStartRefreshFrameRequester() {
@@ -630,23 +768,100 @@ void ZeroHertzAdapterMode::MaybeStartRefreshFrameRequester() {
}
}
+void VSyncEncodeAdapterMode::OnFrame(Timestamp post_time,
+ bool queue_overload,
+ const VideoFrame& frame) {
+ // We expect `metronome_` and `EncodeAllEnqueuedFrames()` runs on
+ // `worker_queue_`.
+ if (!worker_queue_->IsCurrent()) {
+ worker_queue_->PostTask(SafeTask(
+ worker_safety_.flag(), [this, post_time, queue_overload, frame] {
+ OnFrame(post_time, queue_overload, frame);
+ }));
+ return;
+ }
+
+ RTC_DCHECK_RUN_ON(&worker_sequence_checker_);
+ TRACE_EVENT0("webrtc", "VSyncEncodeAdapterMode::OnFrame");
+
+ input_queue_.emplace_back(std::move(frame), post_time);
+
+ // The `metronome_` tick period maybe throttled in some case, so here we only
+ // align encode task to VSync event when `metronome_` tick period is less
+ // than 34ms (30Hz).
+ static constexpr TimeDelta kMaxAllowedDelay = TimeDelta::Millis(34);
+ if (metronome_->TickPeriod() <= kMaxAllowedDelay) {
+ // The metronome is ticking frequently enough that it is worth the extra
+ // delay.
+ metronome_->RequestCallOnNextTick(
+ SafeTask(worker_safety_.flag(), [this] { EncodeAllEnqueuedFrames(); }));
+ } else {
+ // The metronome is ticking too infrequently, encode immediately.
+ EncodeAllEnqueuedFrames();
+ }
+}
+
+void VSyncEncodeAdapterMode::EncodeAllEnqueuedFrames() {
+ RTC_DCHECK_RUN_ON(&worker_sequence_checker_);
+ TRACE_EVENT0("webrtc", "VSyncEncodeAdapterMode::EncodeAllEnqueuedFrames");
+
+ // Local time in webrtc time base.
+ Timestamp post_time = clock_->CurrentTime();
+
+ for (auto& input : input_queue_) {
+ TRACE_EVENT1("webrtc", "FrameCadenceAdapterImpl::EncodeAllEnqueuedFrames",
+ "VSyncEncodeDelay",
+ (post_time - input.time_when_posted_us).ms());
+
+ const VideoFrame frame = std::move(input.video_frame);
+ queue_->PostTask(SafeTask(queue_safety_flag_, [this, post_time, frame] {
+ RTC_DCHECK_RUN_ON(queue_);
+
+ // TODO(b/304158952): Support more refined queue overload control.
+ callback_->OnFrame(post_time, /*queue_overload=*/false, frame);
+ }));
+ }
+
+ input_queue_.clear();
+}
+
FrameCadenceAdapterImpl::FrameCadenceAdapterImpl(
Clock* clock,
TaskQueueBase* queue,
+ Metronome* metronome,
+ TaskQueueBase* worker_queue,
const FieldTrialsView& field_trials)
: clock_(clock),
queue_(queue),
zero_hertz_screenshare_enabled_(
- !field_trials.IsDisabled("WebRTC-ZeroHertzScreenshare")) {}
+ !field_trials.IsDisabled("WebRTC-ZeroHertzScreenshare")),
+ frame_cadence_adapter_zero_hertz_queue_overload_enabled_(
+ !field_trials.IsDisabled("WebRTC-ZeroHertzQueueOverload")),
+ metronome_(metronome),
+ worker_queue_(worker_queue) {}
FrameCadenceAdapterImpl::~FrameCadenceAdapterImpl() {
RTC_DLOG(LS_VERBOSE) << __func__ << " this " << this;
+
+ // VSync adapter needs to be destroyed on worker queue when metronome is
+ // valid.
+ if (metronome_) {
+ absl::Cleanup cleanup = [adapter = std::move(vsync_encode_adapter_)] {};
+ worker_queue_->PostTask([cleanup = std::move(cleanup)] {});
+ }
}
void FrameCadenceAdapterImpl::Initialize(Callback* callback) {
callback_ = callback;
- passthrough_adapter_.emplace(clock_, callback);
- current_adapter_mode_ = &passthrough_adapter_.value();
+ // Use VSync encode mode if metronome is valid, otherwise passthrough mode
+ // would be used.
+ if (metronome_) {
+ vsync_encode_adapter_ = std::make_unique<VSyncEncodeAdapterMode>(
+ clock_, queue_, safety_.flag(), metronome_, worker_queue_, callback_);
+ } else {
+ passthrough_adapter_.emplace(clock_, callback);
+ }
+ ConfigureCurrentAdapterWithoutZeroHertz();
}
void FrameCadenceAdapterImpl::SetZeroHertzModeEnabled(
@@ -665,9 +880,16 @@ absl::optional<uint32_t> FrameCadenceAdapterImpl::GetInputFrameRateFps() {
void FrameCadenceAdapterImpl::UpdateFrameRate() {
RTC_DCHECK_RUN_ON(queue_);
// The frame rate need not be updated for the zero-hertz adapter. The
- // passthrough adapter however uses it. Always pass frames into the
- // passthrough to keep the estimation alive should there be an adapter switch.
- passthrough_adapter_->UpdateFrameRate();
+ // vsync encode and passthrough adapter however uses it. Always pass frames
+ // into the vsync encode or passthrough to keep the estimation alive should
+ // there be an adapter switch.
+ if (metronome_) {
+ RTC_CHECK(vsync_encode_adapter_);
+ vsync_encode_adapter_->UpdateFrameRate();
+ } else {
+ RTC_CHECK(passthrough_adapter_);
+ passthrough_adapter_->UpdateFrameRate();
+ }
}
void FrameCadenceAdapterImpl::UpdateLayerQualityConvergence(
@@ -710,21 +932,8 @@ void FrameCadenceAdapterImpl::OnFrame(const VideoFrame& frame) {
// Local time in webrtc time base.
Timestamp post_time = clock_->CurrentTime();
frames_scheduled_for_processing_.fetch_add(1, std::memory_order_relaxed);
- if (zero_hertz_adapter_is_active_.load(std::memory_order_relaxed)) {
- TRACE_EVENT_ASYNC_BEGIN0(TRACE_DISABLED_BY_DEFAULT("webrtc"),
- "OnFrameToEncode",
- frame.video_frame_buffer().get());
- TRACE_EVENT_ASYNC_BEGIN0(TRACE_DISABLED_BY_DEFAULT("webrtc"),
- "OnFrameToQueue",
- frame.video_frame_buffer().get());
- }
queue_->PostTask(SafeTask(safety_.flag(), [this, post_time, frame] {
RTC_DCHECK_RUN_ON(queue_);
- if (zero_hertz_adapter_is_active_.load(std::memory_order_relaxed)) {
- TRACE_EVENT_ASYNC_END0(TRACE_DISABLED_BY_DEFAULT("webrtc"),
- "OnFrameToQueue",
- frame.video_frame_buffer().get());
- }
if (zero_hertz_adapter_created_timestamp_.has_value()) {
TimeDelta time_until_first_frame =
clock_->CurrentTime() - *zero_hertz_adapter_created_timestamp_;
@@ -780,6 +989,17 @@ bool FrameCadenceAdapterImpl::IsZeroHertzScreenshareEnabled() const {
zero_hertz_params_.has_value();
}
+void FrameCadenceAdapterImpl::ConfigureCurrentAdapterWithoutZeroHertz() {
+ // Enable VSyncEncodeAdapterMode if metronome is valid.
+ if (metronome_) {
+ RTC_CHECK(vsync_encode_adapter_);
+ current_adapter_mode_ = vsync_encode_adapter_.get();
+ } else {
+ RTC_CHECK(passthrough_adapter_);
+ current_adapter_mode_ = &passthrough_adapter_.value();
+ }
+}
+
void FrameCadenceAdapterImpl::MaybeReconfigureAdapters(
bool was_zero_hertz_enabled) {
RTC_DCHECK_RUN_ON(queue_);
@@ -790,8 +1010,10 @@ void FrameCadenceAdapterImpl::MaybeReconfigureAdapters(
if (!was_zero_hertz_enabled || max_fps_has_changed) {
RTC_LOG(LS_INFO) << "Zero hertz mode enabled (max_fps="
<< source_constraints_->max_fps.value() << ")";
- zero_hertz_adapter_.emplace(queue_, clock_, callback_,
- source_constraints_->max_fps.value());
+ zero_hertz_adapter_.emplace(
+ queue_, clock_, callback_, source_constraints_->max_fps.value(),
+ frames_scheduled_for_processing_,
+ frame_cadence_adapter_zero_hertz_queue_overload_enabled_);
zero_hertz_adapter_->UpdateVideoSourceRestrictions(
restricted_max_frame_rate_);
zero_hertz_adapter_created_timestamp_ = clock_->CurrentTime();
@@ -801,10 +1023,9 @@ void FrameCadenceAdapterImpl::MaybeReconfigureAdapters(
} else {
if (was_zero_hertz_enabled) {
zero_hertz_adapter_ = absl::nullopt;
- zero_hertz_adapter_is_active_.store(false, std::memory_order_relaxed);
RTC_LOG(LS_INFO) << "Zero hertz mode disabled.";
}
- current_adapter_mode_ = &passthrough_adapter_.value();
+ ConfigureCurrentAdapterWithoutZeroHertz();
}
}
@@ -813,8 +1034,11 @@ void FrameCadenceAdapterImpl::MaybeReconfigureAdapters(
std::unique_ptr<FrameCadenceAdapterInterface>
FrameCadenceAdapterInterface::Create(Clock* clock,
TaskQueueBase* queue,
+ Metronome* metronome,
+ TaskQueueBase* worker_queue,
const FieldTrialsView& field_trials) {
- return std::make_unique<FrameCadenceAdapterImpl>(clock, queue, field_trials);
+ return std::make_unique<FrameCadenceAdapterImpl>(clock, queue, metronome,
+ worker_queue, field_trials);
}
} // namespace webrtc
diff --git a/third_party/libwebrtc/video/frame_cadence_adapter.h b/third_party/libwebrtc/video/frame_cadence_adapter.h
index 2b62bb26cd..ec8e667b04 100644
--- a/third_party/libwebrtc/video/frame_cadence_adapter.h
+++ b/third_party/libwebrtc/video/frame_cadence_adapter.h
@@ -15,6 +15,7 @@
#include "absl/base/attributes.h"
#include "api/field_trials_view.h"
+#include "api/metronome/metronome.h"
#include "api/task_queue/task_queue_base.h"
#include "api/units/time_delta.h"
#include "api/video/video_frame.h"
@@ -81,6 +82,8 @@ class FrameCadenceAdapterInterface
static std::unique_ptr<FrameCadenceAdapterInterface> Create(
Clock* clock,
TaskQueueBase* queue,
+ Metronome* metronome,
+ TaskQueueBase* worker_queue,
const FieldTrialsView& field_trials);
// Call before using the rest of the API.
diff --git a/third_party/libwebrtc/video/frame_cadence_adapter_unittest.cc b/third_party/libwebrtc/video/frame_cadence_adapter_unittest.cc
index 0fef2400f0..54548de9bb 100644
--- a/third_party/libwebrtc/video/frame_cadence_adapter_unittest.cc
+++ b/third_party/libwebrtc/video/frame_cadence_adapter_unittest.cc
@@ -14,6 +14,7 @@
#include <vector>
#include "absl/functional/any_invocable.h"
+#include "api/metronome/test/fake_metronome.h"
#include "api/task_queue/default_task_queue_factory.h"
#include "api/task_queue/task_queue_base.h"
#include "api/task_queue/task_queue_factory.h"
@@ -38,9 +39,11 @@ namespace {
using ::testing::_;
using ::testing::ElementsAre;
+using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::InvokeWithoutArgs;
using ::testing::Mock;
+using ::testing::NiceMock;
using ::testing::Pair;
using ::testing::Values;
@@ -64,8 +67,9 @@ VideoFrame CreateFrameWithTimestamps(
std::unique_ptr<FrameCadenceAdapterInterface> CreateAdapter(
const FieldTrialsView& field_trials,
Clock* clock) {
- return FrameCadenceAdapterInterface::Create(clock, TaskQueueBase::Current(),
- field_trials);
+ return FrameCadenceAdapterInterface::Create(
+ clock, TaskQueueBase::Current(), /*metronome=*/nullptr,
+ /*worker_queue=*/nullptr, field_trials);
}
class MockCallback : public FrameCadenceAdapterInterface::Callback {
@@ -308,6 +312,7 @@ TEST(FrameCadenceAdapterTest, DelayedProcessingUnderHeavyContention) {
}));
adapter->OnFrame(CreateFrame());
time_controller.SkipForwardBy(time_skipped);
+ time_controller.AdvanceTime(TimeDelta::Zero());
}
TEST(FrameCadenceAdapterTest, RepeatsFramesDelayed) {
@@ -593,7 +598,8 @@ TEST(FrameCadenceAdapterTest, IgnoresDropInducedCallbacksPostDestruction) {
auto queue = time_controller.GetTaskQueueFactory()->CreateTaskQueue(
"queue", TaskQueueFactory::Priority::NORMAL);
auto adapter = FrameCadenceAdapterInterface::Create(
- time_controller.GetClock(), queue.get(), enabler);
+ time_controller.GetClock(), queue.get(), /*metronome=*/nullptr,
+ /*worker_queue=*/nullptr, enabler);
queue->PostTask([&adapter, &callback] {
adapter->Initialize(callback.get());
adapter->SetZeroHertzModeEnabled(
@@ -609,6 +615,82 @@ TEST(FrameCadenceAdapterTest, IgnoresDropInducedCallbacksPostDestruction) {
time_controller.AdvanceTime(3 * TimeDelta::Seconds(1) / kMaxFps);
}
+TEST(FrameCadenceAdapterTest, EncodeFramesAreAlignedWithMetronomeTick) {
+ ZeroHertzFieldTrialEnabler enabler;
+ GlobalSimulatedTimeController time_controller(Timestamp::Zero());
+ // Here the metronome interval is 33ms, because the metronome is not
+ // infrequent then the encode tasks are aligned with the tick period.
+ static constexpr TimeDelta kTickPeriod = TimeDelta::Millis(33);
+ auto queue = time_controller.GetTaskQueueFactory()->CreateTaskQueue(
+ "queue", TaskQueueFactory::Priority::NORMAL);
+ auto worker_queue = time_controller.GetTaskQueueFactory()->CreateTaskQueue(
+ "work_queue", TaskQueueFactory::Priority::NORMAL);
+ static test::FakeMetronome metronome(kTickPeriod);
+ auto adapter = FrameCadenceAdapterInterface::Create(
+ time_controller.GetClock(), queue.get(), &metronome, worker_queue.get(),
+ enabler);
+ MockCallback callback;
+ adapter->Initialize(&callback);
+ auto frame = CreateFrame();
+
+ // `callback->OnFrame()` would not be called if only 32ms went by after
+ // `adapter->OnFrame()`.
+ EXPECT_CALL(callback, OnFrame(_, false, _)).Times(0);
+ adapter->OnFrame(frame);
+ time_controller.AdvanceTime(TimeDelta::Millis(32));
+ Mock::VerifyAndClearExpectations(&callback);
+
+ // `callback->OnFrame()` should be called if 33ms went by after
+ // `adapter->OnFrame()`.
+ EXPECT_CALL(callback, OnFrame(_, false, _)).Times(1);
+ time_controller.AdvanceTime(TimeDelta::Millis(1));
+ Mock::VerifyAndClearExpectations(&callback);
+
+ // `callback->OnFrame()` would not be called if only 32ms went by after
+ // `adapter->OnFrame()`.
+ EXPECT_CALL(callback, OnFrame(_, false, _)).Times(0);
+ // Send two frame before next tick.
+ adapter->OnFrame(frame);
+ adapter->OnFrame(frame);
+ time_controller.AdvanceTime(TimeDelta::Millis(32));
+ Mock::VerifyAndClearExpectations(&callback);
+
+ // `callback->OnFrame()` should be called if 33ms went by after
+ // `adapter->OnFrame()`.
+ EXPECT_CALL(callback, OnFrame(_, false, _)).Times(2);
+ time_controller.AdvanceTime(TimeDelta::Millis(1));
+ Mock::VerifyAndClearExpectations(&callback);
+
+ // Change the metronome tick period to 67ms (15Hz).
+ metronome.SetTickPeriod(TimeDelta::Millis(67));
+ // Expect the encode would happen immediately.
+ EXPECT_CALL(callback, OnFrame(_, false, _)).Times(1);
+ adapter->OnFrame(frame);
+ time_controller.AdvanceTime(TimeDelta::Zero());
+ Mock::VerifyAndClearExpectations(&callback);
+
+ // Change the metronome tick period to 16ms (60Hz).
+ metronome.SetTickPeriod(TimeDelta::Millis(16));
+ // Expect the encode would not happen if only 15ms went by after
+ // `adapter->OnFrame()`.
+ EXPECT_CALL(callback, OnFrame(_, false, _)).Times(0);
+ adapter->OnFrame(frame);
+ time_controller.AdvanceTime(TimeDelta::Millis(15));
+ Mock::VerifyAndClearExpectations(&callback);
+ // `callback->OnFrame()` should be called if 16ms went by after
+ // `adapter->OnFrame()`.
+ EXPECT_CALL(callback, OnFrame(_, false, _)).Times(1);
+ time_controller.AdvanceTime(TimeDelta::Millis(1));
+ Mock::VerifyAndClearExpectations(&callback);
+
+ rtc::Event finalized;
+ queue->PostTask([&] {
+ adapter = nullptr;
+ finalized.Set();
+ });
+ finalized.Wait(rtc::Event::kForever);
+}
+
class FrameCadenceAdapterSimulcastLayersParamTest
: public ::testing::TestWithParam<int> {
public:
@@ -1076,5 +1158,166 @@ TEST(FrameCadenceAdapterRealTimeTest,
finalized.Wait(rtc::Event::kForever);
}
+class ZeroHertzQueueOverloadTest : public ::testing::Test {
+ public:
+ static constexpr int kMaxFps = 10;
+
+ ZeroHertzQueueOverloadTest() {
+ Initialize();
+ metrics::Reset();
+ }
+
+ void Initialize() {
+ adapter_->Initialize(&callback_);
+ adapter_->SetZeroHertzModeEnabled(
+ FrameCadenceAdapterInterface::ZeroHertzModeParams{
+ /*num_simulcast_layers=*/1});
+ adapter_->OnConstraintsChanged(
+ VideoTrackSourceConstraints{/*min_fps=*/0, kMaxFps});
+ time_controller_.AdvanceTime(TimeDelta::Zero());
+ }
+
+ void ScheduleDelayed(TimeDelta delay, absl::AnyInvocable<void() &&> task) {
+ TaskQueueBase::Current()->PostDelayedTask(std::move(task), delay);
+ }
+
+ void PassFrame() { adapter_->OnFrame(CreateFrame()); }
+
+ void AdvanceTime(TimeDelta duration) {
+ time_controller_.AdvanceTime(duration);
+ }
+
+ void SkipForwardBy(TimeDelta duration) {
+ time_controller_.SkipForwardBy(duration);
+ }
+
+ Timestamp CurrentTime() { return time_controller_.GetClock()->CurrentTime(); }
+
+ protected:
+ test::ScopedKeyValueConfig field_trials_;
+ NiceMock<MockCallback> callback_;
+ GlobalSimulatedTimeController time_controller_{Timestamp::Zero()};
+ std::unique_ptr<FrameCadenceAdapterInterface> adapter_{
+ CreateAdapter(field_trials_, time_controller_.GetClock())};
+};
+
+TEST_F(ZeroHertzQueueOverloadTest,
+ ForwardedFramesDuringTooLongEncodeTimeAreFlaggedWithQueueOverload) {
+ InSequence s;
+ PassFrame();
+ EXPECT_CALL(callback_, OnFrame(_, false, _)).WillOnce(InvokeWithoutArgs([&] {
+ PassFrame();
+ PassFrame();
+ PassFrame();
+ SkipForwardBy(TimeDelta::Millis(301));
+ }));
+ EXPECT_CALL(callback_, OnFrame(_, true, _)).Times(3);
+ AdvanceTime(TimeDelta::Millis(100));
+ EXPECT_THAT(metrics::Samples("WebRTC.Screenshare.ZeroHz.QueueOverload"),
+ ElementsAre(Pair(false, 1), Pair(true, 3)));
+}
+
+TEST_F(ZeroHertzQueueOverloadTest,
+ ForwardedFramesAfterOverloadBurstAreNotFlaggedWithQueueOverload) {
+ InSequence s;
+ PassFrame();
+ EXPECT_CALL(callback_, OnFrame(_, false, _)).WillOnce(InvokeWithoutArgs([&] {
+ PassFrame();
+ PassFrame();
+ PassFrame();
+ SkipForwardBy(TimeDelta::Millis(301));
+ }));
+ EXPECT_CALL(callback_, OnFrame(_, true, _)).Times(3);
+ AdvanceTime(TimeDelta::Millis(100));
+ EXPECT_CALL(callback_, OnFrame(_, false, _)).Times(2);
+ PassFrame();
+ PassFrame();
+ AdvanceTime(TimeDelta::Millis(100));
+ EXPECT_THAT(metrics::Samples("WebRTC.Screenshare.ZeroHz.QueueOverload"),
+ ElementsAre(Pair(false, 3), Pair(true, 3)));
+}
+
+TEST_F(ZeroHertzQueueOverloadTest,
+ ForwardedFramesDuringNormalEncodeTimeAreNotFlaggedWithQueueOverload) {
+ InSequence s;
+ PassFrame();
+ EXPECT_CALL(callback_, OnFrame(_, false, _)).WillOnce(InvokeWithoutArgs([&] {
+ PassFrame();
+ PassFrame();
+ PassFrame();
+ // Long but not too long encode time.
+ SkipForwardBy(TimeDelta::Millis(99));
+ }));
+ EXPECT_CALL(callback_, OnFrame(_, false, _)).Times(3);
+ AdvanceTime(TimeDelta::Millis(199));
+ EXPECT_THAT(metrics::Samples("WebRTC.Screenshare.ZeroHz.QueueOverload"),
+ ElementsAre(Pair(false, 4)));
+}
+
+TEST_F(
+ ZeroHertzQueueOverloadTest,
+ AvoidSettingQueueOverloadAndSendRepeatWhenNoNewPacketsWhileTooLongEncode) {
+ // Receive one frame only and let OnFrame take such a long time that an
+ // overload normally is warranted. But the fact that no new frames arrive
+ // while being blocked should trigger a non-idle repeat to ensure that the
+ // video stream does not freeze and queue overload should be false.
+ PassFrame();
+ EXPECT_CALL(callback_, OnFrame(_, false, _))
+ .WillOnce(
+ InvokeWithoutArgs([&] { SkipForwardBy(TimeDelta::Millis(101)); }))
+ .WillOnce(InvokeWithoutArgs([&] {
+ // Non-idle repeat.
+ EXPECT_EQ(CurrentTime(), Timestamp::Zero() + TimeDelta::Millis(201));
+ }));
+ AdvanceTime(TimeDelta::Millis(100));
+ EXPECT_THAT(metrics::Samples("WebRTC.Screenshare.ZeroHz.QueueOverload"),
+ ElementsAre(Pair(false, 2)));
+}
+
+TEST_F(ZeroHertzQueueOverloadTest,
+ EnterFastRepeatAfterQueueOverloadWhenReceivedOnlyOneFrameDuringEncode) {
+ InSequence s;
+ // - Forward one frame frame during high load which triggers queue overload.
+ // - Receive only one new frame while being blocked and verify that the
+ // cancelled repeat was for the first frame and not the second.
+ // - Fast repeat mode should happen after second frame.
+ PassFrame();
+ EXPECT_CALL(callback_, OnFrame(_, false, _)).WillOnce(InvokeWithoutArgs([&] {
+ PassFrame();
+ SkipForwardBy(TimeDelta::Millis(101));
+ }));
+ EXPECT_CALL(callback_, OnFrame(_, true, _));
+ AdvanceTime(TimeDelta::Millis(100));
+
+ // Fast repeats should take place from here on.
+ EXPECT_CALL(callback_, OnFrame(_, false, _)).Times(5);
+ AdvanceTime(TimeDelta::Millis(500));
+ EXPECT_THAT(metrics::Samples("WebRTC.Screenshare.ZeroHz.QueueOverload"),
+ ElementsAre(Pair(false, 6), Pair(true, 1)));
+}
+
+TEST_F(ZeroHertzQueueOverloadTest,
+ QueueOverloadIsDisabledForZeroHerzWhenKillSwitchIsEnabled) {
+ webrtc::test::ScopedKeyValueConfig field_trials(
+ field_trials_, "WebRTC-ZeroHertzQueueOverload/Disabled/");
+ adapter_.reset();
+ adapter_ = CreateAdapter(field_trials, time_controller_.GetClock());
+ Initialize();
+
+ // Same as ForwardedFramesDuringTooLongEncodeTimeAreFlaggedWithQueueOverload
+ // but this time the queue overload mechanism is disabled.
+ InSequence s;
+ PassFrame();
+ EXPECT_CALL(callback_, OnFrame(_, false, _)).WillOnce(InvokeWithoutArgs([&] {
+ PassFrame();
+ PassFrame();
+ PassFrame();
+ SkipForwardBy(TimeDelta::Millis(301));
+ }));
+ EXPECT_CALL(callback_, OnFrame(_, false, _)).Times(3);
+ AdvanceTime(TimeDelta::Millis(100));
+ EXPECT_EQ(metrics::NumSamples("WebRTC.Screenshare.ZeroHz.QueueOverload"), 0);
+}
+
} // namespace
} // namespace webrtc
diff --git a/third_party/libwebrtc/video/full_stack_tests.cc b/third_party/libwebrtc/video/full_stack_tests.cc
index 7791afc854..335a9363af 100644
--- a/third_party/libwebrtc/video/full_stack_tests.cc
+++ b/third_party/libwebrtc/video/full_stack_tests.cc
@@ -135,7 +135,7 @@ TEST(FullStackTest, Generator_Net_Delay_0_0_Plr_0_VP9Profile2) {
return;
auto fixture = CreateVideoQualityTestFixture();
- SdpVideoFormat::Parameters vp92 = {
+ CodecParameterMap vp92 = {
{kVP9FmtpProfileId, VP9ProfileToString(VP9Profile::kProfile2)}};
ParamsWithLogging generator;
generator.call.send_side_bwe = true;
diff --git a/third_party/libwebrtc/video/render/BUILD.gn b/third_party/libwebrtc/video/render/BUILD.gn
index ff721dc61c..a948a0e2fa 100644
--- a/third_party/libwebrtc/video/render/BUILD.gn
+++ b/third_party/libwebrtc/video/render/BUILD.gn
@@ -26,7 +26,6 @@ rtc_library("incoming_video_stream") {
"../../rtc_base:event_tracer",
"../../rtc_base:macromagic",
"../../rtc_base:race_checker",
- "../../rtc_base:rtc_task_queue",
]
absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
diff --git a/third_party/libwebrtc/video/render/incoming_video_stream.cc b/third_party/libwebrtc/video/render/incoming_video_stream.cc
index e740c47bd0..650036ddc9 100644
--- a/third_party/libwebrtc/video/render/incoming_video_stream.cc
+++ b/third_party/libwebrtc/video/render/incoming_video_stream.cc
@@ -33,17 +33,23 @@ IncomingVideoStream::IncomingVideoStream(
IncomingVideoStream::~IncomingVideoStream() {
RTC_DCHECK(main_thread_checker_.IsCurrent());
+ // The queue must be destroyed before its pointer is invalidated to avoid race
+ // between destructor and posting task to the task queue from itself.
+ // std::unique_ptr destructor does the same two operations in reverse order as
+ // it doesn't expect member would be used after its destruction has started.
+ incoming_render_queue_.get_deleter()(incoming_render_queue_.get());
+ incoming_render_queue_.release();
}
void IncomingVideoStream::OnFrame(const VideoFrame& video_frame) {
TRACE_EVENT0("webrtc", "IncomingVideoStream::OnFrame");
RTC_CHECK_RUNS_SERIALIZED(&decoder_race_checker_);
- RTC_DCHECK(!incoming_render_queue_.IsCurrent());
+ RTC_DCHECK(!incoming_render_queue_->IsCurrent());
// TODO(srte): Using video_frame = std::move(video_frame) would move the frame
// into the lambda instead of copying it, but it doesn't work unless we change
// OnFrame to take its frame argument by value instead of const reference.
- incoming_render_queue_.PostTask([this, video_frame = video_frame]() mutable {
- RTC_DCHECK_RUN_ON(&incoming_render_queue_);
+ incoming_render_queue_->PostTask([this, video_frame = video_frame]() mutable {
+ RTC_DCHECK_RUN_ON(incoming_render_queue_.get());
if (render_buffers_.AddFrame(std::move(video_frame)) == 1)
Dequeue();
});
@@ -51,14 +57,14 @@ void IncomingVideoStream::OnFrame(const VideoFrame& video_frame) {
void IncomingVideoStream::Dequeue() {
TRACE_EVENT0("webrtc", "IncomingVideoStream::Dequeue");
- RTC_DCHECK_RUN_ON(&incoming_render_queue_);
+ RTC_DCHECK_RUN_ON(incoming_render_queue_.get());
absl::optional<VideoFrame> frame_to_render = render_buffers_.FrameToRender();
if (frame_to_render)
callback_->OnFrame(*frame_to_render);
if (render_buffers_.HasPendingFrames()) {
uint32_t wait_time = render_buffers_.TimeToNextFrameRelease();
- incoming_render_queue_.PostDelayedHighPrecisionTask(
+ incoming_render_queue_->PostDelayedHighPrecisionTask(
[this]() { Dequeue(); }, TimeDelta::Millis(wait_time));
}
}
diff --git a/third_party/libwebrtc/video/render/incoming_video_stream.h b/third_party/libwebrtc/video/render/incoming_video_stream.h
index 4873ae7dcb..066c0db317 100644
--- a/third_party/libwebrtc/video/render/incoming_video_stream.h
+++ b/third_party/libwebrtc/video/render/incoming_video_stream.h
@@ -13,12 +13,14 @@
#include <stdint.h>
+#include <memory>
+
#include "api/sequence_checker.h"
+#include "api/task_queue/task_queue_base.h"
#include "api/task_queue/task_queue_factory.h"
#include "api/video/video_frame.h"
#include "api/video/video_sink_interface.h"
#include "rtc_base/race_checker.h"
-#include "rtc_base/task_queue.h"
#include "rtc_base/thread_annotations.h"
#include "video/render/video_render_frames.h"
@@ -38,9 +40,9 @@ class IncomingVideoStream : public rtc::VideoSinkInterface<VideoFrame> {
SequenceChecker main_thread_checker_;
rtc::RaceChecker decoder_race_checker_;
- VideoRenderFrames render_buffers_ RTC_GUARDED_BY(&incoming_render_queue_);
+ VideoRenderFrames render_buffers_ RTC_GUARDED_BY(incoming_render_queue_);
rtc::VideoSinkInterface<VideoFrame>* const callback_;
- rtc::TaskQueue incoming_render_queue_;
+ std::unique_ptr<TaskQueueBase, TaskQueueDeleter> incoming_render_queue_;
};
} // namespace webrtc
diff --git a/third_party/libwebrtc/video/rtp_video_stream_receiver2.cc b/third_party/libwebrtc/video/rtp_video_stream_receiver2.cc
index c4a021d6c0..077f522d41 100644
--- a/third_party/libwebrtc/video/rtp_video_stream_receiver2.cc
+++ b/third_party/libwebrtc/video/rtp_video_stream_receiver2.cc
@@ -358,7 +358,7 @@ RtpVideoStreamReceiver2::~RtpVideoStreamReceiver2() {
void RtpVideoStreamReceiver2::AddReceiveCodec(
uint8_t payload_type,
VideoCodecType video_codec,
- const std::map<std::string, std::string>& codec_params,
+ const webrtc::CodecParameterMap& codec_params,
bool raw_payload) {
RTC_DCHECK_RUN_ON(&packet_sequence_checker_);
if (codec_params.count(cricket::kH264FmtpSpsPpsIdrInKeyframe) > 0 ||
@@ -433,23 +433,21 @@ RtpVideoStreamReceiver2::ParseGenericDependenciesExtension(
const RtpPacketReceived& rtp_packet,
RTPVideoHeader* video_header) {
RTC_DCHECK_RUN_ON(&packet_sequence_checker_);
- if (rtp_packet.HasExtension<RtpDependencyDescriptorExtension>()) {
- webrtc::DependencyDescriptor dependency_descriptor;
+ if (DependencyDescriptorMandatory dd_mandatory;
+ rtp_packet.GetExtension<RtpDependencyDescriptorExtensionMandatory>(
+ &dd_mandatory)) {
+ const int64_t frame_id =
+ frame_id_unwrapper_.Unwrap(dd_mandatory.frame_number());
+ DependencyDescriptor dependency_descriptor;
if (!rtp_packet.GetExtension<RtpDependencyDescriptorExtension>(
video_structure_.get(), &dependency_descriptor)) {
- // Descriptor is there, but failed to parse. Either it is invalid,
- // or too old packet (after relevant video_structure_ changed),
- // or too new packet (before relevant video_structure_ arrived).
- // Drop such packet to be on the safe side.
- // TODO(bugs.webrtc.org/10342): Stash too new packet.
- Timestamp now = clock_->CurrentTime();
- if (now - last_logged_failed_to_parse_dd_ > TimeDelta::Seconds(1)) {
- last_logged_failed_to_parse_dd_ = now;
- RTC_LOG(LS_WARNING) << "ssrc: " << rtp_packet.Ssrc()
- << " Failed to parse dependency descriptor.";
+ if (!video_structure_frame_id_ || frame_id < video_structure_frame_id_) {
+ return kDropPacket;
+ } else {
+ return kStashPacket;
}
- return kDropPacket;
}
+
if (dependency_descriptor.attached_structure != nullptr &&
!dependency_descriptor.first_packet_in_frame) {
RTC_LOG(LS_WARNING) << "ssrc: " << rtp_packet.Ssrc()
@@ -462,8 +460,6 @@ RtpVideoStreamReceiver2::ParseGenericDependenciesExtension(
video_header->is_last_packet_in_frame =
dependency_descriptor.last_packet_in_frame;
- int64_t frame_id =
- frame_id_unwrapper_.Unwrap(dependency_descriptor.frame_number);
auto& generic_descriptor_info = video_header->generic.emplace();
generic_descriptor_info.frame_id = frame_id;
generic_descriptor_info.spatial_index =
@@ -538,10 +534,11 @@ RtpVideoStreamReceiver2::ParseGenericDependenciesExtension(
return kHasGenericDescriptor;
}
-void RtpVideoStreamReceiver2::OnReceivedPayloadData(
+bool RtpVideoStreamReceiver2::OnReceivedPayloadData(
rtc::CopyOnWriteBuffer codec_payload,
const RtpPacketReceived& rtp_packet,
- const RTPVideoHeader& video) {
+ const RTPVideoHeader& video,
+ int times_nacked) {
RTC_DCHECK_RUN_ON(&packet_sequence_checker_);
auto packet =
@@ -594,16 +591,23 @@ void RtpVideoStreamReceiver2::OnReceivedPayloadData(
video_header.playout_delay = rtp_packet.GetExtension<PlayoutDelayLimits>();
}
- ParseGenericDependenciesResult generic_descriptor_state =
- ParseGenericDependenciesExtension(rtp_packet, &video_header);
-
if (!rtp_packet.recovered()) {
UpdatePacketReceiveTimestamps(
rtp_packet, video_header.frame_type == VideoFrameType::kVideoFrameKey);
}
- if (generic_descriptor_state == kDropPacket) {
+ ParseGenericDependenciesResult generic_descriptor_state =
+ ParseGenericDependenciesExtension(rtp_packet, &video_header);
+
+ if (generic_descriptor_state == kStashPacket) {
+ return true;
+ } else if (generic_descriptor_state == kDropPacket) {
Timestamp now = clock_->CurrentTime();
+ if (now - last_logged_failed_to_parse_dd_ > TimeDelta::Seconds(1)) {
+ last_logged_failed_to_parse_dd_ = now;
+ RTC_LOG(LS_WARNING) << "ssrc: " << rtp_packet.Ssrc()
+ << " Failed to parse dependency descriptor.";
+ }
if (video_structure_ == nullptr &&
next_keyframe_request_for_missing_video_structure_ < now) {
// No video structure received yet, most likely part of the initial
@@ -612,7 +616,7 @@ void RtpVideoStreamReceiver2::OnReceivedPayloadData(
next_keyframe_request_for_missing_video_structure_ =
now + TimeDelta::Seconds(1);
}
- return;
+ return false;
}
// Color space should only be transmitted in the last packet of a frame,
@@ -658,21 +662,12 @@ void RtpVideoStreamReceiver2::OnReceivedPayloadData(
}
}
- if (nack_module_) {
- const bool is_keyframe =
- video_header.is_first_packet_in_frame &&
- video_header.frame_type == VideoFrameType::kVideoFrameKey;
-
- packet->times_nacked = nack_module_->OnReceivedPacket(
- rtp_packet.SequenceNumber(), is_keyframe, rtp_packet.recovered());
- } else {
- packet->times_nacked = -1;
- }
+ packet->times_nacked = times_nacked;
if (codec_payload.size() == 0) {
NotifyReceiverOfEmptyPacket(packet->seq_num);
rtcp_feedback_buffer_.SendBufferedRtcpFeedback();
- return;
+ return false;
}
if (packet->codec() == kVideoCodecH264) {
@@ -695,7 +690,7 @@ void RtpVideoStreamReceiver2::OnReceivedPayloadData(
rtcp_feedback_buffer_.SendBufferedRtcpFeedback();
[[fallthrough]];
case video_coding::H264SpsPpsTracker::kDrop:
- return;
+ return false;
case video_coding::H264SpsPpsTracker::kInsert:
packet->video_payload = std::move(fixed.bitstream);
break;
@@ -708,6 +703,7 @@ void RtpVideoStreamReceiver2::OnReceivedPayloadData(
rtcp_feedback_buffer_.SendBufferedRtcpFeedback();
frame_counter_.Add(packet->timestamp);
OnInsertedPacket(packet_buffer_.InsertPacket(std::move(packet)));
+ return false;
}
void RtpVideoStreamReceiver2::OnRecoveredPacket(
@@ -1111,15 +1107,51 @@ void RtpVideoStreamReceiver2::ReceivePacket(const RtpPacketReceived& packet) {
if (type_it == payload_type_map_.end()) {
return;
}
- absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed_payload =
- type_it->second->Parse(packet.PayloadBuffer());
- if (parsed_payload == absl::nullopt) {
- RTC_LOG(LS_WARNING) << "Failed parsing payload.";
- return;
- }
- OnReceivedPayloadData(std::move(parsed_payload->video_payload), packet,
- parsed_payload->video_header);
+ auto parse_and_insert = [&](const RtpPacketReceived& packet) {
+ RTC_DCHECK_RUN_ON(&packet_sequence_checker_);
+ absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed_payload =
+ type_it->second->Parse(packet.PayloadBuffer());
+ if (parsed_payload == absl::nullopt) {
+ RTC_LOG(LS_WARNING) << "Failed parsing payload.";
+ return false;
+ }
+
+ int times_nacked = nack_module_
+ ? nack_module_->OnReceivedPacket(
+ packet.SequenceNumber(), packet.recovered())
+ : -1;
+
+ return OnReceivedPayloadData(std::move(parsed_payload->video_payload),
+ packet, parsed_payload->video_header,
+ times_nacked);
+ };
+
+ // When the dependency descriptor is used and the descriptor fail to parse
+ // then `OnReceivedPayloadData` may return true to signal the the packet
+ // should be retried at a later stage, which is why they are stashed here.
+ //
+ // TODO(bugs.webrtc.org/15782):
+ // This is an ugly solution. The way things should work is for the
+ // `RtpFrameReferenceFinder` to stash assembled frames until the keyframe with
+ // the relevant template structure has been received, but unfortunately the
+ // `frame_transformer_delegate_` is called before the frames are inserted into
+ // the `RtpFrameReferenceFinder`, and it expects the dependency descriptor to
+ // be parsed at that stage.
+ if (parse_and_insert(packet)) {
+ if (stashed_packets_.size() == 100) {
+ stashed_packets_.clear();
+ }
+ stashed_packets_.push_back(packet);
+ } else {
+ for (auto it = stashed_packets_.begin(); it != stashed_packets_.end();) {
+ if (parse_and_insert(*it)) {
+ ++it; // keep in the stash.
+ } else {
+ it = stashed_packets_.erase(it);
+ }
+ }
+ }
}
void RtpVideoStreamReceiver2::ParseAndHandleEncapsulatingHeader(
@@ -1151,8 +1183,7 @@ void RtpVideoStreamReceiver2::NotifyReceiverOfEmptyPacket(uint16_t seq_num) {
OnInsertedPacket(packet_buffer_.InsertPadding(seq_num));
if (nack_module_) {
- nack_module_->OnReceivedPacket(seq_num, /* is_keyframe = */ false,
- /* is _recovered = */ false);
+ nack_module_->OnReceivedPacket(seq_num, /*is_recovered=*/false);
}
if (loss_notification_controller_) {
// TODO(bugs.webrtc.org/10336): Handle empty packets.
diff --git a/third_party/libwebrtc/video/rtp_video_stream_receiver2.h b/third_party/libwebrtc/video/rtp_video_stream_receiver2.h
index 10329005ba..b942cb97a6 100644
--- a/third_party/libwebrtc/video/rtp_video_stream_receiver2.h
+++ b/third_party/libwebrtc/video/rtp_video_stream_receiver2.h
@@ -104,7 +104,7 @@ class RtpVideoStreamReceiver2 : public LossNotificationSender,
void AddReceiveCodec(uint8_t payload_type,
VideoCodecType video_codec,
- const std::map<std::string, std::string>& codec_params,
+ const webrtc::CodecParameterMap& codec_params,
bool raw_payload);
void RemoveReceiveCodec(uint8_t payload_type);
@@ -135,9 +135,11 @@ class RtpVideoStreamReceiver2 : public LossNotificationSender,
void OnRtpPacket(const RtpPacketReceived& packet) override;
// Public only for tests.
- void OnReceivedPayloadData(rtc::CopyOnWriteBuffer codec_payload,
+ // Returns true if the packet should be stashed and retried at a later stage.
+ bool OnReceivedPayloadData(rtc::CopyOnWriteBuffer codec_payload,
const RtpPacketReceived& rtp_packet,
- const RTPVideoHeader& video);
+ const RTPVideoHeader& video,
+ int times_nacked);
// Implements RecoveredPacketReceiver.
void OnRecoveredPacket(const RtpPacketReceived& packet) override;
@@ -288,6 +290,7 @@ class RtpVideoStreamReceiver2 : public LossNotificationSender,
RTC_GUARDED_BY(packet_sequence_checker_);
};
enum ParseGenericDependenciesResult {
+ kStashPacket,
kDropPacket,
kHasGenericDescriptor,
kNoGenericDescriptor
@@ -403,7 +406,7 @@ class RtpVideoStreamReceiver2 : public LossNotificationSender,
// TODO(johan): Remove pt_codec_params_ once
// https://bugs.chromium.org/p/webrtc/issues/detail?id=6883 is resolved.
// Maps a payload type to a map of out-of-band supplied codec parameters.
- std::map<uint8_t, std::map<std::string, std::string>> pt_codec_params_
+ std::map<uint8_t, webrtc::CodecParameterMap> pt_codec_params_
RTC_GUARDED_BY(packet_sequence_checker_);
int16_t last_payload_type_ RTC_GUARDED_BY(packet_sequence_checker_) = -1;
@@ -440,6 +443,8 @@ class RtpVideoStreamReceiver2 : public LossNotificationSender,
RTC_GUARDED_BY(packet_sequence_checker_);
std::map<int64_t, RtpPacketInfo> packet_infos_
RTC_GUARDED_BY(packet_sequence_checker_);
+ std::vector<RtpPacketReceived> stashed_packets_
+ RTC_GUARDED_BY(packet_sequence_checker_);
Timestamp next_keyframe_request_for_missing_video_structure_ =
Timestamp::MinusInfinity();
diff --git a/third_party/libwebrtc/video/rtp_video_stream_receiver2_unittest.cc b/third_party/libwebrtc/video/rtp_video_stream_receiver2_unittest.cc
index d82f7bb9a5..f039bf29b1 100644
--- a/third_party/libwebrtc/video/rtp_video_stream_receiver2_unittest.cc
+++ b/third_party/libwebrtc/video/rtp_video_stream_receiver2_unittest.cc
@@ -118,7 +118,7 @@ class MockOnCompleteFrameCallback
void AppendExpectedBitstream(const uint8_t data[], size_t size_in_bytes) {
// TODO(Johan): Let rtc::ByteBuffer handle uint8_t* instead of char*.
- buffer_.WriteBytes(reinterpret_cast<const char*>(data), size_in_bytes);
+ buffer_.WriteBytes(data, size_in_bytes);
}
rtc::ByteBufferWriter buffer_;
};
@@ -307,7 +307,7 @@ TEST_F(RtpVideoStreamReceiver2Test, CacheColorSpaceFromLastPacketOfKeyframe) {
received_packet_generator.SetColorSpace(kColorSpace);
// Prepare the receiver for VP9.
- std::map<std::string, std::string> codec_params;
+ webrtc::CodecParameterMap codec_params;
rtp_video_stream_receiver_->AddReceiveCodec(kVp9PayloadType, kVideoCodecVP9,
codec_params,
/*raw_payload=*/false);
@@ -368,7 +368,7 @@ TEST_F(RtpVideoStreamReceiver2Test, GenericKeyFrame) {
data.size());
EXPECT_CALL(mock_on_complete_frame_callback_, DoOnCompleteFrame(_));
rtp_video_stream_receiver_->OnReceivedPayloadData(data, rtp_packet,
- video_header);
+ video_header, 0);
}
TEST_F(RtpVideoStreamReceiver2Test, SetProtectionPayloadTypes) {
@@ -407,7 +407,7 @@ TEST_F(RtpVideoStreamReceiver2Test, PacketInfoIsPropagatedIntoVideoFrames) {
ElementsAre(kAbsoluteCaptureTimestamp));
}));
rtp_video_stream_receiver_->OnReceivedPayloadData(data, rtp_packet,
- video_header);
+ video_header, 0);
}
TEST_F(RtpVideoStreamReceiver2Test,
@@ -436,7 +436,7 @@ TEST_F(RtpVideoStreamReceiver2Test,
data.size());
EXPECT_CALL(mock_on_complete_frame_callback_, DoOnCompleteFrame(_));
rtp_video_stream_receiver_->OnReceivedPayloadData(data, rtp_packet,
- video_header);
+ video_header, 0);
// Rtp packet without absolute capture time.
rtp_packet = RtpPacketReceived(&extension_map);
@@ -453,7 +453,7 @@ TEST_F(RtpVideoStreamReceiver2Test,
EXPECT_THAT(GetAbsoluteCaptureTimestamps(frame), SizeIs(1));
}));
rtp_video_stream_receiver_->OnReceivedPayloadData(data, rtp_packet,
- video_header);
+ video_header, 0);
}
TEST_F(RtpVideoStreamReceiver2Test,
@@ -508,7 +508,7 @@ TEST_F(RtpVideoStreamReceiver2Test, GenericKeyFrameBitstreamError) {
EXPECT_CALL(mock_on_complete_frame_callback_,
DoOnCompleteFrameFailBitstream(_));
rtp_video_stream_receiver_->OnReceivedPayloadData(data, rtp_packet,
- video_header);
+ video_header, 0);
}
class RtpVideoStreamReceiver2TestH264
@@ -536,7 +536,7 @@ TEST_P(RtpVideoStreamReceiver2TestH264, InBandSpsPps) {
mock_on_complete_frame_callback_.AppendExpectedBitstream(sps_data.data(),
sps_data.size());
rtp_video_stream_receiver_->OnReceivedPayloadData(sps_data, rtp_packet,
- sps_video_header);
+ sps_video_header, 0);
rtc::CopyOnWriteBuffer pps_data;
RTPVideoHeader pps_video_header = GetDefaultH264VideoHeader();
@@ -549,7 +549,7 @@ TEST_P(RtpVideoStreamReceiver2TestH264, InBandSpsPps) {
mock_on_complete_frame_callback_.AppendExpectedBitstream(pps_data.data(),
pps_data.size());
rtp_video_stream_receiver_->OnReceivedPayloadData(pps_data, rtp_packet,
- pps_video_header);
+ pps_video_header, 0);
rtc::CopyOnWriteBuffer idr_data;
RTPVideoHeader idr_video_header = GetDefaultH264VideoHeader();
@@ -566,12 +566,12 @@ TEST_P(RtpVideoStreamReceiver2TestH264, InBandSpsPps) {
idr_data.size());
EXPECT_CALL(mock_on_complete_frame_callback_, DoOnCompleteFrame(_));
rtp_video_stream_receiver_->OnReceivedPayloadData(idr_data, rtp_packet,
- idr_video_header);
+ idr_video_header, 0);
}
TEST_P(RtpVideoStreamReceiver2TestH264, OutOfBandFmtpSpsPps) {
constexpr int kPayloadType = 99;
- std::map<std::string, std::string> codec_params;
+ webrtc::CodecParameterMap codec_params;
// Example parameter sets from https://tools.ietf.org/html/rfc3984#section-8.2
// .
codec_params.insert(
@@ -607,12 +607,12 @@ TEST_P(RtpVideoStreamReceiver2TestH264, OutOfBandFmtpSpsPps) {
data.size());
EXPECT_CALL(mock_on_complete_frame_callback_, DoOnCompleteFrame(_));
rtp_video_stream_receiver_->OnReceivedPayloadData(data, rtp_packet,
- video_header);
+ video_header, 0);
}
TEST_P(RtpVideoStreamReceiver2TestH264, ForceSpsPpsIdrIsKeyframe) {
constexpr int kPayloadType = 99;
- std::map<std::string, std::string> codec_params;
+ webrtc::CodecParameterMap codec_params;
if (GetParam() ==
"") { // Forcing can be done either with field trial or codec_params.
codec_params.insert({cricket::kH264FmtpSpsPpsIdrInKeyframe, ""});
@@ -633,7 +633,7 @@ TEST_P(RtpVideoStreamReceiver2TestH264, ForceSpsPpsIdrIsKeyframe) {
mock_on_complete_frame_callback_.AppendExpectedBitstream(sps_data.data(),
sps_data.size());
rtp_video_stream_receiver_->OnReceivedPayloadData(sps_data, rtp_packet,
- sps_video_header);
+ sps_video_header, 0);
rtc::CopyOnWriteBuffer pps_data;
RTPVideoHeader pps_video_header = GetDefaultH264VideoHeader();
@@ -646,7 +646,7 @@ TEST_P(RtpVideoStreamReceiver2TestH264, ForceSpsPpsIdrIsKeyframe) {
mock_on_complete_frame_callback_.AppendExpectedBitstream(pps_data.data(),
pps_data.size());
rtp_video_stream_receiver_->OnReceivedPayloadData(pps_data, rtp_packet,
- pps_video_header);
+ pps_video_header, 0);
rtc::CopyOnWriteBuffer idr_data;
RTPVideoHeader idr_video_header = GetDefaultH264VideoHeader();
@@ -665,7 +665,7 @@ TEST_P(RtpVideoStreamReceiver2TestH264, ForceSpsPpsIdrIsKeyframe) {
.WillOnce(
[&](EncodedFrame* frame) { EXPECT_TRUE(frame->is_keyframe()); });
rtp_video_stream_receiver_->OnReceivedPayloadData(idr_data, rtp_packet,
- idr_video_header);
+ idr_video_header, 0);
mock_on_complete_frame_callback_.ClearExpectedBitstream();
mock_on_complete_frame_callback_.AppendExpectedBitstream(
kH264StartCode, sizeof(kH264StartCode));
@@ -676,7 +676,7 @@ TEST_P(RtpVideoStreamReceiver2TestH264, ForceSpsPpsIdrIsKeyframe) {
.WillOnce(
[&](EncodedFrame* frame) { EXPECT_FALSE(frame->is_keyframe()); });
rtp_video_stream_receiver_->OnReceivedPayloadData(idr_data, rtp_packet,
- idr_video_header);
+ idr_video_header, 0);
}
TEST_F(RtpVideoStreamReceiver2Test, PaddingInMediaStream) {
@@ -694,26 +694,26 @@ TEST_F(RtpVideoStreamReceiver2Test, PaddingInMediaStream) {
EXPECT_CALL(mock_on_complete_frame_callback_, DoOnCompleteFrame(_));
rtp_video_stream_receiver_->OnReceivedPayloadData(data, rtp_packet,
- video_header);
+ video_header, 0);
rtp_packet.SetSequenceNumber(3);
rtp_video_stream_receiver_->OnReceivedPayloadData({}, rtp_packet,
- video_header);
+ video_header, 0);
rtp_packet.SetSequenceNumber(4);
EXPECT_CALL(mock_on_complete_frame_callback_, DoOnCompleteFrame(_));
video_header.frame_type = VideoFrameType::kVideoFrameDelta;
rtp_video_stream_receiver_->OnReceivedPayloadData(data, rtp_packet,
- video_header);
+ video_header, 0);
rtp_packet.SetSequenceNumber(6);
rtp_video_stream_receiver_->OnReceivedPayloadData(data, rtp_packet,
- video_header);
+ video_header, 0);
EXPECT_CALL(mock_on_complete_frame_callback_, DoOnCompleteFrame(_));
rtp_packet.SetSequenceNumber(5);
rtp_video_stream_receiver_->OnReceivedPayloadData({}, rtp_packet,
- video_header);
+ video_header, 0);
}
TEST_F(RtpVideoStreamReceiver2Test, RequestKeyframeIfFirstFrameIsDelta) {
@@ -725,7 +725,7 @@ TEST_F(RtpVideoStreamReceiver2Test, RequestKeyframeIfFirstFrameIsDelta) {
GetGenericVideoHeader(VideoFrameType::kVideoFrameDelta);
rtp_video_stream_receiver_->OnReceivedPayloadData(data, rtp_packet,
- video_header);
+ video_header, 0);
EXPECT_THAT(rtcp_packet_parser_.pli()->num_packets(), Eq(1));
}
@@ -744,12 +744,12 @@ TEST_F(RtpVideoStreamReceiver2Test, RequestKeyframeWhenPacketBufferGetsFull) {
while (rtp_packet.SequenceNumber() - start_sequence_number <
kPacketBufferMaxSize) {
rtp_video_stream_receiver_->OnReceivedPayloadData(data, rtp_packet,
- video_header);
+ video_header, 0);
rtp_packet.SetSequenceNumber(rtp_packet.SequenceNumber() + 2);
}
rtp_video_stream_receiver_->OnReceivedPayloadData(data, rtp_packet,
- video_header);
+ video_header, 0);
EXPECT_THAT(rtcp_packet_parser_.pli()->num_packets(), Eq(1));
}
@@ -1144,6 +1144,103 @@ TEST_F(RtpVideoStreamReceiver2DependencyDescriptorTest,
EXPECT_THAT(rtcp_packet_parser_.pli()->num_packets(), Eq(2));
}
+TEST_F(RtpVideoStreamReceiver2DependencyDescriptorTest,
+ RetryStashedPacketsAfterReceivingScalabilityStructure) {
+ FrameDependencyStructure stream_structure1 = CreateStreamStructure();
+ FrameDependencyStructure stream_structure2 = CreateStreamStructure();
+ // Make sure template ids for these two structures do not collide:
+ // adjust structure_id (that is also used as template id offset).
+ stream_structure1.structure_id = 13;
+ stream_structure2.structure_id =
+ stream_structure1.structure_id + stream_structure1.templates.size();
+
+ DependencyDescriptor keyframe1_descriptor;
+ keyframe1_descriptor.attached_structure =
+ std::make_unique<FrameDependencyStructure>(stream_structure1);
+ keyframe1_descriptor.frame_dependencies = stream_structure1.templates[0];
+ keyframe1_descriptor.frame_number = 1;
+
+ DependencyDescriptor keyframe2_descriptor;
+ keyframe2_descriptor.attached_structure =
+ std::make_unique<FrameDependencyStructure>(stream_structure2);
+ keyframe2_descriptor.frame_dependencies = stream_structure2.templates[0];
+ keyframe2_descriptor.frame_number = 2;
+
+ DependencyDescriptor deltaframe_descriptor;
+ deltaframe_descriptor.frame_dependencies = stream_structure2.templates[1];
+ deltaframe_descriptor.frame_number = 3;
+
+ EXPECT_CALL(mock_on_complete_frame_callback_, DoOnCompleteFrame)
+ .WillOnce(
+ [&](EncodedFrame* frame) { EXPECT_EQ(frame->Id() & 0xFFFF, 1); })
+ .WillOnce(
+ [&](EncodedFrame* frame) { EXPECT_EQ(frame->Id() & 0xFFFF, 2); })
+ .WillOnce(
+ [&](EncodedFrame* frame) { EXPECT_EQ(frame->Id() & 0xFFFF, 3); });
+
+ InjectPacketWith(stream_structure1, keyframe1_descriptor);
+ InjectPacketWith(stream_structure2, deltaframe_descriptor);
+ InjectPacketWith(stream_structure2, keyframe2_descriptor);
+}
+
+TEST_F(RtpVideoStreamReceiver2DependencyDescriptorTest,
+ RetryStashedPacketsAfterReceivingEarlierScalabilityStructure) {
+ FrameDependencyStructure stream_structure1 = CreateStreamStructure();
+ FrameDependencyStructure stream_structure2 = CreateStreamStructure();
+ FrameDependencyStructure stream_structure3 = CreateStreamStructure();
+ // Make sure template ids for these two structures do not collide:
+ // adjust structure_id (that is also used as template id offset).
+ stream_structure1.structure_id = 13;
+ stream_structure2.structure_id =
+ stream_structure1.structure_id + stream_structure1.templates.size();
+ stream_structure3.structure_id =
+ stream_structure2.structure_id + stream_structure2.templates.size();
+
+ DependencyDescriptor keyframe1_descriptor;
+ keyframe1_descriptor.attached_structure =
+ std::make_unique<FrameDependencyStructure>(stream_structure1);
+ keyframe1_descriptor.frame_dependencies = stream_structure1.templates[0];
+ keyframe1_descriptor.frame_number = 1;
+
+ DependencyDescriptor keyframe2_descriptor;
+ keyframe2_descriptor.attached_structure =
+ std::make_unique<FrameDependencyStructure>(stream_structure2);
+ keyframe2_descriptor.frame_dependencies = stream_structure2.templates[0];
+ keyframe2_descriptor.frame_number = 2;
+
+ DependencyDescriptor deltaframe2_descriptor;
+ deltaframe2_descriptor.frame_dependencies = stream_structure2.templates[1];
+ deltaframe2_descriptor.frame_number = 3;
+
+ DependencyDescriptor keyframe3_descriptor;
+ keyframe3_descriptor.attached_structure =
+ std::make_unique<FrameDependencyStructure>(stream_structure3);
+ keyframe3_descriptor.frame_dependencies = stream_structure3.templates[0];
+ keyframe3_descriptor.frame_number = 4;
+
+ DependencyDescriptor deltaframe3_descriptor;
+ deltaframe3_descriptor.frame_dependencies = stream_structure3.templates[1];
+ deltaframe3_descriptor.frame_number = 5;
+
+ EXPECT_CALL(mock_on_complete_frame_callback_, DoOnCompleteFrame)
+ .WillOnce(
+ [&](EncodedFrame* frame) { EXPECT_EQ(frame->Id() & 0xFFFF, 1); })
+ .WillOnce(
+ [&](EncodedFrame* frame) { EXPECT_EQ(frame->Id() & 0xFFFF, 2); })
+ .WillOnce(
+ [&](EncodedFrame* frame) { EXPECT_EQ(frame->Id() & 0xFFFF, 3); })
+ .WillOnce(
+ [&](EncodedFrame* frame) { EXPECT_EQ(frame->Id() & 0xFFFF, 4); })
+ .WillOnce(
+ [&](EncodedFrame* frame) { EXPECT_EQ(frame->Id() & 0xFFFF, 5); });
+
+ InjectPacketWith(stream_structure1, keyframe1_descriptor);
+ InjectPacketWith(stream_structure2, deltaframe2_descriptor);
+ InjectPacketWith(stream_structure3, deltaframe3_descriptor);
+ InjectPacketWith(stream_structure2, keyframe2_descriptor);
+ InjectPacketWith(stream_structure3, keyframe3_descriptor);
+}
+
TEST_F(RtpVideoStreamReceiver2Test, TransformFrame) {
rtc::scoped_refptr<MockFrameTransformer> mock_frame_transformer =
rtc::make_ref_counted<testing::NiceMock<MockFrameTransformer>>();
@@ -1166,7 +1263,7 @@ TEST_F(RtpVideoStreamReceiver2Test, TransformFrame) {
mock_on_complete_frame_callback_.AppendExpectedBitstream(data.data(),
data.size());
EXPECT_CALL(*mock_frame_transformer, Transform(_));
- receiver->OnReceivedPayloadData(data, rtp_packet, video_header);
+ receiver->OnReceivedPayloadData(data, rtp_packet, video_header, 0);
EXPECT_CALL(*mock_frame_transformer,
UnregisterTransformedFrameSinkCallback(config_.rtp.remote_ssrc));
@@ -1233,7 +1330,7 @@ TEST_P(RtpVideoStreamReceiver2TestPlayoutDelay, PlayoutDelay) {
EXPECT_EQ(frame->EncodedImage().PlayoutDelay(), expected_playout_delay);
}));
rtp_video_stream_receiver_->OnReceivedPayloadData(
- received_packet.PayloadBuffer(), received_packet, video_header);
+ received_packet.PayloadBuffer(), received_packet, video_header, 0);
}
} // namespace webrtc
diff --git a/third_party/libwebrtc/video/video_gn/moz.build b/third_party/libwebrtc/video/video_gn/moz.build
index 5e3d75d621..1106f274c2 100644
--- a/third_party/libwebrtc/video/video_gn/moz.build
+++ b/third_party/libwebrtc/video/video_gn/moz.build
@@ -49,7 +49,6 @@ UNIFIED_SOURCES += [
"/third_party/libwebrtc/video/transport_adapter.cc",
"/third_party/libwebrtc/video/video_quality_observer2.cc",
"/third_party/libwebrtc/video/video_receive_stream2.cc",
- "/third_party/libwebrtc/video/video_send_stream.cc",
"/third_party/libwebrtc/video/video_send_stream_impl.cc",
"/third_party/libwebrtc/video/video_stream_decoder2.cc"
]
diff --git a/third_party/libwebrtc/video/video_receive_stream2.cc b/third_party/libwebrtc/video/video_receive_stream2.cc
index 33e2f39ced..8675ab9979 100644
--- a/third_party/libwebrtc/video/video_receive_stream2.cc
+++ b/third_party/libwebrtc/video/video_receive_stream2.cc
@@ -177,31 +177,30 @@ TimeDelta DetermineMaxWaitForFrame(TimeDelta rtp_history, bool is_keyframe) {
}
VideoReceiveStream2::VideoReceiveStream2(
- TaskQueueFactory* task_queue_factory,
+ const Environment& env,
Call* call,
int num_cpu_cores,
PacketRouter* packet_router,
VideoReceiveStreamInterface::Config config,
CallStats* call_stats,
- Clock* clock,
std::unique_ptr<VCMTiming> timing,
NackPeriodicProcessor* nack_periodic_processor,
- DecodeSynchronizer* decode_sync,
- RtcEventLog* event_log)
- : task_queue_factory_(task_queue_factory),
+ DecodeSynchronizer* decode_sync)
+ : env_(env),
+ packet_sequence_checker_(SequenceChecker::kDetached),
+ decode_sequence_checker_(SequenceChecker::kDetached),
transport_adapter_(config.rtcp_send_transport),
config_(std::move(config)),
num_cpu_cores_(num_cpu_cores),
call_(call),
- clock_(clock),
call_stats_(call_stats),
- source_tracker_(clock_),
- stats_proxy_(remote_ssrc(), clock_, call->worker_thread()),
- rtp_receive_statistics_(ReceiveStatistics::Create(clock_)),
+ source_tracker_(&env_.clock()),
+ stats_proxy_(remote_ssrc(), &env_.clock(), call->worker_thread()),
+ rtp_receive_statistics_(ReceiveStatistics::Create(&env_.clock())),
timing_(std::move(timing)),
- video_receiver_(clock_, timing_.get(), call->trials()),
+ video_receiver_(&env_.clock(), timing_.get(), env_.field_trials()),
rtp_video_stream_receiver_(call->worker_thread(),
- clock_,
+ &env_.clock(),
&transport_adapter_,
call_stats->AsRtcpRttStats(),
packet_router,
@@ -214,8 +213,8 @@ VideoReceiveStream2::VideoReceiveStream2(
this, // OnCompleteFrameCallback
std::move(config_.frame_decryptor),
std::move(config_.frame_transformer),
- call->trials(),
- event_log),
+ env_.field_trials(),
+ &env_.event_log()),
rtp_stream_sync_(call->worker_thread(), this),
max_wait_for_keyframe_(DetermineMaxWaitForFrame(
TimeDelta::Millis(config_.rtp.nack.rtp_history_ms),
@@ -223,7 +222,7 @@ VideoReceiveStream2::VideoReceiveStream2(
max_wait_for_frame_(DetermineMaxWaitForFrame(
TimeDelta::Millis(config_.rtp.nack.rtp_history_ms),
false)),
- decode_queue_(task_queue_factory_->CreateTaskQueue(
+ decode_queue_(env_.task_queue_factory().CreateTaskQueue(
"DecodingQueue",
TaskQueueFactory::Priority::HIGH)) {
RTC_LOG(LS_INFO) << "VideoReceiveStream2: " << config_.ToString();
@@ -231,7 +230,6 @@ VideoReceiveStream2::VideoReceiveStream2(
RTC_DCHECK(call_->worker_thread());
RTC_DCHECK(config_.renderer);
RTC_DCHECK(call_stats_);
- packet_sequence_checker_.Detach();
RTC_DCHECK(!config_.decoders.empty());
RTC_CHECK(config_.decoder_factory);
@@ -249,11 +247,11 @@ VideoReceiveStream2::VideoReceiveStream2(
std::unique_ptr<FrameDecodeScheduler> scheduler =
decode_sync ? decode_sync->CreateSynchronizedFrameScheduler()
: std::make_unique<TaskQueueFrameDecodeScheduler>(
- clock, call_->worker_thread());
+ &env_.clock(), call_->worker_thread());
buffer_ = std::make_unique<VideoStreamBufferController>(
- clock_, call_->worker_thread(), timing_.get(), &stats_proxy_, this,
+ &env_.clock(), call_->worker_thread(), timing_.get(), &stats_proxy_, this,
max_wait_for_keyframe_, max_wait_for_frame_, std::move(scheduler),
- call_->trials());
+ env_.field_trials());
if (!config_.rtp.rtx_associated_payload_types.empty()) {
rtx_receive_stream_ = std::make_unique<RtxReceiveStream>(
@@ -346,7 +344,7 @@ void VideoReceiveStream2::Start() {
rtc::VideoSinkInterface<VideoFrame>* renderer = nullptr;
if (config_.enable_prerenderer_smoothing) {
incoming_video_stream_.reset(new IncomingVideoStream(
- task_queue_factory_, config_.render_delay_ms, this));
+ &env_.task_queue_factory(), config_.render_delay_ms, this));
renderer = incoming_video_stream_.get();
} else {
renderer = this;
@@ -357,7 +355,7 @@ void VideoReceiveStream2::Start() {
settings.set_codec_type(
PayloadStringToCodecType(decoder.video_format.name));
settings.set_max_render_resolution(
- InitialDecoderResolution(call_->trials()));
+ InitialDecoderResolution(env_.field_trials()));
settings.set_number_of_cores(num_cpu_cores_);
const bool raw_payload =
@@ -382,8 +380,8 @@ void VideoReceiveStream2::Start() {
// Start decoding on task queue.
stats_proxy_.DecoderThreadStarting();
- decode_queue_.PostTask([this] {
- RTC_DCHECK_RUN_ON(&decode_queue_);
+ decode_queue_->PostTask([this] {
+ RTC_DCHECK_RUN_ON(&decode_sequence_checker_);
decoder_stopped_ = false;
});
buffer_->StartNextDecode(true);
@@ -413,8 +411,8 @@ void VideoReceiveStream2::Stop() {
if (decoder_running_) {
rtc::Event done;
- decode_queue_.PostTask([this, &done] {
- RTC_DCHECK_RUN_ON(&decode_queue_);
+ decode_queue_->PostTask([this, &done] {
+ RTC_DCHECK_RUN_ON(&decode_sequence_checker_);
// Set `decoder_stopped_` before deregistering all decoders. This means
// that any pending encoded frame will return early without trying to
// access the decoder database.
@@ -532,7 +530,7 @@ void VideoReceiveStream2::CreateAndRegisterExternalDecoder(
}
std::string decoded_output_file =
- call_->trials().Lookup("WebRTC-DecoderDataDumpDirectory");
+ env_.field_trials().Lookup("WebRTC-DecoderDataDumpDirectory");
// Because '/' can't be used inside a field trial parameter, we use ';'
// instead.
// This is only relevant to WebRTC-DecoderDataDumpDirectory
@@ -640,7 +638,7 @@ void VideoReceiveStream2::OnFrame(const VideoFrame& video_frame) {
// renderer. Frame may or may be not rendered by this time. This results in
// inaccuracy but is still the best we can do in the absence of "frame
// rendered" callback from the renderer.
- VideoFrameMetaData frame_meta(video_frame, clock_->CurrentTime());
+ VideoFrameMetaData frame_meta(video_frame, env_.clock().CurrentTime());
call_->worker_thread()->PostTask(
SafeTask(task_safety_.flag(), [frame_meta, this]() {
RTC_DCHECK_RUN_ON(&worker_sequence_checker_);
@@ -759,7 +757,7 @@ bool VideoReceiveStream2::SetMinimumPlayoutDelay(int delay_ms) {
void VideoReceiveStream2::OnEncodedFrame(std::unique_ptr<EncodedFrame> frame) {
RTC_DCHECK_RUN_ON(&packet_sequence_checker_);
- Timestamp now = clock_->CurrentTime();
+ Timestamp now = env_.clock().CurrentTime();
const bool keyframe_request_is_due =
!last_keyframe_request_ ||
now >= (*last_keyframe_request_ + max_wait_for_keyframe_);
@@ -776,10 +774,10 @@ void VideoReceiveStream2::OnEncodedFrame(std::unique_ptr<EncodedFrame> frame) {
}
stats_proxy_.OnPreDecode(frame->CodecSpecific()->codecType, qp);
- decode_queue_.PostTask([this, now, keyframe_request_is_due,
- received_frame_is_keyframe, frame = std::move(frame),
- keyframe_required = keyframe_required_]() mutable {
- RTC_DCHECK_RUN_ON(&decode_queue_);
+ decode_queue_->PostTask([this, now, keyframe_request_is_due,
+ received_frame_is_keyframe, frame = std::move(frame),
+ keyframe_required = keyframe_required_]() mutable {
+ RTC_DCHECK_RUN_ON(&decode_sequence_checker_);
if (decoder_stopped_)
return;
DecodeFrameResult result = HandleEncodedFrameOnDecodeQueue(
@@ -808,7 +806,7 @@ void VideoReceiveStream2::OnEncodedFrame(std::unique_ptr<EncodedFrame> frame) {
void VideoReceiveStream2::OnDecodableFrameTimeout(TimeDelta wait) {
RTC_DCHECK_RUN_ON(&packet_sequence_checker_);
- Timestamp now = clock_->CurrentTime();
+ Timestamp now = env_.clock().CurrentTime();
absl::optional<int64_t> last_packet_ms =
rtp_video_stream_receiver_.LastReceivedPacketMs();
@@ -843,7 +841,7 @@ VideoReceiveStream2::HandleEncodedFrameOnDecodeQueue(
std::unique_ptr<EncodedFrame> frame,
bool keyframe_request_is_due,
bool keyframe_required) {
- RTC_DCHECK_RUN_ON(&decode_queue_);
+ RTC_DCHECK_RUN_ON(&decode_sequence_checker_);
bool force_request_key_frame = false;
absl::optional<int64_t> decoded_frame_picture_id;
@@ -885,7 +883,7 @@ VideoReceiveStream2::HandleEncodedFrameOnDecodeQueue(
int VideoReceiveStream2::DecodeAndMaybeDispatchEncodedFrame(
std::unique_ptr<EncodedFrame> frame) {
- RTC_DCHECK_RUN_ON(&decode_queue_);
+ RTC_DCHECK_RUN_ON(&decode_sequence_checker_);
// If `buffered_encoded_frames_` grows out of control (=60 queued frames),
// maybe due to a stuck decoder, we just halt the process here and log the
@@ -1064,14 +1062,14 @@ VideoReceiveStream2::SetAndGetRecordingState(RecordingState state,
last_keyframe_request = last_keyframe_request_;
last_keyframe_request_ =
generate_key_frame
- ? clock_->CurrentTime()
+ ? env_.clock().CurrentTime()
: Timestamp::Millis(state.last_keyframe_request_ms.value_or(0));
}
- decode_queue_.PostTask(
+ decode_queue_->PostTask(
[this, &event, &old_state, callback = std::move(state.callback),
last_keyframe_request = std::move(last_keyframe_request)] {
- RTC_DCHECK_RUN_ON(&decode_queue_);
+ RTC_DCHECK_RUN_ON(&decode_sequence_checker_);
old_state.callback = std::move(encoded_frame_buffer_function_);
encoded_frame_buffer_function_ = std::move(callback);
@@ -1096,7 +1094,7 @@ VideoReceiveStream2::SetAndGetRecordingState(RecordingState state,
void VideoReceiveStream2::GenerateKeyFrame() {
RTC_DCHECK_RUN_ON(&packet_sequence_checker_);
- RequestKeyFrame(clock_->CurrentTime());
+ RequestKeyFrame(env_.clock().CurrentTime());
keyframe_generation_requested_ = true;
}
diff --git a/third_party/libwebrtc/video/video_receive_stream2.h b/third_party/libwebrtc/video/video_receive_stream2.h
index 31b9a7eb7c..cfdea630b0 100644
--- a/third_party/libwebrtc/video/video_receive_stream2.h
+++ b/third_party/libwebrtc/video/video_receive_stream2.h
@@ -17,9 +17,10 @@
#include <vector>
#include "absl/types/optional.h"
+#include "api/environment/environment.h"
#include "api/sequence_checker.h"
#include "api/task_queue/pending_task_safety_flag.h"
-#include "api/task_queue/task_queue_factory.h"
+#include "api/task_queue/task_queue_base.h"
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
#include "api/video/recordable_encoded_frame.h"
@@ -31,9 +32,7 @@
#include "modules/video_coding/nack_requester.h"
#include "modules/video_coding/video_receiver2.h"
#include "rtc_base/system/no_unique_address.h"
-#include "rtc_base/task_queue.h"
#include "rtc_base/thread_annotations.h"
-#include "system_wrappers/include/clock.h"
#include "video/receive_statistics_proxy.h"
#include "video/rtp_streams_synchronizer2.h"
#include "video/rtp_video_stream_receiver2.h"
@@ -94,17 +93,15 @@ class VideoReceiveStream2
// configured.
static constexpr size_t kBufferedEncodedFramesMaxSize = 60;
- VideoReceiveStream2(TaskQueueFactory* task_queue_factory,
+ VideoReceiveStream2(const Environment& env,
Call* call,
int num_cpu_cores,
PacketRouter* packet_router,
VideoReceiveStreamInterface::Config config,
CallStats* call_stats,
- Clock* clock,
std::unique_ptr<VCMTiming> timing,
NackPeriodicProcessor* nack_periodic_processor,
- DecodeSynchronizer* decode_sync,
- RtcEventLog* event_log);
+ DecodeSynchronizer* decode_sync);
// Destruction happens on the worker thread. Prior to destruction the caller
// must ensure that a registration with the transport has been cleared. See
// `RegisterWithTransport` for details.
@@ -227,7 +224,7 @@ class VideoReceiveStream2
DecodeFrameResult HandleEncodedFrameOnDecodeQueue(
std::unique_ptr<EncodedFrame> frame,
bool keyframe_request_is_due,
- bool keyframe_required) RTC_RUN_ON(decode_queue_);
+ bool keyframe_required) RTC_RUN_ON(decode_sequence_checker_);
void UpdatePlayoutDelays() const
RTC_EXCLUSIVE_LOCKS_REQUIRED(worker_sequence_checker_);
void RequestKeyFrame(Timestamp now) RTC_RUN_ON(packet_sequence_checker_);
@@ -239,10 +236,12 @@ class VideoReceiveStream2
bool IsReceivingKeyFrame(Timestamp timestamp) const
RTC_RUN_ON(packet_sequence_checker_);
int DecodeAndMaybeDispatchEncodedFrame(std::unique_ptr<EncodedFrame> frame)
- RTC_RUN_ON(decode_queue_);
+ RTC_RUN_ON(decode_sequence_checker_);
void UpdateHistograms();
+ const Environment env_;
+
RTC_NO_UNIQUE_ADDRESS SequenceChecker worker_sequence_checker_;
// TODO(bugs.webrtc.org/11993): This checker conceptually represents
// operations that belong to the network thread. The Call class is currently
@@ -253,18 +252,17 @@ class VideoReceiveStream2
// on the network thread, this comment will be deleted.
RTC_NO_UNIQUE_ADDRESS SequenceChecker packet_sequence_checker_;
- TaskQueueFactory* const task_queue_factory_;
+ RTC_NO_UNIQUE_ADDRESS SequenceChecker decode_sequence_checker_;
TransportAdapter transport_adapter_;
const VideoReceiveStreamInterface::Config config_;
const int num_cpu_cores_;
Call* const call_;
- Clock* const clock_;
CallStats* const call_stats_;
bool decoder_running_ RTC_GUARDED_BY(worker_sequence_checker_) = false;
- bool decoder_stopped_ RTC_GUARDED_BY(decode_queue_) = true;
+ bool decoder_stopped_ RTC_GUARDED_BY(decode_sequence_checker_) = true;
SourceTracker source_tracker_;
ReceiveStatisticsProxy stats_proxy_;
@@ -300,7 +298,7 @@ class VideoReceiveStream2
bool keyframe_required_ RTC_GUARDED_BY(packet_sequence_checker_) = true;
// If we have successfully decoded any frame.
- bool frame_decoded_ RTC_GUARDED_BY(decode_queue_) = false;
+ bool frame_decoded_ RTC_GUARDED_BY(decode_sequence_checker_) = false;
absl::optional<Timestamp> last_keyframe_request_
RTC_GUARDED_BY(packet_sequence_checker_);
@@ -329,7 +327,7 @@ class VideoReceiveStream2
// Function that is triggered with encoded frames, if not empty.
std::function<void(const RecordableEncodedFrame&)>
- encoded_frame_buffer_function_ RTC_GUARDED_BY(decode_queue_);
+ encoded_frame_buffer_function_ RTC_GUARDED_BY(decode_sequence_checker_);
// Set to true while we're requesting keyframes but not yet received one.
bool keyframe_generation_requested_ RTC_GUARDED_BY(packet_sequence_checker_) =
false;
@@ -342,13 +340,16 @@ class VideoReceiveStream2
RTC_GUARDED_BY(pending_resolution_mutex_);
// Buffered encoded frames held while waiting for decoded resolution.
std::vector<std::unique_ptr<EncodedFrame>> buffered_encoded_frames_
- RTC_GUARDED_BY(decode_queue_);
-
- // Defined last so they are destroyed before all other members.
- rtc::TaskQueue decode_queue_;
+ RTC_GUARDED_BY(decode_sequence_checker_);
// Used to signal destruction to potentially pending tasks.
ScopedTaskSafety task_safety_;
+
+ // Defined last so they are destroyed before all other members, in particular
+ // `decode_queue_` should be stopped before `decode_sequence_checker_` is
+ // destructed to avoid races when running tasks on the `decode_queue_` during
+ // VideoReceiveStream2 destruction.
+ std::unique_ptr<TaskQueueBase, TaskQueueDeleter> decode_queue_;
};
} // namespace internal
diff --git a/third_party/libwebrtc/video/video_receive_stream2_unittest.cc b/third_party/libwebrtc/video/video_receive_stream2_unittest.cc
index 084b128af8..50e00aa31b 100644
--- a/third_party/libwebrtc/video/video_receive_stream2_unittest.cc
+++ b/third_party/libwebrtc/video/video_receive_stream2_unittest.cc
@@ -23,6 +23,8 @@
#include "absl/memory/memory.h"
#include "absl/types/optional.h"
+#include "api/environment/environment.h"
+#include "api/environment/environment_factory.h"
#include "api/metronome/test/fake_metronome.h"
#include "api/test/mock_video_decoder.h"
#include "api/test/mock_video_decoder_factory.h"
@@ -192,12 +194,13 @@ class VideoReceiveStream2Test : public ::testing::TestWithParam<bool> {
VideoReceiveStream2Test()
: time_controller_(kStartTime),
- clock_(time_controller_.GetClock()),
+ env_(CreateEnvironment(time_controller_.CreateTaskQueueFactory(),
+ time_controller_.GetClock())),
config_(&mock_transport_, &mock_h264_decoder_factory_),
- call_stats_(clock_, time_controller_.GetMainThread()),
+ call_stats_(&env_.clock(), time_controller_.GetMainThread()),
fake_renderer_(&time_controller_),
fake_metronome_(TimeDelta::Millis(16)),
- decode_sync_(clock_,
+ decode_sync_(&env_.clock(),
&fake_metronome_,
time_controller_.GetMainThread()),
h264_decoder_factory_(&mock_decoder_) {
@@ -255,13 +258,13 @@ class VideoReceiveStream2Test : public ::testing::TestWithParam<bool> {
video_receive_stream_->UnregisterFromTransport();
video_receive_stream_ = nullptr;
}
- timing_ = new VCMTiming(clock_, fake_call_.trials());
+ timing_ = new VCMTiming(&env_.clock(), env_.field_trials());
video_receive_stream_ =
std::make_unique<webrtc::internal::VideoReceiveStream2>(
- time_controller_.GetTaskQueueFactory(), &fake_call_,
- kDefaultNumCpuCores, &packet_router_, config_.Copy(), &call_stats_,
- clock_, absl::WrapUnique(timing_), &nack_periodic_processor_,
- UseMetronome() ? &decode_sync_ : nullptr, nullptr);
+ env_, &fake_call_, kDefaultNumCpuCores, &packet_router_,
+ config_.Copy(), &call_stats_, absl::WrapUnique(timing_),
+ &nack_periodic_processor_,
+ UseMetronome() ? &decode_sync_ : nullptr);
video_receive_stream_->RegisterWithTransport(
&rtp_stream_receiver_controller_);
if (state)
@@ -270,7 +273,7 @@ class VideoReceiveStream2Test : public ::testing::TestWithParam<bool> {
protected:
GlobalSimulatedTimeController time_controller_;
- Clock* const clock_;
+ Environment env_;
NackPeriodicProcessor nack_periodic_processor_;
testing::NiceMock<MockVideoDecoderFactory> mock_h264_decoder_factory_;
VideoReceiveStreamInterface::Config config_;
@@ -542,16 +545,16 @@ TEST_P(VideoReceiveStream2Test, RenderedFrameUpdatesGetSources) {
info.set_csrcs({kCsrc});
info.set_rtp_timestamp(kRtpTimestamp);
- info.set_receive_time(clock_->CurrentTime() - TimeDelta::Millis(5000));
+ info.set_receive_time(env_.clock().CurrentTime() - TimeDelta::Millis(5000));
infos.push_back(info);
- info.set_receive_time(clock_->CurrentTime() - TimeDelta::Millis(3000));
+ info.set_receive_time(env_.clock().CurrentTime() - TimeDelta::Millis(3000));
infos.push_back(info);
- info.set_receive_time(clock_->CurrentTime() - TimeDelta::Millis(2000));
+ info.set_receive_time(env_.clock().CurrentTime() - TimeDelta::Millis(2000));
infos.push_back(info);
- info.set_receive_time(clock_->CurrentTime() - TimeDelta::Millis(1000));
+ info.set_receive_time(env_.clock().CurrentTime() - TimeDelta::Millis(1000));
infos.push_back(info);
packet_infos = RtpPacketInfos(std::move(infos));
@@ -563,12 +566,12 @@ TEST_P(VideoReceiveStream2Test, RenderedFrameUpdatesGetSources) {
EXPECT_THAT(video_receive_stream_->GetSources(), IsEmpty());
// Render one video frame.
- Timestamp timestamp_min = clock_->CurrentTime();
+ Timestamp timestamp_min = env_.clock().CurrentTime();
video_receive_stream_->OnCompleteFrame(std::move(test_frame));
// Verify that the per-packet information is passed to the renderer.
EXPECT_THAT(fake_renderer_.WaitForFrame(kDefaultTimeOut),
RenderedFrameWith(PacketInfos(ElementsAreArray(packet_infos))));
- Timestamp timestamp_max = clock_->CurrentTime();
+ Timestamp timestamp_max = env_.clock().CurrentTime();
// Verify that the per-packet information also updates `GetSources()`.
std::vector<RtpSource> sources = video_receive_stream_->GetSources();
@@ -813,15 +816,15 @@ TEST_P(VideoReceiveStream2Test, FramesScheduledInOrder) {
EXPECT_CALL(mock_decoder_,
Decode(test::RtpTimestamp(RtpTimestampForFrame(2)), _))
.Times(1);
- key_frame->SetReceivedTime(clock_->CurrentTime().ms());
+ key_frame->SetReceivedTime(env_.clock().CurrentTime().ms());
video_receive_stream_->OnCompleteFrame(std::move(key_frame));
EXPECT_THAT(fake_renderer_.WaitForFrame(TimeDelta::Zero()), RenderedFrame());
- delta_frame2->SetReceivedTime(clock_->CurrentTime().ms());
+ delta_frame2->SetReceivedTime(env_.clock().CurrentTime().ms());
video_receive_stream_->OnCompleteFrame(std::move(delta_frame2));
EXPECT_THAT(fake_renderer_.WaitForFrame(k30FpsDelay), DidNotReceiveFrame());
// `delta_frame1` arrives late.
- delta_frame1->SetReceivedTime(clock_->CurrentTime().ms());
+ delta_frame1->SetReceivedTime(env_.clock().CurrentTime().ms());
video_receive_stream_->OnCompleteFrame(std::move(delta_frame1));
EXPECT_THAT(fake_renderer_.WaitForFrame(k30FpsDelay), RenderedFrame());
EXPECT_THAT(fake_renderer_.WaitForFrame(k30FpsDelay * 2), RenderedFrame());
@@ -854,7 +857,7 @@ TEST_P(VideoReceiveStream2Test, WaitsforAllSpatialLayers) {
// No decodes should be called until `sl2` is received.
EXPECT_CALL(mock_decoder_, Decode(_, _)).Times(0);
- sl0->SetReceivedTime(clock_->CurrentTime().ms());
+ sl0->SetReceivedTime(env_.clock().CurrentTime().ms());
video_receive_stream_->OnCompleteFrame(std::move(sl0));
EXPECT_THAT(fake_renderer_.WaitForFrame(TimeDelta::Zero()),
DidNotReceiveFrame());
@@ -984,7 +987,7 @@ TEST_P(VideoReceiveStream2Test, RtpTimestampWrapAround) {
.Id(0)
.PayloadType(99)
.Time(kBaseRtp)
- .ReceivedTime(clock_->CurrentTime())
+ .ReceivedTime(env_.clock().CurrentTime())
.AsLast()
.Build());
EXPECT_THAT(fake_renderer_.WaitForFrame(TimeDelta::Zero()), RenderedFrame());
@@ -994,7 +997,7 @@ TEST_P(VideoReceiveStream2Test, RtpTimestampWrapAround) {
.Id(1)
.PayloadType(99)
.Time(kBaseRtp + k30FpsRtpTimestampDelta)
- .ReceivedTime(clock_->CurrentTime())
+ .ReceivedTime(env_.clock().CurrentTime())
.AsLast()
.Build());
EXPECT_THAT(fake_renderer_.WaitForFrame(k30FpsDelay), RenderedFrame());
@@ -1014,7 +1017,7 @@ TEST_P(VideoReceiveStream2Test, RtpTimestampWrapAround) {
.Id(2)
.PayloadType(99)
.Time(kWrapAroundRtp)
- .ReceivedTime(clock_->CurrentTime())
+ .ReceivedTime(env_.clock().CurrentTime())
.AsLast()
.Build());
EXPECT_CALL(mock_decoder_, Decode(test::RtpTimestamp(kWrapAroundRtp), _))
@@ -1067,10 +1070,11 @@ TEST_P(VideoReceiveStream2Test, PoorConnectionWithFpsChangeDuringLostFrame) {
// 2 second of frames at 15 fps, and then a keyframe.
time_controller_.AdvanceTime(k30FpsDelay);
- Timestamp send_30fps_end_time = clock_->CurrentTime() + TimeDelta::Seconds(2);
+ Timestamp send_30fps_end_time =
+ env_.clock().CurrentTime() + TimeDelta::Seconds(2);
int id = 3;
EXPECT_CALL(mock_transport_, SendRtcp).Times(AnyNumber());
- while (clock_->CurrentTime() < send_30fps_end_time) {
+ while (env_.clock().CurrentTime() < send_30fps_end_time) {
++id;
video_receive_stream_->OnCompleteFrame(
test::FakeFrameBuilder()
@@ -1085,8 +1089,9 @@ TEST_P(VideoReceiveStream2Test, PoorConnectionWithFpsChangeDuringLostFrame) {
Eq(absl::nullopt));
}
uint32_t current_rtp = RtpTimestampForFrame(id);
- Timestamp send_15fps_end_time = clock_->CurrentTime() + TimeDelta::Seconds(2);
- while (clock_->CurrentTime() < send_15fps_end_time) {
+ Timestamp send_15fps_end_time =
+ env_.clock().CurrentTime() + TimeDelta::Seconds(2);
+ while (env_.clock().CurrentTime() < send_15fps_end_time) {
++id;
current_rtp += k15FpsRtpTimestampDelta;
video_receive_stream_->OnCompleteFrame(
@@ -1094,7 +1099,7 @@ TEST_P(VideoReceiveStream2Test, PoorConnectionWithFpsChangeDuringLostFrame) {
.Id(id)
.PayloadType(99)
.Time(current_rtp)
- .ReceivedTime(clock_->CurrentTime())
+ .ReceivedTime(env_.clock().CurrentTime())
.Refs({id - 1})
.AsLast()
.Build());
@@ -1112,7 +1117,7 @@ TEST_P(VideoReceiveStream2Test, PoorConnectionWithFpsChangeDuringLostFrame) {
.Id(id)
.PayloadType(99)
.Time(current_rtp)
- .ReceivedTime(clock_->CurrentTime() + kKeyframeDelay)
+ .ReceivedTime(env_.clock().CurrentTime() + kKeyframeDelay)
.AsLast()
.Build());
// If the framerate was not updated to be 15fps from the frames that arrived
@@ -1166,7 +1171,7 @@ TEST_P(VideoReceiveStream2Test, StreamShouldNotTimeoutWhileWaitingForFrame) {
.Id(121)
.PayloadType(99)
.Time(late_decode_rtp)
- .ReceivedTime(clock_->CurrentTime())
+ .ReceivedTime(env_.clock().CurrentTime())
.AsLast()
.Build());
EXPECT_THAT(fake_renderer_.WaitForFrame(TimeDelta::Millis(100),
diff --git a/third_party/libwebrtc/video/video_send_stream.cc b/third_party/libwebrtc/video/video_send_stream.cc
deleted file mode 100644
index b99b08eefb..0000000000
--- a/third_party/libwebrtc/video/video_send_stream.cc
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
- *
- * Use of this source code is governed by a BSD-style license
- * that can be found in the LICENSE file in the root of the source
- * tree. An additional intellectual property rights grant can be found
- * in the file PATENTS. All contributing project authors may
- * be found in the AUTHORS file in the root of the source tree.
- */
-#include "video/video_send_stream.h"
-
-#include <utility>
-
-#include "api/array_view.h"
-#include "api/task_queue/task_queue_base.h"
-#include "api/video/video_stream_encoder_settings.h"
-#include "modules/rtp_rtcp/include/rtp_header_extension_map.h"
-#include "modules/rtp_rtcp/source/rtp_header_extension_size.h"
-#include "modules/rtp_rtcp/source/rtp_sender.h"
-#include "rtc_base/checks.h"
-#include "rtc_base/logging.h"
-#include "rtc_base/strings/string_builder.h"
-#include "system_wrappers/include/clock.h"
-#include "video/adaptation/overuse_frame_detector.h"
-#include "video/frame_cadence_adapter.h"
-#include "video/video_stream_encoder.h"
-
-namespace webrtc {
-
-namespace {
-
-size_t CalculateMaxHeaderSize(const RtpConfig& config) {
- size_t header_size = kRtpHeaderSize;
- size_t extensions_size = 0;
- size_t fec_extensions_size = 0;
- if (!config.extensions.empty()) {
- RtpHeaderExtensionMap extensions_map(config.extensions);
- extensions_size = RtpHeaderExtensionSize(RTPSender::VideoExtensionSizes(),
- extensions_map);
- fec_extensions_size =
- RtpHeaderExtensionSize(RTPSender::FecExtensionSizes(), extensions_map);
- }
- header_size += extensions_size;
- if (config.flexfec.payload_type >= 0) {
- // All FEC extensions again plus maximum FlexFec overhead.
- header_size += fec_extensions_size + 32;
- } else {
- if (config.ulpfec.ulpfec_payload_type >= 0) {
- // Header with all the FEC extensions will be repeated plus maximum
- // UlpFec overhead.
- header_size += fec_extensions_size + 18;
- }
- if (config.ulpfec.red_payload_type >= 0) {
- header_size += 1; // RED header.
- }
- }
- // Additional room for Rtx.
- if (config.rtx.payload_type >= 0)
- header_size += kRtxHeaderSize;
- return header_size;
-}
-
-VideoStreamEncoder::BitrateAllocationCallbackType
-GetBitrateAllocationCallbackType(const VideoSendStream::Config& config,
- const FieldTrialsView& field_trials) {
- if (webrtc::RtpExtension::FindHeaderExtensionByUri(
- config.rtp.extensions,
- webrtc::RtpExtension::kVideoLayersAllocationUri,
- config.crypto_options.srtp.enable_encrypted_rtp_header_extensions
- ? RtpExtension::Filter::kPreferEncryptedExtension
- : RtpExtension::Filter::kDiscardEncryptedExtension)) {
- return VideoStreamEncoder::BitrateAllocationCallbackType::
- kVideoLayersAllocation;
- }
- if (field_trials.IsEnabled("WebRTC-Target-Bitrate-Rtcp")) {
- return VideoStreamEncoder::BitrateAllocationCallbackType::
- kVideoBitrateAllocation;
- }
- return VideoStreamEncoder::BitrateAllocationCallbackType::
- kVideoBitrateAllocationWhenScreenSharing;
-}
-
-RtpSenderFrameEncryptionConfig CreateFrameEncryptionConfig(
- const VideoSendStream::Config* config) {
- RtpSenderFrameEncryptionConfig frame_encryption_config;
- frame_encryption_config.frame_encryptor = config->frame_encryptor.get();
- frame_encryption_config.crypto_options = config->crypto_options;
- return frame_encryption_config;
-}
-
-RtpSenderObservers CreateObservers(RtcpRttStats* call_stats,
- EncoderRtcpFeedback* encoder_feedback,
- SendStatisticsProxy* stats_proxy,
- SendPacketObserver* send_packet_observer) {
- RtpSenderObservers observers;
- observers.rtcp_rtt_stats = call_stats;
- observers.intra_frame_callback = encoder_feedback;
- observers.rtcp_loss_notification_observer = encoder_feedback;
- observers.report_block_data_observer = stats_proxy;
- observers.rtp_stats = stats_proxy;
- observers.bitrate_observer = stats_proxy;
- observers.frame_count_observer = stats_proxy;
- observers.rtcp_type_observer = stats_proxy;
- observers.send_packet_observer = send_packet_observer;
- return observers;
-}
-
-std::unique_ptr<VideoStreamEncoder> CreateVideoStreamEncoder(
- Clock* clock,
- int num_cpu_cores,
- TaskQueueFactory* task_queue_factory,
- SendStatisticsProxy* stats_proxy,
- const VideoStreamEncoderSettings& encoder_settings,
- VideoStreamEncoder::BitrateAllocationCallbackType
- bitrate_allocation_callback_type,
- const FieldTrialsView& field_trials,
- webrtc::VideoEncoderFactory::EncoderSelectorInterface* encoder_selector) {
- std::unique_ptr<TaskQueueBase, TaskQueueDeleter> encoder_queue =
- task_queue_factory->CreateTaskQueue("EncoderQueue",
- TaskQueueFactory::Priority::NORMAL);
- TaskQueueBase* encoder_queue_ptr = encoder_queue.get();
- return std::make_unique<VideoStreamEncoder>(
- clock, num_cpu_cores, stats_proxy, encoder_settings,
- std::make_unique<OveruseFrameDetector>(stats_proxy),
- FrameCadenceAdapterInterface::Create(clock, encoder_queue_ptr,
- field_trials),
- std::move(encoder_queue), bitrate_allocation_callback_type, field_trials,
- encoder_selector);
-}
-
-} // namespace
-
-namespace internal {
-
-VideoSendStream::VideoSendStream(
- Clock* clock,
- int num_cpu_cores,
- TaskQueueFactory* task_queue_factory,
- TaskQueueBase* network_queue,
- RtcpRttStats* call_stats,
- RtpTransportControllerSendInterface* transport,
- BitrateAllocatorInterface* bitrate_allocator,
- SendDelayStats* send_delay_stats,
- RtcEventLog* event_log,
- VideoSendStream::Config config,
- VideoEncoderConfig encoder_config,
- const std::map<uint32_t, RtpState>& suspended_ssrcs,
- const std::map<uint32_t, RtpPayloadState>& suspended_payload_states,
- std::unique_ptr<FecController> fec_controller,
- const FieldTrialsView& field_trials)
- : transport_(transport),
- stats_proxy_(clock, config, encoder_config.content_type, field_trials),
- send_packet_observer_(&stats_proxy_, send_delay_stats),
- config_(std::move(config)),
- content_type_(encoder_config.content_type),
- video_stream_encoder_(CreateVideoStreamEncoder(
- clock,
- num_cpu_cores,
- task_queue_factory,
- &stats_proxy_,
- config_.encoder_settings,
- GetBitrateAllocationCallbackType(config_, field_trials),
- field_trials,
- config_.encoder_selector)),
- encoder_feedback_(
- clock,
- config_.rtp.ssrcs,
- video_stream_encoder_.get(),
- [this](uint32_t ssrc, const std::vector<uint16_t>& seq_nums) {
- return rtp_video_sender_->GetSentRtpPacketInfos(ssrc, seq_nums);
- }),
- rtp_video_sender_(transport->CreateRtpVideoSender(
- suspended_ssrcs,
- suspended_payload_states,
- config_.rtp,
- config_.rtcp_report_interval_ms,
- config_.send_transport,
- CreateObservers(call_stats,
- &encoder_feedback_,
- &stats_proxy_,
- &send_packet_observer_),
- event_log,
- std::move(fec_controller),
- CreateFrameEncryptionConfig(&config_),
- config_.frame_transformer)),
- send_stream_(clock,
- &stats_proxy_,
- transport,
- bitrate_allocator,
- video_stream_encoder_.get(),
- &config_,
- encoder_config.max_bitrate_bps,
- encoder_config.bitrate_priority,
- encoder_config.content_type,
- rtp_video_sender_,
- field_trials) {
- RTC_DCHECK(config_.encoder_settings.encoder_factory);
- RTC_DCHECK(config_.encoder_settings.bitrate_allocator_factory);
-
- video_stream_encoder_->SetFecControllerOverride(rtp_video_sender_);
-
- ReconfigureVideoEncoder(std::move(encoder_config));
-}
-
-VideoSendStream::~VideoSendStream() {
- RTC_DCHECK_RUN_ON(&thread_checker_);
- RTC_DCHECK(!running_);
- transport_->DestroyRtpVideoSender(rtp_video_sender_);
-}
-
-void VideoSendStream::Start() {
- const std::vector<bool> active_layers(config_.rtp.ssrcs.size(), true);
- StartPerRtpStream(active_layers);
-}
-
-void VideoSendStream::StartPerRtpStream(const std::vector<bool> active_layers) {
- RTC_DCHECK_RUN_ON(&thread_checker_);
-
- // Keep our `running_` flag expected state in sync with active layers since
- // the `send_stream_` will be implicitly stopped/started depending on the
- // state of the layers.
- bool running = false;
-
- rtc::StringBuilder active_layers_string;
- active_layers_string << "{";
- for (size_t i = 0; i < active_layers.size(); ++i) {
- if (active_layers[i]) {
- running = true;
- active_layers_string << "1";
- } else {
- active_layers_string << "0";
- }
- if (i < active_layers.size() - 1) {
- active_layers_string << ", ";
- }
- }
- active_layers_string << "}";
- RTC_LOG(LS_INFO) << "StartPerRtpStream: " << active_layers_string.str();
- send_stream_.StartPerRtpStream(active_layers);
- running_ = running;
-}
-
-void VideoSendStream::Stop() {
- RTC_DCHECK_RUN_ON(&thread_checker_);
- if (!running_)
- return;
- RTC_DLOG(LS_INFO) << "VideoSendStream::Stop";
- running_ = false;
- send_stream_.Stop();
-}
-
-bool VideoSendStream::started() {
- RTC_DCHECK_RUN_ON(&thread_checker_);
- return running_;
-}
-
-void VideoSendStream::AddAdaptationResource(
- rtc::scoped_refptr<Resource> resource) {
- RTC_DCHECK_RUN_ON(&thread_checker_);
- video_stream_encoder_->AddAdaptationResource(resource);
-}
-
-std::vector<rtc::scoped_refptr<Resource>>
-VideoSendStream::GetAdaptationResources() {
- RTC_DCHECK_RUN_ON(&thread_checker_);
- return video_stream_encoder_->GetAdaptationResources();
-}
-
-void VideoSendStream::SetSource(
- rtc::VideoSourceInterface<webrtc::VideoFrame>* source,
- const DegradationPreference& degradation_preference) {
- RTC_DCHECK_RUN_ON(&thread_checker_);
- video_stream_encoder_->SetSource(source, degradation_preference);
-}
-
-void VideoSendStream::ReconfigureVideoEncoder(VideoEncoderConfig config) {
- ReconfigureVideoEncoder(std::move(config), nullptr);
-}
-
-void VideoSendStream::ReconfigureVideoEncoder(VideoEncoderConfig config,
- SetParametersCallback callback) {
- RTC_DCHECK_RUN_ON(&thread_checker_);
- RTC_DCHECK_EQ(content_type_, config.content_type);
- RTC_LOG(LS_VERBOSE) << "Encoder config: " << config.ToString()
- << " VideoSendStream config: " << config_.ToString();
- video_stream_encoder_->ConfigureEncoder(
- std::move(config),
- config_.rtp.max_packet_size - CalculateMaxHeaderSize(config_.rtp),
- std::move(callback));
-}
-
-VideoSendStream::Stats VideoSendStream::GetStats() {
- RTC_DCHECK_RUN_ON(&thread_checker_);
- return stats_proxy_.GetStats();
-}
-
-absl::optional<float> VideoSendStream::GetPacingFactorOverride() const {
- return send_stream_.configured_pacing_factor();
-}
-
-void VideoSendStream::StopPermanentlyAndGetRtpStates(
- VideoSendStream::RtpStateMap* rtp_state_map,
- VideoSendStream::RtpPayloadStateMap* payload_state_map) {
- RTC_DCHECK_RUN_ON(&thread_checker_);
- video_stream_encoder_->Stop();
-
- running_ = false;
- // Always run these cleanup steps regardless of whether running_ was set
- // or not. This will unregister callbacks before destruction.
- // See `VideoSendStreamImpl::StopVideoSendStream` for more.
- send_stream_.Stop();
- *rtp_state_map = send_stream_.GetRtpStates();
- *payload_state_map = send_stream_.GetRtpPayloadStates();
-}
-
-void VideoSendStream::DeliverRtcp(const uint8_t* packet, size_t length) {
- RTC_DCHECK_RUN_ON(&thread_checker_);
- send_stream_.DeliverRtcp(packet, length);
-}
-
-void VideoSendStream::GenerateKeyFrame(const std::vector<std::string>& rids) {
- RTC_DCHECK_RUN_ON(&thread_checker_);
- // Map rids to layers. If rids is empty, generate a keyframe for all layers.
- std::vector<VideoFrameType> next_frames(config_.rtp.ssrcs.size(),
- VideoFrameType::kVideoFrameKey);
- if (!config_.rtp.rids.empty() && !rids.empty()) {
- std::fill(next_frames.begin(), next_frames.end(),
- VideoFrameType::kVideoFrameDelta);
- for (const auto& rid : rids) {
- for (size_t i = 0; i < config_.rtp.rids.size(); i++) {
- if (config_.rtp.rids[i] == rid) {
- next_frames[i] = VideoFrameType::kVideoFrameKey;
- break;
- }
- }
- }
- }
- if (video_stream_encoder_) {
- video_stream_encoder_->SendKeyFrame(next_frames);
- }
-}
-
-} // namespace internal
-} // namespace webrtc
diff --git a/third_party/libwebrtc/video/video_send_stream.h b/third_party/libwebrtc/video/video_send_stream.h
deleted file mode 100644
index 4afafcf8e4..0000000000
--- a/third_party/libwebrtc/video/video_send_stream.h
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
- *
- * Use of this source code is governed by a BSD-style license
- * that can be found in the LICENSE file in the root of the source
- * tree. An additional intellectual property rights grant can be found
- * in the file PATENTS. All contributing project authors may
- * be found in the AUTHORS file in the root of the source tree.
- */
-
-#ifndef VIDEO_VIDEO_SEND_STREAM_H_
-#define VIDEO_VIDEO_SEND_STREAM_H_
-
-#include <map>
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "api/fec_controller.h"
-#include "api/field_trials_view.h"
-#include "api/sequence_checker.h"
-#include "api/task_queue/pending_task_safety_flag.h"
-#include "call/bitrate_allocator.h"
-#include "call/video_receive_stream.h"
-#include "call/video_send_stream.h"
-#include "rtc_base/event.h"
-#include "rtc_base/system/no_unique_address.h"
-#include "video/encoder_rtcp_feedback.h"
-#include "video/send_delay_stats.h"
-#include "video/send_statistics_proxy.h"
-#include "video/video_send_stream_impl.h"
-#include "video/video_stream_encoder_interface.h"
-
-namespace webrtc {
-namespace test {
-class VideoSendStreamPeer;
-} // namespace test
-
-class IvfFileWriter;
-class RateLimiter;
-class RtpRtcp;
-class RtpTransportControllerSendInterface;
-class RtcEventLog;
-
-namespace internal {
-
-class VideoSendStreamImpl;
-
-// VideoSendStream implements webrtc::VideoSendStream.
-// Internally, it delegates all public methods to VideoSendStreamImpl and / or
-// VideoStreamEncoder.
-class VideoSendStream : public webrtc::VideoSendStream {
- public:
- using RtpStateMap = std::map<uint32_t, RtpState>;
- using RtpPayloadStateMap = std::map<uint32_t, RtpPayloadState>;
-
- VideoSendStream(
- Clock* clock,
- int num_cpu_cores,
- TaskQueueFactory* task_queue_factory,
- TaskQueueBase* network_queue,
- RtcpRttStats* call_stats,
- RtpTransportControllerSendInterface* transport,
- BitrateAllocatorInterface* bitrate_allocator,
- SendDelayStats* send_delay_stats,
- RtcEventLog* event_log,
- VideoSendStream::Config config,
- VideoEncoderConfig encoder_config,
- const std::map<uint32_t, RtpState>& suspended_ssrcs,
- const std::map<uint32_t, RtpPayloadState>& suspended_payload_states,
- std::unique_ptr<FecController> fec_controller,
- const FieldTrialsView& field_trials);
-
- ~VideoSendStream() override;
-
- void DeliverRtcp(const uint8_t* packet, size_t length);
-
- // webrtc::VideoSendStream implementation.
- void Start() override;
- void StartPerRtpStream(std::vector<bool> active_layers) override;
- void Stop() override;
- bool started() override;
-
- void AddAdaptationResource(rtc::scoped_refptr<Resource> resource) override;
- std::vector<rtc::scoped_refptr<Resource>> GetAdaptationResources() override;
-
- void SetSource(rtc::VideoSourceInterface<webrtc::VideoFrame>* source,
- const DegradationPreference& degradation_preference) override;
-
- void ReconfigureVideoEncoder(VideoEncoderConfig config) override;
- void ReconfigureVideoEncoder(VideoEncoderConfig config,
- SetParametersCallback callback) override;
- Stats GetStats() override;
-
- void StopPermanentlyAndGetRtpStates(RtpStateMap* rtp_state_map,
- RtpPayloadStateMap* payload_state_map);
- void GenerateKeyFrame(const std::vector<std::string>& rids) override;
-
- private:
- friend class test::VideoSendStreamPeer;
- class OnSendPacketObserver : public SendPacketObserver {
- public:
- OnSendPacketObserver(SendStatisticsProxy* stats_proxy,
- SendDelayStats* send_delay_stats)
- : stats_proxy_(*stats_proxy), send_delay_stats_(*send_delay_stats) {}
-
- void OnSendPacket(absl::optional<uint16_t> packet_id,
- Timestamp capture_time,
- uint32_t ssrc) override {
- stats_proxy_.OnSendPacket(ssrc, capture_time);
- if (packet_id.has_value()) {
- send_delay_stats_.OnSendPacket(*packet_id, capture_time, ssrc);
- }
- }
-
- private:
- SendStatisticsProxy& stats_proxy_;
- SendDelayStats& send_delay_stats_;
- };
-
- absl::optional<float> GetPacingFactorOverride() const;
-
- RTC_NO_UNIQUE_ADDRESS SequenceChecker thread_checker_;
- RtpTransportControllerSendInterface* const transport_;
-
- SendStatisticsProxy stats_proxy_;
- OnSendPacketObserver send_packet_observer_;
- const VideoSendStream::Config config_;
- const VideoEncoderConfig::ContentType content_type_;
- std::unique_ptr<VideoStreamEncoderInterface> video_stream_encoder_;
- EncoderRtcpFeedback encoder_feedback_;
- RtpVideoSenderInterface* const rtp_video_sender_;
- VideoSendStreamImpl send_stream_;
- bool running_ RTC_GUARDED_BY(thread_checker_) = false;
-};
-
-} // namespace internal
-} // namespace webrtc
-
-#endif // VIDEO_VIDEO_SEND_STREAM_H_
diff --git a/third_party/libwebrtc/video/video_send_stream_impl.cc b/third_party/libwebrtc/video/video_send_stream_impl.cc
index ee023d9fec..23dbb7177f 100644
--- a/third_party/libwebrtc/video/video_send_stream_impl.cc
+++ b/third_party/libwebrtc/video/video_send_stream_impl.cc
@@ -13,20 +13,51 @@
#include <algorithm>
#include <cstdint>
+#include <map>
+#include <memory>
#include <string>
#include <utility>
+#include <vector>
#include "absl/algorithm/container.h"
+#include "absl/types/optional.h"
+#include "api/adaptation/resource.h"
+#include "api/call/bitrate_allocation.h"
#include "api/crypto/crypto_options.h"
+#include "api/fec_controller.h"
+#include "api/field_trials_view.h"
+#include "api/metronome/metronome.h"
#include "api/rtp_parameters.h"
+#include "api/rtp_sender_interface.h"
#include "api/scoped_refptr.h"
#include "api/sequence_checker.h"
#include "api/task_queue/pending_task_safety_flag.h"
#include "api/task_queue/task_queue_base.h"
+#include "api/task_queue/task_queue_factory.h"
+#include "api/units/data_rate.h"
+#include "api/units/time_delta.h"
+#include "api/video/encoded_image.h"
+#include "api/video/video_bitrate_allocation.h"
+#include "api/video/video_codec_constants.h"
+#include "api/video/video_codec_type.h"
+#include "api/video/video_frame.h"
+#include "api/video/video_frame_type.h"
+#include "api/video/video_layers_allocation.h"
+#include "api/video/video_source_interface.h"
+#include "api/video/video_stream_encoder_settings.h"
#include "api/video_codecs/video_codec.h"
+#include "api/video_codecs/video_encoder.h"
+#include "api/video_codecs/video_encoder_factory.h"
+#include "call/bitrate_allocator.h"
+#include "call/rtp_config.h"
#include "call/rtp_transport_controller_send_interface.h"
#include "call/video_send_stream.h"
#include "modules/pacing/pacing_controller.h"
+#include "modules/rtp_rtcp/include/rtp_header_extension_map.h"
+#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
+#include "modules/rtp_rtcp/source/rtp_header_extension_size.h"
+#include "modules/rtp_rtcp/source/rtp_sender.h"
+#include "modules/video_coding/include/video_codec_interface.h"
#include "rtc_base/checks.h"
#include "rtc_base/experiments/alr_experiment.h"
#include "rtc_base/experiments/field_trial_parser.h"
@@ -34,9 +65,18 @@
#include "rtc_base/experiments/rate_control_settings.h"
#include "rtc_base/logging.h"
#include "rtc_base/numerics/safe_conversions.h"
+#include "rtc_base/strings/string_builder.h"
+#include "rtc_base/task_utils/repeating_task.h"
#include "rtc_base/trace_event.h"
#include "system_wrappers/include/clock.h"
-#include "system_wrappers/include/field_trial.h"
+#include "video/adaptation/overuse_frame_detector.h"
+#include "video/config/video_encoder_config.h"
+#include "video/encoder_rtcp_feedback.h"
+#include "video/frame_cadence_adapter.h"
+#include "video/send_delay_stats.h"
+#include "video/send_statistics_proxy.h"
+#include "video/video_stream_encoder.h"
+#include "video/video_stream_encoder_interface.h"
namespace webrtc {
namespace internal {
@@ -139,12 +179,15 @@ int CalculateMaxPadBitrateBps(const std::vector<VideoStream>& streams,
}
absl::optional<AlrExperimentSettings> GetAlrSettings(
+ const FieldTrialsView& field_trials,
VideoEncoderConfig::ContentType content_type) {
if (content_type == VideoEncoderConfig::ContentType::kScreen) {
return AlrExperimentSettings::CreateFromFieldTrial(
+ field_trials,
AlrExperimentSettings::kScreenshareProbingBweExperimentName);
}
return AlrExperimentSettings::CreateFromFieldTrial(
+ field_trials,
AlrExperimentSettings::kStrictPacingAndProbingExperimentName);
}
@@ -171,7 +214,7 @@ absl::optional<float> GetConfiguredPacingFactor(
return absl::nullopt;
absl::optional<AlrExperimentSettings> alr_settings =
- GetAlrSettings(content_type);
+ GetAlrSettings(field_trials, content_type);
if (alr_settings)
return alr_settings->pacing_factor;
@@ -181,6 +224,19 @@ absl::optional<float> GetConfiguredPacingFactor(
default_pacing_config.pacing_factor);
}
+int GetEncoderPriorityBitrate(std::string codec_name,
+ const FieldTrialsView& field_trials) {
+ int priority_bitrate = 0;
+ if (PayloadStringToCodecType(codec_name) == VideoCodecType::kVideoCodecAV1) {
+ webrtc::FieldTrialParameter<int> av1_priority_bitrate("bitrate", 0);
+ webrtc::ParseFieldTrial(
+ {&av1_priority_bitrate},
+ field_trials.Lookup("WebRTC-AV1-OverridePriorityBitrate"));
+ priority_bitrate = av1_priority_bitrate;
+ }
+ return priority_bitrate;
+}
+
uint32_t GetInitialEncoderMaxBitrate(int initial_encoder_max_bitrate) {
if (initial_encoder_max_bitrate > 0)
return rtc::dchecked_cast<uint32_t>(initial_encoder_max_bitrate);
@@ -205,6 +261,107 @@ int GetDefaultMinVideoBitrateBps(VideoCodecType codec_type) {
return kDefaultMinVideoBitrateBps;
}
+size_t CalculateMaxHeaderSize(const RtpConfig& config) {
+ size_t header_size = kRtpHeaderSize;
+ size_t extensions_size = 0;
+ size_t fec_extensions_size = 0;
+ if (!config.extensions.empty()) {
+ RtpHeaderExtensionMap extensions_map(config.extensions);
+ extensions_size = RtpHeaderExtensionSize(RTPSender::VideoExtensionSizes(),
+ extensions_map);
+ fec_extensions_size =
+ RtpHeaderExtensionSize(RTPSender::FecExtensionSizes(), extensions_map);
+ }
+ header_size += extensions_size;
+ if (config.flexfec.payload_type >= 0) {
+ // All FEC extensions again plus maximum FlexFec overhead.
+ header_size += fec_extensions_size + 32;
+ } else {
+ if (config.ulpfec.ulpfec_payload_type >= 0) {
+ // Header with all the FEC extensions will be repeated plus maximum
+ // UlpFec overhead.
+ header_size += fec_extensions_size + 18;
+ }
+ if (config.ulpfec.red_payload_type >= 0) {
+ header_size += 1; // RED header.
+ }
+ }
+ // Additional room for Rtx.
+ if (config.rtx.payload_type >= 0)
+ header_size += kRtxHeaderSize;
+ return header_size;
+}
+
+VideoStreamEncoder::BitrateAllocationCallbackType
+GetBitrateAllocationCallbackType(const VideoSendStream::Config& config,
+ const FieldTrialsView& field_trials) {
+ if (webrtc::RtpExtension::FindHeaderExtensionByUri(
+ config.rtp.extensions,
+ webrtc::RtpExtension::kVideoLayersAllocationUri,
+ config.crypto_options.srtp.enable_encrypted_rtp_header_extensions
+ ? RtpExtension::Filter::kPreferEncryptedExtension
+ : RtpExtension::Filter::kDiscardEncryptedExtension)) {
+ return VideoStreamEncoder::BitrateAllocationCallbackType::
+ kVideoLayersAllocation;
+ }
+ if (field_trials.IsEnabled("WebRTC-Target-Bitrate-Rtcp")) {
+ return VideoStreamEncoder::BitrateAllocationCallbackType::
+ kVideoBitrateAllocation;
+ }
+ return VideoStreamEncoder::BitrateAllocationCallbackType::
+ kVideoBitrateAllocationWhenScreenSharing;
+}
+
+RtpSenderFrameEncryptionConfig CreateFrameEncryptionConfig(
+ const VideoSendStream::Config* config) {
+ RtpSenderFrameEncryptionConfig frame_encryption_config;
+ frame_encryption_config.frame_encryptor = config->frame_encryptor.get();
+ frame_encryption_config.crypto_options = config->crypto_options;
+ return frame_encryption_config;
+}
+
+RtpSenderObservers CreateObservers(RtcpRttStats* call_stats,
+ EncoderRtcpFeedback* encoder_feedback,
+ SendStatisticsProxy* stats_proxy,
+ SendPacketObserver* send_packet_observer) {
+ RtpSenderObservers observers;
+ observers.rtcp_rtt_stats = call_stats;
+ observers.intra_frame_callback = encoder_feedback;
+ observers.rtcp_loss_notification_observer = encoder_feedback;
+ observers.report_block_data_observer = stats_proxy;
+ observers.rtp_stats = stats_proxy;
+ observers.bitrate_observer = stats_proxy;
+ observers.frame_count_observer = stats_proxy;
+ observers.rtcp_type_observer = stats_proxy;
+ observers.send_packet_observer = send_packet_observer;
+ return observers;
+}
+
+std::unique_ptr<VideoStreamEncoderInterface> CreateVideoStreamEncoder(
+ Clock* clock,
+ int num_cpu_cores,
+ TaskQueueFactory* task_queue_factory,
+ SendStatisticsProxy* stats_proxy,
+ const VideoStreamEncoderSettings& encoder_settings,
+ VideoStreamEncoder::BitrateAllocationCallbackType
+ bitrate_allocation_callback_type,
+ const FieldTrialsView& field_trials,
+ Metronome* metronome,
+ webrtc::VideoEncoderFactory::EncoderSelectorInterface* encoder_selector) {
+ std::unique_ptr<TaskQueueBase, TaskQueueDeleter> encoder_queue =
+ task_queue_factory->CreateTaskQueue("EncoderQueue",
+ TaskQueueFactory::Priority::NORMAL);
+ TaskQueueBase* encoder_queue_ptr = encoder_queue.get();
+ return std::make_unique<VideoStreamEncoder>(
+ clock, num_cpu_cores, stats_proxy, encoder_settings,
+ std::make_unique<OveruseFrameDetector>(stats_proxy),
+ FrameCadenceAdapterInterface::Create(
+ clock, encoder_queue_ptr, metronome,
+ /*worker_queue=*/TaskQueueBase::Current(), field_trials),
+ std::move(encoder_queue), bitrate_allocation_callback_type, field_trials,
+ encoder_selector);
+}
+
} // namespace
PacingConfig::PacingConfig(const FieldTrialsView& field_trials)
@@ -218,58 +375,90 @@ PacingConfig::~PacingConfig() = default;
VideoSendStreamImpl::VideoSendStreamImpl(
Clock* clock,
- SendStatisticsProxy* stats_proxy,
+ int num_cpu_cores,
+ TaskQueueFactory* task_queue_factory,
+ RtcpRttStats* call_stats,
RtpTransportControllerSendInterface* transport,
+ Metronome* metronome,
BitrateAllocatorInterface* bitrate_allocator,
- VideoStreamEncoderInterface* video_stream_encoder,
- const VideoSendStream::Config* config,
- int initial_encoder_max_bitrate,
- double initial_encoder_bitrate_priority,
- VideoEncoderConfig::ContentType content_type,
- RtpVideoSenderInterface* rtp_video_sender,
- const FieldTrialsView& field_trials)
- : clock_(clock),
- has_alr_probing_(config->periodic_alr_bandwidth_probing ||
- GetAlrSettings(content_type)),
+ SendDelayStats* send_delay_stats,
+ RtcEventLog* event_log,
+ VideoSendStream::Config config,
+ VideoEncoderConfig encoder_config,
+ const std::map<uint32_t, RtpState>& suspended_ssrcs,
+ const std::map<uint32_t, RtpPayloadState>& suspended_payload_states,
+ std::unique_ptr<FecController> fec_controller,
+ const FieldTrialsView& field_trials,
+ std::unique_ptr<VideoStreamEncoderInterface> video_stream_encoder_for_test)
+ : transport_(transport),
+ stats_proxy_(clock, config, encoder_config.content_type, field_trials),
+ send_packet_observer_(&stats_proxy_, send_delay_stats),
+ config_(std::move(config)),
+ content_type_(encoder_config.content_type),
+ video_stream_encoder_(
+ video_stream_encoder_for_test
+ ? std::move(video_stream_encoder_for_test)
+ : CreateVideoStreamEncoder(
+ clock,
+ num_cpu_cores,
+ task_queue_factory,
+ &stats_proxy_,
+ config_.encoder_settings,
+ GetBitrateAllocationCallbackType(config_, field_trials),
+ field_trials,
+ metronome,
+ config_.encoder_selector)),
+ encoder_feedback_(
+ clock,
+ config_.rtp.ssrcs,
+ video_stream_encoder_.get(),
+ [this](uint32_t ssrc, const std::vector<uint16_t>& seq_nums) {
+ return rtp_video_sender_->GetSentRtpPacketInfos(ssrc, seq_nums);
+ }),
+ rtp_video_sender_(transport->CreateRtpVideoSender(
+ suspended_ssrcs,
+ suspended_payload_states,
+ config_.rtp,
+ config_.rtcp_report_interval_ms,
+ config_.send_transport,
+ CreateObservers(call_stats,
+ &encoder_feedback_,
+ &stats_proxy_,
+ &send_packet_observer_),
+ event_log,
+ std::move(fec_controller),
+ CreateFrameEncryptionConfig(&config_),
+ config_.frame_transformer)),
+ clock_(clock),
+ has_alr_probing_(
+ config_.periodic_alr_bandwidth_probing ||
+ GetAlrSettings(field_trials, encoder_config.content_type)),
pacing_config_(PacingConfig(field_trials)),
- stats_proxy_(stats_proxy),
- config_(config),
worker_queue_(TaskQueueBase::Current()),
timed_out_(false),
- transport_(transport),
+
bitrate_allocator_(bitrate_allocator),
disable_padding_(true),
max_padding_bitrate_(0),
encoder_min_bitrate_bps_(0),
encoder_max_bitrate_bps_(
- GetInitialEncoderMaxBitrate(initial_encoder_max_bitrate)),
+ GetInitialEncoderMaxBitrate(encoder_config.max_bitrate_bps)),
encoder_target_rate_bps_(0),
- encoder_bitrate_priority_(initial_encoder_bitrate_priority),
- video_stream_encoder_(video_stream_encoder),
- rtp_video_sender_(rtp_video_sender),
- configured_pacing_factor_(GetConfiguredPacingFactor(*config_,
- content_type,
+ encoder_bitrate_priority_(encoder_config.bitrate_priority),
+ encoder_av1_priority_bitrate_override_bps_(
+ GetEncoderPriorityBitrate(config_.rtp.payload_name, field_trials)),
+ configured_pacing_factor_(GetConfiguredPacingFactor(config_,
+ content_type_,
pacing_config_,
field_trials)) {
- RTC_DCHECK_GE(config_->rtp.payload_type, 0);
- RTC_DCHECK_LE(config_->rtp.payload_type, 127);
- RTC_DCHECK(!config_->rtp.ssrcs.empty());
+ RTC_DCHECK_GE(config_.rtp.payload_type, 0);
+ RTC_DCHECK_LE(config_.rtp.payload_type, 127);
+ RTC_DCHECK(!config_.rtp.ssrcs.empty());
RTC_DCHECK(transport_);
- RTC_DCHECK_NE(initial_encoder_max_bitrate, 0);
- RTC_LOG(LS_INFO) << "VideoSendStreamImpl: " << config_->ToString();
+ RTC_DCHECK_NE(encoder_max_bitrate_bps_, 0);
+ RTC_LOG(LS_INFO) << "VideoSendStreamImpl: " << config_.ToString();
- RTC_CHECK(AlrExperimentSettings::MaxOneFieldTrialEnabled());
-
- // Only request rotation at the source when we positively know that the remote
- // side doesn't support the rotation extension. This allows us to prepare the
- // encoder in the expectation that rotation is supported - which is the common
- // case.
- bool rotation_applied = absl::c_none_of(
- config_->rtp.extensions, [](const RtpExtension& extension) {
- return extension.uri == RtpExtension::kVideoRotationUri;
- });
-
- video_stream_encoder_->SetSink(this, rotation_applied);
+ RTC_CHECK(AlrExperimentSettings::MaxOneFieldTrialEnabled(field_trials));
absl::optional<bool> enable_alr_bw_probing;
@@ -277,7 +466,7 @@ VideoSendStreamImpl::VideoSendStreamImpl(
// pacing settings.
if (configured_pacing_factor_) {
absl::optional<AlrExperimentSettings> alr_settings =
- GetAlrSettings(content_type);
+ GetAlrSettings(field_trials, content_type_);
int queue_time_limit_ms;
if (alr_settings) {
enable_alr_bw_probing = true;
@@ -289,11 +478,11 @@ VideoSendStreamImpl::VideoSendStreamImpl(
queue_time_limit_ms = pacing_config_.max_pacing_delay.Get().ms();
}
- transport->SetQueueTimeLimit(queue_time_limit_ms);
+ transport_->SetQueueTimeLimit(queue_time_limit_ms);
}
- if (config_->periodic_alr_bandwidth_probing) {
- enable_alr_bw_probing = config_->periodic_alr_bandwidth_probing;
+ if (config_.periodic_alr_bandwidth_probing) {
+ enable_alr_bw_probing = config_.periodic_alr_bandwidth_probing;
}
if (enable_alr_bw_probing) {
@@ -303,13 +492,110 @@ VideoSendStreamImpl::VideoSendStreamImpl(
if (configured_pacing_factor_)
transport_->SetPacingFactor(*configured_pacing_factor_);
+ // Only request rotation at the source when we positively know that the remote
+ // side doesn't support the rotation extension. This allows us to prepare the
+ // encoder in the expectation that rotation is supported - which is the common
+ // case.
+ bool rotation_applied = absl::c_none_of(
+ config_.rtp.extensions, [](const RtpExtension& extension) {
+ return extension.uri == RtpExtension::kVideoRotationUri;
+ });
+
+ video_stream_encoder_->SetSink(this, rotation_applied);
video_stream_encoder_->SetStartBitrate(
bitrate_allocator_->GetStartBitrate(this));
+ video_stream_encoder_->SetFecControllerOverride(rtp_video_sender_);
+ ReconfigureVideoEncoder(std::move(encoder_config));
}
VideoSendStreamImpl::~VideoSendStreamImpl() {
RTC_DCHECK_RUN_ON(&thread_checker_);
- RTC_LOG(LS_INFO) << "~VideoSendStreamImpl: " << config_->ToString();
+ RTC_LOG(LS_INFO) << "~VideoSendStreamImpl: " << config_.ToString();
+ RTC_DCHECK(!started());
+ transport_->DestroyRtpVideoSender(rtp_video_sender_);
+}
+
+void VideoSendStreamImpl::AddAdaptationResource(
+ rtc::scoped_refptr<Resource> resource) {
+ RTC_DCHECK_RUN_ON(&thread_checker_);
+ video_stream_encoder_->AddAdaptationResource(resource);
+}
+
+std::vector<rtc::scoped_refptr<Resource>>
+VideoSendStreamImpl::GetAdaptationResources() {
+ RTC_DCHECK_RUN_ON(&thread_checker_);
+ return video_stream_encoder_->GetAdaptationResources();
+}
+
+void VideoSendStreamImpl::SetSource(
+ rtc::VideoSourceInterface<webrtc::VideoFrame>* source,
+ const DegradationPreference& degradation_preference) {
+ RTC_DCHECK_RUN_ON(&thread_checker_);
+ video_stream_encoder_->SetSource(source, degradation_preference);
+}
+
+void VideoSendStreamImpl::ReconfigureVideoEncoder(VideoEncoderConfig config) {
+ ReconfigureVideoEncoder(std::move(config), nullptr);
+}
+
+void VideoSendStreamImpl::ReconfigureVideoEncoder(
+ VideoEncoderConfig config,
+ SetParametersCallback callback) {
+ RTC_DCHECK_RUN_ON(&thread_checker_);
+ RTC_DCHECK_EQ(content_type_, config.content_type);
+ RTC_LOG(LS_VERBOSE) << "Encoder config: " << config.ToString()
+ << " VideoSendStream config: " << config_.ToString();
+ video_stream_encoder_->ConfigureEncoder(
+ std::move(config),
+ config_.rtp.max_packet_size - CalculateMaxHeaderSize(config_.rtp),
+ std::move(callback));
+}
+
+VideoSendStream::Stats VideoSendStreamImpl::GetStats() {
+ RTC_DCHECK_RUN_ON(&thread_checker_);
+ return stats_proxy_.GetStats();
+}
+
+absl::optional<float> VideoSendStreamImpl::GetPacingFactorOverride() const {
+ return configured_pacing_factor_;
+}
+
+void VideoSendStreamImpl::StopPermanentlyAndGetRtpStates(
+ VideoSendStreamImpl::RtpStateMap* rtp_state_map,
+ VideoSendStreamImpl::RtpPayloadStateMap* payload_state_map) {
+ RTC_DCHECK_RUN_ON(&thread_checker_);
+ video_stream_encoder_->Stop();
+
+ running_ = false;
+ // Always run these cleanup steps regardless of whether running_ was set
+ // or not. This will unregister callbacks before destruction.
+ // See `VideoSendStreamImpl::StopVideoSendStream` for more.
+ Stop();
+ *rtp_state_map = GetRtpStates();
+ *payload_state_map = GetRtpPayloadStates();
+}
+
+void VideoSendStreamImpl::GenerateKeyFrame(
+ const std::vector<std::string>& rids) {
+ RTC_DCHECK_RUN_ON(&thread_checker_);
+ // Map rids to layers. If rids is empty, generate a keyframe for all layers.
+ std::vector<VideoFrameType> next_frames(config_.rtp.ssrcs.size(),
+ VideoFrameType::kVideoFrameKey);
+ if (!config_.rtp.rids.empty() && !rids.empty()) {
+ std::fill(next_frames.begin(), next_frames.end(),
+ VideoFrameType::kVideoFrameDelta);
+ for (const auto& rid : rids) {
+ for (size_t i = 0; i < config_.rtp.rids.size(); i++) {
+ if (config_.rtp.rids[i] == rid) {
+ next_frames[i] = VideoFrameType::kVideoFrameKey;
+ break;
+ }
+ }
+ }
+ }
+ if (video_stream_encoder_) {
+ video_stream_encoder_->SendKeyFrame(next_frames);
+ }
}
void VideoSendStreamImpl::DeliverRtcp(const uint8_t* packet, size_t length) {
@@ -317,9 +603,35 @@ void VideoSendStreamImpl::DeliverRtcp(const uint8_t* packet, size_t length) {
rtp_video_sender_->DeliverRtcp(packet, length);
}
+bool VideoSendStreamImpl::started() {
+ RTC_DCHECK_RUN_ON(&thread_checker_);
+ return rtp_video_sender_->IsActive();
+}
+
+void VideoSendStreamImpl::Start() {
+ const std::vector<bool> active_layers(config_.rtp.ssrcs.size(), true);
+ StartPerRtpStream(active_layers);
+}
+
void VideoSendStreamImpl::StartPerRtpStream(
const std::vector<bool> active_layers) {
RTC_DCHECK_RUN_ON(&thread_checker_);
+
+ rtc::StringBuilder active_layers_string;
+ active_layers_string << "{";
+ for (size_t i = 0; i < active_layers.size(); ++i) {
+ if (active_layers[i]) {
+ active_layers_string << "1";
+ } else {
+ active_layers_string << "0";
+ }
+ if (i < active_layers.size() - 1) {
+ active_layers_string << ", ";
+ }
+ }
+ active_layers_string << "}";
+ RTC_LOG(LS_INFO) << "StartPerRtpStream: " << active_layers_string.str();
+
bool previously_active = rtp_video_sender_->IsActive();
rtp_video_sender_->SetActiveModules(active_layers);
if (!rtp_video_sender_->IsActive() && previously_active) {
@@ -377,7 +689,7 @@ void VideoSendStreamImpl::StopVideoSendStream() {
check_encoder_activity_task_.Stop();
video_stream_encoder_->OnBitrateUpdated(DataRate::Zero(), DataRate::Zero(),
DataRate::Zero(), 0, 0, 0);
- stats_proxy_->OnSetEncoderTargetRate(0);
+ stats_proxy_.OnSetEncoderTargetRate(0);
}
void VideoSendStreamImpl::SignalEncoderTimedOut() {
@@ -460,8 +772,8 @@ MediaStreamAllocationConfig VideoSendStreamImpl::GetAllocationConfig() const {
static_cast<uint32_t>(encoder_min_bitrate_bps_),
encoder_max_bitrate_bps_,
static_cast<uint32_t>(disable_padding_ ? 0 : max_padding_bitrate_),
- /* priority_bitrate */ 0,
- !config_->suspend_below_min_bitrate,
+ encoder_av1_priority_bitrate_override_bps_,
+ !config_.suspend_below_min_bitrate,
encoder_bitrate_priority_};
}
@@ -474,12 +786,12 @@ void VideoSendStreamImpl::OnEncoderConfigurationChanged(
RTC_DCHECK(!worker_queue_->IsCurrent());
auto closure = [this, streams = std::move(streams), is_svc, content_type,
min_transmit_bitrate_bps]() mutable {
- RTC_DCHECK_GE(config_->rtp.ssrcs.size(), streams.size());
+ RTC_DCHECK_GE(config_.rtp.ssrcs.size(), streams.size());
TRACE_EVENT0("webrtc", "VideoSendStream::OnEncoderConfigurationChanged");
RTC_DCHECK_RUN_ON(&thread_checker_);
const VideoCodecType codec_type =
- PayloadStringToCodecType(config_->rtp.payload_name);
+ PayloadStringToCodecType(config_.rtp.payload_name);
const absl::optional<DataRate> experimental_min_bitrate =
GetExperimentalMinVideoBitrate(codec_type);
@@ -508,11 +820,11 @@ void VideoSendStreamImpl::OnEncoderConfigurationChanged(
// TODO(bugs.webrtc.org/10266): Query the VideoBitrateAllocator instead.
max_padding_bitrate_ = CalculateMaxPadBitrateBps(
streams, is_svc, content_type, min_transmit_bitrate_bps,
- config_->suspend_below_min_bitrate, has_alr_probing_);
+ config_.suspend_below_min_bitrate, has_alr_probing_);
// Clear stats for disabled layers.
- for (size_t i = streams.size(); i < config_->rtp.ssrcs.size(); ++i) {
- stats_proxy_->OnInactiveSsrc(config_->rtp.ssrcs[i]);
+ for (size_t i = streams.size(); i < config_.rtp.ssrcs.size(); ++i) {
+ stats_proxy_.OnInactiveSsrc(config_.rtp.ssrcs[i]);
}
const size_t num_temporal_layers =
@@ -588,7 +900,7 @@ uint32_t VideoSendStreamImpl::OnBitrateUpdated(BitrateAllocationUpdate update) {
update.stable_target_bitrate = update.target_bitrate;
}
- rtp_video_sender_->OnBitrateUpdated(update, stats_proxy_->GetSendFrameRate());
+ rtp_video_sender_->OnBitrateUpdated(update, stats_proxy_.GetSendFrameRate());
encoder_target_rate_bps_ = rtp_video_sender_->GetPayloadBitrateBps();
const uint32_t protection_bitrate_bps =
rtp_video_sender_->GetProtectionBitrateBps();
@@ -619,7 +931,7 @@ uint32_t VideoSendStreamImpl::OnBitrateUpdated(BitrateAllocationUpdate update) {
encoder_target_rate, encoder_stable_target_rate, link_allocation,
rtc::dchecked_cast<uint8_t>(update.packet_loss_ratio * 256),
update.round_trip_time.ms(), update.cwnd_reduce_ratio);
- stats_proxy_->OnSetEncoderTargetRate(encoder_target_rate_bps_);
+ stats_proxy_.OnSetEncoderTargetRate(encoder_target_rate_bps_);
return protection_bitrate_bps;
}
diff --git a/third_party/libwebrtc/video/video_send_stream_impl.h b/third_party/libwebrtc/video/video_send_stream_impl.h
index c5e0980f6d..758e12c095 100644
--- a/third_party/libwebrtc/video/video_send_stream_impl.h
+++ b/third_party/libwebrtc/video/video_send_stream_impl.h
@@ -16,21 +16,22 @@
#include <atomic>
#include <map>
#include <memory>
+#include <string>
#include <vector>
#include "absl/types/optional.h"
#include "api/field_trials_view.h"
+#include "api/metronome/metronome.h"
#include "api/task_queue/pending_task_safety_flag.h"
#include "api/task_queue/task_queue_base.h"
#include "api/video/encoded_image.h"
#include "api/video/video_bitrate_allocation.h"
-#include "api/video/video_bitrate_allocator.h"
#include "api/video_codecs/video_encoder.h"
#include "call/bitrate_allocator.h"
#include "call/rtp_config.h"
#include "call/rtp_transport_controller_send_interface.h"
#include "call/rtp_video_sender_interface.h"
-#include "modules/include/module_common_types.h"
+#include "call/video_send_stream.h"
#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
#include "modules/video_coding/include/video_codec_interface.h"
#include "rtc_base/experiments/field_trial_parser.h"
@@ -38,10 +39,17 @@
#include "rtc_base/task_utils/repeating_task.h"
#include "rtc_base/thread_annotations.h"
#include "video/config/video_encoder_config.h"
+#include "video/encoder_rtcp_feedback.h"
+#include "video/send_delay_stats.h"
#include "video/send_statistics_proxy.h"
#include "video/video_stream_encoder_interface.h"
namespace webrtc {
+
+namespace test {
+class VideoSendStreamPeer;
+} // namespace test
+
namespace internal {
// Pacing buffer config; overridden by ALR config if provided.
@@ -54,32 +62,58 @@ struct PacingConfig {
FieldTrialParameter<TimeDelta> max_pacing_delay;
};
-// VideoSendStreamImpl implements internal::VideoSendStream.
-// It is created and destroyed on `rtp_transport_queue`. The intent is to
-// decrease the need for locking and to ensure methods are called in sequence.
-// Public methods except `DeliverRtcp` must be called on `rtp_transport_queue`.
-// DeliverRtcp is called on the libjingle worker thread or a network thread.
+// VideoSendStreamImpl implements webrtc::VideoSendStream.
+// It is created and destroyed on `worker queue`. The intent is to
// An encoder may deliver frames through the EncodedImageCallback on an
// arbitrary thread.
-class VideoSendStreamImpl : public webrtc::BitrateAllocatorObserver,
+class VideoSendStreamImpl : public webrtc::VideoSendStream,
+ public webrtc::BitrateAllocatorObserver,
public VideoStreamEncoderInterface::EncoderSink {
public:
+ using RtpStateMap = std::map<uint32_t, RtpState>;
+ using RtpPayloadStateMap = std::map<uint32_t, RtpPayloadState>;
+
VideoSendStreamImpl(Clock* clock,
- SendStatisticsProxy* stats_proxy,
+ int num_cpu_cores,
+ TaskQueueFactory* task_queue_factory,
+ RtcpRttStats* call_stats,
RtpTransportControllerSendInterface* transport,
+ Metronome* metronome,
BitrateAllocatorInterface* bitrate_allocator,
- VideoStreamEncoderInterface* video_stream_encoder,
- const VideoSendStream::Config* config,
- int initial_encoder_max_bitrate,
- double initial_encoder_bitrate_priority,
- VideoEncoderConfig::ContentType content_type,
- RtpVideoSenderInterface* rtp_video_sender,
- const FieldTrialsView& field_trials);
+ SendDelayStats* send_delay_stats,
+ RtcEventLog* event_log,
+ VideoSendStream::Config config,
+ VideoEncoderConfig encoder_config,
+ const RtpStateMap& suspended_ssrcs,
+ const RtpPayloadStateMap& suspended_payload_states,
+ std::unique_ptr<FecController> fec_controller,
+ const FieldTrialsView& field_trials,
+ std::unique_ptr<VideoStreamEncoderInterface>
+ video_stream_encoder_for_test = nullptr);
~VideoSendStreamImpl() override;
void DeliverRtcp(const uint8_t* packet, size_t length);
- void StartPerRtpStream(std::vector<bool> active_layers);
- void Stop();
+
+ // webrtc::VideoSendStream implementation.
+ void Start() override;
+ void StartPerRtpStream(std::vector<bool> active_layers) override;
+ void Stop() override;
+ bool started() override;
+
+ void AddAdaptationResource(rtc::scoped_refptr<Resource> resource) override;
+ std::vector<rtc::scoped_refptr<Resource>> GetAdaptationResources() override;
+
+ void SetSource(rtc::VideoSourceInterface<webrtc::VideoFrame>* source,
+ const DegradationPreference& degradation_preference) override;
+
+ void ReconfigureVideoEncoder(VideoEncoderConfig config) override;
+ void ReconfigureVideoEncoder(VideoEncoderConfig config,
+ SetParametersCallback callback) override;
+ Stats GetStats() override;
+
+ void StopPermanentlyAndGetRtpStates(RtpStateMap* rtp_state_map,
+ RtpPayloadStateMap* payload_state_map);
+ void GenerateKeyFrame(const std::vector<std::string>& rids) override;
// TODO(holmer): Move these to RtpTransportControllerSend.
std::map<uint32_t, RtpState> GetRtpStates() const;
@@ -91,6 +125,28 @@ class VideoSendStreamImpl : public webrtc::BitrateAllocatorObserver,
}
private:
+ friend class test::VideoSendStreamPeer;
+ class OnSendPacketObserver : public SendPacketObserver {
+ public:
+ OnSendPacketObserver(SendStatisticsProxy* stats_proxy,
+ SendDelayStats* send_delay_stats)
+ : stats_proxy_(*stats_proxy), send_delay_stats_(*send_delay_stats) {}
+
+ void OnSendPacket(absl::optional<uint16_t> packet_id,
+ Timestamp capture_time,
+ uint32_t ssrc) override {
+ stats_proxy_.OnSendPacket(ssrc, capture_time);
+ if (packet_id.has_value()) {
+ send_delay_stats_.OnSendPacket(*packet_id, capture_time, ssrc);
+ }
+ }
+
+ private:
+ SendStatisticsProxy& stats_proxy_;
+ SendDelayStats& send_delay_stats_;
+ };
+
+ absl::optional<float> GetPacingFactorOverride() const;
// Implements BitrateAllocatorObserver.
uint32_t OnBitrateUpdated(BitrateAllocationUpdate update) override;
@@ -130,13 +186,22 @@ class VideoSendStreamImpl : public webrtc::BitrateAllocatorObserver,
RTC_RUN_ON(thread_checker_);
RTC_NO_UNIQUE_ADDRESS SequenceChecker thread_checker_;
+
+ RtpTransportControllerSendInterface* const transport_;
+
+ SendStatisticsProxy stats_proxy_;
+ OnSendPacketObserver send_packet_observer_;
+ const VideoSendStream::Config config_;
+ const VideoEncoderConfig::ContentType content_type_;
+ std::unique_ptr<VideoStreamEncoderInterface> video_stream_encoder_;
+ EncoderRtcpFeedback encoder_feedback_;
+ RtpVideoSenderInterface* const rtp_video_sender_;
+ bool running_ RTC_GUARDED_BY(thread_checker_) = false;
+
Clock* const clock_;
const bool has_alr_probing_;
const PacingConfig pacing_config_;
- SendStatisticsProxy* const stats_proxy_;
- const VideoSendStream::Config* const config_;
-
TaskQueueBase* const worker_queue_;
RepeatingTaskHandle check_encoder_activity_task_
@@ -145,7 +210,6 @@ class VideoSendStreamImpl : public webrtc::BitrateAllocatorObserver,
std::atomic_bool activity_;
bool timed_out_ RTC_GUARDED_BY(thread_checker_);
- RtpTransportControllerSendInterface* const transport_;
BitrateAllocatorInterface* const bitrate_allocator_;
bool disable_padding_ RTC_GUARDED_BY(thread_checker_);
@@ -154,9 +218,8 @@ class VideoSendStreamImpl : public webrtc::BitrateAllocatorObserver,
uint32_t encoder_max_bitrate_bps_ RTC_GUARDED_BY(thread_checker_);
uint32_t encoder_target_rate_bps_ RTC_GUARDED_BY(thread_checker_);
double encoder_bitrate_priority_ RTC_GUARDED_BY(thread_checker_);
-
- VideoStreamEncoderInterface* const video_stream_encoder_;
- RtpVideoSenderInterface* const rtp_video_sender_;
+ const int encoder_av1_priority_bitrate_override_bps_
+ RTC_GUARDED_BY(thread_checker_);
ScopedTaskSafety worker_queue_safety_;
diff --git a/third_party/libwebrtc/video/video_send_stream_impl_unittest.cc b/third_party/libwebrtc/video/video_send_stream_impl_unittest.cc
index c88ad06cfb..ba492ae66f 100644
--- a/third_party/libwebrtc/video/video_send_stream_impl_unittest.cc
+++ b/third_party/libwebrtc/video/video_send_stream_impl_unittest.cc
@@ -11,31 +11,50 @@
#include "video/video_send_stream_impl.h"
#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <map>
#include <memory>
#include <string>
+#include <utility>
+#include <vector>
#include "absl/types/optional.h"
+#include "api/array_view.h"
+#include "api/call/bitrate_allocation.h"
#include "api/rtc_event_log/rtc_event_log.h"
-#include "api/sequence_checker.h"
+#include "api/rtp_parameters.h"
#include "api/task_queue/task_queue_base.h"
+#include "api/task_queue/task_queue_factory.h"
+#include "api/units/data_rate.h"
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
-#include "call/rtp_video_sender.h"
+#include "api/video/encoded_image.h"
+#include "api/video/video_bitrate_allocation.h"
+#include "api/video/video_layers_allocation.h"
+#include "api/video_codecs/video_encoder.h"
+#include "call/bitrate_allocator.h"
+#include "call/rtp_config.h"
+#include "call/rtp_video_sender_interface.h"
#include "call/test/mock_bitrate_allocator.h"
#include "call/test/mock_rtp_transport_controller_send.h"
+#include "call/video_send_stream.h"
+#include "modules/pacing/packet_router.h"
+#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
#include "modules/rtp_rtcp/source/rtp_sequence_number_map.h"
-#include "modules/video_coding/fec_controller_default.h"
-#include "rtc_base/event.h"
+#include "modules/video_coding/include/video_codec_interface.h"
#include "rtc_base/experiments/alr_experiment.h"
-#include "rtc_base/fake_clock.h"
-#include "rtc_base/logging.h"
+#include "test/field_trial.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "test/mock_transport.h"
#include "test/scoped_key_value_config.h"
#include "test/time_controller/simulated_time_controller.h"
+#include "video/config/video_encoder_config.h"
+#include "video/send_delay_stats.h"
+#include "video/send_statistics_proxy.h"
#include "video/test/mock_video_stream_encoder.h"
-#include "video/video_send_stream.h"
+#include "video/video_stream_encoder_interface.h"
namespace webrtc {
@@ -114,6 +133,7 @@ BitrateAllocationUpdate CreateAllocation(int bitrate_bps) {
update.round_trip_time = TimeDelta::Zero();
return update;
}
+
} // namespace
class VideoSendStreamImplTest : public ::testing::Test {
@@ -155,17 +175,35 @@ class VideoSendStreamImplTest : public ::testing::Test {
std::unique_ptr<VideoSendStreamImpl> CreateVideoSendStreamImpl(
int initial_encoder_max_bitrate,
double initial_encoder_bitrate_priority,
- VideoEncoderConfig::ContentType content_type) {
+ VideoEncoderConfig::ContentType content_type,
+ absl::optional<std::string> codec = std::nullopt) {
EXPECT_CALL(bitrate_allocator_, GetStartBitrate(_))
.WillOnce(Return(123000));
+ VideoEncoderConfig encoder_config;
+ encoder_config.max_bitrate_bps = initial_encoder_max_bitrate;
+ encoder_config.bitrate_priority = initial_encoder_bitrate_priority;
+ encoder_config.content_type = content_type;
+ if (codec) {
+ config_.rtp.payload_name = *codec;
+ }
+
std::map<uint32_t, RtpState> suspended_ssrcs;
std::map<uint32_t, RtpPayloadState> suspended_payload_states;
+
+ std::unique_ptr<NiceMock<MockVideoStreamEncoder>> video_stream_encoder =
+ std::make_unique<NiceMock<MockVideoStreamEncoder>>();
+ video_stream_encoder_ = video_stream_encoder.get();
+
auto ret = std::make_unique<VideoSendStreamImpl>(
- time_controller_.GetClock(), &stats_proxy_, &transport_controller_,
- &bitrate_allocator_, &video_stream_encoder_, &config_,
- initial_encoder_max_bitrate, initial_encoder_bitrate_priority,
- content_type, &rtp_video_sender_, field_trials_);
+ time_controller_.GetClock(),
+ /*num_cpu_cores=*/1, time_controller_.GetTaskQueueFactory(),
+ /*call_stats=*/nullptr, &transport_controller_,
+ /*metronome=*/nullptr, &bitrate_allocator_, &send_delay_stats_,
+ /*event_log=*/nullptr, config_.Copy(), encoder_config.Copy(),
+ suspended_ssrcs, suspended_payload_states,
+ /*fec_controller=*/nullptr, field_trials_,
+ std::move(video_stream_encoder));
// The call to GetStartBitrate() executes asynchronously on the tq.
// Ensure all tasks get to run.
@@ -181,7 +219,7 @@ class VideoSendStreamImplTest : public ::testing::Test {
NiceMock<MockTransport> transport_;
NiceMock<MockRtpTransportControllerSend> transport_controller_;
NiceMock<MockBitrateAllocator> bitrate_allocator_;
- NiceMock<MockVideoStreamEncoder> video_stream_encoder_;
+ NiceMock<MockVideoStreamEncoder>* video_stream_encoder_ = nullptr;
NiceMock<MockRtpVideoSender> rtp_video_sender_;
std::vector<bool> active_modules_;
@@ -218,6 +256,9 @@ TEST_F(VideoSendStreamImplTest, UpdatesObserverOnConfigurationChange) {
config_.suspend_below_min_bitrate = kSuspend;
config_.rtp.extensions.emplace_back(RtpExtension::kTransportSequenceNumberUri,
1);
+ config_.rtp.ssrcs.emplace_back(1);
+ config_.rtp.ssrcs.emplace_back(2);
+
auto vss_impl = CreateVideoSendStreamImpl(
kDefaultInitialBitrateBps, kDefaultBitratePriority,
VideoEncoderConfig::ContentType::kRealtimeVideo);
@@ -248,9 +289,6 @@ TEST_F(VideoSendStreamImplTest, UpdatesObserverOnConfigurationChange) {
int min_transmit_bitrate_bps = 30000;
- config_.rtp.ssrcs.emplace_back(1);
- config_.rtp.ssrcs.emplace_back(2);
-
EXPECT_CALL(bitrate_allocator_, AddObserver(vss_impl.get(), _))
.WillRepeatedly(Invoke(
[&](BitrateAllocatorObserver*, MediaStreamAllocationConfig config) {
@@ -284,6 +322,9 @@ TEST_F(VideoSendStreamImplTest, UpdatesObserverOnConfigurationChangeWithAlr) {
config_.rtp.extensions.emplace_back(RtpExtension::kTransportSequenceNumberUri,
1);
config_.periodic_alr_bandwidth_probing = true;
+ config_.rtp.ssrcs.emplace_back(1);
+ config_.rtp.ssrcs.emplace_back(2);
+
auto vss_impl = CreateVideoSendStreamImpl(
kDefaultInitialBitrateBps, kDefaultBitratePriority,
VideoEncoderConfig::ContentType::kScreen);
@@ -316,9 +357,6 @@ TEST_F(VideoSendStreamImplTest, UpdatesObserverOnConfigurationChangeWithAlr) {
// low_stream.target_bitrate_bps + high_stream.min_bitrate_bps.
int min_transmit_bitrate_bps = 400000;
- config_.rtp.ssrcs.emplace_back(1);
- config_.rtp.ssrcs.emplace_back(2);
-
EXPECT_CALL(bitrate_allocator_, AddObserver(vss_impl.get(), _))
.WillRepeatedly(Invoke(
[&](BitrateAllocatorObserver*, MediaStreamAllocationConfig config) {
@@ -347,6 +385,8 @@ TEST_F(VideoSendStreamImplTest,
UpdatesObserverOnConfigurationChangeWithSimulcastVideoHysteresis) {
test::ScopedKeyValueConfig hysteresis_experiment(
field_trials_, "WebRTC-VideoRateControl/video_hysteresis:1.25/");
+ config_.rtp.ssrcs.emplace_back(1);
+ config_.rtp.ssrcs.emplace_back(2);
auto vss_impl = CreateVideoSendStreamImpl(
kDefaultInitialBitrateBps, kDefaultBitratePriority,
@@ -374,9 +414,6 @@ TEST_F(VideoSendStreamImplTest,
high_stream.max_qp = 56;
high_stream.bitrate_priority = 1;
- config_.rtp.ssrcs.emplace_back(1);
- config_.rtp.ssrcs.emplace_back(2);
-
EXPECT_CALL(bitrate_allocator_, AddObserver(vss_impl.get(), _))
.WillRepeatedly(Invoke([&](BitrateAllocatorObserver*,
MediaStreamAllocationConfig config) {
@@ -397,7 +434,8 @@ TEST_F(VideoSendStreamImplTest,
->OnEncoderConfigurationChanged(
std::vector<VideoStream>{low_stream, high_stream}, false,
VideoEncoderConfig::ContentType::kRealtimeVideo,
- /*min_transmit_bitrate_bps=*/0);
+ /*min_transmit_bitrate_bps=*/
+ 0);
});
time_controller_.AdvanceTime(TimeDelta::Zero());
vss_impl->Stop();
@@ -681,6 +719,76 @@ TEST_F(VideoSendStreamImplTest, ForwardsVideoBitrateAllocationAfterTimeout) {
vss_impl->Stop();
}
+TEST_F(VideoSendStreamImplTest, PriorityBitrateConfigInactiveByDefault) {
+ auto vss_impl = CreateVideoSendStreamImpl(
+ kDefaultInitialBitrateBps, kDefaultBitratePriority,
+ VideoEncoderConfig::ContentType::kRealtimeVideo);
+ EXPECT_CALL(
+ bitrate_allocator_,
+ AddObserver(
+ vss_impl.get(),
+ Field(&MediaStreamAllocationConfig::priority_bitrate_bps, 0)));
+ vss_impl->StartPerRtpStream({true});
+ EXPECT_CALL(bitrate_allocator_, RemoveObserver(vss_impl.get())).Times(1);
+ vss_impl->Stop();
+}
+
+TEST_F(VideoSendStreamImplTest, PriorityBitrateConfigAffectsAV1) {
+ test::ScopedFieldTrials override_priority_bitrate(
+ "WebRTC-AV1-OverridePriorityBitrate/bitrate:20000/");
+ auto vss_impl = CreateVideoSendStreamImpl(
+ kDefaultInitialBitrateBps, kDefaultBitratePriority,
+ VideoEncoderConfig::ContentType::kRealtimeVideo, "AV1");
+ EXPECT_CALL(
+ bitrate_allocator_,
+ AddObserver(
+ vss_impl.get(),
+ Field(&MediaStreamAllocationConfig::priority_bitrate_bps, 20000)));
+ vss_impl->StartPerRtpStream({true});
+ EXPECT_CALL(bitrate_allocator_, RemoveObserver(vss_impl.get())).Times(1);
+ vss_impl->Stop();
+}
+
+TEST_F(VideoSendStreamImplTest,
+ PriorityBitrateConfigSurvivesConfigurationChange) {
+ VideoStream qvga_stream;
+ qvga_stream.width = 320;
+ qvga_stream.height = 180;
+ qvga_stream.max_framerate = 30;
+ qvga_stream.min_bitrate_bps = 30000;
+ qvga_stream.target_bitrate_bps = 150000;
+ qvga_stream.max_bitrate_bps = 200000;
+ qvga_stream.max_qp = 56;
+ qvga_stream.bitrate_priority = 1;
+
+ int min_transmit_bitrate_bps = 30000;
+
+ test::ScopedFieldTrials override_priority_bitrate(
+ "WebRTC-AV1-OverridePriorityBitrate/bitrate:20000/");
+ auto vss_impl = CreateVideoSendStreamImpl(
+ kDefaultInitialBitrateBps, kDefaultBitratePriority,
+ VideoEncoderConfig::ContentType::kRealtimeVideo, "AV1");
+ EXPECT_CALL(
+ bitrate_allocator_,
+ AddObserver(
+ vss_impl.get(),
+ Field(&MediaStreamAllocationConfig::priority_bitrate_bps, 20000)))
+ .Times(2);
+ vss_impl->StartPerRtpStream({true});
+
+ encoder_queue_->PostTask([&] {
+ static_cast<VideoStreamEncoderInterface::EncoderSink*>(vss_impl.get())
+ ->OnEncoderConfigurationChanged(
+ std::vector<VideoStream>{qvga_stream}, false,
+ VideoEncoderConfig::ContentType::kRealtimeVideo,
+ min_transmit_bitrate_bps);
+ });
+ time_controller_.AdvanceTime(TimeDelta::Zero());
+
+ EXPECT_CALL(bitrate_allocator_, RemoveObserver(vss_impl.get())).Times(1);
+ vss_impl->Stop();
+}
+
TEST_F(VideoSendStreamImplTest, CallsVideoStreamEncoderOnBitrateUpdate) {
const bool kSuspend = false;
config_.suspend_below_min_bitrate = kSuspend;
@@ -723,7 +831,7 @@ TEST_F(VideoSendStreamImplTest, CallsVideoStreamEncoderOnBitrateUpdate) {
EXPECT_CALL(rtp_video_sender_, GetPayloadBitrateBps())
.WillOnce(Return(network_constrained_rate.bps()));
EXPECT_CALL(
- video_stream_encoder_,
+ *video_stream_encoder_,
OnBitrateUpdated(network_constrained_rate, network_constrained_rate,
network_constrained_rate, 0, _, 0));
static_cast<BitrateAllocatorObserver*>(vss_impl.get())
@@ -740,7 +848,7 @@ TEST_F(VideoSendStreamImplTest, CallsVideoStreamEncoderOnBitrateUpdate) {
EXPECT_CALL(rtp_video_sender_, OnBitrateUpdated(update, _));
EXPECT_CALL(rtp_video_sender_, GetPayloadBitrateBps())
.WillOnce(Return(rate_with_headroom.bps()));
- EXPECT_CALL(video_stream_encoder_,
+ EXPECT_CALL(*video_stream_encoder_,
OnBitrateUpdated(qvga_max_bitrate, qvga_max_bitrate,
rate_with_headroom, 0, _, 0));
static_cast<BitrateAllocatorObserver*>(vss_impl.get())
@@ -757,7 +865,7 @@ TEST_F(VideoSendStreamImplTest, CallsVideoStreamEncoderOnBitrateUpdate) {
.WillOnce(Return(rate_with_headroom.bps()));
const DataRate headroom_minus_protection =
rate_with_headroom - DataRate::BitsPerSec(protection_bitrate_bps);
- EXPECT_CALL(video_stream_encoder_,
+ EXPECT_CALL(*video_stream_encoder_,
OnBitrateUpdated(qvga_max_bitrate, qvga_max_bitrate,
headroom_minus_protection, 0, _, 0));
static_cast<BitrateAllocatorObserver*>(vss_impl.get())
@@ -770,14 +878,14 @@ TEST_F(VideoSendStreamImplTest, CallsVideoStreamEncoderOnBitrateUpdate) {
EXPECT_CALL(rtp_video_sender_, OnBitrateUpdated(update, _));
EXPECT_CALL(rtp_video_sender_, GetPayloadBitrateBps())
.WillOnce(Return(rate_with_headroom.bps()));
- EXPECT_CALL(video_stream_encoder_,
+ EXPECT_CALL(*video_stream_encoder_,
OnBitrateUpdated(qvga_max_bitrate, qvga_max_bitrate,
qvga_max_bitrate, 0, _, 0));
static_cast<BitrateAllocatorObserver*>(vss_impl.get())
->OnBitrateUpdated(update);
// Set rates to zero on stop.
- EXPECT_CALL(video_stream_encoder_,
+ EXPECT_CALL(*video_stream_encoder_,
OnBitrateUpdated(DataRate::Zero(), DataRate::Zero(),
DataRate::Zero(), 0, 0, 0));
vss_impl->Stop();
diff --git a/third_party/libwebrtc/video/video_send_stream_tests.cc b/third_party/libwebrtc/video/video_send_stream_tests.cc
index 3241740d95..37acd2dc49 100644
--- a/third_party/libwebrtc/video/video_send_stream_tests.cc
+++ b/third_party/libwebrtc/video/video_send_stream_tests.cc
@@ -75,7 +75,7 @@
#include "video/config/encoder_stream_factory.h"
#include "video/send_statistics_proxy.h"
#include "video/transport_adapter.h"
-#include "video/video_send_stream.h"
+#include "video/video_send_stream_impl.h"
namespace webrtc {
namespace test {
@@ -83,13 +83,13 @@ class VideoSendStreamPeer {
public:
explicit VideoSendStreamPeer(webrtc::VideoSendStream* base_class_stream)
: internal_stream_(
- static_cast<internal::VideoSendStream*>(base_class_stream)) {}
+ static_cast<internal::VideoSendStreamImpl*>(base_class_stream)) {}
absl::optional<float> GetPacingFactorOverride() const {
return internal_stream_->GetPacingFactorOverride();
}
private:
- internal::VideoSendStream const* const internal_stream_;
+ internal::VideoSendStreamImpl const* const internal_stream_;
};
} // namespace test
diff --git a/third_party/libwebrtc/video/video_stream_encoder.cc b/third_party/libwebrtc/video/video_stream_encoder.cc
index 669f165635..d74f440996 100644
--- a/third_party/libwebrtc/video/video_stream_encoder.cc
+++ b/third_party/libwebrtc/video/video_stream_encoder.cc
@@ -713,10 +713,10 @@ VideoStreamEncoder::VideoStreamEncoder(
RTC_DCHECK_GE(number_of_cores, 1);
frame_cadence_adapter_->Initialize(&cadence_callback_);
- stream_resource_manager_.Initialize(encoder_queue_.Get());
+ stream_resource_manager_.Initialize(encoder_queue_.get());
- encoder_queue_.PostTask([this] {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ encoder_queue_->PostTask([this] {
+ RTC_DCHECK_RUN_ON(encoder_queue_.get());
resource_adaptation_processor_ =
std::make_unique<ResourceAdaptationProcessor>(
@@ -742,6 +742,14 @@ VideoStreamEncoder::~VideoStreamEncoder() {
RTC_DCHECK_RUN_ON(worker_queue_);
RTC_DCHECK(!video_source_sink_controller_.HasSource())
<< "Must call ::Stop() before destruction.";
+
+ // The queue must be destroyed before its pointer is invalidated to avoid race
+ // between destructor and running task that check if function is called on the
+ // encoder_queue_.
+ // std::unique_ptr destructor does the same two operations in reverse order as
+ // it doesn't expect member would be used after its destruction has started.
+ encoder_queue_.get_deleter()(encoder_queue_.get());
+ encoder_queue_.release();
}
void VideoStreamEncoder::Stop() {
@@ -750,8 +758,8 @@ void VideoStreamEncoder::Stop() {
rtc::Event shutdown_event;
absl::Cleanup shutdown = [&shutdown_event] { shutdown_event.Set(); };
- encoder_queue_.PostTask([this, shutdown = std::move(shutdown)] {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ encoder_queue_->PostTask([this, shutdown = std::move(shutdown)] {
+ RTC_DCHECK_RUN_ON(encoder_queue_.get());
if (resource_adaptation_processor_) {
stream_resource_manager_.StopManagedResources();
for (auto* constraint : adaptation_constraints_) {
@@ -779,8 +787,8 @@ void VideoStreamEncoder::Stop() {
void VideoStreamEncoder::SetFecControllerOverride(
FecControllerOverride* fec_controller_override) {
- encoder_queue_.PostTask([this, fec_controller_override] {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ encoder_queue_->PostTask([this, fec_controller_override] {
+ RTC_DCHECK_RUN_ON(encoder_queue_.get());
RTC_DCHECK(!fec_controller_override_);
fec_controller_override_ = fec_controller_override;
if (encoder_) {
@@ -798,10 +806,10 @@ void VideoStreamEncoder::AddAdaptationResource(
// of this MapResourceToReason() call.
TRACE_EVENT_ASYNC_BEGIN0(
"webrtc", "VideoStreamEncoder::AddAdaptationResource(latency)", this);
- encoder_queue_.PostTask([this, resource = std::move(resource)] {
+ encoder_queue_->PostTask([this, resource = std::move(resource)] {
TRACE_EVENT_ASYNC_END0(
"webrtc", "VideoStreamEncoder::AddAdaptationResource(latency)", this);
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ RTC_DCHECK_RUN_ON(encoder_queue_.get());
additional_resources_.push_back(resource);
stream_resource_manager_.AddResource(resource, VideoAdaptationReason::kCpu);
});
@@ -816,8 +824,8 @@ VideoStreamEncoder::GetAdaptationResources() {
// here.
rtc::Event event;
std::vector<rtc::scoped_refptr<Resource>> resources;
- encoder_queue_.PostTask([&] {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ encoder_queue_->PostTask([&] {
+ RTC_DCHECK_RUN_ON(encoder_queue_.get());
resources = resource_adaptation_processor_->GetResources();
event.Set();
});
@@ -833,8 +841,8 @@ void VideoStreamEncoder::SetSource(
input_state_provider_.OnHasInputChanged(source);
// This may trigger reconfiguring the QualityScaler on the encoder queue.
- encoder_queue_.PostTask([this, degradation_preference] {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ encoder_queue_->PostTask([this, degradation_preference] {
+ RTC_DCHECK_RUN_ON(encoder_queue_.get());
degradation_preference_manager_->SetDegradationPreference(
degradation_preference);
stream_resource_manager_.SetDegradationPreferences(degradation_preference);
@@ -852,15 +860,15 @@ void VideoStreamEncoder::SetSink(EncoderSink* sink, bool rotation_applied) {
video_source_sink_controller_.SetRotationApplied(rotation_applied);
video_source_sink_controller_.PushSourceSinkSettings();
- encoder_queue_.PostTask([this, sink] {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ encoder_queue_->PostTask([this, sink] {
+ RTC_DCHECK_RUN_ON(encoder_queue_.get());
sink_ = sink;
});
}
void VideoStreamEncoder::SetStartBitrate(int start_bitrate_bps) {
- encoder_queue_.PostTask([this, start_bitrate_bps] {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ encoder_queue_->PostTask([this, start_bitrate_bps] {
+ RTC_DCHECK_RUN_ON(encoder_queue_.get());
RTC_LOG(LS_INFO) << "SetStartBitrate " << start_bitrate_bps;
encoder_target_bitrate_bps_ =
start_bitrate_bps != 0 ? absl::optional<uint32_t>(start_bitrate_bps)
@@ -879,10 +887,10 @@ void VideoStreamEncoder::ConfigureEncoder(VideoEncoderConfig config,
size_t max_data_payload_length,
SetParametersCallback callback) {
RTC_DCHECK_RUN_ON(worker_queue_);
- encoder_queue_.PostTask([this, config = std::move(config),
- max_data_payload_length,
- callback = std::move(callback)]() mutable {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ encoder_queue_->PostTask([this, config = std::move(config),
+ max_data_payload_length,
+ callback = std::move(callback)]() mutable {
+ RTC_DCHECK_RUN_ON(encoder_queue_.get());
RTC_DCHECK(sink_);
RTC_LOG(LS_INFO) << "ConfigureEncoder requested.";
@@ -1484,7 +1492,7 @@ void VideoStreamEncoder::OnEncoderSettingsChanged() {
void VideoStreamEncoder::OnFrame(Timestamp post_time,
bool queue_overload,
const VideoFrame& video_frame) {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ RTC_DCHECK_RUN_ON(encoder_queue_.get());
VideoFrame incoming_frame = video_frame;
// In some cases, e.g., when the frame from decoder is fed to encoder,
@@ -1579,7 +1587,7 @@ void VideoStreamEncoder::OnDiscardedFrame() {
}
bool VideoStreamEncoder::EncoderPaused() const {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ RTC_DCHECK_RUN_ON(encoder_queue_.get());
// Pause video if paused by caller or as long as the network is down or the
// pacer queue has grown too large in buffered mode.
// If the pacer queue has grown too large or the network is down,
@@ -1589,7 +1597,7 @@ bool VideoStreamEncoder::EncoderPaused() const {
}
void VideoStreamEncoder::TraceFrameDropStart() {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ RTC_DCHECK_RUN_ON(encoder_queue_.get());
// Start trace event only on the first frame after encoder is paused.
if (!encoder_paused_and_dropped_frame_) {
TRACE_EVENT_ASYNC_BEGIN0("webrtc", "EncoderPaused", this);
@@ -1598,7 +1606,7 @@ void VideoStreamEncoder::TraceFrameDropStart() {
}
void VideoStreamEncoder::TraceFrameDropEnd() {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ RTC_DCHECK_RUN_ON(encoder_queue_.get());
// End trace event on first frame after encoder resumes, if frame was dropped.
if (encoder_paused_and_dropped_frame_) {
TRACE_EVENT_ASYNC_END0("webrtc", "EncoderPaused", this);
@@ -1731,7 +1739,7 @@ void VideoStreamEncoder::SetEncoderRates(
void VideoStreamEncoder::MaybeEncodeVideoFrame(const VideoFrame& video_frame,
int64_t time_when_posted_us) {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ RTC_DCHECK_RUN_ON(encoder_queue_.get());
input_state_provider_.OnFrameSizeObserved(video_frame.size());
if (!last_frame_info_ || video_frame.width() != last_frame_info_->width ||
@@ -1863,7 +1871,7 @@ void VideoStreamEncoder::MaybeEncodeVideoFrame(const VideoFrame& video_frame,
void VideoStreamEncoder::EncodeVideoFrame(const VideoFrame& video_frame,
int64_t time_when_posted_us) {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ RTC_DCHECK_RUN_ON(encoder_queue_.get());
RTC_LOG(LS_VERBOSE) << __func__ << " posted " << time_when_posted_us
<< " ntp time " << video_frame.ntp_time_ms();
@@ -2030,11 +2038,11 @@ void VideoStreamEncoder::RequestRefreshFrame() {
void VideoStreamEncoder::SendKeyFrame(
const std::vector<VideoFrameType>& layers) {
- if (!encoder_queue_.IsCurrent()) {
- encoder_queue_.PostTask([this, layers] { SendKeyFrame(layers); });
+ if (!encoder_queue_->IsCurrent()) {
+ encoder_queue_->PostTask([this, layers] { SendKeyFrame(layers); });
return;
}
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ RTC_DCHECK_RUN_ON(encoder_queue_.get());
TRACE_EVENT0("webrtc", "OnKeyFrameRequest");
RTC_DCHECK(!next_frame_types_.empty());
@@ -2059,13 +2067,13 @@ void VideoStreamEncoder::SendKeyFrame(
void VideoStreamEncoder::OnLossNotification(
const VideoEncoder::LossNotification& loss_notification) {
- if (!encoder_queue_.IsCurrent()) {
- encoder_queue_.PostTask(
+ if (!encoder_queue_->IsCurrent()) {
+ encoder_queue_->PostTask(
[this, loss_notification] { OnLossNotification(loss_notification); });
return;
}
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ RTC_DCHECK_RUN_ON(encoder_queue_.get());
if (encoder_) {
encoder_->OnLossNotification(loss_notification);
}
@@ -2120,10 +2128,11 @@ EncodedImageCallback::Result VideoStreamEncoder::OnEncodedImage(
// need to update on quality convergence.
unsigned int image_width = image_copy._encodedWidth;
unsigned int image_height = image_copy._encodedHeight;
- encoder_queue_.PostTask([this, codec_type, image_width, image_height,
- simulcast_index,
- at_target_quality = image_copy.IsAtTargetQuality()] {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ encoder_queue_->PostTask([this, codec_type, image_width, image_height,
+ simulcast_index,
+ at_target_quality =
+ image_copy.IsAtTargetQuality()] {
+ RTC_DCHECK_RUN_ON(encoder_queue_.get());
// Let the frame cadence adapter know about quality convergence.
if (frame_cadence_adapter_)
@@ -2201,15 +2210,15 @@ EncodedImageCallback::Result VideoStreamEncoder::OnEncodedImage(
void VideoStreamEncoder::OnDroppedFrame(DropReason reason) {
sink_->OnDroppedFrame(reason);
- encoder_queue_.PostTask([this, reason] {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ encoder_queue_->PostTask([this, reason] {
+ RTC_DCHECK_RUN_ON(encoder_queue_.get());
stream_resource_manager_.OnFrameDropped(reason);
});
}
DataRate VideoStreamEncoder::UpdateTargetBitrate(DataRate target_bitrate,
double cwnd_reduce_ratio) {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ RTC_DCHECK_RUN_ON(encoder_queue_.get());
DataRate updated_target_bitrate = target_bitrate;
// Drop frames when congestion window pushback ratio is larger than 1
@@ -2241,10 +2250,10 @@ void VideoStreamEncoder::OnBitrateUpdated(DataRate target_bitrate,
int64_t round_trip_time_ms,
double cwnd_reduce_ratio) {
RTC_DCHECK_GE(link_allocation, target_bitrate);
- if (!encoder_queue_.IsCurrent()) {
- encoder_queue_.PostTask([this, target_bitrate, stable_target_bitrate,
- link_allocation, fraction_lost, round_trip_time_ms,
- cwnd_reduce_ratio] {
+ if (!encoder_queue_->IsCurrent()) {
+ encoder_queue_->PostTask([this, target_bitrate, stable_target_bitrate,
+ link_allocation, fraction_lost,
+ round_trip_time_ms, cwnd_reduce_ratio] {
DataRate updated_target_bitrate =
UpdateTargetBitrate(target_bitrate, cwnd_reduce_ratio);
OnBitrateUpdated(updated_target_bitrate, stable_target_bitrate,
@@ -2253,7 +2262,7 @@ void VideoStreamEncoder::OnBitrateUpdated(DataRate target_bitrate,
});
return;
}
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ RTC_DCHECK_RUN_ON(encoder_queue_.get());
const bool video_is_suspended = target_bitrate == DataRate::Zero();
const bool video_suspension_changed = video_is_suspended != EncoderPaused();
@@ -2353,7 +2362,7 @@ void VideoStreamEncoder::OnVideoSourceRestrictionsUpdated(
const VideoAdaptationCounters& adaptation_counters,
rtc::scoped_refptr<Resource> reason,
const VideoSourceRestrictions& unfiltered_restrictions) {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ RTC_DCHECK_RUN_ON(encoder_queue_.get());
RTC_LOG(LS_INFO) << "Updating sink restrictions from "
<< (reason ? reason->Name() : std::string("<null>"))
<< " to " << restrictions.ToString();
@@ -2379,15 +2388,15 @@ void VideoStreamEncoder::RunPostEncode(const EncodedImage& encoded_image,
int64_t time_sent_us,
int temporal_index,
DataSize frame_size) {
- if (!encoder_queue_.IsCurrent()) {
- encoder_queue_.PostTask([this, encoded_image, time_sent_us, temporal_index,
- frame_size] {
+ if (!encoder_queue_->IsCurrent()) {
+ encoder_queue_->PostTask([this, encoded_image, time_sent_us, temporal_index,
+ frame_size] {
RunPostEncode(encoded_image, time_sent_us, temporal_index, frame_size);
});
return;
}
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ RTC_DCHECK_RUN_ON(encoder_queue_.get());
absl::optional<int> encode_duration_us;
if (encoded_image.timing_.flags != VideoSendTiming::kInvalid) {
@@ -2539,8 +2548,8 @@ void VideoStreamEncoder::CheckForAnimatedContent(
void VideoStreamEncoder::InjectAdaptationResource(
rtc::scoped_refptr<Resource> resource,
VideoAdaptationReason reason) {
- encoder_queue_.PostTask([this, resource = std::move(resource), reason] {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ encoder_queue_->PostTask([this, resource = std::move(resource), reason] {
+ RTC_DCHECK_RUN_ON(encoder_queue_.get());
additional_resources_.push_back(resource);
stream_resource_manager_.AddResource(resource, reason);
});
@@ -2549,8 +2558,8 @@ void VideoStreamEncoder::InjectAdaptationResource(
void VideoStreamEncoder::InjectAdaptationConstraint(
AdaptationConstraint* adaptation_constraint) {
rtc::Event event;
- encoder_queue_.PostTask([this, adaptation_constraint, &event] {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ encoder_queue_->PostTask([this, adaptation_constraint, &event] {
+ RTC_DCHECK_RUN_ON(encoder_queue_.get());
if (!resource_adaptation_processor_) {
// The VideoStreamEncoder was stopped and the processor destroyed before
// this task had a chance to execute. No action needed.
@@ -2566,8 +2575,8 @@ void VideoStreamEncoder::InjectAdaptationConstraint(
void VideoStreamEncoder::AddRestrictionsListenerForTesting(
VideoSourceRestrictionsListener* restrictions_listener) {
rtc::Event event;
- encoder_queue_.PostTask([this, restrictions_listener, &event] {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ encoder_queue_->PostTask([this, restrictions_listener, &event] {
+ RTC_DCHECK_RUN_ON(encoder_queue_.get());
RTC_DCHECK(resource_adaptation_processor_);
video_stream_adapter_->AddRestrictionsListener(restrictions_listener);
event.Set();
@@ -2578,8 +2587,8 @@ void VideoStreamEncoder::AddRestrictionsListenerForTesting(
void VideoStreamEncoder::RemoveRestrictionsListenerForTesting(
VideoSourceRestrictionsListener* restrictions_listener) {
rtc::Event event;
- encoder_queue_.PostTask([this, restrictions_listener, &event] {
- RTC_DCHECK_RUN_ON(&encoder_queue_);
+ encoder_queue_->PostTask([this, restrictions_listener, &event] {
+ RTC_DCHECK_RUN_ON(encoder_queue_.get());
RTC_DCHECK(resource_adaptation_processor_);
video_stream_adapter_->RemoveRestrictionsListener(restrictions_listener);
event.Set();
diff --git a/third_party/libwebrtc/video/video_stream_encoder.h b/third_party/libwebrtc/video/video_stream_encoder.h
index f2c21c12b0..2a542ffe40 100644
--- a/third_party/libwebrtc/video/video_stream_encoder.h
+++ b/third_party/libwebrtc/video/video_stream_encoder.h
@@ -42,7 +42,6 @@
#include "rtc_base/numerics/exp_filter.h"
#include "rtc_base/race_checker.h"
#include "rtc_base/rate_statistics.h"
-#include "rtc_base/task_queue.h"
#include "rtc_base/thread_annotations.h"
#include "system_wrappers/include/clock.h"
#include "video/adaptation/video_stream_encoder_resource_manager.h"
@@ -136,7 +135,7 @@ class VideoStreamEncoder : public VideoStreamEncoderInterface,
// Used for testing. For example the `ScalingObserverInterface` methods must
// be called on `encoder_queue_`.
- TaskQueueBase* encoder_queue() { return encoder_queue_.Get(); }
+ TaskQueueBase* encoder_queue() { return encoder_queue_.get(); }
void OnVideoSourceRestrictionsUpdated(
VideoSourceRestrictions restrictions,
@@ -210,8 +209,8 @@ class VideoStreamEncoder : public VideoStreamEncoderInterface,
class DegradationPreferenceManager;
- void ReconfigureEncoder() RTC_RUN_ON(&encoder_queue_);
- void OnEncoderSettingsChanged() RTC_RUN_ON(&encoder_queue_);
+ void ReconfigureEncoder() RTC_RUN_ON(encoder_queue_);
+ void OnEncoderSettingsChanged() RTC_RUN_ON(encoder_queue_);
void OnFrame(Timestamp post_time,
bool queue_overload,
const VideoFrame& video_frame);
@@ -225,7 +224,7 @@ class VideoStreamEncoder : public VideoStreamEncoderInterface,
int64_t time_when_posted_in_ms);
// Indicates whether frame should be dropped because the pixel count is too
// large for the current bitrate configuration.
- bool DropDueToSize(uint32_t pixel_count) const RTC_RUN_ON(&encoder_queue_);
+ bool DropDueToSize(uint32_t pixel_count) const RTC_RUN_ON(encoder_queue_);
// Implements EncodedImageCallback.
EncodedImageCallback::Result OnEncodedImage(
@@ -241,25 +240,25 @@ class VideoStreamEncoder : public VideoStreamEncoderInterface,
// Returns a copy of `rate_settings` with the `bitrate` field updated using
// the current VideoBitrateAllocator.
EncoderRateSettings UpdateBitrateAllocation(
- const EncoderRateSettings& rate_settings) RTC_RUN_ON(&encoder_queue_);
+ const EncoderRateSettings& rate_settings) RTC_RUN_ON(encoder_queue_);
- uint32_t GetInputFramerateFps() RTC_RUN_ON(&encoder_queue_);
+ uint32_t GetInputFramerateFps() RTC_RUN_ON(encoder_queue_);
void SetEncoderRates(const EncoderRateSettings& rate_settings)
- RTC_RUN_ON(&encoder_queue_);
+ RTC_RUN_ON(encoder_queue_);
void RunPostEncode(const EncodedImage& encoded_image,
int64_t time_sent_us,
int temporal_index,
DataSize frame_size);
- void ReleaseEncoder() RTC_RUN_ON(&encoder_queue_);
+ void ReleaseEncoder() RTC_RUN_ON(encoder_queue_);
// After calling this function `resource_adaptation_processor_` will be null.
void ShutdownResourceAdaptationQueue();
void CheckForAnimatedContent(const VideoFrame& frame,
int64_t time_when_posted_in_ms)
- RTC_RUN_ON(&encoder_queue_);
+ RTC_RUN_ON(encoder_queue_);
- void RequestEncoderSwitch() RTC_RUN_ON(&encoder_queue_);
+ void RequestEncoderSwitch() RTC_RUN_ON(encoder_queue_);
// Augments an EncodedImage received from an encoder with parsable
// information.
@@ -269,7 +268,7 @@ class VideoStreamEncoder : public VideoStreamEncoderInterface,
void ProcessDroppedFrame(const VideoFrame& frame,
VideoStreamEncoderObserver::DropReason reason)
- RTC_RUN_ON(&encoder_queue_);
+ RTC_RUN_ON(encoder_queue_);
const FieldTrialsView& field_trials_;
TaskQueueBase* const worker_queue_;
@@ -296,67 +295,66 @@ class VideoStreamEncoder : public VideoStreamEncoderInterface,
// Frame cadence encoder adapter. Frames enter this adapter first, and it then
// forwards them to our OnFrame method.
std::unique_ptr<FrameCadenceAdapterInterface> frame_cadence_adapter_
- RTC_GUARDED_BY(&encoder_queue_) RTC_PT_GUARDED_BY(&encoder_queue_);
+ RTC_GUARDED_BY(encoder_queue_) RTC_PT_GUARDED_BY(encoder_queue_);
- VideoEncoderConfig encoder_config_ RTC_GUARDED_BY(&encoder_queue_);
- std::unique_ptr<VideoEncoder> encoder_ RTC_GUARDED_BY(&encoder_queue_)
- RTC_PT_GUARDED_BY(&encoder_queue_);
+ VideoEncoderConfig encoder_config_ RTC_GUARDED_BY(encoder_queue_);
+ std::unique_ptr<VideoEncoder> encoder_ RTC_GUARDED_BY(encoder_queue_)
+ RTC_PT_GUARDED_BY(encoder_queue_);
bool encoder_initialized_ = false;
std::unique_ptr<VideoBitrateAllocator> rate_allocator_
- RTC_GUARDED_BY(&encoder_queue_) RTC_PT_GUARDED_BY(&encoder_queue_);
- int max_framerate_ RTC_GUARDED_BY(&encoder_queue_) = -1;
+ RTC_GUARDED_BY(encoder_queue_) RTC_PT_GUARDED_BY(encoder_queue_);
+ int max_framerate_ RTC_GUARDED_BY(encoder_queue_) = -1;
// Set when ConfigureEncoder has been called in order to lazy reconfigure the
// encoder on the next frame.
- bool pending_encoder_reconfiguration_ RTC_GUARDED_BY(&encoder_queue_) = false;
+ bool pending_encoder_reconfiguration_ RTC_GUARDED_BY(encoder_queue_) = false;
// Set when configuration must create a new encoder object, e.g.,
// because of a codec change.
- bool pending_encoder_creation_ RTC_GUARDED_BY(&encoder_queue_) = false;
+ bool pending_encoder_creation_ RTC_GUARDED_BY(encoder_queue_) = false;
absl::InlinedVector<SetParametersCallback, 2> encoder_configuration_callbacks_
- RTC_GUARDED_BY(&encoder_queue_);
+ RTC_GUARDED_BY(encoder_queue_);
absl::optional<VideoFrameInfo> last_frame_info_
- RTC_GUARDED_BY(&encoder_queue_);
- int crop_width_ RTC_GUARDED_BY(&encoder_queue_) = 0;
- int crop_height_ RTC_GUARDED_BY(&encoder_queue_) = 0;
+ RTC_GUARDED_BY(encoder_queue_);
+ int crop_width_ RTC_GUARDED_BY(encoder_queue_) = 0;
+ int crop_height_ RTC_GUARDED_BY(encoder_queue_) = 0;
absl::optional<uint32_t> encoder_target_bitrate_bps_
- RTC_GUARDED_BY(&encoder_queue_);
- size_t max_data_payload_length_ RTC_GUARDED_BY(&encoder_queue_) = 0;
+ RTC_GUARDED_BY(encoder_queue_);
+ size_t max_data_payload_length_ RTC_GUARDED_BY(encoder_queue_) = 0;
absl::optional<EncoderRateSettings> last_encoder_rate_settings_
- RTC_GUARDED_BY(&encoder_queue_);
- bool encoder_paused_and_dropped_frame_ RTC_GUARDED_BY(&encoder_queue_) =
- false;
+ RTC_GUARDED_BY(encoder_queue_);
+ bool encoder_paused_and_dropped_frame_ RTC_GUARDED_BY(encoder_queue_) = false;
// Set to true if at least one frame was sent to encoder since last encoder
// initialization.
bool was_encode_called_since_last_initialization_
- RTC_GUARDED_BY(&encoder_queue_) = false;
+ RTC_GUARDED_BY(encoder_queue_) = false;
- bool encoder_failed_ RTC_GUARDED_BY(&encoder_queue_) = false;
+ bool encoder_failed_ RTC_GUARDED_BY(encoder_queue_) = false;
Clock* const clock_;
// Used to make sure incoming time stamp is increasing for every frame.
- int64_t last_captured_timestamp_ RTC_GUARDED_BY(&encoder_queue_) = 0;
+ int64_t last_captured_timestamp_ RTC_GUARDED_BY(encoder_queue_) = 0;
// Delta used for translating between NTP and internal timestamps.
- const int64_t delta_ntp_internal_ms_ RTC_GUARDED_BY(&encoder_queue_);
+ const int64_t delta_ntp_internal_ms_ RTC_GUARDED_BY(encoder_queue_);
- int64_t last_frame_log_ms_ RTC_GUARDED_BY(&encoder_queue_);
- int captured_frame_count_ RTC_GUARDED_BY(&encoder_queue_) = 0;
- int dropped_frame_cwnd_pushback_count_ RTC_GUARDED_BY(&encoder_queue_) = 0;
- int dropped_frame_encoder_block_count_ RTC_GUARDED_BY(&encoder_queue_) = 0;
- absl::optional<VideoFrame> pending_frame_ RTC_GUARDED_BY(&encoder_queue_);
- int64_t pending_frame_post_time_us_ RTC_GUARDED_BY(&encoder_queue_) = 0;
+ int64_t last_frame_log_ms_ RTC_GUARDED_BY(encoder_queue_);
+ int captured_frame_count_ RTC_GUARDED_BY(encoder_queue_) = 0;
+ int dropped_frame_cwnd_pushback_count_ RTC_GUARDED_BY(encoder_queue_) = 0;
+ int dropped_frame_encoder_block_count_ RTC_GUARDED_BY(encoder_queue_) = 0;
+ absl::optional<VideoFrame> pending_frame_ RTC_GUARDED_BY(encoder_queue_);
+ int64_t pending_frame_post_time_us_ RTC_GUARDED_BY(encoder_queue_) = 0;
VideoFrame::UpdateRect accumulated_update_rect_
- RTC_GUARDED_BY(&encoder_queue_);
- bool accumulated_update_rect_is_valid_ RTC_GUARDED_BY(&encoder_queue_) = true;
+ RTC_GUARDED_BY(encoder_queue_);
+ bool accumulated_update_rect_is_valid_ RTC_GUARDED_BY(encoder_queue_) = true;
// Used for automatic content type detection.
absl::optional<VideoFrame::UpdateRect> last_update_rect_
- RTC_GUARDED_BY(&encoder_queue_);
- Timestamp animation_start_time_ RTC_GUARDED_BY(&encoder_queue_) =
+ RTC_GUARDED_BY(encoder_queue_);
+ Timestamp animation_start_time_ RTC_GUARDED_BY(encoder_queue_) =
Timestamp::PlusInfinity();
- bool cap_resolution_due_to_video_content_ RTC_GUARDED_BY(&encoder_queue_) =
+ bool cap_resolution_due_to_video_content_ RTC_GUARDED_BY(encoder_queue_) =
false;
// Used to correctly ignore changes in update_rect introduced by
// resize triggered by animation detection.
@@ -364,24 +362,24 @@ class VideoStreamEncoder : public VideoStreamEncoderInterface,
kNoResize, // Normal operation.
kResize, // Resize was triggered by the animation detection.
kFirstFrameAfterResize // Resize observed.
- } expect_resize_state_ RTC_GUARDED_BY(&encoder_queue_) =
+ } expect_resize_state_ RTC_GUARDED_BY(encoder_queue_) =
ExpectResizeState::kNoResize;
FecControllerOverride* fec_controller_override_
- RTC_GUARDED_BY(&encoder_queue_) = nullptr;
+ RTC_GUARDED_BY(encoder_queue_) = nullptr;
absl::optional<int64_t> last_parameters_update_ms_
- RTC_GUARDED_BY(&encoder_queue_);
- absl::optional<int64_t> last_encode_info_ms_ RTC_GUARDED_BY(&encoder_queue_);
+ RTC_GUARDED_BY(encoder_queue_);
+ absl::optional<int64_t> last_encode_info_ms_ RTC_GUARDED_BY(encoder_queue_);
- VideoEncoder::EncoderInfo encoder_info_ RTC_GUARDED_BY(&encoder_queue_);
- VideoCodec send_codec_ RTC_GUARDED_BY(&encoder_queue_);
+ VideoEncoder::EncoderInfo encoder_info_ RTC_GUARDED_BY(encoder_queue_);
+ VideoCodec send_codec_ RTC_GUARDED_BY(encoder_queue_);
- FrameDropper frame_dropper_ RTC_GUARDED_BY(&encoder_queue_);
+ FrameDropper frame_dropper_ RTC_GUARDED_BY(encoder_queue_);
// If frame dropper is not force disabled, frame dropping might still be
// disabled if VideoEncoder::GetEncoderInfo() indicates that the encoder has a
// trusted rate controller. This is determined on a per-frame basis, as the
// encoder behavior might dynamically change.
- bool force_disable_frame_dropper_ RTC_GUARDED_BY(&encoder_queue_) = false;
+ bool force_disable_frame_dropper_ RTC_GUARDED_BY(encoder_queue_) = false;
// Incremented on worker thread whenever `frame_dropper_` determines that a
// frame should be dropped. Decremented on whichever thread runs
// OnEncodedImage(), which is only called by one thread but not necessarily
@@ -390,16 +388,16 @@ class VideoStreamEncoder : public VideoStreamEncoderInterface,
// Congestion window frame drop ratio (drop 1 in every
// cwnd_frame_drop_interval_ frames).
- absl::optional<int> cwnd_frame_drop_interval_ RTC_GUARDED_BY(&encoder_queue_);
+ absl::optional<int> cwnd_frame_drop_interval_ RTC_GUARDED_BY(encoder_queue_);
// Frame counter for congestion window frame drop.
- int cwnd_frame_counter_ RTC_GUARDED_BY(&encoder_queue_) = 0;
+ int cwnd_frame_counter_ RTC_GUARDED_BY(encoder_queue_) = 0;
std::unique_ptr<EncoderBitrateAdjuster> bitrate_adjuster_
- RTC_GUARDED_BY(&encoder_queue_);
+ RTC_GUARDED_BY(encoder_queue_);
// TODO(sprang): Change actually support keyframe per simulcast stream, or
// turn this into a simple bool `pending_keyframe_request_`.
- std::vector<VideoFrameType> next_frame_types_ RTC_GUARDED_BY(&encoder_queue_);
+ std::vector<VideoFrameType> next_frame_types_ RTC_GUARDED_BY(encoder_queue_);
FrameEncodeMetadataWriter frame_encode_metadata_writer_{this};
@@ -421,22 +419,22 @@ class VideoStreamEncoder : public VideoStreamEncoderInterface,
ParseAutomatincAnimationDetectionFieldTrial() const;
AutomaticAnimationDetectionExperiment
- automatic_animation_detection_experiment_ RTC_GUARDED_BY(&encoder_queue_);
+ automatic_animation_detection_experiment_ RTC_GUARDED_BY(encoder_queue_);
// Provides video stream input states: current resolution and frame rate.
VideoStreamInputStateProvider input_state_provider_;
const std::unique_ptr<VideoStreamAdapter> video_stream_adapter_
- RTC_GUARDED_BY(&encoder_queue_);
+ RTC_GUARDED_BY(encoder_queue_);
// Responsible for adapting input resolution or frame rate to ensure resources
// (e.g. CPU or bandwidth) are not overused. Adding resources can occur on any
// thread.
std::unique_ptr<ResourceAdaptationProcessorInterface>
- resource_adaptation_processor_ RTC_GUARDED_BY(&encoder_queue_);
+ resource_adaptation_processor_ RTC_GUARDED_BY(encoder_queue_);
std::unique_ptr<DegradationPreferenceManager> degradation_preference_manager_
- RTC_GUARDED_BY(&encoder_queue_);
+ RTC_GUARDED_BY(encoder_queue_);
std::vector<AdaptationConstraint*> adaptation_constraints_
- RTC_GUARDED_BY(&encoder_queue_);
+ RTC_GUARDED_BY(encoder_queue_);
// Handles input, output and stats reporting related to VideoStreamEncoder
// specific resources, such as "encode usage percent" measurements and "QP
// scaling". Also involved with various mitigations such as initial frame
@@ -445,9 +443,9 @@ class VideoStreamEncoder : public VideoStreamEncoderInterface,
// tied to the VideoStreamEncoder (which is destroyed off the encoder queue)
// and its resource list is accessible from any thread.
VideoStreamEncoderResourceManager stream_resource_manager_
- RTC_GUARDED_BY(&encoder_queue_);
+ RTC_GUARDED_BY(encoder_queue_);
std::vector<rtc::scoped_refptr<Resource>> additional_resources_
- RTC_GUARDED_BY(&encoder_queue_);
+ RTC_GUARDED_BY(encoder_queue_);
// Carries out the VideoSourceRestrictions provided by the
// ResourceAdaptationProcessor, i.e. reconfigures the source of video frames
// to provide us with different resolution or frame rate.
@@ -479,9 +477,9 @@ class VideoStreamEncoder : public VideoStreamEncoderInterface,
// so that ownership on restrictions/wants is kept on &encoder_queue_, that
// these extra copies would not be needed.
absl::optional<VideoSourceRestrictions> latest_restrictions_
- RTC_GUARDED_BY(&encoder_queue_);
+ RTC_GUARDED_BY(encoder_queue_);
absl::optional<VideoSourceRestrictions> animate_restrictions_
- RTC_GUARDED_BY(&encoder_queue_);
+ RTC_GUARDED_BY(encoder_queue_);
// Used to cancel any potentially pending tasks to the worker thread.
// Refrenced by tasks running on `encoder_queue_` so need to be destroyed
@@ -489,9 +487,7 @@ class VideoStreamEncoder : public VideoStreamEncoderInterface,
// `worker_queue_`.
ScopedTaskSafety task_safety_;
- // Public methods are proxied to the task queues. The queues must be destroyed
- // first to make sure no tasks run that use other members.
- rtc::TaskQueue encoder_queue_;
+ std::unique_ptr<TaskQueueBase, TaskQueueDeleter> encoder_queue_;
};
} // namespace webrtc
diff --git a/third_party/libwebrtc/video/video_stream_encoder_unittest.cc b/third_party/libwebrtc/video/video_stream_encoder_unittest.cc
index 6fa99081cd..d752e1b23b 100644
--- a/third_party/libwebrtc/video/video_stream_encoder_unittest.cc
+++ b/third_party/libwebrtc/video/video_stream_encoder_unittest.cc
@@ -875,8 +875,9 @@ class VideoStreamEncoderTest : public ::testing::Test {
"EncoderQueue", TaskQueueFactory::Priority::NORMAL);
TaskQueueBase* encoder_queue_ptr = encoder_queue.get();
std::unique_ptr<FrameCadenceAdapterInterface> cadence_adapter =
- FrameCadenceAdapterInterface::Create(time_controller_.GetClock(),
- encoder_queue_ptr, field_trials_);
+ FrameCadenceAdapterInterface::Create(
+ time_controller_.GetClock(), encoder_queue_ptr,
+ /*metronome=*/nullptr, /*worker_queue=*/nullptr, field_trials_);
video_stream_encoder_ = std::make_unique<VideoStreamEncoderUnderTest>(
&time_controller_, std::move(cadence_adapter), std::move(encoder_queue),
stats_proxy_.get(), video_send_config_.encoder_settings,
@@ -9556,7 +9557,7 @@ TEST(VideoStreamEncoderFrameCadenceTest,
"WebRTC-ZeroHertzScreenshare/Enabled/");
auto adapter = FrameCadenceAdapterInterface::Create(
factory.GetTimeController()->GetClock(), encoder_queue.get(),
- field_trials);
+ /*metronome=*/nullptr, /*worker_queue=*/nullptr, field_trials);
FrameCadenceAdapterInterface* adapter_ptr = adapter.get();
MockVideoSourceInterface mock_source;
diff --git a/third_party/libwebrtc/webrtc_lib_link_test.cc b/third_party/libwebrtc/webrtc_lib_link_test.cc
index 64da01e4ef..e129ad55d2 100644
--- a/third_party/libwebrtc/webrtc_lib_link_test.cc
+++ b/third_party/libwebrtc/webrtc_lib_link_test.cc
@@ -57,8 +57,7 @@ webrtc::PeerConnectionFactoryDependencies CreateSomePcfDeps() {
pcf_deps.signaling_thread = rtc::Thread::Current();
pcf_deps.network_thread = rtc::Thread::Current();
pcf_deps.worker_thread = rtc::Thread::Current();
- pcf_deps.event_log_factory = std::make_unique<webrtc::RtcEventLogFactory>(
- pcf_deps.task_queue_factory.get());
+ pcf_deps.event_log_factory = std::make_unique<webrtc::RtcEventLogFactory>();
CreateSomeMediaDeps(pcf_deps);
EnableMedia(pcf_deps);
return pcf_deps;
diff --git a/third_party/libwebrtc/whitespace.txt b/third_party/libwebrtc/whitespace.txt
index ea60e6175b..e4ba9abb0d 100644
--- a/third_party/libwebrtc/whitespace.txt
+++ b/third_party/libwebrtc/whitespace.txt
@@ -2,6 +2,6 @@ You can modify this file to create no-op changelists.
Try to write something funny. And please don't add trailing whitespace.
-Once upon a time there was an elephant in Stockholm.
+Once upon a time there was a white elephant in Stockholm.
Why did the elephant get kicked out of the Swedish Parliament?
Because it kept making trunk calls!